From 9c36d1801d4141cc0807abde83845c9f5ce179cf Mon Sep 17 00:00:00 2001 From: Andres Date: Fri, 9 May 2025 14:01:19 +0200 Subject: [PATCH 1/5] wip --- src/app/(home)/admin/releases/page.tsx | 31 +++++++ .../(home)/admin/releases/releases-client.tsx | 90 +++++++++++++++++++ src/server/api/root.ts | 2 + src/server/api/routers/releases.ts | 38 ++++++++ src/server/api/trpc.ts | 14 +++ src/server/db/schema.ts | 13 +++ 6 files changed, 188 insertions(+) create mode 100644 src/app/(home)/admin/releases/page.tsx create mode 100644 src/app/(home)/admin/releases/releases-client.tsx create mode 100644 src/server/api/routers/releases.ts 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()), +}) From 91771bffc9d90698883867ba3d7188d58e105394 Mon Sep 17 00:00:00 2001 From: Andres Date: Tue, 13 May 2025 14:35:58 +0200 Subject: [PATCH 2/5] add releases endpoint --- src/app/api/releases/route.ts | 8 ++++++++ src/server/api/routers/releases.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/app/api/releases/route.ts diff --git a/src/app/api/releases/route.ts b/src/app/api/releases/route.ts new file mode 100644 index 0000000..a69d24d --- /dev/null +++ b/src/app/api/releases/route.ts @@ -0,0 +1,8 @@ +import { db } from '@/server/db' +import { releases } from '@/server/db/schema' + +export async function GET() { + const res = await db.select().from(releases) + + return Response.json(res) +} diff --git a/src/server/api/routers/releases.ts b/src/server/api/routers/releases.ts index c9b6d3c..9a053b3 100644 --- a/src/server/api/routers/releases.ts +++ b/src/server/api/routers/releases.ts @@ -10,7 +10,6 @@ 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 From fc4a20c1fc1e8900da51acead443df16389441c7 Mon Sep 17 00:00:00 2001 From: Andres Date: Sun, 8 Jun 2025 15:34:42 +0200 Subject: [PATCH 3/5] wip --- bun.lock | 3 + package.json | 1 + src/app/(home)/admin/releases/page.tsx | 6 +- .../(home)/admin/releases/releases-client.tsx | 398 ++++++++++++++++-- src/server/api/routers/releases.ts | 46 ++ src/server/db/schema.ts | 2 + 6 files changed, 422 insertions(+), 34 deletions(-) diff --git a/bun.lock b/bun.lock index b3ebcf4..8faeae4 100644 --- a/bun.lock +++ b/bun.lock @@ -77,6 +77,7 @@ "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", "vaul": "^1.1.2", + "zlib": "^1.0.5", "zod": "^3.24.2", }, "devDependencies": { @@ -1129,6 +1130,8 @@ "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + "zlib": ["zlib@1.0.5", "", {}, "sha512-40fpE2II+Cd3k8HWTWONfeKE2jL+P42iWJ1zzps5W51qcTsOUKM5Q5m2PFb0CLxlmFAaUuUdJGc3OfZy947v0w=="], + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], diff --git a/package.json b/package.json index cf33c60..1818d23 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "tailwind-merge": "^3.1.0", "tw-animate-css": "^1.2.5", "vaul": "^1.1.2", + "zlib": "^1.0.5", "zod": "^3.24.2" }, "devDependencies": { diff --git a/src/app/(home)/admin/releases/page.tsx b/src/app/(home)/admin/releases/page.tsx index baebd2d..c1c5c86 100644 --- a/src/app/(home)/admin/releases/page.tsx +++ b/src/app/(home)/admin/releases/page.tsx @@ -22,7 +22,11 @@ export default async function ReleasesPage() { return ( -
+
diff --git a/src/app/(home)/admin/releases/releases-client.tsx b/src/app/(home)/admin/releases/releases-client.tsx index 8282cc9..1c74bcc 100644 --- a/src/app/(home)/admin/releases/releases-client.tsx +++ b/src/app/(home)/admin/releases/releases-client.tsx @@ -1,9 +1,34 @@ 'use client' +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { Card, CardContent } from '@/components/ui/card' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' import { Table, TableBody, @@ -12,79 +37,386 @@ import { TableHeader, TableRow, } from '@/components/ui/table' +import { Textarea } from '@/components/ui/textarea' import { api } from '@/trpc/react' +import { Pencil, Trash2 } from 'lucide-react' +import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' +import { toast } from 'sonner' + export function ReleasesClient() { + const utils = api.useUtils() const [releases] = api.releases.getReleases.useSuspenseQuery() - const addRelease = api.releases.addRelease.useMutation() + const addRelease = api.releases.addRelease.useMutation({ + onSuccess: () => { + utils.releases.getReleases.invalidate() + toast.success('Release added successfully') + form.reset() + }, + onError: (error) => { + toast.error(`Error adding release: ${error.message}`) + }, + }) + const updateRelease = api.releases.updateRelease.useMutation({ + onSuccess: () => { + utils.releases.getReleases.invalidate() + toast.success('Release updated successfully') + setEditDialogOpen(false) + }, + onError: (error) => { + toast.error(`Error updating release: ${error.message}`) + }, + }) + const deleteRelease = api.releases.deleteRelease.useMutation({ + onSuccess: () => { + utils.releases.getReleases.invalidate() + toast.success('Release deleted successfully') + setDeleteDialogOpen(false) + }, + onError: (error) => { + toast.error(`Error deleting release: ${error.message}`) + }, + }) + + const [smodsVersions, setSmodsVersions] = useState(['latest']) + const [lovelyVersions, setLovelyVersions] = useState(['latest']) + const [editDialogOpen, setEditDialogOpen] = useState(false) + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [selectedRelease, setSelectedRelease] = useState< + (typeof releases)[0] | null + >(null) + + const SMODS_RELEASES_URL = + 'https://api.github.com/repos/Steamodded/smods/releases' + const LOVELY_RELEASES_BASE_URL = + 'https://github.com/ethangreen-dev/lovely-injector/releases' + + useEffect(() => { + // Fetch Steamodded versions + fetch(SMODS_RELEASES_URL) + .then((response) => response.json()) + .then((data) => { + const versions = data.map((release: any) => release.tag_name) + setSmodsVersions(['latest', ...versions]) + }) + .catch((error) => { + console.error('Error fetching Steamodded versions:', error) + }) + + // Fetch lovely injector versions + // Since we don't have a direct API for lovely injector, we'll use GitHub API + fetch( + 'https://api.github.com/repos/ethangreen-dev/lovely-injector/releases' + ) + .then((response) => response.json()) + .then((data) => { + const versions = data.map((release: any) => release.tag_name) + setLovelyVersions(['latest', ...versions]) + }) + .catch((error) => { + console.error('Error fetching lovely injector versions:', error) + }) + }, []) + const form = useForm({ defaultValues: { name: '', version: '', description: '', url: '', + smods_version: 'latest', + lovely_version: 'latest', }, }) + + const editForm = useForm({ + defaultValues: { + id: 0, + name: '', + version: '', + description: '', + url: '', + smods_version: 'latest', + lovely_version: 'latest', + }, + }) + + const handleEditRelease = (release: (typeof releases)[0]) => { + setSelectedRelease(release) + editForm.reset({ + id: release.id, + name: release.name, + version: release.version, + description: release.description || '', + url: release.url, + smods_version: release.smods_version || 'latest', + lovely_version: release.lovely_version || 'latest', + }) + setEditDialogOpen(true) + } + + const handleDeleteRelease = (release: (typeof releases)[0]) => { + setSelectedRelease(release) + setDeleteDialogOpen(true) + } return ( -
-

Releases

-
- +
+

Releases

+
+
- + Name Version Description URL + Steamodded Version + Lovely Injector Version + Actions {releases.map((release) => ( - - {release.name} + + {release.name} {release.version} - {release.description} - + +
+ {release.description} +
+
+ - {release.url} +
{release.url}
+ {release.smods_version || 'latest'} + {release.lovely_version || 'latest'} + + + +
))}
- - + +
+
+

Add New Release

addRelease.mutate(values))} > -
- - +
+ +
-
- - +
+ +
-
- - +
+ +