Files
drinkmarble/app/api/rooms/join/route.ts

121 lines
3.3 KiB
TypeScript

import { NextResponse } from 'next/server';
import { parseChzzkChannelId } from '@/lib/chzzk/channel';
import {
allowChannelIdJoin,
isChzzkOAuthConfigured,
requireChzzkLoginForChannelRooms,
} from '@/lib/auth/mode';
import { getRequestAuthSession } from '@/lib/auth/room-access';
import {
cancelScheduledDisconnectRoomChat,
ensureRoomChat,
} from '@/lib/chzzk/chat-session';
import {
createRoom,
findRoomByChannelId,
getRoom,
toPublicRoom,
} from '@/lib/rooms/store';
export const runtime = 'nodejs';
/**
* 치지직 채널 ID(또는 라이브 URL)로 룸 입장.
* - 개발: 채널 ID 직접 입장 가능 (+ 로그인 세션 있으면 본인 채널도 가능)
* - 릴리즈: 로그인 세션의 채널 ID와 일치할 때만 입장
*/
export async function POST(request: Request) {
const body = (await request.json().catch(() => ({}))) as {
channelIdOrUrl?: string;
cheesePerDice?: number;
multiplyByCheese?: boolean;
maxMultiplier?: number;
};
const session = await getRequestAuthSession(request);
const raw = body.channelIdOrUrl || '';
let channelId = parseChzzkChannelId(raw);
if (requireChzzkLoginForChannelRooms()) {
if (!session) {
return NextResponse.json(
{
error:
'릴리즈 환경에서는 치지직 로그인 후 본인 채널로만 입장할 수 있습니다.',
code: 'CHZZK_AUTH_REQUIRED',
allowChannelIdJoin: allowChannelIdJoin(),
oauthConfigured: isChzzkOAuthConfigured(),
},
{ status: 401 },
);
}
// 입력된 채널이 있으면 세션과 일치해야 함. 없으면 세션 채널 사용.
if (channelId && channelId !== session.channelId) {
return NextResponse.json(
{
error: '로그인한 채널과 다른 채널 ID로는 입장할 수 없습니다.',
code: 'CHANNEL_MISMATCH',
},
{ status: 403 },
);
}
channelId = session.channelId;
} else if (!channelId) {
// 개발: 세션만 있으면 세션 채널로 입장 가능
if (session?.channelId) channelId = session.channelId;
}
if (!channelId) {
return NextResponse.json(
{
error:
'유효한 치지직 채널 ID 또는 라이브 URL이 필요합니다. 예: https://chzzk.naver.com/live/{채널ID}',
},
{ status: 400 },
);
}
let room = findRoomByChannelId(channelId);
let created = false;
if (!room) {
created = true;
room = createRoom({
chzzkEnabled: true,
liveUrl: raw.trim() || `https://chzzk.naver.com/live/${channelId}`,
channelId,
cheesePerDice: body.cheesePerDice,
multiplyByCheese: body.multiplyByCheese,
maxMultiplier: body.maxMultiplier,
});
}
cancelScheduledDisconnectRoomChat(room.id);
if (room.settings.chzzkEnabled) {
try {
await ensureRoomChat(room.id);
} catch (e) {
if (created) {
return NextResponse.json(
{
error:
e instanceof Error
? e.message
: '채팅 연결에 실패했습니다. 방송 중인지 확인해 주세요.',
room: toPublicRoom(room),
created,
},
{ status: 502 },
);
}
}
}
const latest = getRoom(room.id) ?? room;
return NextResponse.json({
room: toPublicRoom(latest),
created,
});
}