mirror of
https://github.com/ershisan99/www.git
synced 2025-12-17 12:34:17 +00:00
wip
This commit is contained in:
31
src/app/(home)/admin/releases/page.tsx
Normal file
31
src/app/(home)/admin/releases/page.tsx
Normal file
@@ -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 (
|
||||
<div className={'container mx-auto pt-8'}>
|
||||
<div className={'prose'}>
|
||||
<h1>Forbidden</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
await api.releases.getReleases.prefetch()
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<HydrateClient>
|
||||
<div className={'container mx-auto pt-8'}>
|
||||
<ReleasesClient />
|
||||
</div>
|
||||
</HydrateClient>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
90
src/app/(home)/admin/releases/releases-client.tsx
Normal file
90
src/app/(home)/admin/releases/releases-client.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<h1 className={'text-2xl'}>Releases</h1>
|
||||
<div className={'mt-4'}>
|
||||
<Table className={'w-full table-auto'}>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Version</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>URL</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{releases.map((release) => (
|
||||
<TableRow key={release.id}>
|
||||
<TableCell>{release.name}</TableCell>
|
||||
<TableCell>{release.version}</TableCell>
|
||||
<TableCell>{release.description}</TableCell>
|
||||
<TableCell>
|
||||
<a
|
||||
href={release.url}
|
||||
target={'_blank'}
|
||||
rel={'noopener noreferrer'}
|
||||
className={'hover:underline'}
|
||||
>
|
||||
{release.url}
|
||||
</a>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Card className={'mt-8 max-w-xl'}>
|
||||
<CardContent>
|
||||
<form
|
||||
className={'space-y-4'}
|
||||
onSubmit={form.handleSubmit((values) => addRelease.mutate(values))}
|
||||
>
|
||||
<div className={'grid grid-cols-1 gap-2'}>
|
||||
<Label>Title</Label>
|
||||
<Input {...form.register('name')} />
|
||||
</div>
|
||||
<div className={'grid grid-cols-1 gap-2'}>
|
||||
<Label>Version</Label>
|
||||
<Input {...form.register('version')} />
|
||||
</div>
|
||||
<div className={'grid grid-cols-1 gap-2'}>
|
||||
<Label>Description</Label>
|
||||
<Input {...form.register('description')} />
|
||||
</div>
|
||||
<div className={'grid grid-cols-1 gap-2'}>
|
||||
<Label>URL</Label>
|
||||
<Input {...form.register('url')} />
|
||||
</div>
|
||||
<Button type={'submit'}>Add new release</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
38
src/server/api/routers/releases.ts
Normal file
38
src/server/api/routers/releases.ts
Normal file
@@ -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]
|
||||
}),
|
||||
})
|
||||
@@ -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 },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user