Add Backend Project and add Tournament Detail Page

This commit is contained in:
2026-03-04 11:13:08 +01:00
parent 3ba492b74c
commit b083d26666
13 changed files with 295 additions and 6 deletions

View File

@@ -0,0 +1 @@
PORT=3000

View File

@@ -0,0 +1,8 @@
node_modules
dist/*
!dist/csv/
dist/csv/*
!dist/csv/tournaments.csv
*.sqlite
_requests.http
request_logs.txt

View File

@@ -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
1 id title author year
2 Demo Tournament This is a demo tournament 6 0
3 Demo Tournament 2 This is a second demo tournament 12 5
4 Demo Tournament 3 This is a third demo tournament 8 8

View File

@@ -0,0 +1,10 @@
{
"watch": [
"src"
],
"ext": "ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"exec": "ts-node ./src/app.ts"
}

View File

@@ -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"
}
}

View File

@@ -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}`);
});

View File

@@ -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;

View File

@@ -0,0 +1,7 @@
export interface Tournament {
id:number;
name:String;
description:String;
maxTeamAmount:number;
currentTeamAmount:number;
}

View File

@@ -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<Tournament[]> {
return new Promise<Tournament[]>((resolve, reject) => {
this.db.all(`SELECT *
FROM Tournaments`,
(err: RunResult, rows: Tournament[]) => {
if (!err) {
resolve(rows);
}
reject(err);
});
});
}
getBookById(id: number): Promise<Tournament> {
return new Promise<Tournament>((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<void> {
return new Promise<void>((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<void> {
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<void> {
return new Promise((resolve, reject) => {
this.db.run('Delete From Tournaments where id = $id', {
$id: id,
});
resolve();
})
}
}

View File

@@ -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"
]
}

View File

@@ -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<StateProvider>(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}");
},)
);
}

View File

@@ -30,5 +30,5 @@ class StateProvider extends ChangeNotifier {
}
return[];
}
List<Tournament> get user => _availableTournaments ?? [];
List<Tournament> get availableTournaments => _availableTournaments ?? [];
}

View File

@@ -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,),
),
);
},