🚀 Patch auto

This commit is contained in:
karim hassan
2025-08-24 23:00:40 +00:00
parent 92e6afff00
commit f1103d67a0
1314 changed files with 2511 additions and 562 deletions

8
.env
View File

@@ -1,20 +1,20 @@
# === Super Sunday PROD Environment === # === Super Sunday PROD Environment ===
NODE_ENV=production NODE_ENV=production
PORT=8080 PORT=4000
# Database # Database
POSTGRES_DB=supersunday POSTGRES_DB=supersunday
POSTGRES_USER=supersunday POSTGRES_USER=supersunday
POSTGRES_PASSWORD=Sup3rSund@y2025! POSTGRES_PASSWORD=postgres
DATABASE_URL=postgres://supersunday:Sup3rSund@y2025!@db:5432/supersunday DATABASE_URL=postgres://supersunday:Sup3rSund@y2025!@db:5432/supersunday
# API / Auth # API / Auth
JWT_SECRET=Sup3rSundayUltraSecretKey_2025 JWT_SECRET=Sup3rSundayUltraSecretKey_2025
CORS_ORIGIN=http://localhost:8080 CORS_ORIGIN=http://localhost
# Admin credentials # Admin credentials
ADMIN_EMAIL=admin@supersunday.app ADMIN_EMAIL=admin@supersunday.app
ADMIN_PASSWORD=ChangeMeNow!42 ADMIN_PASSWORD=4575SataUMGF2026+-PROD
# Branding # Branding
APP_NAME="Super Sunday Padel Championship" APP_NAME="Super Sunday Padel Championship"

21
.env.
View File

@@ -1,21 +0,0 @@
# === Super Sunday PROD Environment ===
NODE_ENV=production
PORT=8080
# Database
POSTGRES_DB=supersunday
POSTGRES_USER=supersunday
POSTGRES_PASSWORD=Sup3rSund@y2025!
DATABASE_URL=postgres://supersunday:Sup3rSund@y2025!@db:5432/supersunday
# API / Auth
JWT_SECRET=Sup3rSundayUltraSecretKey_2025
CORS_ORIGIN=http://localhost
# Admin credentials
ADMIN_EMAIL=admin@padel24play.com
ADMIN_PASSWORD=4575SataMGF+-
# Branding
APP_NAME="Super Sunday Padel Championship"
CLUB_NAME="Les Églantiers, Woluwe-Saint-Pierre"

14
.env.example Normal file
View File

@@ -0,0 +1,14 @@
# Backend
PORT=4000
JWT_SECRET=change_me_supersecret
# Admin (simple login)
ADMIN_EMAIL=admin@supersunday.local
ADMIN_PASSWORD=admin1234
# Postgres
PGHOST=db
PGPORT=5432
PGDATABASE=supersunday
PGUSER=supersunday
PGPASSWORD=supersunday

View File

@@ -1,6 +1,6 @@
# === Super Sunday PROD Environment === # === Super Sunday PROD Environment ===
NODE_ENV=production NODE_ENV=production
PORT=8080 PORT=4000
# Database # Database
POSTGRES_DB=supersunday POSTGRES_DB=supersunday

View File

@@ -1,8 +0,0 @@
Patch HTML (header/menu unifié + onglet actif + liens CSS v=4)
- frontend/public/index.html
- frontend/public/tournament/index.html
- frontend/public/admin/index.html
Installation:
unzip -o supersunday_frontend_html_header_patch.zip -d .
docker compose build web && docker compose up -d

View File

@@ -1,12 +0,0 @@
Patch: Typography + titles facelift (hero titles, section titles, kicker labels).
Files:
- frontend/public/assets/style.titles.patch.css
Usage:
1. Copy style.titles.patch.css into frontend/public/assets/
2. In your HTML <head>, include:
<link rel="stylesheet" href="/assets/style.titles.patch.css" />
3. Apply classes .hero-title, .section-title, .kicker, .btn-outline in your HTML.
Rebuild web:
docker compose build web && docker compose up -d

14
backend/.env Normal file
View File

@@ -0,0 +1,14 @@
# --- Backend API ---
PORT=4000
# --- Postgres ---
PGHOST=db
PGUSER=postgres
PGPASSWORD=postgres
PGDATABASE=supersunday
PGPORT=5432
# --- Admin auth ---
ADMIN_EMAIL=admin@supersunday.local
ADMIN_PASSWORD=changeme
JWT_SECRET=supersecret

View File

