From b083d26666e99884a8e39559f6d868d7537d7b0c Mon Sep 17 00:00:00 2001 From: tikaiz Date: Wed, 4 Mar 2026 11:13:08 +0100 Subject: [PATCH] Add Backend Project and add Tournament Detail Page --- backend_splatournament_manager/.env | 1 + backend_splatournament_manager/.gitignore | 8 ++ .../dist/csv/tournaments.csv | 4 + backend_splatournament_manager/nodemon.json | 10 ++ backend_splatournament_manager/package.json | 25 ++++ backend_splatournament_manager/src/app.ts | 68 ++++++++++ .../src/middlewares/logger.ts | 20 +++ .../src/models/tournament.ts | 7 ++ .../src/services/tournament-service.ts | 118 ++++++++++++++++++ backend_splatournament_manager/tsconfig.json | 18 +++ .../lib/pages/tournament_detail_page.dart | 13 +- .../lib/state_provider.dart | 2 +- .../widgets/available_tournament_list.dart | 7 +- 13 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 backend_splatournament_manager/.env create mode 100644 backend_splatournament_manager/.gitignore create mode 100644 backend_splatournament_manager/dist/csv/tournaments.csv create mode 100644 backend_splatournament_manager/nodemon.json create mode 100644 backend_splatournament_manager/package.json create mode 100644 backend_splatournament_manager/src/app.ts create mode 100644 backend_splatournament_manager/src/middlewares/logger.ts create mode 100644 backend_splatournament_manager/src/models/tournament.ts create mode 100644 backend_splatournament_manager/src/services/tournament-service.ts create mode 100644 backend_splatournament_manager/tsconfig.json diff --git a/backend_splatournament_manager/.env b/backend_splatournament_manager/.env new file mode 100644 index 0000000..2fc80e3 --- /dev/null +++ b/backend_splatournament_manager/.env @@ -0,0 +1 @@ +PORT=3000 diff --git a/backend_splatournament_manager/.gitignore b/backend_splatournament_manager/.gitignore new file mode 100644 index 0000000..fce7966 --- /dev/null +++ b/backend_splatournament_manager/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist/* +!dist/csv/ +dist/csv/* +!dist/csv/tournaments.csv +*.sqlite +_requests.http +request_logs.txt \ No newline at end of file diff --git a/backend_splatournament_manager/dist/csv/tournaments.csv b/backend_splatournament_manager/dist/csv/tournaments.csv new file mode 100644 index 0000000..f26a197 --- /dev/null +++ b/backend_splatournament_manager/dist/csv/tournaments.csv @@ -0,0 +1,4 @@ +id,title,author,year +Demo Tournament ,This is a demo tournament, 6, 0 +Demo Tournament 2,This is a second demo tournament, 12, 5 +Demo Tournament 3,This is a third demo tournament, 8, 8 \ No newline at end of file diff --git a/backend_splatournament_manager/nodemon.json b/backend_splatournament_manager/nodemon.json new file mode 100644 index 0000000..ed30724 --- /dev/null +++ b/backend_splatournament_manager/nodemon.json @@ -0,0 +1,10 @@ +{ + "watch": [ + "src" + ], + "ext": "ts,json", + "ignore": [ + "src/**/*.spec.ts" + ], + "exec": "ts-node ./src/app.ts" +} diff --git a/backend_splatournament_manager/package.json b/backend_splatournament_manager/package.json new file mode 100644 index 0000000..c24dc1c --- /dev/null +++ b/backend_splatournament_manager/package.json @@ -0,0 +1,25 @@ +{ + "name": "splatournament-manager-backend", + "version": "0.0.1", + "scripts": { + "start": "node dist/app.js", + "dev": "nodemon", + "build": "tsc" + }, + "dependencies": { + "axios": "1.13.5", + "body-parser": "2.2.1", + "dotenv": "^17.2.3", + "ejs": "^3.1.10", + "express": "^5.1.0", + "sqlite3": "^5.1.7" + }, + "devDependencies": { + "@types/express": "^5.0.3", + "@types/node": "^24.6.1", + "@types/sqlite3": "^3.1.11", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } +} diff --git a/backend_splatournament_manager/src/app.ts b/backend_splatournament_manager/src/app.ts new file mode 100644 index 0000000..1e2d26a --- /dev/null +++ b/backend_splatournament_manager/src/app.ts @@ -0,0 +1,68 @@ +import express, {Request, Response} from 'express'; +import bodyParser from 'body-parser'; +import 'dotenv/config'; + +import {TournamentService} from './services/tournament-service'; +import router from './middlewares/logger'; + + +const tournamentService = new TournamentService(); +const port = process.env.PORT || 3000; +const app = express(); + +app.use(bodyParser.json()); +app.use(router); + +app.get('/tournaments/available', 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 tournament = await tournamentService.getBookById(+req.params.id); + if (!tournament) { + return res.status(404).send({error: 'Book not found'}); + } + res.send(tournament); +}); + +app.post('/books', async (req: Request, res: Response) => { + console.log("post"); + + try { + await tournamentService.addBook(req.body); + res.status(200).send(); + }catch (err){ + console.log(err); + res.status(404).send(); + } +}); + +app.put('/books', async (req: Request, res: Response) => { + if (!req.query.id) { + return res.status(400).send({error: 'Missing id parameter'}); + } + try { + const success = await tournamentService.updateBook(+req.query.id!, req.body); + } catch (err) { + return res.status(400).send({error: 'Failed to update book'}); + } + res.status(200).send({message: 'Book updated successfully'}); +}); + +app.delete('/books', async (req: Request, res: Response) => { + if (!req.query.id) { + return res.status(400).send({error: 'Missing id parameter'}); + } + try { + const success = await tournamentService.deleteBook(+req.query.id!); + } catch (err) { + return res.status(400).send({error: 'Failed to delete book'}); + } + res.status(200).send({message: 'Book deleted successfully'}); +}); + +app.listen(port, () => { + console.log(`server started on port ${port}`); +}); diff --git a/backend_splatournament_manager/src/middlewares/logger.ts b/backend_splatournament_manager/src/middlewares/logger.ts new file mode 100644 index 0000000..0c9cf78 --- /dev/null +++ b/backend_splatournament_manager/src/middlewares/logger.ts @@ -0,0 +1,20 @@ +import express from 'express'; +import path from 'path'; +import fs from 'fs'; + + +const filePath = path.join(process.cwd(), 'request_logs.txt'); +const router = express.Router(); + +router.use((req, res, next) => { + const log = `[${new Date().toLocaleString()}] ${req.method} ${req.url}\n`; + console.log(log); + + fs.appendFile(filePath, log, (err) => { + if (err) console.error("Request Failed", err); + }); + + next(); +}); + +export = router; diff --git a/backend_splatournament_manager/src/models/tournament.ts b/backend_splatournament_manager/src/models/tournament.ts new file mode 100644 index 0000000..19bcf91 --- /dev/null +++ b/backend_splatournament_manager/src/models/tournament.ts @@ -0,0 +1,7 @@ +export interface Tournament { + id:number; + name:String; + description:String; + maxTeamAmount:number; + currentTeamAmount:number; +} diff --git a/backend_splatournament_manager/src/services/tournament-service.ts b/backend_splatournament_manager/src/services/tournament-service.ts new file mode 100644 index 0000000..807cdc5 --- /dev/null +++ b/backend_splatournament_manager/src/services/tournament-service.ts @@ -0,0 +1,118 @@ +import {Tournament} from '../models/tournament'; +import fs from 'fs'; +import path from 'path'; +import {Database, RunResult} from "sqlite3"; + +export class TournamentService { + private csvFilename = 'csv/tournaments.csv'; + private dbFilename = 'tournaments.sqlite'; + private db: Database; + + constructor() { + fs.unlinkSync(this.dbFilename); + this.db = new Database(this.dbFilename); + this.db.serialize(() => { + this.db.run(`CREATE TABLE IF NOT EXISTS Tournaments + ( + id + INTEGER + PRIMARY + KEY + AUTOINCREMENT, + name + TEXT, + description + TEXT, + maxTeamAmount + INTEGER, + currentTeamAmount + INTEGER + )`); + }) + this.seedDb(); + } + + private seedDb() { + fs.readFile(path.join(process.cwd(), "dist", this.csvFilename), 'utf-8', (_, data) => { + const entries = data.split('\n'); + entries.shift(); + const statement = this.db.prepare("INSERT INTO Tournaments ( name, description, maxTeamAmount, currentTeamAmount) VALUES (?, ?, ?, ?)"); + entries.forEach(line => { + if (line) { + const parts = line.split(','); + const tournament = { + id: 0, + name: parts[0].trim(), + description: parts[1].trim(), + maxTeamAmount: +parts[2], + currentTeamAmount: +parts[3] + } as Tournament; + statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.currentTeamAmount); + console.log(tournament) + } + }); + statement.finalize(); + }); + } + + getAllTournaments(): Promise { + return new Promise((resolve, reject) => { + this.db.all(`SELECT * + FROM Tournaments`, + (err: RunResult, rows: Tournament[]) => { + if (!err) { + resolve(rows); + } + reject(err); + }); + }); + } + + getBookById(id: number): Promise { + return new Promise((resolve, reject) => { + this.db.get(`Select * + From Tournaments + WHERE id = ${id}`, (err: RunResult, book: Tournament) => { + if (!err) { + resolve(book); + } + reject(err); + }); + }) + } + + addBook(tournament: Tournament): Promise { + return new Promise((resolve, reject) => { + const statement = this.db.prepare('Insert Into Tournaments (name, description, maxTeamAmount, currentTeamAmount) VALUES (?, ?, ?, ?)') + statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.currentTeamAmount); + resolve(); + }) + } + + updateBook(id: number, updatedBook: Tournament): Promise { + return new Promise((resolve, reject) => { + this.db.run(`Update Tournaments + Set name = $name, + description = $description, + maxTeamAmount = $maxTeamAmount, + currentTeamAmount = $currentTeamAmount + where id = $id`, + { + $id: id, + $description: updatedBook.description, + $maxTeamAmount: updatedBook.maxTeamAmount, + $currentTeamAmount: updatedBook.currentTeamAmount, + }); + resolve(); + }) + } + + deleteBook(id: number): Promise { + return new Promise((resolve, reject) => { + this.db.run('Delete From Tournaments where id = $id', { + $id: id, + }); + resolve(); + }) + } +} diff --git a/backend_splatournament_manager/tsconfig.json b/backend_splatournament_manager/tsconfig.json new file mode 100644 index 0000000..53fbd19 --- /dev/null +++ b/backend_splatournament_manager/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart index 524d2e9..25ffc6f 100644 --- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart +++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart @@ -1,13 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/state_provider.dart'; +import 'package:provider/provider.dart'; class TournamentDetailPage extends StatelessWidget { - const TournamentDetailPage({super.key}); + final int tournamentId; + const TournamentDetailPage({super.key, required this.tournamentId}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Tournament"),), - body: Center(child: Text("Detail"),) + body: Consumer(builder: (BuildContext context, StateProvider value, Widget? child) { + var tournament = value.availableTournaments.where((x) => x.id == tournamentId).firstOrNull; + if(tournament == null){ + return Center(child: Text("Tournament not found!")); + } + return Text("${tournament.maxTeamAmount}"); + },) ); } diff --git a/frontend_splatournament_manager/lib/state_provider.dart b/frontend_splatournament_manager/lib/state_provider.dart index 8ff29a0..3c6272e 100644 --- a/frontend_splatournament_manager/lib/state_provider.dart +++ b/frontend_splatournament_manager/lib/state_provider.dart @@ -30,5 +30,5 @@ class StateProvider extends ChangeNotifier { } return[]; } - List get user => _availableTournaments ?? []; + List get availableTournaments => _availableTournaments ?? []; } \ No newline at end of file diff --git a/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart b/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart index 0e502e6..4a0b677 100644 --- a/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart +++ b/frontend_splatournament_manager/lib/widgets/available_tournament_list.dart @@ -23,15 +23,16 @@ class AvailableTournamentList extends StatelessWidget { return ListView.builder( itemCount: list.length, itemBuilder: (context, index) { + var tournament = list[index]; return ListTile( leading: Icon(Icons.abc), - title: Text(list[index].name), - subtitle: Text(list[index].description), + title: Text(tournament.name), + subtitle: Text(tournament.description), onTap: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => TournamentDetailPage(), + builder: (context) => TournamentDetailPage(tournamentId: tournament.id,), ), ); },