import http from 'http'; import { readFile, writeFile, stat } from 'fs/promises'; import { createReadStream } from 'fs'; import path from 'path'; import url from 'url'; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); const PORT = process.env.PORT || 8080; const DATA_DIR = path.join(__dirname, 'data'); const DB_PATH = path.join(DATA_DIR, 'db.json'); const mime = { '.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'application/javascript; charset=utf-8','.json':'application/json; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.ico':'image/x-icon','.webp':'image/webp' }; function send(res, code, body, headers={}){ res.writeHead(code, { 'content-type':'text/plain; charset=utf-8', ...headers }); res.end(body); } async function ensureDb(){ try { await stat(DB_PATH); } catch { const base = new Date(Date.UTC(2025, 9, 5, 8, 0, 0)); // 5 Oct 2025 08:00 UTC const events = []; for (let i=0;i<8;i++){ const start = new Date(base.getTime() + i*14*24*3600*1000); const end = new Date(start.getTime() + 3*3600*1000); events.push({ id:`ss-winter-${i+1}`, kind:"Americano", name:`SuperSunday — Winter Edition #${i+1}`, date:start.toISOString().slice(0,19), end:end.toISOString().slice(0,19), location:"Sport City Woluwe" }); } const now = new Date(); events.push( { id:"ss-live-now", kind:"Americano", name:"SuperSunday — LIVE (démo)", date: new Date(now.getTime()-30*60*1000).toISOString().slice(0,19), end: new Date(now.getTime()+2.5*3600*1000).toISOString().slice(0,19), location:"TC Églantiers" }, { id:"ss-futur-1", kind:"Americano", name:"SuperSunday — Inscription ouverte", date: new Date(now.getTime()+10*24*3600*1000).toISOString().slice(0,19), end: new Date(now.getTime()+10*24*3600*1000+3*3600*1000).toISOString().slice(0,19), location:"Sport City Woluwe" } ); const names = ["SmashQueen","PadelKing42","MrVibora","LaBandeja","MissChiquita","VoléeMagique"]; const regs = names.map((n,i)=>({ id:`reg_seed_${i+1}`, createdAt:new Date().toISOString(), eventId: i%2===0? "ss-live-now" : "ss-winter-1", player:{ name:n, email:`${n.replace(/[^a-z0-9]/ig,'').toLowerCase()}@demo.local`, phone:`+3247${(1000000+i).toString().padStart(7,'0')}`, level: i%3===0? "Avancé": (i%3===1? "Intermédiaire":"Débutant") }, payment:i%3===0? "card": (i%3===1? "paypal":"onspot"), status: i%2===0? "confirmé":"en_attente", paymentStatus: i%2===0? "paid":"pending" })); await writeFile(DB_PATH, JSON.stringify({ config:{ siteName:"Padel24Play — V1 (8080)", currency:"EUR", priceEur:48, refundWindowHours:48 }, users:[ { email:"admin@supersunday.com", password:"password123", role:"admin", name:"Admin"}, { email:"player@supersunday.com", password:"password123", role:"user", name:"Player"} ], events, registrations: regs }, null, 2)); } } async function loadDb(){ await ensureDb(); return JSON.parse(await readFile(DB_PATH,'utf-8')); } async function saveDb(d){ await writeFile(DB_PATH, JSON.stringify(d,null,2)); } function sendJson(res, obj){ send(res, 200, JSON.stringify(obj), {'content-type':'application/json'}); } async function parseBody(req){ return new Promise((resolve,reject)=>{ let data=''; req.on('data',c=> data+=c); req.on('end', ()=>{ try{ const ct = req.headers['content-type']||''; if (!data) return resolve({}); if (ct.includes('application/json')) return resolve(JSON.parse(data)); if (ct.includes('application/x-www-form-urlencoded')){ const out={}; data.split('&').forEach(kv=>{ const [k,v] = kv.split('='); out[decodeURIComponent(k)] = decodeURIComponent((v||'').replace(/\\+/g,' ')); }); return resolve(out); } resolve({ raw:data }); }catch(e){ reject(e); } }); }); } async function serveStatic(req,res){ const publicDir = path.join(__dirname, 'public'); let pathname = url.parse(req.url).pathname || '/'; if (pathname === '/') pathname = '/index.html'; const filePath = path.join(publicDir, pathname); try { const st = await stat(filePath); if (!st.isDirectory()){ const ext = path.extname(filePath).toLowerCase(); res.writeHead(200, {'content-type': mime[ext] || 'application/octet-stream'}); createReadStream(filePath).pipe(res); return true; } }catch{} return false; } function isoToV(s){ return s.replace(/[-:]/g,'').replace('T','')+'Z'; } async function handleApi(req,res){ const { pathname, query } = url.parse(req.url, true); if (pathname === '/api/config' && req.method==='GET'){ const d = await loadDb(); return sendJson(res, d.config); } if (pathname === '/api/events' && req.method==='GET'){ const d = await loadDb(); return sendJson(res, d.events); } if (pathname === '/api/registrations' && req.method==='GET'){ const d = await loadDb(); const list = query.eventId ? d.registrations.filter(r=> r.eventId===query.eventId) : d.registrations; return sendJson(res, list); } if (pathname === '/api/registrations' && req.method==='POST'){ const d = await loadDb(); const body = await parseBody(req); const id = 'reg_'+Math.random().toString(36).slice(2,10); const rec = { id, createdAt:new Date().toISOString(), status:'en_attente', paymentStatus:'pending', ...body }; d.registrations.push(rec); await saveDb(d); return sendJson(res, rec); } if (pathname.startsWith('/api/ical/') && req.method==='GET'){ const regId = pathname.split('/').pop(); const d = await loadDb(); const reg = d.registrations.find(r=> r.id===regId); if (!reg) return send(res,404,'Not found'); const ev = d.events.find(e=> e.id===reg.eventId); if (!ev) return send(res,404,'Event not found'); const ics = [ 'BEGIN:VCALENDAR','VERSION:2.0','PRODID:-//P24P//SuperSunday//FR', 'BEGIN:VEVENT', `UID:${reg.id}@p24p`, `DTSTAMP:${isoToV(ev.date)}`, `DTSTART:${isoToV(ev.date)}`, `DTEND:${isoToV(ev.end)}`, `SUMMARY:${ev.name}`, `LOCATION:${ev.location}`, `DESCRIPTION:Inscription ${reg.player?.name||''}`, 'END:VEVENT','END:VCALENDAR' ].join('\\r\\n'); return send(res,200,ics,{'content-type':'text/calendar; charset=utf-8','content-disposition':`attachment; filename="${reg.id}.ics"`}); } return false; } const server = http.createServer(async (req,res)=>{ try{ if (req.url.startsWith('/api/')){ const ok = await handleApi(req,res); if (ok===false) return send(res,404,'API not found'); return; } const ok = await serveStatic(req,res); if (!ok) send(res,404,'Not found'); }catch(e){ console.error(e); send(res,500,'Server error'); } }); server.listen(PORT, ()=> console.log(`✅ SuperSunday V1 (prod) http://localhost:${PORT}`));