Remove outdated documentation files for companies and migration guide; implement server session management features including server assignment and session status updates; enhance database schema for servers and server sessions with new fields and validation; add auto-assign functionality for unassigned sessions.

This commit is contained in:
2025-10-06 15:59:55 +05:00
parent 9e4bc7b0f8
commit a49129f643
16 changed files with 2332 additions and 483 deletions
+311
View File
@@ -0,0 +1,311 @@
import { Elysia, t } from "elysia";
import { authMiddleware } from "../middlewares/auth";
import { serverService } from "../services/server";
export const serverController = new Elysia({ prefix: "/servers" })
// POST /servers/register - публичный endpoint для регистрации сервера (без авторизации)
.post(
"/register",
async ({ body, set }) => {
const { localIp, hostname, type, gpuFreeMb, branchId, location, tier } =
body as {
localIp: string;
hostname: string;
type: "stream" | "local";
gpuFreeMb: number;
branchId?: string;
location?: "ru1" | "uae1";
tier?: "demo" | "prod";
};
// Валидация для stream-серверов
if (type === "stream") {
if (!location) {
set.status = 400;
return { error: "Location is required for stream servers" };
}
}
// Валидация для local-серверов
if (type === "local") {
if (!branchId) {
set.status = 400;
return { error: "Branch ID is required for local servers" };
}
}
// Установить tier по умолчанию для stream-серверов
const finalTier = type === "stream" && !tier ? "demo" : tier;
// Проверить, существует ли сервер с таким hostname
const existingServer = await serverService.findByHostname(hostname);
if (existingServer) {
// Если сервер существует, обновить его информацию
const updatedServer = await serverService.update(existingServer.id, {
localIp,
gpuFreeMb,
branchId,
location,
tier: finalTier,
});
return { server: updatedServer, registered: false };
}
// Создать новый сервер
const server = await serverService.create({
localIp,
hostname,
type,
gpuFreeMb,
branchId,
location,
tier: finalTier,
});
return { server, registered: true };
},
{
body: t.Object({
localIp: t.String({ minLength: 7, maxLength: 45 }),
hostname: t.String({ minLength: 1, maxLength: 255 }),
type: t.Union([t.Literal("stream"), t.Literal("local")]),
gpuFreeMb: t.Number({ minimum: 0 }),
branchId: t.Optional(t.String({ format: "uuid" })),
location: t.Optional(t.Union([t.Literal("ru1"), t.Literal("uae1")])),
tier: t.Optional(t.Union([t.Literal("demo"), t.Literal("prod")])),
}),
}
)
// PATCH /servers/:id/gpu - обновить свободную память GPU (публичный endpoint)
.patch(
"/:id/gpu",
async ({ params, body, status }) => {
const { id } = params;
const { gpuFreeMb } = body as { gpuFreeMb: number };
// Проверить существование сервера
const server = await serverService.findById(id);
if (!server) {
return status(404, "Server not found");
}
const updatedServer = await serverService.updateGpuMemory(id, gpuFreeMb);
return { server: updatedServer };
},
{
body: t.Object({
gpuFreeMb: t.Number({ minimum: 0 }),
}),
}
)
// GET /servers/:id/sessions - получить сессии для конкретного сервера (публичный endpoint)
.get("/:id/sessions", async ({ params, query, status }) => {
const { id } = params;
const { statusFilter, mode } = query as {
statusFilter?: "starting" | "started" | "ending" | "ended";
mode?: "stream" | "local";
};
// Проверить существование сервера
const server = await serverService.findById(id);
if (!server) {
return status(404, "Server not found");
}
const { serverSessionService } = await import("../services/serverSession");
// Получаем только сессии, назначенные этому серверу
// Main server автоматически назначает серверы для unassigned сессий
const sessions = await serverSessionService.findByServerId(id, {
status: statusFilter,
mode,
});
return { sessions };
})
// Все остальные роуты требуют авторизации
.use(authMiddleware)
// GET /servers - получить список серверов с фильтрацией
.get("/", async ({ query }) => {
const { type, location, tier, branchId } = query as {
type?: "stream" | "local";
location?: "ru1" | "uae1";
tier?: "demo" | "prod";
branchId?: string;
};
const servers = await serverService.findAll({
type,
location,
tier,
branchId,
});
return { servers };
})
// GET /servers/available/stream - получить доступные stream-серверы
.get("/available/stream", async ({ query }) => {
const { tier } = query as { tier?: "demo" | "prod" };
const servers = await serverService.findAvailableStreamServers(tier);
return { servers };
})
// GET /servers/available/local - получить доступные local-серверы
.get("/available/local", async ({ query }) => {
const { branchId } = query as { branchId?: string };
const servers = await serverService.findAvailableLocalServers(branchId);
return { servers };
})
// GET /servers/:id - получить сервер по ID
.get("/:id", async ({ params, status }) => {
const { id } = params;
const server = await serverService.findById(id);
if (!server) {
return status(404, "Server not found");
}
return { server };
})
// POST /servers - создать сервер
.post(
"/",
async ({ body, set }) => {
const { localIp, hostname, type, gpuFreeMb, branchId, location, tier } =
body as {
localIp: string;
hostname: string;
type: "stream" | "local";
gpuFreeMb: number;
branchId?: string;
location?: "ru1" | "uae1";
tier?: "demo" | "prod";
};
// Валидация для stream-серверов
if (type === "stream") {
if (!location) {
set.status = 400;
return { error: "Location is required for stream servers" };
}
}
// Валидация для local-серверов
if (type === "local") {
if (!branchId) {
set.status = 400;
return { error: "Branch ID is required for local servers" };
}
}
// Установить tier по умолчанию для stream-серверов
const finalTier = type === "stream" && !tier ? "demo" : tier;
const server = await serverService.create({
localIp,
hostname,
type,
gpuFreeMb,
branchId,
location,
tier: finalTier,
});
return { server };
},
{
body: t.Object({
localIp: t.String({ minLength: 7, maxLength: 45 }),
hostname: t.String({ minLength: 1, maxLength: 255 }),
type: t.Union([t.Literal("stream"), t.Literal("local")]),
gpuFreeMb: t.Number({ minimum: 0 }),
branchId: t.Optional(t.String({ format: "uuid" })),
location: t.Optional(t.Union([t.Literal("ru1"), t.Literal("uae1")])),
tier: t.Optional(t.Union([t.Literal("demo"), t.Literal("prod")])),
}),
}
)
// PATCH /servers/:id - обновить сервер
.patch(
"/:id",
async ({ params, body, status, set }) => {
const { id } = params;
const { localIp, hostname, gpuFreeMb, branchId, location, tier } =
body as {
localIp?: string;
hostname?: string;
gpuFreeMb?: number;
branchId?: string;
location?: "ru1" | "uae1";
tier?: "demo" | "prod";
};
// Проверить существование сервера
const server = await serverService.findById(id);
if (!server) {
return status(404, "Server not found");
}
// Валидация для stream-серверов: нельзя удалить location
if (server.type === "stream") {
if (location === undefined && server.location === null) {
set.status = 400;
return { error: "Location cannot be removed from stream servers" };
}
}
// Валидация для local-серверов: нельзя удалить branchId
if (server.type === "local") {
if (branchId === undefined && server.branchId === null) {
set.status = 400;
return { error: "Branch ID cannot be removed from local servers" };
}
}
const updatedServer = await serverService.update(id, {
localIp,
hostname,
gpuFreeMb,
branchId,
location,
tier,
});
return { server: updatedServer };
},
{
body: t.Object({
localIp: t.Optional(t.String({ minLength: 7, maxLength: 45 })),
hostname: t.Optional(t.String({ minLength: 1, maxLength: 255 })),
gpuFreeMb: t.Optional(t.Number({ minimum: 0 })),
branchId: t.Optional(t.String({ format: "uuid" })),
location: t.Optional(t.Union([t.Literal("ru1"), t.Literal("uae1")])),
tier: t.Optional(t.Union([t.Literal("demo"), t.Literal("prod")])),
}),
}
)
// DELETE /servers/:id - удалить сервер
.delete("/:id", async ({ params, status }) => {
const { id } = params;
// Проверить существование сервера
const server = await serverService.findById(id);
if (!server) {
return status(404, "Server not found");
}
await serverService.delete(id);
return { message: "Server deleted successfully" };
});
+72
View File
@@ -6,6 +6,78 @@ import { apps } from "../db/schema/apps";
import { serverSessionService } from "../services/serverSession";
export const sessionController = new Elysia({ prefix: "/sessions" })
// PATCH /sessions/:id/status - обновить статус сессии (публичный endpoint для сессионного сервера)
.patch(
"/:id/status",
async ({ params, body, status }) => {
const { id } = params;
const {
status: sessionStatus,
appPid,
cirrusPid,
} = body as {
status?: "starting" | "started" | "ending" | "ended";
appPid?: number;
cirrusPid?: number;
};
// Проверить, что сессия существует
const session = await serverSessionService.findById(id);
if (!session) {
return status(404, "Session not found");
}
// Обновить сессию
const updatedSession = await serverSessionService.update(id, {
status: sessionStatus,
appPid,
cirrusPid,
});
return { session: updatedSession };
},
{
body: t.Object({
status: t.Optional(
t.Union([
t.Literal("starting"),
t.Literal("started"),
t.Literal("ending"),
t.Literal("ended"),
])
),
appPid: t.Optional(t.Number()),
cirrusPid: t.Optional(t.Number()),
}),
}
)
// POST /sessions/:id/assign-server - назначить сервер для сессии (публичный endpoint для сессионного сервера)
.post(
"/:id/assign-server",
async ({ params, body, status }) => {
const { id } = params;
const { requiredGpuMb } = body as { requiredGpuMb?: number };
try {
const updatedSession = await serverSessionService.assignServer(
id,
requiredGpuMb
);
return { session: updatedSession };
} catch (error) {
if (error instanceof Error) {
return status(400, error.message);
}
return status(500, "Failed to assign server");
}
},
{
body: t.Object({
requiredGpuMb: t.Optional(t.Number({ minimum: 0 })),
}),
}
)
// Все роуты требуют авторизации
.use(authMiddleware)
// GET /sessions - получить список сессий пользователя