From f723d90e657f61a678e42a1f9364e277c41b85fc Mon Sep 17 00:00:00 2001 From: unurled Date: Sat, 5 Jul 2025 23:58:07 +0200 Subject: [PATCH] add rounds and prepare for frontend --- src/lib/server/db/index.ts | 2 + src/lib/server/db/schema/competitions.ts | 48 ++++- src/lib/server/db/schema/matches.ts | 18 +- src/lib/server/db/schema/rounds.ts | 24 +++ src/lib/server/db/schema/schedulingmodes.ts | 10 +- src/routes/+page.server.ts | 14 +- src/routes/competitions/[id]/+page.server.ts | 63 +++--- src/routes/competitions/[id]/+page.svelte | 191 ++++++++++--------- src/routes/competitions/new/+page.server.ts | 1 - 9 files changed, 207 insertions(+), 164 deletions(-) create mode 100644 src/lib/server/db/schema/rounds.ts diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index 08e926a..018c0dd 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -7,6 +7,7 @@ import * as fields from './schema/fields'; import * as matches from './schema/matches'; import * as permissions from './schema/permissions'; import * as roles from './schema/roles'; +import * as rounds from './schema/rounds'; import * as schedulingmodes from './schema/schedulingmodes'; import * as sessions from './schema/sessions'; import * as teams from './schema/teams'; @@ -24,6 +25,7 @@ export const db = drizzle(client, { ...matches, ...permissions, ...roles, + ...rounds, ...schedulingmodes, ...sessions, ...teams, diff --git a/src/lib/server/db/schema/competitions.ts b/src/lib/server/db/schema/competitions.ts index 64c0fdc..1a26b06 100644 --- a/src/lib/server/db/schema/competitions.ts +++ b/src/lib/server/db/schema/competitions.ts @@ -1,12 +1,12 @@ import * as t from 'drizzle-orm/pg-core'; import { timestamps } from '../util'; -import { relations } from 'drizzle-orm'; +import { eq, relations } from 'drizzle-orm'; import { users } from './users'; -import { schedulingModes } from './schedulingmodes'; -import { matches } from './matches'; import { breakperiods } from './breakperiods'; import { fields } from './fields'; import { teams } from './teams'; +import { rounds } from './rounds'; +import { db } from '..'; export const competitions = t.pgTable('competitions', { id: t.serial('id').primaryKey(), @@ -14,21 +14,55 @@ export const competitions = t.pgTable('competitions', { description: t.text('description'), start_date: t.timestamp({ withTimezone: true, mode: 'date' }).notNull(), location: t.text('location'), - current_scheduling_mode_id: t.integer('current_scheduling_mode_id').notNull(), owner: t.text('owner').notNull(), ...timestamps }); export const competitionsRelations = relations(competitions, ({ one, many }) => ({ - scheduling_modes: many(schedulingModes), owner: one(users, { fields: [competitions.owner], references: [users.id] }), - matches: many(matches), breakperiods: many(breakperiods), fields: many(fields), - teams: many(teams) + teams: many(teams), + rounds: many(rounds) })); export type Competition = typeof competitions.$inferSelect; + +export async function getCompetitionWithAll(id: number) { + return await db.query.competitions.findFirst({ + where: eq(competitions.id, id), + with: { + breakperiods: true, + fields: true, + rounds: { + with: { + matches: true, + schedulingMode: true + } + }, + teams: true + } + }); +} + +export async function getCompetitionsWithAll(skip: number = 0, take: number = 10) { + return await db.query.competitions.findMany({ + orderBy: (competitions, { desc }) => [desc(competitions.start_date)], + with: { + breakperiods: true, + fields: true, + rounds: { + with: { + matches: true, + schedulingMode: true + } + }, + teams: true + }, + limit: take, + offset: skip + }); +} diff --git a/src/lib/server/db/schema/matches.ts b/src/lib/server/db/schema/matches.ts index 6ed3c1e..7968418 100644 --- a/src/lib/server/db/schema/matches.ts +++ b/src/lib/server/db/schema/matches.ts @@ -1,16 +1,13 @@ import * as t from 'drizzle-orm/pg-core'; import { timestamps } from '../util'; -import { competitions } from './competitions'; import { relations } from 'drizzle-orm'; import { fields } from './fields'; -import { schedulingModes } from './schedulingmodes'; +import { rounds } from './rounds'; export const status = t.pgEnum('match_status', ['pending', 'running', 'finished', 'cancelled']); -export const matches = t.pgTable('match', { +export const matches = t.pgTable('matches', { id: t.serial('id').primaryKey(), - competition_id: t.integer('competition_id').notNull(), - scheduling_mode_id: t.integer('scheduling_mode_id').notNull(), field_id: t.integer('field_id').notNull(), team1: t.text('team1').notNull(), team2: t.text('team2').notNull(), @@ -20,21 +17,18 @@ export const matches = t.pgTable('match', { score2: t.integer('score2').notNull(), status: status('status').notNull(), match_number: t.integer('match_number').notNull(), + round_id: t.integer('round').notNull(), ...timestamps }); export const matchRelations = relations(matches, ({ one }) => ({ - competition: one(competitions, { - fields: [matches.competition_id], - references: [competitions.id] - }), field: one(fields, { fields: [matches.field_id], references: [fields.id] }), - scheduling_mode: one(schedulingModes, { - fields: [matches.scheduling_mode_id], - references: [schedulingModes.id] + round: one(rounds, { + fields: [matches.round_id], + references: [rounds.id] }) })); diff --git a/src/lib/server/db/schema/rounds.ts b/src/lib/server/db/schema/rounds.ts new file mode 100644 index 0000000..473bfa1 --- /dev/null +++ b/src/lib/server/db/schema/rounds.ts @@ -0,0 +1,24 @@ +import { relations } from 'drizzle-orm'; +import * as t from 'drizzle-orm/pg-core'; +import { matches } from './matches'; +import { competitions } from './competitions'; +import { schedulingModes } from './schedulingmodes'; + +export const rounds = t.pgTable('rounds', { + id: t.serial('id').primaryKey(), + nb_matches: t.integer('nb_matches').notNull(), + competition_id: t.integer('competition_id').notNull(), + scheduling_mode: t.integer('scheduling_mode').notNull() +}); + +export const roundsRelations = relations(rounds, ({ many, one }) => ({ + competition: one(competitions, { + fields: [rounds.competition_id], + references: [competitions.id] + }), + matches: many(matches), + schedulingMode: one(schedulingModes, { + fields: [rounds.scheduling_mode], + references: [schedulingModes.id] + }) +})); diff --git a/src/lib/server/db/schema/schedulingmodes.ts b/src/lib/server/db/schema/schedulingmodes.ts index 8863672..8156115 100644 --- a/src/lib/server/db/schema/schedulingmodes.ts +++ b/src/lib/server/db/schema/schedulingmodes.ts @@ -1,24 +1,24 @@ import * as t from 'drizzle-orm/pg-core'; import { timestamps } from '../util'; -import { competitions } from './competitions'; import { relations } from 'drizzle-orm'; +import { rounds } from './rounds'; export const schedulingModes = t.pgTable('scheduling_modes', { id: t.serial('id').primaryKey(), - competition_id: t.integer('competition_id').notNull(), name: t.text('name').notNull(), description: t.text('description'), algorithm: t.text('algorithm').notNull(), config: t.jsonb('config').notNull(), sequence_order: t.integer('sequence_order').notNull(), status: t.text('status').notNull(), + round_id: t.integer('round_id').notNull(), ...timestamps }); export const schedulingModesRelations = relations(schedulingModes, ({ one }) => ({ - competition: one(competitions, { - fields: [schedulingModes.competition_id], - references: [competitions.id] + round: one(rounds, { + fields: [schedulingModes.round_id], + references: [rounds.id] }) })); diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 0cb3f9c..13de5ea 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,4 +1,4 @@ -import { db } from '$lib/server/db'; +import { getCompetitionsWithAll } from '@/server/db/schema/competitions'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals }) => { @@ -14,17 +14,7 @@ export const load: PageServerLoad = async ({ locals }) => { try { // Fetch competitions from the database - const userCompetitions = db.query.competitions.findMany({ - orderBy: (competitions, { desc }) => [desc(competitions.start_date)], - limit: 10, - with: { - breakperiods: true, - fields: true, - matches: true, - scheduling_modes: true, - teams: true - } - }); + const userCompetitions = getCompetitionsWithAll(); return { competitions: await userCompetitions diff --git a/src/routes/competitions/[id]/+page.server.ts b/src/routes/competitions/[id]/+page.server.ts index 36ef751..d69b36b 100644 --- a/src/routes/competitions/[id]/+page.server.ts +++ b/src/routes/competitions/[id]/+page.server.ts @@ -1,6 +1,6 @@ import { db } from '@/server/db'; import type { Actions, PageServerLoad } from './$types'; -import { competitions } from '@/server/db/schema/competitions'; +import { competitions, getCompetitionWithAll } from '@/server/db/schema/competitions'; import { error, redirect } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; import { and } from 'drizzle-orm'; @@ -10,21 +10,16 @@ export const load: PageServerLoad = async ({ params, locals }) => { try { const id = Number.parseInt(params.id); - const competition = await db.query.competitions.findFirst({ - where: and(eq(competitions.id, id), eq(competitions.owner, locals.user.id)), - with: { - breakperiods: true, - fields: true, - matches: true, - scheduling_modes: true, - teams: true - } - }); + const competition = await getCompetitionWithAll(id); if (!competition) { throw error(404, 'Competition not found'); } + if (competition.owner !== locals.user.id) { + throw error(403, 'You are not authorized to view this competition'); + } + return { competition: competition }; @@ -65,16 +60,11 @@ export const actions = { competition_id: id, name: teamName }); - const comp = await db.query.competitions.findFirst({ - where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)), - with: { - breakperiods: true, - fields: true, - matches: true, - scheduling_modes: true, - teams: true - } - }); + const comp = await getCompetitionWithAll(id); + + if (!comp || comp.owner !== event.locals.user.id) { + throw error(403, 'You are not authorized to update this competition'); + } return { comp @@ -110,16 +100,11 @@ export const actions = { // remove team to competition's teams array await db.delete(teams).where(and(eq(teams.id, teamId), eq(teams.competition_id, id))); - const comp = await db.query.competitions.findFirst({ - where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)), - with: { - breakperiods: true, - fields: true, - matches: true, - scheduling_modes: true, - teams: true - } - }); + const comp = await getCompetitionWithAll(id); + + if (!comp || comp.owner !== event.locals.user.id) { + throw error(403, 'You are not authorized to update this competition'); + } return { comp @@ -151,16 +136,12 @@ export const actions = { // update competition's description await db.update(competitions).set({ description }).where(eq(competitions.id, id)); - const comp = await db.query.competitions.findFirst({ - where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)), - with: { - breakperiods: true, - fields: true, - matches: true, - scheduling_modes: true, - teams: true - } - }); + const comp = await getCompetitionWithAll(id); + + if (!comp || comp.owner !== event.locals.user.id) { + throw error(403, 'You are not authorized to update this competition'); + } + return { comp }; diff --git a/src/routes/competitions/[id]/+page.svelte b/src/routes/competitions/[id]/+page.svelte index 5ae35b8..b83f856 100644 --- a/src/routes/competitions/[id]/+page.svelte +++ b/src/routes/competitions/[id]/+page.svelte @@ -11,6 +11,10 @@ import Input from '@/components/ui/input/input.svelte'; import { enhance } from '$app/forms'; import Textarea from '@/components/ui/textarea/textarea.svelte'; + import Tabs from '@/components/ui/tabs/tabs.svelte'; + import TabsList from '@/components/ui/tabs/tabs-list.svelte'; + import TabsTrigger from '@/components/ui/tabs/tabs-trigger.svelte'; + import TabsContent from '@/components/ui/tabs/tabs-content.svelte'; let { data }: PageProps = $props(); @@ -53,97 +57,112 @@ -
- - -

Description

-
- - {#if showInputDescription} -
-