From 87525a6ef5dc122f3590300906c16999135ee95c Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 23 Jun 2025 00:20:33 +0200 Subject: [PATCH] Add admin page that can edit users Add admin page that's only accessible to admins with a listing of users and the ability to edit the access types of those users. --- components/Header.vue | 3 ++ components/TableUsers.vue | 73 +++++++++++++++++++++++++++++++ pages/admin.vue | 14 ++++++ server/api/admin/user.patch.ts | 79 ++++++++++++++++++++++++++++++++++ stores/account.ts | 1 + 5 files changed, 170 insertions(+) create mode 100644 components/TableUsers.vue create mode 100644 pages/admin.vue create mode 100644 server/api/admin/user.patch.ts diff --git a/components/Header.vue b/components/Header.vue index e4ff675..9096da8 100644 --- a/components/Header.vue +++ b/components/Header.vue @@ -11,6 +11,9 @@
  • Edit
  • +
  • + Admin +
  • diff --git a/components/TableUsers.vue b/components/TableUsers.vue new file mode 100644 index 0000000..78c33d4 --- /dev/null +++ b/components/TableUsers.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/pages/admin.vue b/pages/admin.vue new file mode 100644 index 0000000..44531fd --- /dev/null +++ b/pages/admin.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/server/api/admin/user.patch.ts b/server/api/admin/user.patch.ts new file mode 100644 index 0000000..dd3691d --- /dev/null +++ b/server/api/admin/user.patch.ts @@ -0,0 +1,79 @@ +import { readUsers, ServerUser, writeUsers } from "~/server/database"; +import { ApiUser, apiUserPatchSchema } from "~/shared/types/api"; +import { z } from "zod/v4-mini"; +import { broadcastEvent } from "~/server/streams"; + +function serverUserToApi(user: ServerUser): ApiUser { + if (user.deleted) { + return { + id: user.id, + updatedAt: user.updatedAt, + deleted: true, + } + } + return { + id: user.id, + updatedAt: user.updatedAt, + type: user.type, + name: user.name, + }; +} + +export default defineEventHandler(async (event) => { + const session = await requireServerSession(event); + if (session.account.type !== "admin") { + throw createError({ + statusCode: 403, + statusMessage: "Forbidden", + }); + } + const { success, error, data: patch } = apiUserPatchSchema.safeParse(await readBody(event)); + if (!success) { + throw createError({ + status: 400, + statusText: "Bad Request", + message: z.prettifyError(error), + }); + } + + const users = await readUsers(); + const user = users.find(user => user.id === patch.id); + if (!user || user.deleted) { + throw createError({ + status: 409, + statusText: "Conflict", + message: "User does not exist", + }); + + } + + if (patch.type) { + if (patch.type === "anonymous" || user.type === "anonymous") { + throw createError({ + status: 409, + statusText: "Conflict", + message: "Anonymous user type cannot be changed.", + }); + } + user.type = patch.type; + } + if (patch.name) { + if (user.type === "anonymous") { + throw createError({ + status: 409, + statusText: "Conflict", + message: "Anonymous user cannot have name set.", + }); + } + user.name = patch.name; + } + user.updatedAt = new Date().toISOString(); + await writeUsers(users); + broadcastEvent({ + type: "user-update", + data: serverUserToApi(user), + }) + + // Update Schedule counts. + await updateScheduleInterestedCounts(users); +}) diff --git a/stores/account.ts b/stores/account.ts index bf92f77..d1a80e1 100644 --- a/stores/account.ts +++ b/stores/account.ts @@ -27,6 +27,7 @@ export const useAccountStore = defineStore("account", () => { }); const getters = { + isAdmin: computed(() => state.type.value === "admin"), isCrew: computed(() => state.type.value === "crew" || state.type.value === "admin"), canEdit: computed(() => state.type.value === "admin" || state.type.value === "crew" ), canEditPublic: computed(() => state.type.value === "admin"),