diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 0cb8087..6caa026 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -157,3 +157,56 @@ export const matchGameScore = pgTable( }, (t) => [index('idx_mgs_game').on(t.matchGameId)] ); + +import { relations } from 'drizzle-orm'; + +// ── Relations ─────────────────────────────────────────────────────────── + +export const teamRelations = relations(team, ({ many }) => ({ + members: many(teamMember) +})); + +export const teamMemberRelations = relations(teamMember, ({ one }) => ({ + team: one(team, { fields: [teamMember.teamId], references: [team.id] }) +})); + +export const mappoolRelations = relations(mappool, ({ many }) => ({ + slots: many(mappoolSlot) +})); + +export const mappoolSlotRelations = relations(mappoolSlot, ({ one }) => ({ + mappool: one(mappool, { fields: [mappoolSlot.mappoolId], references: [mappool.id] }) +})); + +export const matchRelations = relations(match, ({ one, many }) => ({ + mappool: one(mappool, { fields: [match.mappoolId], references: [mappool.id] }), + participants: many(matchParticipant), + games: many(matchGame) +})); + +export const matchParticipantRelations = relations(matchParticipant, ({ one, many }) => ({ + match: one(match, { fields: [matchParticipant.matchId], references: [match.id] }), + team: one(team, { fields: [matchParticipant.teamId], references: [team.id] }), + players: many(matchParticipantPlayer) +})); + +export const matchParticipantPlayerRelations = relations(matchParticipantPlayer, ({ one }) => ({ + participant: one(matchParticipant, { + fields: [matchParticipantPlayer.participantId], + references: [matchParticipant.id] + }) +})); + +export const matchGameRelations = relations(matchGame, ({ one, many }) => ({ + match: one(match, { fields: [matchGame.matchId], references: [match.id] }), + slot: one(mappoolSlot, { fields: [matchGame.mappoolSlotId], references: [mappoolSlot.id] }), + scores: many(matchGameScore) +})); + +export const matchGameScoreRelations = relations(matchGameScore, ({ one }) => ({ + game: one(matchGame, { fields: [matchGameScore.matchGameId], references: [matchGame.id] }), + player: one(matchParticipantPlayer, { + fields: [matchGameScore.playerId], + references: [matchParticipantPlayer.id] + }) +})); diff --git a/src/routes/api/mappools/+server.ts b/src/routes/api/mappools/+server.ts new file mode 100644 index 0000000..6e40215 --- /dev/null +++ b/src/routes/api/mappools/+server.ts @@ -0,0 +1,33 @@ +import { json, error } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { mappool } from '$lib/server/db/schema'; +import type { RequestHandler } from './$types'; + +// List all mappools +export const GET: RequestHandler = async ({ locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + const mappools = await db.query.mappool.findMany({ + orderBy: (m, { desc }) => [desc(m.createdAt)] + }); + + return json(mappools); +}; + +// Create a mappool +export const POST: RequestHandler = async ({ request, locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + const { name } = await request.json(); + if (!name?.trim()) error(400, 'Name is required'); + + const [created] = await db + .insert(mappool) + .values({ + name: name.trim(), + createdBy: locals.user.id + }) + .returning(); + + return json(created, { status: 201 }); +}; diff --git a/src/routes/api/mappools/[id]/+server.ts b/src/routes/api/mappools/[id]/+server.ts new file mode 100644 index 0000000..72fdcd0 --- /dev/null +++ b/src/routes/api/mappools/[id]/+server.ts @@ -0,0 +1,31 @@ +import { json, error } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { mappool, mappoolSlot } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import type { RequestHandler } from './$types'; + +// Get a mappool with its slots +export const GET: RequestHandler = async ({ params, locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + const pool = await db.query.mappool.findFirst({ + where: eq(mappool.id, params.id) + }); + + if (!pool) error(404, 'Mappool not found'); + + const slots = await db.query.mappoolSlot.findMany({ + where: eq(mappoolSlot.mappoolId, params.id), + orderBy: (s, { asc }) => [asc(s.category), asc(s.orderInCategory)] + }); + + return json({ ...pool, slots }); +}; + +// Delete a mappool +export const DELETE: RequestHandler = async ({ params, locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + await db.delete(mappool).where(eq(mappool.id, params.id)); + return json({ ok: true }); +}; diff --git a/src/routes/api/mappools/[id]/slots/+server.ts b/src/routes/api/mappools/[id]/slots/+server.ts new file mode 100644 index 0000000..8bb257c --- /dev/null +++ b/src/routes/api/mappools/[id]/slots/+server.ts @@ -0,0 +1,38 @@ +import { json, error } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { mappoolSlot } from '$lib/server/db/schema'; +import { eq, and } from 'drizzle-orm'; +import { getBeatmap } from '$lib/server/osu/api'; +import type { RequestHandler } from './$types'; + +// Add a slot to a mappool +export const POST: RequestHandler = async ({ params, request, locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + const { beatmapId, category, mods } = await request.json(); + + if (!beatmapId || !category) { + error(400, 'beatmapId and category are required'); + } + + // Count existing slots in this category to auto-increment order + const existing = await db.query.mappoolSlot.findMany({ + where: and( + eq(mappoolSlot.mappoolId, params.id), + eq(mappoolSlot.category, category.toUpperCase()) + ) + }); + + const [created] = await db + .insert(mappoolSlot) + .values({ + mappoolId: params.id, + beatmapId: String(beatmapId), + category: category.toUpperCase(), + orderInCategory: existing.length + 1, + mods: mods ?? [] + }) + .returning(); + + return json(created, { status: 201 }); +}; diff --git a/src/routes/api/mappools/[id]/slots/[slotId]/+server.ts b/src/routes/api/mappools/[id]/slots/[slotId]/+server.ts new file mode 100644 index 0000000..f53f26f --- /dev/null +++ b/src/routes/api/mappools/[id]/slots/[slotId]/+server.ts @@ -0,0 +1,12 @@ +import { json, error } from '@sveltejs/kit'; +import { db } from '$lib/server/db'; +import { mappoolSlot } from '$lib/server/db/schema'; +import { eq } from 'drizzle-orm'; +import type { RequestHandler } from './$types'; + +export const DELETE: RequestHandler = async ({ params, locals }) => { + if (!locals.user) error(401, 'Not logged in'); + + await db.delete(mappoolSlot).where(eq(mappoolSlot.id, params.slotId)); + return json({ ok: true }); +};