Added jwt authenticaation
This commit is contained in:
@@ -13,10 +13,12 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^24.6.1",
|
"@types/node": "^24.6.1",
|
||||||
"@types/sqlite3": "^3.1.11",
|
"@types/sqlite3": "^3.1.11",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dotenv/config';
|
|||||||
import {TournamentService} from './services/tournament-service';
|
import {TournamentService} from './services/tournament-service';
|
||||||
import {UserService} from './services/user-service';
|
import {UserService} from './services/user-service';
|
||||||
import {TeamService} from './services/team-service';
|
import {TeamService} from './services/team-service';
|
||||||
|
import {authMiddleware} from './middlewares/auth-middleware';
|
||||||
import router from './middlewares/logger';
|
import router from './middlewares/logger';
|
||||||
import {Database} from 'sqlite3';
|
import {Database} from 'sqlite3';
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -38,7 +39,7 @@ app.get('/tournaments/:id', async (req: Request, res: Response) => {
|
|||||||
res.send(tournament);
|
res.send(tournament);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/tournaments', async (req: Request, res: Response) => {
|
app.post('/tournaments', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await tournamentService.addTournament(req.body);
|
await tournamentService.addTournament(req.body);
|
||||||
res.status(201).send();
|
res.status(201).send();
|
||||||
@@ -48,7 +49,7 @@ app.post('/tournaments', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/tournaments/:id', async (req: Request, res: Response) => {
|
app.put('/tournaments/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await tournamentService.updateTournament(+req.params.id, req.body);
|
await tournamentService.updateTournament(+req.params.id, req.body);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -57,7 +58,7 @@ app.put('/tournaments/:id', async (req: Request, res: Response) => {
|
|||||||
res.status(200).send({message: 'Tournament updated successfully'});
|
res.status(200).send({message: 'Tournament updated successfully'});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/tournaments/:id', async (req: Request, res: Response) => {
|
app.delete('/tournaments/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await tournamentService.deleteTournament(+req.params.id);
|
await tournamentService.deleteTournament(+req.params.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -79,7 +80,7 @@ app.get('/teams/:id', async (req: Request, res: Response) => {
|
|||||||
res.send(team);
|
res.send(team);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/teams', async (req: Request, res: Response) => {
|
app.post('/teams', authMiddleware, async (req: Request, res: Response) => {
|
||||||
const {name, tag, description} = req.body;
|
const {name, tag, description} = req.body;
|
||||||
if (!name || !tag) {
|
if (!name || !tag) {
|
||||||
return res.status(400).send({error: 'name and tag are required'});
|
return res.status(400).send({error: 'name and tag are required'});
|
||||||
@@ -93,7 +94,7 @@ app.post('/teams', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/teams/:id', async (req: Request, res: Response) => {
|
app.put('/teams/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await teamService.updateTeam(+req.params.id, req.body);
|
await teamService.updateTeam(+req.params.id, req.body);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -102,7 +103,7 @@ app.put('/teams/:id', async (req: Request, res: Response) => {
|
|||||||
res.status(200).send({message: 'Team updated successfully'});
|
res.status(200).send({message: 'Team updated successfully'});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/teams/:id', async (req: Request, res: Response) => {
|
app.delete('/teams/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await teamService.deleteTeam(+req.params.id);
|
await teamService.deleteTeam(+req.params.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -116,7 +117,7 @@ app.get('/tournaments/:id/teams', async (req: Request, res: Response) => {
|
|||||||
res.send(teams);
|
res.send(teams);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/tournaments/:id/teams', async (req: Request, res: Response) => {
|
app.post('/tournaments/:id/teams', authMiddleware, async (req: Request, res: Response) => {
|
||||||
const {teamId} = req.body;
|
const {teamId} = req.body;
|
||||||
if (!teamId) {
|
if (!teamId) {
|
||||||
return res.status(400).send({error: 'teamId is required'});
|
return res.status(400).send({error: 'teamId is required'});
|
||||||
@@ -132,7 +133,7 @@ app.post('/tournaments/:id/teams', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/tournaments/:id/teams/:teamId', async (req: Request, res: Response) => {
|
app.delete('/tournaments/:id/teams/:teamId', authMiddleware, async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await teamService.removeTeamFromTournament(+req.params.id, +req.params.teamId);
|
await teamService.removeTeamFromTournament(+req.params.id, +req.params.teamId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import * as jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET || "key"
|
||||||
|
|
||||||
|
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return res.status(401).send({ error: 'Unauthorized: No token provided' });
|
||||||
|
}
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, JWT_SECRET);
|
||||||
|
// @ts-ignore
|
||||||
|
req.user = decoded;
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(401).send({ error: 'Unauthorized: Invalid token' });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import { User } from '../models/user';
|
import { User } from '../models/user';
|
||||||
import { Database } from 'sqlite3';
|
import { Database } from 'sqlite3';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
|
import * as jwt from 'jsonwebtoken';
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET || "key";
|
||||||
|
|
||||||
export class UserService {
|
export class UserService {
|
||||||
private csvFilename = 'csv/users.csv';
|
private csvFilename = 'csv/users.csv';
|
||||||
private db: Database;
|
private db: Database;
|
||||||
@@ -40,7 +43,7 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
register(username: string, password: string): Promise<{ id: number; username: string }> {
|
register(username: string, password: string): Promise<{ id: number; username: string; token: string }> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const hash = await argon2.hash(password);
|
const hash = await argon2.hash(password);
|
||||||
@@ -51,7 +54,8 @@ export class UserService {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
resolve({ id: this.lastID, username });
|
const token = jwt.sign({ id: this.lastID, username }, JWT_SECRET, { expiresIn: '7d' });
|
||||||
|
resolve({ id: this.lastID, username, token });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -60,7 +64,7 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
login(username: string, password: string): Promise<{ id: number; username: string }> {
|
login(username: string, password: string): Promise<{ id: number; username: string; token: string }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.db.get(
|
this.db.get(
|
||||||
'SELECT * FROM Users WHERE username = ?',
|
'SELECT * FROM Users WHERE username = ?',
|
||||||
@@ -77,7 +81,8 @@ export class UserService {
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
return reject(new Error('Invalid password'));
|
return reject(new Error('Invalid password'));
|
||||||
}
|
}
|
||||||
resolve({ id: user.id, username: user.username });
|
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
|
||||||
|
resolve({ id: user.id, username: user.username, token });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
@@ -86,5 +91,3 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:frontend_splatournament_manager/services/auth_service.dart';
|
import 'package:frontend_splatournament_manager/services/auth_service.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||||
|
|
||||||
class AuthProvider extends ChangeNotifier {
|
class AuthProvider extends ChangeNotifier {
|
||||||
final AuthService _authService = AuthService();
|
final AuthService _authService = AuthService();
|
||||||
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
bool _isLoggedIn = false;
|
bool _isLoggedIn = false;
|
||||||
String? _username;
|
String? _username;
|
||||||
@@ -23,6 +26,12 @@ class AuthProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final user = await _authService.login(username, password);
|
final user = await _authService.login(username, password);
|
||||||
|
|
||||||
|
final token = user['token'];
|
||||||
|
if (token != null) {
|
||||||
|
await _storage.write(key: 'jwt_token', value: token);
|
||||||
|
}
|
||||||
|
|
||||||
_isLoggedIn = true;
|
_isLoggedIn = true;
|
||||||
_username = user['username'];
|
_username = user['username'];
|
||||||
_userId = user['id'];
|
_userId = user['id'];
|
||||||
@@ -44,6 +53,12 @@ class AuthProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final user = await _authService.register(username, password);
|
final user = await _authService.register(username, password);
|
||||||
|
|
||||||
|
final token = user['token'];
|
||||||
|
if (token != null) {
|
||||||
|
await _storage.write(key: 'jwt_token', value: token);
|
||||||
|
}
|
||||||
|
|
||||||
_isLoggedIn = true;
|
_isLoggedIn = true;
|
||||||
_username = user['username'];
|
_username = user['username'];
|
||||||
_userId = user['id'];
|
_userId = user['id'];
|
||||||
@@ -58,17 +73,32 @@ class AuthProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void logout() {
|
Future<void> logout() async {
|
||||||
_isLoggedIn = false;
|
_isLoggedIn = false;
|
||||||
_username = null;
|
_username = null;
|
||||||
_userId = null;
|
_userId = null;
|
||||||
_error = null;
|
_error = null;
|
||||||
|
await _storage.delete(key: 'jwt_token');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> checkAuthStatus() async {
|
||||||
|
final token = await _storage.read(key: 'jwt_token');
|
||||||
|
if (token != null) {
|
||||||
|
if (JwtDecoder.isExpired(token)) {
|
||||||
|
await logout();
|
||||||
|
} else {
|
||||||
|
Map<String, dynamic> decodedToken = JwtDecoder.decode(token);
|
||||||
|
_isLoggedIn = true;
|
||||||
|
_userId = decodedToken['id'];
|
||||||
|
_username = decodedToken['username'];
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void clearError() {
|
void clearError() {
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:frontend_splatournament_manager/models/tournament.dart';
|
import 'package:frontend_splatournament_manager/models/tournament.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:frontend_splatournament_manager/services/api_client.dart';
|
||||||
|
|
||||||
import '../main.dart';
|
|
||||||
|
|
||||||
class TournamentProvider extends ChangeNotifier {
|
class TournamentProvider extends ChangeNotifier {
|
||||||
final String baseUrl = SplatournamentApp.baseUrl;
|
|
||||||
|
|
||||||
List<Tournament> _availableTournaments = [];
|
List<Tournament> _availableTournaments = [];
|
||||||
Future<List<Tournament>>? _initialLoadFuture;
|
Future<List<Tournament>>? _initialLoadFuture;
|
||||||
@@ -16,7 +13,7 @@ class TournamentProvider extends ChangeNotifier {
|
|||||||
List<Tournament> get availableTournaments => _availableTournaments;
|
List<Tournament> get availableTournaments => _availableTournaments;
|
||||||
|
|
||||||
Future<List<Tournament>> _fetchTournaments() async {
|
Future<List<Tournament>> _fetchTournaments() async {
|
||||||
final response = await http.get(Uri.parse('$baseUrl/tournaments'));
|
final response = await ApiClient.get('/tournaments');
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
throw Exception('Failed to load tournaments (${response.statusCode})');
|
throw Exception('Failed to load tournaments (${response.statusCode})');
|
||||||
}
|
}
|
||||||
@@ -48,21 +45,15 @@ class TournamentProvider extends ChangeNotifier {
|
|||||||
DateTime registrationStartDate,
|
DateTime registrationStartDate,
|
||||||
DateTime registrationEndDate,
|
DateTime registrationEndDate,
|
||||||
) async {
|
) async {
|
||||||
final response = await http.post(
|
final response = await ApiClient.post(
|
||||||
Uri.parse('$baseUrl/tournaments'),
|
'/tournaments',
|
||||||
headers: {'Content-Type': 'application/json'},
|
{
|
||||||
body: jsonEncode({
|
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description,
|
'description': description,
|
||||||
'maxTeamAmount': maxTeamAmount,
|
'maxTeamAmount': maxTeamAmount,
|
||||||
//weird date formatting
|
'registrationStartDate': registrationStartDate.toIso8601String().split('T')[0],
|
||||||
'registrationStartDate': registrationStartDate.toIso8601String().split(
|
'registrationEndDate': registrationEndDate.toIso8601String().split('T')[0],
|
||||||
'T',
|
},
|
||||||
)[0],
|
|
||||||
'registrationEndDate': registrationEndDate.toIso8601String().split(
|
|
||||||
'T',
|
|
||||||
)[0],
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode != HttpStatus.created) {
|
if (response.statusCode != HttpStatus.created) {
|
||||||
|
|||||||
45
frontend_splatournament_manager/lib/services/api_client.dart
Normal file
45
frontend_splatournament_manager/lib/services/api_client.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:frontend_splatournament_manager/main.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
class ApiClient {
|
||||||
|
static const String baseUrl = SplatournamentApp.baseUrl;
|
||||||
|
static const _storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
static Future<Map<String, String>> _getHeaders() async {
|
||||||
|
final token = await _storage.read(key: 'jwt_token');
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
if (token != null) 'Authorization': 'Bearer $token',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<http.Response> get(String endpoint) async {
|
||||||
|
final headers = await _getHeaders();
|
||||||
|
return await http.get(Uri.parse('$baseUrl$endpoint'), headers: headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<http.Response> post(String endpoint, Map<String, dynamic> body) async {
|
||||||
|
final headers = await _getHeaders();
|
||||||
|
return await http.post(
|
||||||
|
Uri.parse('$baseUrl$endpoint'),
|
||||||
|
headers: headers,
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<http.Response> put(String endpoint, Map<String, dynamic> body) async {
|
||||||
|
final headers = await _getHeaders();
|
||||||
|
return await http.put(
|
||||||
|
Uri.parse('$baseUrl$endpoint'),
|
||||||
|
headers: headers,
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<http.Response> delete(String endpoint) async {
|
||||||
|
final headers = await _getHeaders();
|
||||||
|
return await http.delete(Uri.parse('$baseUrl$endpoint'), headers: headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:frontend_splatournament_manager/main.dart';
|
|
||||||
import 'package:frontend_splatournament_manager/models/team.dart';
|
import 'package:frontend_splatournament_manager/models/team.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:frontend_splatournament_manager/services/api_client.dart';
|
||||||
|
|
||||||
class TeamService {
|
class TeamService {
|
||||||
final String baseUrl = SplatournamentApp.baseUrl;
|
|
||||||
|
|
||||||
Future<List<Team>> getAllTeams() async {
|
Future<List<Team>> getAllTeams() async {
|
||||||
final response = await http.get(Uri.parse('$baseUrl/teams'));
|
final response = await ApiClient.get('/teams');
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
throw Exception('Failed to load teams (${response.statusCode})');
|
throw Exception('Failed to load teams (${response.statusCode})');
|
||||||
}
|
}
|
||||||
@@ -18,7 +16,7 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Team> getTeamById(int id) async {
|
Future<Team> getTeamById(int id) async {
|
||||||
final response = await http.get(Uri.parse('$baseUrl/teams/$id'));
|
final response = await ApiClient.get('/teams/$id');
|
||||||
if (response.statusCode == HttpStatus.notFound) {
|
if (response.statusCode == HttpStatus.notFound) {
|
||||||
throw Exception('Team not found');
|
throw Exception('Team not found');
|
||||||
}
|
}
|
||||||
@@ -33,10 +31,9 @@ class TeamService {
|
|||||||
required String tag,
|
required String tag,
|
||||||
String description = '',
|
String description = '',
|
||||||
}) async {
|
}) async {
|
||||||
final response = await http.post(
|
final response = await ApiClient.post(
|
||||||
Uri.parse('$baseUrl/teams'),
|
'/teams',
|
||||||
headers: {'Content-Type': 'application/json'},
|
{'name': name, 'tag': tag, 'description': description},
|
||||||
body: json.encode({'name': name, 'tag': tag, 'description': description}),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode != HttpStatus.created) {
|
if (response.statusCode != HttpStatus.created) {
|
||||||
final body = json.decode(response.body);
|
final body = json.decode(response.body);
|
||||||
@@ -51,14 +48,13 @@ class TeamService {
|
|||||||
String? tag,
|
String? tag,
|
||||||
String? description,
|
String? description,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await http.put(
|
final response = await ApiClient.put(
|
||||||
Uri.parse('$baseUrl/teams/$id'),
|
'/teams/$id',
|
||||||
headers: {'Content-Type': 'application/json'},
|
{
|
||||||
body: json.encode({
|
if (name != null) 'name': name,
|
||||||
'name': ?name,
|
if (tag != null) 'tag': tag,
|
||||||
'tag': ?tag,
|
if (description != null) 'description': description,
|
||||||
'description': ?description,
|
},
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
final body = json.decode(response.body);
|
final body = json.decode(response.body);
|
||||||
@@ -67,7 +63,7 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteTeam(int id) async {
|
Future<void> deleteTeam(int id) async {
|
||||||
final response = await http.delete(Uri.parse('$baseUrl/teams/$id'));
|
final response = await ApiClient.delete('/teams/$id');
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
final body = json.decode(response.body);
|
final body = json.decode(response.body);
|
||||||
throw Exception(body['error'] ?? 'Failed to delete team');
|
throw Exception(body['error'] ?? 'Failed to delete team');
|
||||||
@@ -75,9 +71,7 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Team>> getTeamsByTournament(int tournamentId) async {
|
Future<List<Team>> getTeamsByTournament(int tournamentId) async {
|
||||||
final response = await http.get(
|
final response = await ApiClient.get('/tournaments/$tournamentId/teams');
|
||||||
Uri.parse('$baseUrl/tournaments/$tournamentId/teams'),
|
|
||||||
);
|
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to load teams for tournament (${response.statusCode})',
|
'Failed to load teams for tournament (${response.statusCode})',
|
||||||
@@ -88,10 +82,9 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> registerTeamForTournament(int tournamentId, int teamId) async {
|
Future<void> registerTeamForTournament(int tournamentId, int teamId) async {
|
||||||
final response = await http.post(
|
final response = await ApiClient.post(
|
||||||
Uri.parse('$baseUrl/tournaments/$tournamentId/teams'),
|
'/tournaments/$tournamentId/teams',
|
||||||
headers: {'Content-Type': 'application/json'},
|
{'teamId': teamId},
|
||||||
body: json.encode({'teamId': teamId}),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode == 409) {
|
if (response.statusCode == 409) {
|
||||||
throw Exception('Team is already registered for this tournament');
|
throw Exception('Team is already registered for this tournament');
|
||||||
@@ -103,9 +96,7 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeTeamFromTournament(int tournamentId, int teamId) async {
|
Future<void> removeTeamFromTournament(int tournamentId, int teamId) async {
|
||||||
final response = await http.delete(
|
final response = await ApiClient.delete('/tournaments/$tournamentId/teams/$teamId');
|
||||||
Uri.parse('$baseUrl/tournaments/$tournamentId/teams/$teamId'),
|
|
||||||
);
|
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
final body = json.decode(response.body);
|
final body = json.decode(response.body);
|
||||||
throw Exception(body['error'] ?? 'Failed to remove team from tournament');
|
throw Exception(body['error'] ?? 'Failed to remove team from tournament');
|
||||||
@@ -113,9 +104,7 @@ class TeamService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getTournamentsByTeam(int teamId) async {
|
Future<List<Map<String, dynamic>>> getTournamentsByTeam(int teamId) async {
|
||||||
final response = await http.get(
|
final response = await ApiClient.get('/teams/$teamId/tournaments');
|
||||||
Uri.parse('$baseUrl/teams/$teamId/tournaments'),
|
|
||||||
);
|
|
||||||
if (response.statusCode != HttpStatus.ok) {
|
if (response.statusCode != HttpStatus.ok) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Failed to load tournaments for team (${response.statusCode})',
|
'Failed to load tournaments for team (${response.statusCode})',
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ dependencies:
|
|||||||
provider: ^6.1.5+1
|
provider: ^6.1.5+1
|
||||||
go_router: ^17.1.0
|
go_router: ^17.1.0
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
flutter_secure_storage: ^10.0.0
|
||||||
|
jwt_decoder: ^2.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user