Header menu via header.js
This commit is contained in:
21
backend/sql/seed.sql
Normal file
21
backend/sql/seed.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Super Sunday Seed (tournoi + joueurs)
|
||||
BEGIN;
|
||||
|
||||
INSERT INTO tournaments (name, location, start_date, end_date)
|
||||
VALUES ('Super Sunday Demo', 'Padel Club', CURRENT_DATE, CURRENT_DATE)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Récupère l'id du tournoi inséré / existant
|
||||
WITH t AS (
|
||||
SELECT id FROM tournaments WHERE name='Super Sunday Demo' ORDER BY id DESC LIMIT 1
|
||||
)
|
||||
INSERT INTO participants (tournament_id, full_name)
|
||||
SELECT t.id, p.full_name
|
||||
FROM t
|
||||
JOIN (VALUES
|
||||
('Alex Dupont'),('Samira El Idrissi'),('Marco Rossi'),('Lina Gomez'),
|
||||
('Yuki Tanaka'),('Nina Kowalski'),('Oliver Smith'),('Fatou Ndiaye')
|
||||
) AS p(full_name) ON TRUE
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,11 +1,15 @@
|
||||
import pg from 'pg';
|
||||
const { Pool } = pg;
|
||||
/**
|
||||
* Minimal PG pool using env vars:
|
||||
* PGHOST, PGUSER, PGPASSWORD, PGDATABASE, PGPORT
|
||||
*/
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const pool = new Pool({
|
||||
host: process.env.PGHOST || 'db',
|
||||
port: Number(process.env.PGPORT || 5432),
|
||||
host: process.env.PGHOST || 'localhost',
|
||||
user: process.env.PGUSER || 'postgres',
|
||||
password: process.env.PGPASSWORD || 'postgres',
|
||||
database: process.env.PGDATABASE || 'supersunday',
|
||||
port: Number(process.env.PGPORT || 5432),
|
||||
});
|
||||
export default pool;
|
||||
|
||||
module.exports = { pool };
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import 'dotenv/config';
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
|
||||
import authRoutes from './routes/auth.js';
|
||||
import tournamentsRoutes from './routes/tournaments.js';
|
||||
import matchesRoutes from './routes/matches.js';
|
||||
import standingsRoutes from './routes/standings.js';
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.get('/api/health', (req,res)=>res.json({ ok:true, ts: Date.now() }));
|
||||
// Health
|
||||
app.get('/api/health', (req, res) => res.json({ ok: true, ts: Date.now() }));
|
||||
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/tournaments', tournamentsRoutes);
|
||||
app.use('/api/matches', matchesRoutes);
|
||||
app.use('/api/tournaments', standingsRoutes);
|
||||
// Routes
|
||||
app.use('/api/tournaments', require('./routes/tournaments'));
|
||||
app.use('/api/participants', require('./routes/participants'));
|
||||
|
||||
const PORT = process.env.PORT || 4000;
|
||||
// "by tournament" nicer path
|
||||
app.get('/api/tournaments/:id/participants', (req, res, next) => {
|
||||
req.url = '/by-tournament/' + req.params.id;
|
||||
return require('./routes/participants')(req, res, next);
|
||||
});
|
||||
|
||||
// 404 for unknown API paths
|
||||
app.use('/api', (req, res) => res.status(404).json({ error: 'not_found' }));
|
||||
|
||||
const PORT = Number(process.env.PORT || 4000);
|
||||
app.listen(PORT, () => console.log(`API listening on :${PORT}`));
|
||||
|
||||
app.use('/api/participants', require('./routes/participants'));
|
||||
app.get('/api/tournaments/:id/participants', (req,res,next)=>{
|
||||
req.url = '/by-tournament/' + req.params.id;
|
||||
return require('./routes/participants')(req,res,next);
|
||||
});
|
||||
|
||||
40
backend/src/routes/participants.js
Normal file
40
backend/src/routes/participants.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { pool } = require('../db');
|
||||
|
||||
// GET /api/tournaments/:id/participants
|
||||
router.get('/by-tournament/:id', async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'bad_tournament_id' });
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'SELECT id, tournament_id, full_name FROM participants WHERE tournament_id = $1 ORDER BY id DESC',
|
||||
[id]
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error('GET participants by tournament', e);
|
||||
res.status(500).json({ error: 'server_error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/participants { tournament_id, full_name }
|
||||
router.post('/', async (req, res) => {
|
||||
const { tournament_id, full_name } = req.body || {};
|
||||
const tid = Number(tournament_id);
|
||||
if (!tid || !full_name || !full_name.trim()) {
|
||||
return res.status(400).json({ error: 'missing_fields' });
|
||||
}
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'INSERT INTO participants (tournament_id, full_name) VALUES ($1, $2) RETURNING id, tournament_id, full_name',
|
||||
[tid, full_name.trim()]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error('POST /participants', e);
|
||||
res.status(500).json({ error: 'server_error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,59 +1,68 @@
|
||||
import { Router } from 'express';
|
||||
import pool from '../db.js';
|
||||
import { requireAuth } from '../middleware/auth.js';
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { pool } = require('../db');
|
||||
|
||||
const router = Router();
|
||||
|
||||
// List
|
||||
router.get('/', async (req,res)=>{
|
||||
const { rows } = await pool.query('select * from tournaments order by start_date nulls last, id desc limit 200');
|
||||
res.json(rows);
|
||||
// GET /api/tournaments -> liste
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
'SELECT id, name, location, start_date, end_date FROM tournaments ORDER BY id DESC'
|
||||
);
|
||||
res.json(rows);
|
||||
} catch (e) {
|
||||
console.error('GET /tournaments', e);
|
||||
res.status(500).json({ error: 'server_error' });
|
||||
}
|
||||
});
|
||||
|
||||
// One
|
||||
router.get('/:id', async (req,res)=>{
|
||||
const { rows } = await pool.query('select * from tournaments where id=$1', [req.params.id]);
|
||||
if (!rows[0]) return res.status(404).json({ error:'not_found' });
|
||||
res.json(rows[0]);
|
||||
});
|
||||
|
||||
// Participants
|
||||
router.get('/:id/participants', async (req,res)=>{
|
||||
const { rows } = await pool.query('select * from participants where tournament_id=$1 order by id', [req.params.id]);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// Matches
|
||||
router.get('/:id/matches', async (req,res)=>{
|
||||
const { rows } = await pool.query('select * from matches where tournament_id=$1 order by starts_at nulls last, id', [req.params.id]);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
// Create tournament (admin)
|
||||
router.post('/', requireAuth, async (req,res)=>{
|
||||
// POST /api/tournaments -> créer
|
||||
router.post('/', async (req, res) => {
|
||||
const { name, location, start_date, end_date } = req.body || {};
|
||||
if (!name) return res.status(400).json({ error:'name_required' });
|
||||
const { rows } = await pool.query(
|
||||
'insert into tournaments(name,location,start_date,end_date) values ($1,$2,$3,$4) returning *',
|
||||
[name, location||null, start_date||null, end_date||null]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
if (!name || !start_date || !end_date) {
|
||||
return res.status(400).json({ error: 'missing_fields' });
|
||||
}
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO tournaments (name, location, start_date, end_date)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, name, location, start_date, end_date`,
|
||||
[name, location || null, start_date, end_date]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
} catch (e) {
|
||||
console.error('POST /tournaments', e);
|
||||
res.status(500).json({ error: 'server_error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Add participant (admin)
|
||||
router.post('/:id/participants', requireAuth, async (req,res)=>{
|
||||
const { full_name } = req.body || {};
|
||||
const tid = Number(req.params.id);
|
||||
if (!tid) return res.status(400).json({ error:'bad_tournament_id' });
|
||||
if (!full_name) return res.status(400).json({ error:'full_name_required' });
|
||||
// ensure tournament exists
|
||||
const t = await pool.query('select id from tournaments where id=$1', [tid]);
|
||||
if (!t.rows[0]) return res.status(404).json({ error:'tournament_not_found' });
|
||||
const { rows } = await pool.query(
|
||||
'insert into participants(tournament_id, full_name) values ($1,$2) returning *',
|
||||
[tid, full_name]
|
||||
);
|
||||
res.status(201).json(rows[0]);
|
||||
// DELETE /api/tournaments/:id -> supprimer (avec nettoyage dépendances si pas de CASCADE)
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: 'bad_id' });
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Si votre schéma n'a PAS de ON DELETE CASCADE, on nettoie à la main :
|
||||
// matches → participants → (tournament row)
|
||||
try { await client.query('DELETE FROM matches WHERE tournament_id = $1', [id]); } catch {}
|
||||
try { await client.query('DELETE FROM participants WHERE tournament_id = $1', [id]); } catch {}
|
||||
|
||||
const result = await client.query('DELETE FROM tournaments WHERE id = $1', [id]);
|
||||
await client.query('COMMIT');
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({ error: 'not_found' });
|
||||
}
|
||||
res.json({ ok: true, id });
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('DELETE /tournaments/:id', e);
|
||||
res.status(500).json({ error: 'server_error' });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user