@@ -1,12 +1,14 @@
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json* .npmrc* ./
RUN npm ci --omit=dev
FROM node:20-alpine FROM node:20-alpine
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules COPY package*.json ./
RUN npm ci --omit=dev || npm install --production
COPY . . COPY . .
EXPOSE 8080
CMD ["node", "server.js"] RUN apk add --no-cache curl
EXPOSE 4000
HEALTHCHECK --interval=10s --timeout=3s --retries=12 --start-period=30s CMD curl -fsS http://localhost:4000/api/health || exit 1
CMD ["node","src/index.js"]

View File

@@ -0,0 +1,45 @@
Supersunday — Backend CRUD patch (final)
Adds:
- src/middleware/auth.js (JWT guard)
- src/routes/auth.js (POST /api/auth/login)
- src/routes/tournaments.js (GET list/one/related, POST create, POST add participant)
- src/routes/matches.js (POST /api/matches/:id/score)
- src/db.js (pg connection)
- src/index.js (mount routes + /api/health)
- sql/schema.sql (tables + indexes)
Install:
1) Unzip into ./backend
unzip -o supersunday_backend_crud_patch.zip -d backend
2) Ensure backend/.env has:
PORT=4000
PGHOST=db
PGUSER=postgres
PGPASSWORD=postgres
PGDATABASE=supersunday
PGPORT=5432
ADMIN_EMAIL=admin@supersunday.local
ADMIN_PASSWORD=changeme
JWT_SECRET=supersecret
3) Apply schema:
docker compose cp backend/sql/schema.sql db:/tmp/schema.sql
docker compose exec db psql -U postgres -d supersunday -f /tmp/schema.sql
4) Rebuild API:
docker compose up -d --build api
Quick test:
- Login:
curl -s http://localhost:4000/api/auth/login -H 'Content-Type: application/json' -d '{"email":"admin@supersunday.local","password":"changeme"}'
- Create tournament:
TOKEN=...; curl -s http://localhost:4000/api/tournaments -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"name":"Super Sunday #2","location":"PC","start_date":"2025-10-05"}'
- Add participant:
curl -s http://localhost:4000/api/tournaments/1/participants -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"full_name":"Nouvel Joueur"}'
- Score match:
curl -s http://localhost:4000/api/matches/1/score -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"score_a":6,"score_b":4,"done":true}'

View File

@@ -1,18 +1,18 @@
{ {
"name": "supersunday-api", "name": "supersunday-backend",
"version": "1.1.0", "version": "1.0.0",
"type": "module", "type": "module",
"private": true, "main": "src/index.js",
"scripts": { "scripts": {
"start": "node server.js" "start": "node src/index.js",
"dev": "node --watch src/index.js"
}, },
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.6.1",
"express": "^4.19.2", "express": "^4.21.2",
"helmet": "^7.1.0", "helmet": "^7.2.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"pg": "^8.11.5" "pg": "^8.11.5"
} }
} }

28
backend/sql/schema.sql Normal file
View File

@@ -0,0 +1,28 @@
create table if not exists tournaments(
id serial primary key,
name text not null,
location text,
start_date date,
end_date date,
created_at timestamp default now()
);
create table if not exists participants(
id serial primary key,
tournament_id int not null references tournaments(id) on delete cascade,
full_name text not null,
created_at timestamp default now()
);
create index if not exists idx_participants_t on participants(tournament_id);
create table if not exists matches(
id serial primary key,
tournament_id int not null references tournaments(id) on delete cascade,
court text,
starts_at timestamp,
team_a text,
team_b text,
score_a int default 0,
score_b int default 0,
done boolean default false,
created_at timestamp default now()
);
create index if not exists idx_matches_t on matches(tournament_id);

11
backend/src/db.js Normal file
View File

@@ -0,0 +1,11 @@
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({
host: process.env.PGHOST || 'db',
port: Number(process.env.PGPORT || 5432),
user: process.env.PGUSER || 'postgres',
password: process.env.PGPASSWORD || 'postgres',
database: process.env.PGDATABASE || 'supersunday',
});
export default pool;

24
backend/src/index.js Normal file
View File

@@ -0,0 +1,24 @@
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 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() }));
app.use('/api/auth', authRoutes);
app.use('/api/tournaments', tournamentsRoutes);
app.use('/api/matches', matchesRoutes);
app.use('/api/tournaments', standingsRoutes);
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => console.log(`API listening on :${PORT}`));

View File

