aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/.server/api/schema.d.ts54
-rw-r--r--frontend/app/.server/auth.ts2
-rw-r--r--frontend/app/routes/admin.dashboard.tsx28
-rw-r--r--frontend/app/routes/admin.users.tsx48
-rw-r--r--frontend/app/routes/dashboard.tsx4
5 files changed, 134 insertions, 2 deletions
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<string, never>;
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.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 (
+ <div>
+ <h1>[Admin] Dashboard</h1>
+ <p>
+ <Link to="/admin/users">Users</Link>
+ </p>
+ </div>
+ );
+}
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<typeof loader>()!;
+
+ return (
+ <div>
+ <div>
+ <h1>[Admin] Users</h1>
+ <ul>
+ {users.map((user) => (
+ <li key={user.user_id}>
+ {user.display_name} (uid={user.user_id} username={user.username})
+ {user.is_admin && <span> admin</span>}
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>
+ );
+}
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: {