diff --git a/src/app/(home)/admin/releases/page.tsx b/src/app/(home)/admin/releases/page.tsx new file mode 100644 index 0000000..baebd2d --- /dev/null +++ b/src/app/(home)/admin/releases/page.tsx @@ -0,0 +1,31 @@ +import { ReleasesClient } from '@/app/(home)/admin/releases/releases-client' +import { auth } from '@/server/auth' +import { HydrateClient, api } from '@/trpc/server' +import { Suspense } from 'react' + +export default async function ReleasesPage() { + const session = await auth() + const isAdmin = session?.user.role === 'admin' + console.log(session) + if (!isAdmin) { + return ( +
+
+

Forbidden

+
+
+ ) + } + + await api.releases.getReleases.prefetch() + + return ( + + +
+ +
+
+
+ ) +} diff --git a/src/app/(home)/admin/releases/releases-client.tsx b/src/app/(home)/admin/releases/releases-client.tsx new file mode 100644 index 0000000..8282cc9 --- /dev/null +++ b/src/app/(home)/admin/releases/releases-client.tsx @@ -0,0 +1,90 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Card, CardContent } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { api } from '@/trpc/react' +import { useForm } from 'react-hook-form' +export function ReleasesClient() { + const [releases] = api.releases.getReleases.useSuspenseQuery() + const addRelease = api.releases.addRelease.useMutation() + const form = useForm({ + defaultValues: { + name: '', + version: '', + description: '', + url: '', + }, + }) + return ( +
+

Releases

+
+ + + + Name + Version + Description + URL + + + + {releases.map((release) => ( + + {release.name} + {release.version} + {release.description} + + + {release.url} + + + + ))} + +
+
+ + +
addRelease.mutate(values))} + > +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ ) +} diff --git a/src/server/api/root.ts b/src/server/api/root.ts index aa5f044..e21cf2e 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -2,6 +2,7 @@ import { discord_router } from '@/server/api/routers/discord' import { history_router } from '@/server/api/routers/history' import { leaderboard_router } from '@/server/api/routers/leaderboard' import { playerStateRouter } from '@/server/api/routers/player-state' +import { releasesRouter } from '@/server/api/routers/releases' import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc' /** @@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({ discord: discord_router, leaderboard: leaderboard_router, playerState: playerStateRouter, + releases: releasesRouter, }) // export type definition of API diff --git a/src/server/api/routers/releases.ts b/src/server/api/routers/releases.ts new file mode 100644 index 0000000..c9b6d3c --- /dev/null +++ b/src/server/api/routers/releases.ts @@ -0,0 +1,38 @@ +import { + adminProcedure, + createTRPCRouter, + publicProcedure, +} from '@/server/api/trpc' +import { db } from '@/server/db' +import { releases } from '@/server/db/schema' +import { z } from 'zod' + +export const releasesRouter = createTRPCRouter({ + getReleases: publicProcedure.query(async () => { + const res = await db.select().from(releases) + console.log(res) + return res + }), + addRelease: adminProcedure + .input( + z.object({ + version: z.string(), + url: z.string(), + name: z.string(), + description: z.string(), + }) + ) + .mutation(async ({ input }) => { + const res = await db + .insert(releases) + .values({ + version: input.version, + url: input.url, + name: input.name, + description: input.description, + }) + .returning() + + return res[0] + }), +}) diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 4974a32..920f978 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -131,3 +131,17 @@ export const protectedProcedure = t.procedure }, }) }) + +export const adminProcedure = t.procedure + .use(timingMiddleware) + .use(({ ctx, next }) => { + if (!ctx.session?.user || ctx.session.user.role !== 'admin') { + throw new TRPCError({ code: 'FORBIDDEN' }) + } + return next({ + ctx: { + // infers the `session` as non-nullable + session: { ...ctx.session, user: ctx.session.user }, + }, + }) + }) diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index cc047bb..9a209f0 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -124,3 +124,16 @@ export const verificationTokens = pgTable( }), (t) => [primaryKey({ columns: [t.identifier, t.token] })] ) + +export const releases = pgTable('mod_release', { + id: integer('id').primaryKey().generatedByDefaultAsIdentity(), + name: text('name').notNull(), + description: text('description'), + version: text('version').notNull(), + url: text('url').notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at') + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), +})