@@ -0,0 +1,14 @@
import jwt from 'jsonwebtoken';
export function requireAuth(req, res, next) {
try {
const h = req.headers.authorization || '';
const token = h.startsWith('Bearer ') ? h.slice(7) : '';
if (!token) return res.status(401).json({ error: 'missing_token' });
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'supersecret');
req.user = decoded;
next();
} catch (e) {
res.status(401).json({ error: 'invalid_token' });
}
}

View File

@@ -0,0 +1,17 @@
import { Router } from 'express';
import jwt from 'jsonwebtoken';
const router = Router();
router.post('/login', async (req, res) => {
const { email, password } = req.body || {};
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@supersunday.local';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme';
if (email === ADMIN_EMAIL && password === ADMIN_PASSWORD) {
const token = jwt.sign({ sub: email, role: 'admin' }, process.env.JWT_SECRET || 'supersecret', { expiresIn: '12h' });
return res.json({ token });
}
return res.status(401).json({ error: 'bad_credentials' });
});
export default router;

View File

@@ -0,0 +1,20 @@
import { Router } from 'express';
import pool from '../db.js';
import { requireAuth } from '../middleware/auth.js';
const router = Router();
// Score a match (admin)
router.post('/:id/score', requireAuth, async (req,res)=>{
const mid = Number(req.params.id);
const { score_a, score_b, done } = req.body || {};
if (!mid) return res.status(400).json({ error:'bad_match_id' });
const { rows } = await pool.query(
'update matches set score_a=$2, score_b=$3, done=coalesce($4, done) where id=$1 returning *',
[mid, score_a ?? 0, score_b ?? 0, done]
);
if (!rows[0]) return res.status(404).json({ error:'match_not_found' });
res.json(rows[0]);
});
export default router;

View File

@@ -0,0 +1,78 @@
import { Router } from 'express';
import pool from '../db.js';
const router = Router();
// GET /api/tournaments/:id/standings
router.get('/:id/standings', async (req, res) => {
const tid = Number(req.params.id);
if (!tid) return res.status(400).json({ error: 'bad_tournament_id' });
const { rows: players } = await pool.query('select id, full_name from participants where tournament_id=$1', [tid]);
const { rows: matches } = await pool.query('select * from matches where tournament_id=$1 order by id', [tid]);
const nameToId = new Map(players.map(p => [p.full_name, p.id]));
const stats = new Map(players.map(p => [p.id, {
player_id: p.id, name: p.full_name,
played: 0, wins: 0, draws: 0, losses: 0,
games_for: 0, games_against: 0, points: 0
}]));
const addStats = (pid, gf, ga, wdl) => {
const s = stats.get(pid);
s.played += 1;
s.games_for += gf;
s.games_against += ga;
if (wdl === 'W') s.wins += 1;
else if (wdl === 'D') s.draws += 1;
else if (wdl === 'L') s.losses += 1;
s.points += gf; // Americano style: points = games won
};
for (const m of matches) {
if (!m.team_a || !m.team_b) continue;
const aNames = m.team_a.split('&').map(s => s.trim());
const bNames = m.team_b.split('&').map(s => s.trim());
const aIds = aNames.map(n => nameToId.get(n)).filter(Boolean);
const bIds = bNames.map(n => nameToId.get(n)).filter(Boolean);
const sa = Number(m.score_a || 0);
const sb = Number(m.score_b || 0);
const considered = m.done === true || sa > 0 || sb > 0;
if (!considered) continue;
let outcomeA = 'D', outcomeB = 'D';
if (sa > sb) { outcomeA = 'W'; outcomeB = 'L'; }
else if (sb > sa) { outcomeA = 'L'; outcomeB = 'W'; }
for (const pid of aIds) addStats(pid, sa, sb, outcomeA);
for (const pid of bIds) addStats(pid, sb, sa, outcomeB);
}
const table = Array.from(stats.values()).map(s => ({
...s,
diff: s.games_for - s.games_against,
rank_key: [-(s.points), -(s.diff), -(s.wins), s.name.toLowerCase()]
}));
table.sort((x, y) => {
for (let i = 0; i < x.rank_key.length; i++) {
if (x.rank_key[i] < y.rank_key[i]) return -1;
if (x.rank_key[i] > y.rank_key[i]) return 1;
}
return 0;
});
let rank = 0, prevKey = null;
for (const row of table) {
const key = JSON.stringify(row.rank_key);
if (key !== prevKey) rank += 1;
row.rank = rank;
prevKey = key;
delete row.rank_key;
}
res.json({ tournament_id: tid, standings: table });
});
export default router;

View File

