From 2a58a47077ea878a59dabd84de814ebbbbe56b3f Mon Sep 17 00:00:00 2001 From: inmake Date: Mon, 15 Dec 2025 16:28:48 +0500 Subject: [PATCH] Add DemoPage route and integrate appController in server - Introduced a new DemoPage component and added a corresponding route for it in the client application. - Integrated appController into the server to manage application-related functionalities. --- client/src/main.tsx | 5 + client/src/pages/DemoPage.tsx | 166 ++++++++++++++++++++++++++++++++++ server/src/controllers/app.ts | 64 +++++++++++++ server/src/index.ts | 2 + 4 files changed, 237 insertions(+) create mode 100644 client/src/pages/DemoPage.tsx create mode 100644 server/src/controllers/app.ts diff --git a/client/src/main.tsx b/client/src/main.tsx index 0294a18..3141878 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -14,6 +14,7 @@ import PopupContainer from "./components/PopupContainer"; import ToastsContainer from "./components/toasts/ToastsContainer"; import TestPage from "./pages/TestPage"; import SessionPage from "./pages/SessionPage"; +import DemoPage from "./pages/DemoPage"; import { Toaster } from "react-hot-toast"; const router = createBrowserRouter([ @@ -45,6 +46,10 @@ const router = createBrowserRouter([ path: "/test", element: , }, + { + path: "/demo/:appName", + element: , + }, { path: "/sessions/:id", element: , diff --git a/client/src/pages/DemoPage.tsx b/client/src/pages/DemoPage.tsx new file mode 100644 index 0000000..8e877e9 --- /dev/null +++ b/client/src/pages/DemoPage.tsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router"; +import { api } from "../lib/api"; +import toast from "react-hot-toast"; +import { extractErrorMessage } from "../lib/errorUtils"; +import LoaderIcon from "../components/icons/LoaderIcon"; +import WarningIcon from "../components/icons/WarningIcon"; +import Button from "../components/ui/Button"; + +interface Session { + id: string; + appId: string; + userId: string | null; + mode: "stream" | "local"; + status: "starting" | "started" | "ending" | "ended"; + tier: "demo" | "prod"; + serverId: string | null; +} + +type PageState = "loading" | "error" | "not_found"; + +function DemoPage() { + const { appName } = useParams<{ appName: string }>(); + const navigate = useNavigate(); + const [state, setState] = useState("loading"); + const [error, setError] = useState(null); + + useEffect(() => { + if (!appName) { + setState("not_found"); + return; + } + + const startDemo = async () => { + try { + // Один запрос: создаём сессию по имени приложения + const response = await api + .post(`apps/demo/${encodeURIComponent(appName)}`) + .json<{ session: Session }>(); + + // Перенаправляем на страницу сессии + navigate(`/sessions/${response.session.id}`, { replace: true }); + } catch (err) { + console.error("Failed to start demo:", err); + + // Проверяем, не найдено ли приложение + if (err && typeof err === "object" && "response" in err) { + const response = (err as { response: Response }).response; + if (response.status === 404) { + setState("not_found"); + return; + } + } + + const errorMessage = await extractErrorMessage(err); + setError(errorMessage); + setState("error"); + + toast.error(errorMessage, { + duration: 5000, + position: "top-center", + }); + } + }; + + startDemo(); + }, [appName, navigate]); + + // Состояние загрузки + if (state === "loading") { + return ( +
+
+
+ +
+
+

+ Запуск демо +

+

+ Подготовка приложения... +

+
+
+ + {/* Декоративные элементы */} +
+
+
+
+
+ ); + } + + // Приложение не найдено + if (state === "not_found") { + return ( +
+
+
+
+
+ +
+
+
+

+ Приложение не найдено +

+

+ Приложение «{appName}» не существует или недоступно для демо-режима. +

+ +
+
+
+
+ ); + } + + // Ошибка создания сессии + if (state === "error") { + return ( +
+
+
+
+
+ +
+
+
+

+ Не удалось запустить демо +

+

+ {error || "Произошла неизвестная ошибка"} +

+
+ + +
+
+
+
+
+ ); + } + + return null; +} + +export default DemoPage; diff --git a/server/src/controllers/app.ts b/server/src/controllers/app.ts new file mode 100644 index 0000000..1c112d2 --- /dev/null +++ b/server/src/controllers/app.ts @@ -0,0 +1,64 @@ +import { Elysia, t } from "elysia"; +import { eq } from "drizzle-orm"; +import db from "../db"; +import { apps } from "../db/schema/apps"; +import { optionalAuthMiddleware } from "../middlewares/optionalAuth"; +import { serverSessionService } from "../services/serverSession"; +import { serverService } from "../services/server"; + +export const appController = new Elysia({ prefix: "/apps" }) + .use(optionalAuthMiddleware) + // POST /apps/demo/:name - создать демо-сессию по имени приложения + .post( + "/demo/:name", + async ({ params, currentUser, guestId, status }) => { + const { name } = params; + + // Найти приложение по имени + const app = await db.query.apps.findFirst({ + where: eq(apps.name, name), + }); + + if (!app) { + return status(404, "Приложение не найдено"); + } + + // Проверяем наличие guestId для неавторизованных пользователей + if (!currentUser && !guestId) { + return status(400, "Для неавторизованных пользователей требуется Guest ID"); + } + + // Проверяем, что есть доступные demo-серверы + const demoServers = await serverService.findAvailableStreamServers("demo"); + + if (demoServers.length === 0) { + return status( + 503, + "Нет доступных demo серверов. Пожалуйста, попробуйте позже." + ); + } + + // Создаём демо-сессию + try { + const newSession = await serverSessionService.create({ + appId: app.id, + userId: currentUser?.id, + guestId: currentUser ? undefined : guestId || undefined, + mode: "stream", + tier: "demo", + }); + + return { session: newSession }; + } catch (error) { + if (error instanceof Error) { + return status(503, error.message); + } + return status(500, "Не удалось создать сессию"); + } + }, + { + params: t.Object({ + name: t.String(), + }), + } + ); diff --git a/server/src/index.ts b/server/src/index.ts index 9393018..053353f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -6,6 +6,7 @@ import { companyController } from "./controllers/company"; import { branchController } from "./controllers/branch"; import { serverController } from "./controllers/server"; import { chatController } from "./controllers/chat"; +import { appController } from "./controllers/app"; import { serverSessionService } from "./services/serverSession"; import { saveChatMessage } from "./services/chat"; import { Server } from "socket.io"; @@ -27,6 +28,7 @@ app.use(companyController); app.use(branchController); app.use(serverController); app.use(chatController); +app.use(appController); app.listen(process.env.PORT || 3000);