Added routes for login and bug reporting
This commit is contained in:
@@ -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)" },
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user