@@ -0,0 +1,59 @@
import { Router } from 'express';
import pool from '../db.js';
import { requireAuth } from '../middleware/auth.js';
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);
});
// 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)=>{
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]);
});
// 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]);
});
export default router;

1
data/db/PG_VERSION Normal file
View File

@@ -0,0 +1 @@
16

BIN
data/db/base/1/112 Normal file

Binary file not shown.

BIN
data/db/base/1/113 Normal file

Binary file not shown.

BIN
data/db/base/1/1247 Normal file

Binary file not shown.

BIN
data/db/base/1/1247_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/1247_vm Normal file

Binary file not shown.

BIN
data/db/base/1/1249 Normal file

Binary file not shown.

BIN
data/db/base/1/1249_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/1249_vm Normal file

Binary file not shown.

BIN
data/db/base/1/1255 Normal file

Binary file not shown.

BIN
data/db/base/1/1255_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/1255_vm Normal file

Binary file not shown.

BIN
data/db/base/1/1259 Normal file

Binary file not shown.

BIN
data/db/base/1/1259_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/1259_vm Normal file

Binary file not shown.

BIN
data/db/base/1/13460 Normal file

Binary file not shown.

BIN
data/db/base/1/13460_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/13460_vm Normal file

Binary file not shown.

0
data/db/base/1/13463 Normal file
View File

BIN
data/db/base/1/13464 Normal file

Binary file not shown.

BIN
data/db/base/1/13465 Normal file

Binary file not shown.

BIN
data/db/base/1/13465_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/13465_vm Normal file

Binary file not shown.

0
data/db/base/1/13468 Normal file
View File

BIN
data/db/base/1/13469 Normal file

Binary file not shown.

BIN
data/db/base/1/13470 Normal file

Binary file not shown.

BIN
data/db/base/1/13470_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/13470_vm Normal file

Binary file not shown.

0
data/db/base/1/13473 Normal file
View File

BIN
data/db/base/1/13474 Normal file

Binary file not shown.

BIN
data/db/base/1/13475 Normal file

Binary file not shown.

BIN
data/db/base/1/13475_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/13475_vm Normal file

Binary file not shown.

0
data/db/base/1/13478 Normal file
View File

BIN
data/db/base/1/13479 Normal file

Binary file not shown.

0
data/db/base/1/1417 Normal file
View File

0
data/db/base/1/1418 Normal file
View File

BIN
data/db/base/1/174 Normal file

Binary file not shown.

BIN
data/db/base/1/175 Normal file

Binary file not shown.

BIN
data/db/base/1/2187 Normal file

Binary file not shown.

0
data/db/base/1/2224 Normal file
View File

BIN
data/db/base/1/2228 Normal file

Binary file not shown.

0
data/db/base/1/2328 Normal file
View File

0
data/db/base/1/2336 Normal file
View File

BIN
data/db/base/1/2337 Normal file

Binary file not shown.

BIN
data/db/base/1/2579 Normal file

Binary file not shown.

BIN
data/db/base/1/2600 Normal file

Binary file not shown.

BIN
data/db/base/1/2600_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2600_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2601 Normal file

Binary file not shown.

BIN
data/db/base/1/2601_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2601_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2602 Normal file

Binary file not shown.

BIN
data/db/base/1/2602_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2602_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2603 Normal file

Binary file not shown.

BIN
data/db/base/1/2603_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2603_vm Normal file

Binary file not shown.

0
data/db/base/1/2604 Normal file
View File

BIN
data/db/base/1/2605 Normal file

Binary file not shown.

BIN
data/db/base/1/2605_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2605_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2606 Normal file

Binary file not shown.

BIN
data/db/base/1/2606_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2606_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2607 Normal file

Binary file not shown.

BIN
data/db/base/1/2607_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2607_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2608 Normal file

Binary file not shown.

BIN
data/db/base/1/2608_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2608_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2609 Normal file

Binary file not shown.

BIN
data/db/base/1/2609_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2609_vm Normal file

Binary file not shown.

BIN
data/db/base/1/2610 Normal file

Binary file not shown.

BIN
data/db/base/1/2610_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2610_vm Normal file

Binary file not shown.

0
data/db/base/1/2611 Normal file
View File

BIN
data/db/base/1/2612 Normal file

Binary file not shown.

BIN
data/db/base/1/2612_fsm Normal file

Binary file not shown.

BIN
data/db/base/1/2612_vm Normal file

Binary file not shown.

0
data/db/base/1/2613 Normal file
View File

Some files were not shown because too many files have changed in this diff Show More