Files
stream.graff.tech-new/server/src/services/serverSession/index.ts
T

267 lines
6.5 KiB
TypeScript

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<string | undefined> {
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;
},
};