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 (
+
+ )
+ }
+
+ 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}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ )
+}
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()),
+})