Add Backend Project and add Tournament Detail Page
This commit is contained in:
1
backend_splatournament_manager/.env
Normal file
1
backend_splatournament_manager/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PORT=3000
|
||||||
8
backend_splatournament_manager/.gitignore
vendored
Normal file
8
backend_splatournament_manager/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
dist/*
|
||||||
|
!dist/csv/
|
||||||
|
dist/csv/*
|
||||||
|
!dist/csv/tournaments.csv
|
||||||
|
*.sqlite
|
||||||
|
_requests.http
|
||||||
|
request_logs.txt
|
||||||
4
backend_splatournament_manager/dist/csv/tournaments.csv
vendored
Normal file
4
backend_splatournament_manager/dist/csv/tournaments.csv
vendored
Normal 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
|
||||||
|
10
backend_splatournament_manager/nodemon.json
Normal file
10
backend_splatournament_manager/nodemon.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"watch": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"ext": "ts,json",
|
||||||
|
"ignore": [
|
||||||
|
"src/**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"exec": "ts-node ./src/app.ts"
|
||||||
|
}
|
||||||
25
backend_splatournament_manager/package.json
Normal file
25
backend_splatournament_manager/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
68
backend_splatournament_manager/src/app.ts
Normal file
68
backend_splatournament_manager/src/app.ts
Normal 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}`);
|
||||||
|
});
|
||||||
20
backend_splatournament_manager/src/middlewares/logger.ts
Normal file
20
backend_splatournament_manager/src/middlewares/logger.ts
Normal 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;
|
||||||
7
backend_splatournament_manager/src/models/tournament.ts
Normal file
7
backend_splatournament_manager/src/models/tournament.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Tournament {
|
||||||
|
id:number;
|
||||||
|
name:String;
|
||||||
|
description:String;
|
||||||
|
maxTeamAmount:number;
|
||||||
|
currentTeamAmount:number;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
18
backend_splatournament_manager/tsconfig.json
Normal file
18
backend_splatournament_manager/tsconfig.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend_splatournament_manager/state_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class TournamentDetailPage extends StatelessWidget {
|
class TournamentDetailPage extends StatelessWidget {
|
||||||
const TournamentDetailPage({super.key});
|
final int tournamentId;
|
||||||
|
const TournamentDetailPage({super.key, required this.tournamentId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text("Tournament"),),
|
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}");
|
||||||
|
},)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ class StateProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
return[];
|
return[];
|
||||||
}
|
}
|
||||||
List<Tournament> get user => _availableTournaments ?? [];
|
List<Tournament> get availableTournaments => _availableTournaments ?? [];
|
||||||
}
|
}
|
||||||
@@ -23,15 +23,16 @@ class AvailableTournamentList extends StatelessWidget {
|
|||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
var tournament = list[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(Icons.abc),
|
leading: Icon(Icons.abc),
|
||||||
title: Text(list[index].name),
|
title: Text(tournament.name),
|
||||||
subtitle: Text(list[index].description),
|
subtitle: Text(tournament.description),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => TournamentDetailPage(),
|
builder: (context) => TournamentDetailPage(tournamentId: tournament.id,),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user