diff --git a/src/app/(home)/admin/releases/page.tsx b/src/app/(home)/admin/releases/page.tsx index c1c5c86..5098ed1 100644 --- a/src/app/(home)/admin/releases/page.tsx +++ b/src/app/(home)/admin/releases/page.tsx @@ -17,7 +17,10 @@ export default async function ReleasesPage() { ) } - await api.releases.getReleases.prefetch() + await Promise.all([ + api.releases.getReleases.prefetch(), + api.branches.getBranches.prefetch(), + ]) return ( diff --git a/src/app/(home)/admin/releases/releases-client.tsx b/src/app/(home)/admin/releases/releases-client.tsx index 7484741..43aca41 100644 --- a/src/app/(home)/admin/releases/releases-client.tsx +++ b/src/app/(home)/admin/releases/releases-client.tsx @@ -11,7 +11,6 @@ import { AlertDialogTitle, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, @@ -80,8 +79,42 @@ export function ReleasesClient() { const [smodsVersions, setSmodsVersions] = useState(['latest']) const [lovelyVersions, setLovelyVersions] = useState(['latest']) + const [newBranch, setNewBranch] = useState('') + + // Fetch branches from the database + const [branches] = api.branches.getBranches.useSuspenseQuery() + console.log(branches) + // Add branch mutation + const addBranch = api.branches.addBranch.useMutation({ + onSuccess: () => { + utils.branches.getBranches.invalidate() + toast.success('Branch added successfully') + setNewBranch('') + }, + onError: (error) => { + toast.error(`Error adding branch: ${error.message}`) + }, + }) + + // Delete branch mutation + const deleteBranch = api.branches.deleteBranch.useMutation({ + onSuccess: () => { + utils.branches.getBranches.invalidate() + toast.success('Branch deleted successfully') + }, + onError: (error) => { + toast.error(`Error deleting branch: ${error.message}`) + }, + }) + + const handleAddBranch = () => { + if (newBranch && !branches.some((branch) => branch.name === newBranch)) { + addBranch.mutate({ name: newBranch }) + } + } const [editDialogOpen, setEditDialogOpen] = useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) + const [branchManagementOpen, setBranchManagementOpen] = useState(false) const [selectedRelease, setSelectedRelease] = useState< (typeof releases)[0] | null >(null) @@ -126,6 +159,7 @@ export function ReleasesClient() { url: '', smods_version: 'latest', lovely_version: 'latest', + branchId: 1, }, }) @@ -138,6 +172,7 @@ export function ReleasesClient() { url: '', smods_version: 'latest', lovely_version: 'latest', + branchId: 1, }, }) @@ -151,6 +186,7 @@ export function ReleasesClient() { url: release.url, smods_version: release.smods_version || 'latest', lovely_version: release.lovely_version || 'latest', + branchId: release.branchId, }) setEditDialogOpen(true) } @@ -170,6 +206,7 @@ export function ReleasesClient() { Version Description URL + Branch Steamodded Version Lovely Injector Version Actions @@ -196,6 +233,7 @@ export function ReleasesClient() {
{release.url}
+ {release.branchName || 'main'} {release.smods_version || 'latest'} {release.lovely_version || 'latest'} @@ -247,7 +285,7 @@ export function ReleasesClient() { -
+
+
+ +
+ +
+
+
+
+
+ + +
-
+
+
+ + +
@@ -414,6 +513,80 @@ export function ReleasesClient() { + + {/* Branch Management Modal */} + + + + Manage Branches + + Add new branches or remove existing ones. + + +
+
+ +
+ setNewBranch(e.target.value)} + placeholder='Enter new branch name' + /> + +
+
+
+ +
+ {branches.length === 0 ? ( +

+ No branches found +

+ ) : ( +
    + {branches.map((branch) => ( +
  • + {branch.name} + {branch.name !== 'main' && ( + + )} +
  • + ))} +
+ )} +
+
+
+ + + +
+
) } diff --git a/src/app/api/releases/route.ts b/src/app/api/releases/route.ts index a69d24d..33c3861 100644 --- a/src/app/api/releases/route.ts +++ b/src/app/api/releases/route.ts @@ -1,8 +1,24 @@ import { db } from '@/server/db' -import { releases } from '@/server/db/schema' +import { branches, releases } from '@/server/db/schema' +import { eq } from 'drizzle-orm' export async function GET() { - const res = await db.select().from(releases) + const res = await db + .select({ + id: releases.id, + name: releases.name, + description: releases.description, + version: releases.version, + url: releases.url, + smods_version: releases.smods_version, + lovely_version: releases.lovely_version, + branchId: releases.branchId, + branchName: branches.name, + createdAt: releases.createdAt, + updatedAt: releases.updatedAt, + }) + .from(releases) + .leftJoin(branches, eq(releases.branchId, branches.id)) return Response.json(res) } diff --git a/src/server/api/root.ts b/src/server/api/root.ts index e21cf2e..b860e49 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -1,3 +1,4 @@ +import { branchesRouter } from '@/server/api/routers/branches' import { discord_router } from '@/server/api/routers/discord' import { history_router } from '@/server/api/routers/history' import { leaderboard_router } from '@/server/api/routers/leaderboard' @@ -11,6 +12,7 @@ import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc' * All routers added in /api/routers should be manually added here. */ export const appRouter = createTRPCRouter({ + branches: branchesRouter, history: history_router, discord: discord_router, leaderboard: leaderboard_router, diff --git a/src/server/api/routers/branches.ts b/src/server/api/routers/branches.ts new file mode 100644 index 0000000..9207515 --- /dev/null +++ b/src/server/api/routers/branches.ts @@ -0,0 +1,53 @@ +import { + adminProcedure, + createTRPCRouter, + publicProcedure, +} from '@/server/api/trpc' +import { db } from '@/server/db' +import { branches } from '@/server/db/schema' +import { z } from 'zod' +import { eq } from 'drizzle-orm' + +export const branchesRouter = createTRPCRouter({ + getBranches: publicProcedure.query(async () => { + const res = await db.select().from(branches) + return res + }), + addBranch: adminProcedure + .input( + z.object({ + name: z.string(), + }) + ) + .mutation(async ({ input }) => { + try { + const res = await db + .insert(branches) + .values({ + name: input.name, + }) + .returning() + + return res[0] + } catch (error) { + // Handle unique constraint violation + if (error instanceof Error && error.message.includes('unique constraint')) { + throw new Error('Branch with this name already exists') + } + throw error + } + }), + deleteBranch: adminProcedure + .input( + z.object({ + id: z.number(), + }) + ) + .mutation(async ({ input }) => { + await db + .delete(branches) + .where(eq(branches.id, input.id)) + + return { success: true } + }), +}) \ No newline at end of file diff --git a/src/server/api/routers/releases.ts b/src/server/api/routers/releases.ts index 75aa9c0..e338b44 100644 --- a/src/server/api/routers/releases.ts +++ b/src/server/api/routers/releases.ts @@ -4,13 +4,28 @@ import { publicProcedure, } from '@/server/api/trpc' import { db } from '@/server/db' -import { releases } from '@/server/db/schema' -import { z } from 'zod' +import { branches, releases } from '@/server/db/schema' import { eq } from 'drizzle-orm' +import { z } from 'zod' export const releasesRouter = createTRPCRouter({ getReleases: publicProcedure.query(async () => { - const res = await db.select().from(releases) + const res = await db + .select({ + id: releases.id, + name: releases.name, + description: releases.description, + version: releases.version, + url: releases.url, + smods_version: releases.smods_version, + lovely_version: releases.lovely_version, + branchId: releases.branchId, + branchName: branches.name, + createdAt: releases.createdAt, + updatedAt: releases.updatedAt, + }) + .from(releases) + .leftJoin(branches, eq(releases.branchId, branches.id)) return res }), addRelease: adminProcedure @@ -22,6 +37,7 @@ export const releasesRouter = createTRPCRouter({ description: z.string(), smods_version: z.string().default('latest'), lovely_version: z.string().default('latest'), + branchId: z.number().default(1), }) ) .mutation(async ({ input }) => { @@ -34,6 +50,7 @@ export const releasesRouter = createTRPCRouter({ description: input.description, smods_version: input.smods_version, lovely_version: input.lovely_version, + branchId: input.branchId, }) .returning() @@ -49,6 +66,7 @@ export const releasesRouter = createTRPCRouter({ description: z.string(), smods_version: z.string().default('latest'), lovely_version: z.string().default('latest'), + branchId: z.number().default(1), }) ) .mutation(async ({ input }) => { @@ -61,6 +79,7 @@ export const releasesRouter = createTRPCRouter({ description: input.description, smods_version: input.smods_version, lovely_version: input.lovely_version, + branchId: input.branchId, }) .where(eq(releases.id, input.id)) .returning() @@ -74,9 +93,7 @@ export const releasesRouter = createTRPCRouter({ }) ) .mutation(async ({ input }) => { - await db - .delete(releases) - .where(eq(releases.id, input.id)) + await db.delete(releases).where(eq(releases.id, input.id)) return { success: true } }), diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 2ee5845..1cb6380 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -125,6 +125,13 @@ export const verificationTokens = pgTable( (t) => [primaryKey({ columns: [t.identifier, t.token] })] ) +export const branches = pgTable('mod_branches', { + id: integer('id').primaryKey().generatedByDefaultAsIdentity(), + name: text('name').notNull().unique(), + description: text('description'), + createdAt: timestamp('created_at').notNull().defaultNow(), +}) + export const releases = pgTable('mod_release', { id: integer('id').primaryKey().generatedByDefaultAsIdentity(), name: text('name').notNull(), @@ -133,9 +140,24 @@ export const releases = pgTable('mod_release', { url: text('url').notNull(), smods_version: text('smods_version').default('latest'), lovely_version: text('lovely_version').default('latest'), + branchId: integer('branch_id') + .references(() => branches.id) + .notNull() + .default(1), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at') .notNull() .defaultNow() .$onUpdate(() => new Date()), }) + +export const branchesRelations = relations(branches, ({ many }) => ({ + releases: many(releases), +})) + +export const releasesRelations = relations(releases, ({ one }) => ({ + branch: one(branches, { + fields: [releases.branchId], + references: [branches.id], + }), +}))