From 2385e1832fa0acc98fd285453701d5e829670955 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Jul 2024 01:35:29 +0900 Subject: feat: implement /admin/users page --- frontend/app/.server/api/schema.d.ts | 54 +++++++++++++++++++++++++++++++++++- frontend/app/.server/auth.ts | 2 +- frontend/app/routes/admin.users.tsx | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 frontend/app/routes/admin.users.tsx (limited to 'frontend/app') diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index cd409f7..bd96a00 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -223,11 +223,63 @@ export interface paths { patch?: never; trace?: never; }; + "/admin/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all users */ + get: { + parameters: { + query?: never; + header: { + Authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of users */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + users: components["schemas"]["User"][]; + }; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example Forbidden operation */ + message: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { schemas: { - JwtPayload: { + User: { /** @example 123 */ user_id: number; /** @example john */ diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts index 988b30c..b80166b 100644 --- a/frontend/app/.server/auth.ts +++ b/frontend/app/.server/auth.ts @@ -30,7 +30,7 @@ authenticator.use( "default", ); -export type User = components["schemas"]["JwtPayload"]; +export type User = components["schemas"]["User"]; export async function isAuthenticated( request: Request | Session, diff --git a/frontend/app/routes/admin.users.tsx b/frontend/app/routes/admin.users.tsx new file mode 100644 index 0000000..d9901a2 --- /dev/null +++ b/frontend/app/routes/admin.users.tsx @@ -0,0 +1,48 @@ +import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; +import { isAuthenticated } from "../.server/auth"; +import { apiClient } from "../.server/api/client"; + +export const meta: MetaFunction = () => { + return [{ title: "[Admin] Users | iOSDC 2024 Albatross.swift" }]; +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const { user, token } = await isAuthenticated(request, { + failureRedirect: "/login", + }); + if (!user.is_admin) { + throw new Error("Unauthorized"); + } + const { data, error } = await apiClient.GET("/admin/users", { + params: { + header: { + Authorization: `Bearer ${token}`, + }, + }, + }); + if (error) { + throw new Error(error.message); + } + return { users: data.users }; +} + +export default function AdminUsers() { + const { users } = useLoaderData()!; + + return ( +
+
+

[Admin] Users

+
    + {users.map((user) => ( +
  • + {user.display_name} (uid={user.user_id} username={user.username}) + {user.is_admin && admin} +
  • + ))} +
+
+
+ ); +} -- cgit v1.2.3-70-g09d2 From 5bcffc6a83021b2bcb06b8c6f622a1d623fc753e Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Jul 2024 01:49:28 +0900 Subject: feat(frontend): implement /admin/dashboard page --- frontend/app/routes/admin.dashboard.tsx | 28 ++++++++++++++++++++++++++++ frontend/app/routes/dashboard.tsx | 4 ++++ 2 files changed, 32 insertions(+) create mode 100644 frontend/app/routes/admin.dashboard.tsx (limited to 'frontend/app') diff --git a/frontend/app/routes/admin.dashboard.tsx b/frontend/app/routes/admin.dashboard.tsx new file mode 100644 index 0000000..d5f3809 --- /dev/null +++ b/frontend/app/routes/admin.dashboard.tsx @@ -0,0 +1,28 @@ +import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; +import { Link } from "@remix-run/react"; +import { isAuthenticated } from "../.server/auth"; + +export const meta: MetaFunction = () => { + return [{ title: "[Admin] Dashboard | iOSDC 2024 Albatross.swift" }]; +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const { user } = await isAuthenticated(request, { + failureRedirect: "/login", + }); + if (!user.is_admin) { + throw new Error("Unauthorized"); + } + return null; +} + +export default function AdminDashboard() { + return ( +
+

[Admin] Dashboard

+

+ Users +

+
+ ); +} diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx index 9afee86..badf8c4 100644 --- a/frontend/app/routes/dashboard.tsx +++ b/frontend/app/routes/dashboard.tsx @@ -1,4 +1,5 @@ import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; +import { redirect } from "@remix-run/node"; import { Link, useLoaderData, Form } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; import { apiClient } from "../.server/api/client"; @@ -11,6 +12,9 @@ export async function loader({ request }: LoaderFunctionArgs) { const { user, token } = await isAuthenticated(request, { failureRedirect: "/login", }); + if (user.is_admin) { + return redirect("/admin/dashboard"); + } const { data, error } = await apiClient.GET("/games", { params: { query: { -- cgit v1.2.3-70-g09d2