Added routes for login and bug reporting

This commit is contained in:
Flavio Fois
2026-02-26 08:53:50 +01:00
parent 19e199a578
commit 5761cbaa55
9 changed files with 518 additions and 16 deletions

View File

@@ -6,7 +6,17 @@ import {
getFile,
deleteBugReport,
updateBugReportStatus,
countNewReports,
generateReportZip,
} from "../services/bugReportService";
import {
listUsers,
createUser,
updateUser,
resetPassword,
deleteUser,
getUserById,
} from "../services/userService";
import { Log } from "../logger";
import type { BugReportStatus } from "../types";
@@ -18,9 +28,10 @@ export const adminRoutes = new Elysia({ prefix: "/api/admin" })
const page = parseInt(query.page || "1");
const pageSize = Math.min(parseInt(query.pageSize || "20"), 100);
const status = query.status as BugReportStatus | undefined;
const search = query.search || undefined;
Log("ADMIN", `List bug reports page=${page} pageSize=${pageSize} status=${status || "all"}`);
return await listBugReports({ page, pageSize, status });
Log("ADMIN", `List bug reports page=${page} pageSize=${pageSize} status=${status || "all"} search=${search || ""}`);
return await listBugReports({ page, pageSize, status, search });
},
{
query: t.Object({
@@ -34,10 +45,19 @@ export const adminRoutes = new Elysia({ prefix: "/api/admin" })
t.Literal("closed"),
])
),
search: t.Optional(t.String()),
}),
detail: { summary: "List bug reports (paginated)" },
detail: { summary: "List bug reports (paginated, filterable)" },
}
)
.get(
"/bug-reports/count",
async () => {
const count = await countNewReports();
return { count };
},
{ detail: { summary: "Count new bug reports" } }
)
.get(
"/bug-reports/:id",
async ({ params, error }) => {
@@ -93,6 +113,25 @@ export const adminRoutes = new Elysia({ prefix: "/api/admin" })
detail: { summary: "Download a bug report file" },
}
)
.get(
"/bug-reports/:id/download",
async ({ params, error, set }) => {
Log("ADMIN", `Download zip for report id=${params.id}`);
const zipBuffer = await generateReportZip(parseInt(params.id));
if (!zipBuffer)
return error(404, { success: false, message: "Report not found" });
set.headers["content-type"] = "application/zip";
set.headers["content-disposition"] =
`attachment; filename="report-${params.id}.zip"`;
set.headers["content-length"] = String(zipBuffer.length);
return new Response(new Uint8Array(zipBuffer));
},
{
params: t.Object({ id: t.String() }),
detail: { summary: "Download all files for a bug report as ZIP" },
}
)
.delete(
"/bug-reports/:id",
async ({ params, error }) => {
@@ -106,4 +145,91 @@ export const adminRoutes = new Elysia({ prefix: "/api/admin" })
params: t.Object({ id: t.String() }),
detail: { summary: "Delete a bug report and its files" },
}
)
// User management
.get(
"/users",
async () => {
Log("ADMIN", "List users");
return await listUsers();
},
{ detail: { summary: "List all users" } }
)
.post(
"/users",
async ({ body, error }) => {
Log("ADMIN", `Create user username=${body.username}`);
try {
const user = await createUser(body);
return { success: true, user };
} catch (err) {
if (err instanceof Error && err.message === "Username already exists") {
return error(409, { success: false, message: err.message });
}
throw err;
}
},
{
body: t.Object({
username: t.String({ minLength: 3, maxLength: 255 }),
displayname: t.String({ default: "" }),
password: t.String({ minLength: 1 }),
role: t.Union([t.Literal("admin"), t.Literal("user")]),
}),
detail: { summary: "Create a new user" },
}
)
.patch(
"/users/:id",
async ({ params, body, error }) => {
Log("ADMIN", `Update user id=${params.id}`);
const updated = await updateUser(params.id, body);
if (!updated)
return error(404, { success: false, message: "User not found" });
return { success: true, message: "User updated" };
},
{
params: t.Object({ id: t.String() }),
body: t.Object({
displayname: t.Optional(t.String()),
enabled: t.Optional(t.Boolean()),
}),
detail: { summary: "Update user displayname or enabled status" },
}
)
.post(
"/users/:id/reset-password",
async ({ params, body, error }) => {
Log("ADMIN", `Reset password for user id=${params.id}`);
const updated = await resetPassword(params.id, body.password);
if (!updated)
return error(404, { success: false, message: "User not found" });
return { success: true, message: "Password reset" };
},
{
params: t.Object({ id: t.String() }),
body: t.Object({ password: t.String({ minLength: 1 }) }),
detail: { summary: "Reset a user's password" },
}
)
.delete(
"/users/:id",
async ({ params, error }) => {
Log("ADMIN", `Delete user id=${params.id}`);
const user = await getUserById(params.id);
if (!user)
return error(404, { success: false, message: "User not found" });
if (user.role === "admin")
return error(400, { success: false, message: "Cannot delete an admin user" });
const deleted = await deleteUser(params.id);
if (!deleted)
return error(404, { success: false, message: "User not found" });
return { success: true, message: "User deleted" };
},
{
params: t.Object({ id: t.String() }),
detail: { summary: "Delete a user (non-admin only)" },
}
);