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:
@@ -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" };
|
||||
});
|
||||
@@ -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 - получить список сессий пользователя
|
||||
|
||||
Reference in New Issue
Block a user