${escapeHtml(race.name)}
${banner}${result ? 'La pagina mostra solo le foto restituite da FaceAI.' : 'In questa simulazione il vecchio select tipoPuntoFoto รจ sostituito dal pulsante Face ID.'}
- ${photoList}
import express from 'express'; import cors from 'cors'; import cookieParser from 'cookie-parser'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { config } from './config.js'; import { signPayload, verifySignedPayload } from './auth.js'; import { createSession, createSearch, completeSearch, getResult, getSearch, getSession, mockCatalog } from './store.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const frontendDist = path.resolve(__dirname, '../../frontend/dist'); const app = express(); app.use(cookieParser()); app.use(express.json()); if (config.enableLocalLegacyStatic && fs.existsSync(config.localLegacyStaticRoot)) { app.use('/legacy-static', express.static(config.localLegacyStaticRoot)); } else { app.use('/legacy-static', (req, res) => { res.status(404).type('text/plain').send('Legacy static assets are not configured in this environment.'); }); } app.use(cors({ origin: config.frontendUrl, credentials: true })); function getFaceAiSession(req) { const sessionId = req.cookies[config.sessionCookieName]; return sessionId ? getSession(sessionId) : null; } function requireSession(req, res, next) { const session = getFaceAiSession(req); if (!session) { res.status(401).json({ error: 'Not authenticated with FaceAI' }); return; } req.faceaiSession = session; next(); } function issueHandoffToken({ raceId, raceSlug, lang, returnUrl }) { const race = mockCatalog[raceId] || { id: raceId, slug: raceSlug || `race-${raceId}`, name: raceSlug || `Race ${raceId}` }; return signPayload({ type: 'handoff', user: { id: 'legacy-user-1', displayName: 'Mario Rossi', email: 'mario.rossi@example.test', membershipStatus: 'active' }, race: { id: race.id, slug: race.slug, name: race.name }, lang: lang || 'it', returnUrl, expiresAt: Date.now() + 5 * 60 * 1000 }, config.sharedSecret); } function issueReturnToken(result) { return signPayload({ type: 'return', resultId: result.id, raceId: result.raceId, userId: result.userId, expiresAt: Date.now() + 5 * 60 * 1000 }, config.sharedSecret); } function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function renderLegacyRacePage({ raceId, lang = 'it', result = null }) { const race = mockCatalog[raceId] || { id: raceId, name: `Race ${raceId}`, slug: `race-${raceId}`, photos: [] }; const returnUrl = `${config.publicBaseUrl}/dev/legacy/race?raceId=${encodeURIComponent(race.id)}&lang=${encodeURIComponent(lang)}`; const photos = result ? result.matches : race.photos; const banner = result ? `
` : ''; const photoList = photos.length ? photos.map((photo) => `${result ? 'La pagina mostra solo le foto restituite da FaceAI.' : 'In questa simulazione il vecchio select tipoPuntoFoto รจ sostituito dal pulsante Face ID.'}
${escapeHtml(error.message)}
`); } }); app.post('/api/auth/exchange', (req, res) => { try { const { token } = req.body; const payload = verifySignedPayload(token, config.sharedSecret); if (payload.type !== 'handoff') { throw new Error('Wrong token type'); } const sessionId = createSession({ user: payload.user, race: payload.race, lang: payload.lang, returnUrl: payload.returnUrl, access: { faceAiAllowed: payload.user.membershipStatus === 'active' } }); res.cookie(config.sessionCookieName, sessionId, { httpOnly: true, sameSite: 'lax', secure: false, path: '/' }); res.json({ user: payload.user, race: payload.race, lang: payload.lang, returnUrl: payload.returnUrl, access: { faceAiAllowed: true } }); } catch (error) { res.status(400).json({ error: error.message }); } }); app.get('/api/session', requireSession, (req, res) => { res.json(req.faceaiSession); }); app.post('/api/searches', requireSession, (req, res) => { const raceId = String(req.body.raceId || req.faceaiSession.race.id); const selfieName = String(req.body.selfieName || 'selfie.jpg'); const search = createSearch({ raceId, selfieName, user: req.faceaiSession.user, returnUrl: req.faceaiSession.returnUrl, lang: req.faceaiSession.lang }); setTimeout(() => { completeSearch(search.id); }, 3500); res.status(201).json({ id: search.id, status: search.status, raceId: search.raceId, selfieName: search.selfieName }); }); app.get('/api/searches/:id', requireSession, (req, res) => { const search = getSearch(req.params.id); if (!search || search.user.id !== req.faceaiSession.user.id) { res.status(404).json({ error: 'Search not found' }); return; } res.json({ id: search.id, status: search.status, raceId: search.raceId, resultId: search.resultId, createdAt: search.createdAt, completedAt: search.completedAt, matchCount: search.matches.length }); }); app.get('/api/searches/:id/redirect', requireSession, (req, res) => { const search = getSearch(req.params.id); if (!search || search.user.id !== req.faceaiSession.user.id) { res.status(404).json({ error: 'Search not found' }); return; } if (search.status !== 'completed' || !search.resultId) { res.status(409).json({ error: 'Search not completed yet' }); return; } const result = getResult(search.resultId); const token = issueReturnToken(result); res.json({ url: `${config.legacyReturnUrl}?resultId=${encodeURIComponent(result.id)}&token=${encodeURIComponent(token)}` }); }); app.get('/bridge/results/:id', (req, res) => { try { const token = String(req.query.token || ''); const payload = verifySignedPayload(token, config.sharedSecret); if (payload.type !== 'return') { throw new Error('Wrong token type'); } if (String(payload.resultId || '') !== String(req.params.id)) { throw new Error('Result id mismatch'); } const result = getResult(req.params.id); if (!result || result.userId !== payload.userId) { throw new Error('Result not found'); } res.json({ id: result.id, raceId: result.raceId, raceName: result.raceName, userId: result.userId, returnUrl: result.returnUrl, lang: result.lang, matches: result.matches }); } catch (error) { res.status(400).json({ error: error.message }); } }); if (fs.existsSync(frontendDist)) { app.use(express.static(frontendDist)); app.get('*', (req, res, next) => { if (req.path.startsWith('/api/') || req.path.startsWith('/dev/')) { next(); return; } res.sendFile(path.join(frontendDist, 'index.html')); }); } app.listen(config.port, () => { console.log(`FaceAI backend listening on http://localhost:${config.port}`); });