diff --git a/backend_splatournament_manager/src/app.ts b/backend_splatournament_manager/src/app.ts index 1ddb853..1172edf 100644 --- a/backend_splatournament_manager/src/app.ts +++ b/backend_splatournament_manager/src/app.ts @@ -4,6 +4,7 @@ import 'dotenv/config'; import {TournamentService} from './services/tournament-service'; import {UserService} from './services/user-service'; +import {TeamService} from './services/team-service'; import router from './middlewares/logger'; import {Database} from 'sqlite3'; import fs from "fs"; @@ -17,6 +18,7 @@ if (fs.existsSync(dbFilename)){ const db = new Database(dbFilename); const tournamentService = new TournamentService(db); const userService = new UserService(db); +const teamService = new TeamService(db); const port = process.env.PORT || 3000; const app = express(); @@ -24,12 +26,11 @@ app.use(bodyParser.json()); app.use(router); app.get('/tournaments', async (req: Request, res: Response) => { - console.log(req.params) - if (!req.params.id) { - const tournaments = await tournamentService.getAllTournaments(); - console.log(tournaments) - return res.send(tournaments); - } + const tournaments = await tournamentService.getAllTournaments(); + return res.send(tournaments); +}); + +app.get('/tournaments/:id', async (req: Request, res: Response) => { const tournament = await tournamentService.getTournamentById(+req.params.id); if (!tournament) { return res.status(404).send({error: 'Tournament not found'}); @@ -38,42 +39,113 @@ app.get('/tournaments', async (req: Request, res: Response) => { }); app.post('/tournaments', async (req: Request, res: Response) => { - console.log("post"); - try { await tournamentService.addTournament(req.body); - res.status(200).send(); - }catch (err){ + res.status(201).send(); + } catch (err){ console.log(err); - res.status(404).send(); + res.status(400).send({error: 'Failed to create tournament'}); } }); -app.put('/tournaments', async (req: Request, res: Response) => { - if (!req.query.id) { - return res.status(400).send({error: 'Missing id parameter'}); - } +app.put('/tournaments/:id', async (req: Request, res: Response) => { try { - const success = await tournamentService.updateTournament(+req.query.id!, req.body); + await tournamentService.updateTournament(+req.params.id, req.body); } catch (err) { return res.status(400).send({error: 'Failed to update Tournament'}); } res.status(200).send({message: 'Tournament updated successfully'}); }); -app.delete('/tournaments', async (req: Request, res: Response) => { - if (!req.query.id) { - return res.status(400).send({error: 'Missing id parameter'}); - } +app.delete('/tournaments/:id', async (req: Request, res: Response) => { try { - const success = await tournamentService.deleteTournament(+req.query.id!); + await tournamentService.deleteTournament(+req.params.id); } catch (err) { return res.status(400).send({error: 'Failed to delete Tournament'}); } res.status(200).send({message: 'Tournament deleted successfully'}); }); -// Auth routes +app.get('/teams', async (req: Request, res: Response) => { + const teams = await teamService.getAllTeams(); + res.send(teams); +}); + +app.get('/teams/:id', async (req: Request, res: Response) => { + const team = await teamService.getTeamById(+req.params.id); + if (!team) { + return res.status(404).send({error: 'Team not found'}); + } + res.send(team); +}); + +app.post('/teams', async (req: Request, res: Response) => { + const {name, tag, description} = req.body; + if (!name || !tag) { + return res.status(400).send({error: 'name and tag are required'}); + } + try { + const team = await teamService.addTeam({name, tag, description: description ?? ''}); + res.status(201).send(team); + } catch (err) { + console.log(err); + res.status(400).send({error: 'Failed to create team'}); + } +}); + +app.put('/teams/:id', async (req: Request, res: Response) => { + try { + await teamService.updateTeam(+req.params.id, req.body); + } catch (err) { + return res.status(400).send({error: 'Failed to update team'}); + } + res.status(200).send({message: 'Team updated successfully'}); +}); + +app.delete('/teams/:id', async (req: Request, res: Response) => { + try { + await teamService.deleteTeam(+req.params.id); + } catch (err) { + return res.status(400).send({error: 'Failed to delete team'}); + } + res.status(200).send({message: 'Team deleted successfully'}); +}); + +app.get('/tournaments/:id/teams', async (req: Request, res: Response) => { + const teams = await teamService.getTeamsByTournamentId(+req.params.id); + res.send(teams); +}); + +app.post('/tournaments/:id/teams', async (req: Request, res: Response) => { + const {teamId} = req.body; + if (!teamId) { + return res.status(400).send({error: 'teamId is required'}); + } + try { + const entry = await teamService.registerTeamForTournament(+req.params.id, +teamId); + res.status(201).send(entry); + } catch (err: any) { + if (err.message?.includes('UNIQUE constraint failed')) { + return res.status(409).send({error: 'Team is already registered for this tournament'}); + } + res.status(400).send({error: 'Failed to register team'}); + } +}); + +app.delete('/tournaments/:id/teams/:teamId', async (req: Request, res: Response) => { + try { + await teamService.removeTeamFromTournament(+req.params.id, +req.params.teamId); + } catch (err) { + return res.status(400).send({error: 'Failed to remove team from tournament'}); + } + res.status(200).send({message: 'Team removed from tournament'}); +}); + +app.get('/teams/:id/tournaments', async (req: Request, res: Response) => { + const entries = await teamService.getTournamentsByTeamId(+req.params.id); + res.send(entries); +}); + app.post('/register', async (req: Request, res: Response) => { const { username, password } = req.body; if (!username || !password) { diff --git a/backend_splatournament_manager/src/models/team.ts b/backend_splatournament_manager/src/models/team.ts new file mode 100644 index 0000000..44d3e17 --- /dev/null +++ b/backend_splatournament_manager/src/models/team.ts @@ -0,0 +1,15 @@ +export interface Team { + id: number; + name: string; + tag: string; + description: string; + createdAt: string; +} + +export interface TournamentTeam { + id: number; + tournamentId: number; + teamId: number; + registeredAt: string; +} + diff --git a/backend_splatournament_manager/src/models/tournament.ts b/backend_splatournament_manager/src/models/tournament.ts index 813049b..bcf1488 100644 --- a/backend_splatournament_manager/src/models/tournament.ts +++ b/backend_splatournament_manager/src/models/tournament.ts @@ -1,9 +1,12 @@ +import { Team } from './team'; + export interface Tournament { - id:number; - name:String; - description:String; - maxTeamAmount:number; - currentTeamAmount:number; - registrationStartDate:String; - registrationEndDate:String; + id: number; + name: String; + description: String; + maxTeamAmount: number; + currentTeamAmount: number; + registrationStartDate: String; + registrationEndDate: String; + teams?: Team[]; } diff --git a/backend_splatournament_manager/src/services/team-service.ts b/backend_splatournament_manager/src/services/team-service.ts new file mode 100644 index 0000000..8a988ba --- /dev/null +++ b/backend_splatournament_manager/src/services/team-service.ts @@ -0,0 +1,149 @@ +import { Team, TournamentTeam } from '../models/team'; +import { Database, RunResult } from 'sqlite3'; + +export class TeamService { + private db: Database; + + constructor(db: Database) { + this.db = db; + this.db.serialize(() => { + this.db.run(`CREATE TABLE IF NOT EXISTS Teams + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + tag TEXT NOT NULL, + description TEXT, + createdAt TEXT NOT NULL DEFAULT (datetime('now')) + )`); + + this.db.run(`CREATE TABLE IF NOT EXISTS TournamentTeams + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tournamentId INTEGER NOT NULL, + teamId INTEGER NOT NULL, + registeredAt TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (tournamentId) REFERENCES Tournaments (id) ON DELETE CASCADE, + FOREIGN KEY (teamId) REFERENCES Teams (id) ON DELETE CASCADE, + UNIQUE (tournamentId, teamId) + )`); + }); + } + + getAllTeams(): Promise { + return new Promise((resolve, reject) => { + this.db.all(`SELECT * FROM Teams`, (err: Error | null, rows: Team[]) => { + if (err) return reject(err); + resolve(rows); + }); + }); + } + + getTeamById(id: number): Promise { + return new Promise((resolve, reject) => { + this.db.get( + `SELECT * FROM Teams WHERE id = ?`, + [id], + (err: Error | null, row: Team | undefined) => { + if (err) return reject(err); + resolve(row); + } + ); + }); + } + + addTeam(team: Omit): Promise { + return new Promise((resolve, reject) => { + const stmt = this.db.prepare( + `INSERT INTO Teams (name, tag, description) VALUES (?, ?, ?)` + ); + stmt.run(team.name, team.tag, team.description, function (this: RunResult, err: Error | null) { + if (err) return reject(err); + resolve({ id: (this as any).lastID, createdAt: new Date().toISOString(), ...team }); + }); + stmt.finalize(); + }); + } + + updateTeam(id: number, team: Partial>): Promise { + return new Promise((resolve, reject) => { + this.db.run( + `UPDATE Teams SET name = COALESCE(?, name), tag = COALESCE(?, tag), description = COALESCE(?, description) WHERE id = ?`, + [team.name ?? null, team.tag ?? null, team.description ?? null, id], + (err: Error | null) => { + if (err) return reject(err); + resolve(); + } + ); + }); + } + + deleteTeam(id: number): Promise { + return new Promise((resolve, reject) => { + this.db.run(`DELETE FROM Teams WHERE id = ?`, [id], (err: Error | null) => { + if (err) return reject(err); + resolve(); + }); + }); + } + + getTeamsByTournamentId(tournamentId: number): Promise { + return new Promise((resolve, reject) => { + this.db.all( + `SELECT t.*, tt.registeredAt + FROM Teams t + INNER JOIN TournamentTeams tt ON t.id = tt.teamId + WHERE tt.tournamentId = ?`, + [tournamentId], + (err: Error | null, rows: Team[]) => { + if (err) return reject(err); + resolve(rows); + } + ); + }); + } + + getTournamentsByTeamId(teamId: number): Promise { + return new Promise((resolve, reject) => { + this.db.all( + `SELECT * FROM TournamentTeams WHERE teamId = ?`, + [teamId], + (err: Error | null, rows: TournamentTeam[]) => { + if (err) return reject(err); + resolve(rows); + } + ); + }); + } + + registerTeamForTournament(tournamentId: number, teamId: number): Promise { + return new Promise((resolve, reject) => { + const stmt = this.db.prepare( + `INSERT INTO TournamentTeams (tournamentId, teamId) VALUES (?, ?)` + ); + stmt.run(tournamentId, teamId, function (this: RunResult, err: Error | null) { + if (err) return reject(err); + resolve({ + id: (this as any).lastID, + tournamentId, + teamId, + registeredAt: new Date().toISOString(), + }); + }); + stmt.finalize(); + }); + } + + removeTeamFromTournament(tournamentId: number, teamId: number): Promise { + return new Promise((resolve, reject) => { + this.db.run( + `DELETE FROM TournamentTeams WHERE tournamentId = ? AND teamId = ?`, + [tournamentId, teamId], + (err: Error | null) => { + if (err) return reject(err); + resolve(); + } + ); + }); + } +} + diff --git a/backend_splatournament_manager/src/services/tournament-service.ts b/backend_splatournament_manager/src/services/tournament-service.ts index ed4eb49..100094a 100644 --- a/backend_splatournament_manager/src/services/tournament-service.ts +++ b/backend_splatournament_manager/src/services/tournament-service.ts @@ -1,4 +1,5 @@ import {Tournament} from '../models/tournament'; +import {Team} from '../models/team'; import fs from 'fs'; import path from 'path'; import {Database, RunResult} from "sqlite3"; @@ -26,27 +27,83 @@ export class TournamentService { getAllTournaments(): Promise { return new Promise((resolve, reject) => { - this.db.all(`SELECT * - FROM Tournaments`, (err: RunResult, rows: Tournament[]) => { - if (!err) { - resolve(rows); + this.db.all( + `SELECT t.*, tm.id as teamId, tm.name as teamName, tm.tag as teamTag, + tm.description as teamDescription, tm.createdAt as teamCreatedAt + FROM Tournaments t + LEFT JOIN TournamentTeams tt ON t.id = tt.tournamentId + LEFT JOIN Teams tm ON tt.teamId = tm.id`, + (err: Error | null, rows: any[]) => { + if (err) return reject(err); + const tournamentsMap = new Map(); + for (const row of rows) { + if (!tournamentsMap.has(row.id)) { + tournamentsMap.set(row.id, { + id: row.id, + name: row.name, + description: row.description, + maxTeamAmount: row.maxTeamAmount, + currentTeamAmount: row.currentTeamAmount, + registrationStartDate: row.registrationStartDate, + registrationEndDate: row.registrationEndDate, + teams: [], + }); + } + if (row.teamId) { + tournamentsMap.get(row.id)!.teams!.push({ + id: row.teamId, + name: row.teamName, + tag: row.teamTag, + description: row.teamDescription, + createdAt: row.teamCreatedAt, + } as Team); + } + } + resolve(Array.from(tournamentsMap.values())); } - reject(err); - }); + ); }); } - getTournamentById(id: number): Promise { - return new Promise((resolve, reject) => { - this.db.get(`Select * - From Tournaments - WHERE id = ${id}`, (err: RunResult, tournament: Tournament) => { - if (!err) { + getTournamentById(id: number): Promise { + return new Promise((resolve, reject) => { + this.db.all( + `SELECT t.*, tm.id as teamId, tm.name as teamName, tm.tag as teamTag, + tm.description as teamDescription, tm.createdAt as teamCreatedAt + FROM Tournaments t + LEFT JOIN TournamentTeams tt ON t.id = tt.tournamentId + LEFT JOIN Teams tm ON tt.teamId = tm.id + WHERE t.id = ?`, + [id], + (err: Error | null, rows: any[]) => { + if (err) return reject(err); + if (!rows || rows.length === 0) return resolve(undefined); + const first = rows[0]; + const tournament: Tournament = { + id: first.id, + name: first.name, + description: first.description, + maxTeamAmount: first.maxTeamAmount, + currentTeamAmount: first.currentTeamAmount, + registrationStartDate: first.registrationStartDate, + registrationEndDate: first.registrationEndDate, + teams: [], + }; + for (const row of rows) { + if (row.teamId) { + tournament.teams!.push({ + id: row.teamId, + name: row.teamName, + tag: row.teamTag, + description: row.teamDescription, + createdAt: row.teamCreatedAt, + } as Team); + } + } resolve(tournament); } - reject(err); - }); - }) + ); + }); } addTournament(tournament: Tournament): Promise { diff --git a/frontend_splatournament_manager/lib/models/team.dart b/frontend_splatournament_manager/lib/models/team.dart new file mode 100644 index 0000000..fbd16bf --- /dev/null +++ b/frontend_splatournament_manager/lib/models/team.dart @@ -0,0 +1,36 @@ +class Team { + final int id; + final String name; + final String tag; + final String description; + final String createdAt; + + Team({ + required this.id, + required this.name, + required this.tag, + required this.description, + required this.createdAt, + }); + + factory Team.fromJson(Map json) { + return Team( + id: json['id'] as int, + name: json['name'] as String, + tag: json['tag'] as String, + description: (json['description'] as String?) ?? '', + createdAt: (json['createdAt'] as String?) ?? '', + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'tag': tag, + 'description': description, + 'createdAt': createdAt, + }; + } +} + diff --git a/frontend_splatournament_manager/lib/models/tournament.dart b/frontend_splatournament_manager/lib/models/tournament.dart index 323577d..5268170 100644 --- a/frontend_splatournament_manager/lib/models/tournament.dart +++ b/frontend_splatournament_manager/lib/models/tournament.dart @@ -1,3 +1,5 @@ +import 'package:frontend_splatournament_manager/models/team.dart'; + class Tournament { final int id; final String name; @@ -6,6 +8,7 @@ class Tournament { final int currentTeamAmount; final String registrationStartDate; final String registrationEndDate; + final List teams; Tournament({ required this.id, @@ -15,6 +18,7 @@ class Tournament { required this.currentTeamAmount, required this.registrationStartDate, required this.registrationEndDate, + this.teams = const [], }); factory Tournament.fromJson(dynamic json) { @@ -26,6 +30,9 @@ class Tournament { currentTeamAmount: json['currentTeamAmount'], registrationStartDate: json['registrationStartDate'], registrationEndDate: json['registrationEndDate'], + teams: (json['teams'] as List? ?? []) + .map((t) => Team.fromJson(t as Map)) + .toList(), ); }