import { eq, and, or } from "drizzle-orm"; import db from "../../db"; import { serverSessions } from "../../db/schema/serverSessions"; import { servers } from "../../db/schema/servers"; import { apps } from "../../db/schema/apps"; export type SessionMode = "stream" | "local"; export type SessionStatus = "starting" | "started" | "ending" | "ended"; export interface CreateSessionParams { appId: string; userId: string; mode: SessionMode; serverId?: string; } export interface UpdateSessionParams { status?: SessionStatus; appPid?: number; cirrusPid?: number; endAt?: Date; } /** * Сервис для работы с игровыми/streaming сессиями */ export const serverSessionService = { /** * Найти сессию по ID */ async findById(sessionId: string) { const session = await db.query.serverSessions.findFirst({ where: eq(serverSessions.id, sessionId), with: { app: true, user: { columns: { id: true, email: true, role: true, }, }, server: true, }, }); return session || null; }, /** * Найти сессию по ID для конкретного пользователя */ async findByIdForUser(sessionId: string, userId: string) { const session = await db.query.serverSessions.findFirst({ where: and( eq(serverSessions.id, sessionId), eq(serverSessions.userId, userId) ), with: { app: true, user: { columns: { id: true, email: true, role: true, }, }, server: true, }, }); return session || null; }, /** * Получить все сессии пользователя */ async findByUserId( userId: string, filters?: { status?: SessionStatus; mode?: SessionMode; } ) { const conditions = [eq(serverSessions.userId, userId)]; if (filters?.status) { conditions.push(eq(serverSessions.status, filters.status)); } if (filters?.mode) { conditions.push(eq(serverSessions.mode, filters.mode)); } const sessions = await db .select({ id: serverSessions.id, serverId: serverSessions.serverId, appId: serverSessions.appId, userId: serverSessions.userId, startAt: serverSessions.startAt, endAt: serverSessions.endAt, appPid: serverSessions.appPid, cirrusPid: serverSessions.cirrusPid, mode: serverSessions.mode, status: serverSessions.status, createdAt: serverSessions.createdAt, updatedAt: serverSessions.updatedAt, app: { id: apps.id, name: apps.name, title: apps.title, }, }) .from(serverSessions) .leftJoin(apps, eq(serverSessions.appId, apps.id)) .where(and(...conditions)) .orderBy(serverSessions.createdAt); return sessions; }, /** * Проверить, есть ли у пользователя активная сессия для данного приложения */ async hasActiveSession(userId: string, appId: string) { const session = await db.query.serverSessions.findFirst({ where: and( eq(serverSessions.userId, userId), eq(serverSessions.appId, appId), or( eq(serverSessions.status, "starting"), eq(serverSessions.status, "started") ) ), }); return !!session; }, /** * Выбрать доступный сервер */ async selectAvailableServer(mode: SessionMode): Promise { if (mode === "stream") { const server = await db.query.servers.findFirst({ where: and(eq(servers.type, "stream"), eq(servers.tier, "prod")), }); return server?.id; } else { const server = await db.query.servers.findFirst({ where: eq(servers.type, "local"), }); return server?.id; } }, /** * Создать новую сессию */ async create(params: CreateSessionParams) { const { appId, userId, mode, serverId } = params; // Выбрать сервер (если не указан) let selectedServerId = serverId; if (!selectedServerId) { selectedServerId = await this.selectAvailableServer(mode); if (!selectedServerId) { throw new Error(`No available ${mode} servers`); } } // Вычислить время окончания (по умолчанию +30 минут) const endAt = new Date(); endAt.setMinutes(endAt.getMinutes() + 30); // Создать сессию const [newSession] = await db .insert(serverSessions) .values({ serverId: selectedServerId, appId, userId, mode, status: "starting", endAt, }) .returning(); return newSession; }, /** * Обновить сессию */ async update(sessionId: string, params: UpdateSessionParams) { const updateData: any = { updatedAt: new Date(), }; if (params.status) { updateData.status = params.status; } if (params.appPid !== undefined) { updateData.appPid = params.appPid; } if (params.cirrusPid !== undefined) { updateData.cirrusPid = params.cirrusPid; } if (params.endAt) { updateData.endAt = params.endAt; } const [updatedSession] = await db .update(serverSessions) .set(updateData) .where(eq(serverSessions.id, sessionId)) .returning(); return updatedSession; }, /** * Завершить сессию (изменить статус на "ending") */ async end(sessionId: string) { const [updatedSession] = await db .update(serverSessions) .set({ status: "ending", updatedAt: new Date(), }) .where(eq(serverSessions.id, sessionId)) .returning(); return updatedSession; }, /** * Продлить сессию */ async extend(sessionId: string, minutes: number) { const session = await this.findById(sessionId); if (!session) { throw new Error("Session not found"); } const newEndAt = session.endAt ? new Date(session.endAt) : new Date(); newEndAt.setMinutes(newEndAt.getMinutes() + minutes); const [updatedSession] = await db .update(serverSessions) .set({ endAt: newEndAt, updatedAt: new Date(), }) .where(eq(serverSessions.id, sessionId)) .returning(); return updatedSession; }, };