Refactor Protected and Public Routes for Consistent Loading UI; Enhance HomePage with User Company and Branch Details; Update LoginPage Layout; Introduce User Relations in Auth Services

This commit is contained in:
2025-10-03 17:52:56 +05:00
parent 531e2d2e7e
commit 0b024af454
31 changed files with 1770 additions and 115 deletions
+12 -1
View File
@@ -4,6 +4,7 @@ import {
loginService,
registerService,
sessionService,
userService,
} from "../services/auth";
import type { LoginData, RegisterData } from "../services/auth/types";
@@ -43,7 +44,17 @@ export const authController = new Elysia({ prefix: "/auth" })
.use(authMiddleware)
// GET /me
.get("/me", async ({ currentUser }) => {
return { user: currentUser };
// Получить полную информацию о пользователе с филиалом и компанией
const userWithRelations = await userService.findByIdWithRelations(
currentUser.id
);
if (!userWithRelations) {
return { user: currentUser };
}
const userResponse = userService.sanitizeWithRelations(userWithRelations);
return { user: userResponse };
})
// POST /logout
.post("/logout", async ({ authSession }) => {
+194
View File
@@ -0,0 +1,194 @@
import { Elysia, t } from "elysia";
import { authMiddleware } from "../middlewares/auth";
import { branchService } from "../services/branch";
import { companyService } from "../services/company";
export const branchController = new Elysia({ prefix: "/branches" })
// Все роуты требуют авторизации
.use(authMiddleware)
// GET /branches - получить филиалы пользователя
.get("/my", async ({ currentUser }) => {
const branches = await branchService.findByUserId(currentUser.id);
return { branches };
})
// GET /branches/:id - получить филиал по ID
.get("/:id", async ({ params, status }) => {
const { id } = params;
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
return { branch };
})
// GET /branches/:id/users - получить пользователей филиала
.get("/:id/users", async ({ params, status }) => {
const { id } = params;
// Проверить существование филиала
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
const users = await branchService.getUsersByBranchId(id);
return { users };
})
// POST /branches - создать филиал
.post(
"/",
async ({ body, status }) => {
const { companyId, name, address, city, country } = body as {
companyId: string;
name: string;
address?: string;
city?: string;
country?: string;
};
// Проверить существование компании
const company = await companyService.findById(companyId);
if (!company) {
return status(404, "Company not found");
}
const branch = await branchService.create({
companyId,
name,
address,
city,
country,
});
return { branch };
},
{
body: t.Object({
companyId: t.String({ format: "uuid" }),
name: t.String({ minLength: 1, maxLength: 255 }),
address: t.Optional(t.String({ maxLength: 500 })),
city: t.Optional(t.String({ maxLength: 100 })),
country: t.Optional(t.String({ maxLength: 100 })),
}),
}
)
// PATCH /branches/:id - обновить филиал
.patch(
"/:id",
async ({ params, body, status }) => {
const { id } = params;
const { name, address, city, country } = body as {
name?: string;
address?: string;
city?: string;
country?: string;
};
// Проверить существование филиала
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
const updatedBranch = await branchService.update(id, {
name,
address,
city,
country,
});
return { branch: updatedBranch };
},
{
body: t.Object({
name: t.Optional(t.String({ minLength: 1, maxLength: 255 })),
address: t.Optional(t.String({ maxLength: 500 })),
city: t.Optional(t.String({ maxLength: 100 })),
country: t.Optional(t.String({ maxLength: 100 })),
}),
}
)
// DELETE /branches/:id - удалить филиал
.delete("/:id", async ({ params, status }) => {
const { id } = params;
// Проверить существование филиала
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
await branchService.delete(id);
return { message: "Branch deleted successfully" };
})
// POST /branches/:id/users - привязать пользователя к филиалу
.post(
"/:id/users",
async ({ params, body, status }) => {
const { id } = params;
const { userId } = body as { userId: string };
// Проверить существование филиала
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
try {
await branchService.assignUserToBranch(userId, id);
return { message: "User assigned to branch successfully" };
} catch (error) {
return status(409, "User is already assigned to this branch");
}
},
{
body: t.Object({
userId: t.String({ format: "uuid" }),
}),
}
)
// DELETE /branches/:id/users/:userId - отвязать пользователя от филиала
.delete("/:id/users/:userId", async ({ params, status }) => {
const { id, userId } = params;
// Проверить существование филиала
const branch = await branchService.findById(id);
if (!branch) {
return status(404, "Branch not found");
}
await branchService.removeUserFromBranch(userId, id);
return { message: "User removed from branch successfully" };
})
// POST /branches/:id/select - установить филиал как текущий для пользователя
.post("/:id/select", async ({ params, currentUser, status }) => {
const { id } = params;
try {
const updatedUser = await branchService.setUserCurrentBranch(
currentUser.id,
id
);
return {
message: "Current branch updated successfully",
currentBranchId: updatedUser.currentBranchId,
};
} catch (error) {
if (error instanceof Error) {
return status(400, error.message);
}
return status(500, "Failed to update current branch");
}
});
+93
View File
@@ -0,0 +1,93 @@
import { Elysia, t } from "elysia";
import { authMiddleware } from "../middlewares/auth";
import { companyService } from "../services/company";
export const companyController = new Elysia({ prefix: "/companies" })
// Все роуты требуют авторизации
.use(authMiddleware)
// GET /companies - получить все компании
.get("/", async () => {
const companies = await companyService.findAll();
return { companies };
})
// GET /companies/:id - получить компанию по ID
.get("/:id", async ({ params, status }) => {
const { id } = params;
const company = await companyService.findById(id);
if (!company) {
return status(404, "Company not found");
}
return { company };
})
// POST /companies - создать компанию
.post(
"/",
async ({ body }) => {
const { name, description } = body as {
name: string;
description?: string;
};
const company = await companyService.create({
name,
description,
});
return { company };
},
{
body: t.Object({
name: t.String({ minLength: 1, maxLength: 255 }),
description: t.Optional(t.String({ maxLength: 1000 })),
}),
}
)
// PATCH /companies/:id - обновить компанию
.patch(
"/:id",
async ({ params, body, status }) => {
const { id } = params;
const { name, description } = body as {
name?: string;
description?: string;
};
// Проверить существование компании
const company = await companyService.findById(id);
if (!company) {
return status(404, "Company not found");
}
const updatedCompany = await companyService.update(id, {
name,
description,
});
return { company: updatedCompany };
},
{
body: t.Object({
name: t.Optional(t.String({ minLength: 1, maxLength: 255 })),
description: t.Optional(t.String({ maxLength: 1000 })),
}),
}
)
// DELETE /companies/:id - удалить компанию
.delete("/:id", async ({ params, status }) => {
const { id } = params;
// Проверить существование компании
const company = await companyService.findById(id);
if (!company) {
return status(404, "Company not found");
}
await companyService.delete(id);
return { message: "Company deleted successfully" };
});
+208
View File
@@ -0,0 +1,208 @@
import { Elysia, t } from "elysia";
import { authMiddleware } from "../middlewares/auth";
import { eq } from "drizzle-orm";
import db from "../db";
import { apps } from "../db/schema/apps";
import { serverSessionService } from "../services/serverSession";
export const sessionController = new Elysia({ prefix: "/sessions" })
// Все роуты требуют авторизации
.use(authMiddleware)
// GET /sessions - получить список сессий пользователя
.get("/", async ({ currentUser, query }) => {
const { status, mode } = query as {
status?: "starting" | "started" | "ending" | "ended";
mode?: "stream" | "local";
};
const sessions = await serverSessionService.findByUserId(currentUser.id, {
status,
mode,
});
return { sessions };
})
// GET /sessions/:id - получить информацию о конкретной сессии
.get("/:id", async ({ params, currentUser, status }) => {
const { id } = params;
const session = await serverSessionService.findByIdForUser(
id,
currentUser.id
);
if (!session) {
return status(404, "Session not found");
}
return { session };
})
// POST /sessions - создать новую сессию
.post(
"/",
async ({ body, currentUser, status }) => {
const { appId, mode, serverId } = body as {
appId: string;
mode: "stream" | "local";
serverId?: string;
};
// Проверить, что приложение существует
const app = await db.query.apps.findFirst({
where: eq(apps.id, appId),
});
if (!app) {
return status(404, "App not found");
}
// Проверить, что пользователь не имеет активных сессий этого приложения
const hasActive = await serverSessionService.hasActiveSession(
currentUser.id,
appId
);
if (hasActive) {
return status(409, "User already has an active session for this app");
}
// Создать сессию
try {
const newSession = await serverSessionService.create({
appId,
userId: currentUser.id,
mode,
serverId,
});
return { session: newSession };
} catch (error) {
if (error instanceof Error) {
return status(503, error.message);
}
return status(500, "Failed to create session");
}
},
{
body: t.Object({
appId: t.String({ format: "uuid" }),
mode: t.Union([t.Literal("stream"), t.Literal("local")]),
serverId: t.Optional(t.String({ format: "uuid" })),
}),
}
)
// PATCH /sessions/:id - обновить статус сессии
.patch(
"/:id",
async ({ params, body, currentUser, status }) => {
const { id } = params;
const {
status: sessionStatus,
appPid,
cirrusPid,
endAt,
} = body as {
status?: "starting" | "started" | "ending" | "ended";
appPid?: number;
cirrusPid?: number;
endAt?: string;
};
// Проверить, что сессия существует и принадлежит пользователю
const session = await serverSessionService.findByIdForUser(
id,
currentUser.id
);
if (!session) {
return status(404, "Session not found");
}
// Обновить сессию
const updatedSession = await serverSessionService.update(id, {
status: sessionStatus,
appPid,
cirrusPid,
endAt: endAt ? new Date(endAt) : undefined,
});
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()),
endAt: t.Optional(t.String({ format: "date-time" })),
}),
}
)
// DELETE /sessions/:id - удалить (завершить) сессию
.delete("/:id", async ({ params, currentUser, status }) => {
const { id } = params;
// Проверить, что сессия существует и принадлежит пользователю
const session = await serverSessionService.findByIdForUser(
id,
currentUser.id
);
if (!session) {
return status(404, "Session not found");
}
// Если сессия активна, изменить статус на "ending"
if (session.status === "started" || session.status === "starting") {
await serverSessionService.end(id);
return { message: "Session is ending" };
}
// Если сессия уже завершена или завершается
return { message: "Session already ended or ending" };
})
// POST /sessions/:id/extend - продлить сессию
.post(
"/:id/extend",
async ({ params, body, currentUser, status }) => {
const { id } = params;
const { minutes } = body as { minutes: number };
// Проверить, что сессия существует и принадлежит пользователю
const session = await serverSessionService.findByIdForUser(
id,
currentUser.id
);
if (!session) {
return status(404, "Session not found");
}
// Проверить, что сессия активна
if (session.status !== "started") {
return status(400, "Can only extend active sessions");
}
// Продлить сессию
try {
const updatedSession = await serverSessionService.extend(id, minutes);
return { session: updatedSession };
} catch (error) {
if (error instanceof Error) {
return status(400, error.message);
}
return status(500, "Failed to extend session");
}
},
{
body: t.Object({
minutes: t.Number({ minimum: 1, maximum: 120 }),
}),
}
);