- Add <thead>/<th> to rate limiting and error codes tables in doc.html.twig
- Ignore SonarQube css:S4662 rule on SCSS files (Tailwind v4 @source)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add proper <thead> with <th> headers to tables in email templates:
order_cancelled_orga, order_notification_orga, order_refunded,
organizer_invitation, payment_failed, scan_force_notification
- Add proper <thead> with <th> headers to tables in PDF templates:
attestation_ventes, billet, export_recap, invoice
- Fix testInfraPageWithSnapshotData: provide complete server data
(os, uptime, cpu, ram, disk, services, ssl) required by the template
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add POST /admin/commandes/{id}/forcer-validation to force validate pending
orders (generates tickets, sends emails, notifies organizer)
- Add "Forcer validation" button in orders template for pending orders
- Fix retrievePaymentIntent to query on organizer's Connect account
- Update stripe:sync to pass organizer stripeAccountId when checking payments
- Add 3 tests for force validation (pending, non-pending, not found)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add responsive breakpoints (sm/md) to event_detail.html.twig: adaptive titles, stacked ticket layout on mobile, reduced padding/spacing
- Add responsive breakpoints to order templates (guest, summary, public, payment, success): adaptive typography, padding, and layouts
- Fix BreadcrumbList JSON-LD: escape names with json_encode, remove item URL from last breadcrumb
- Update deploy.yml cron schedule from 3h/13h/19h/23h to 1h/22h
- Add <title> tags to rgpd_deletion.html.twig and rgpd_access.html.twig
- Add scope attributes to all <th> tags in rgpd_access.html.twig
- Replace deprecated width/cellpadding/cellspacing HTML attributes with CSS in scan_force_notification email
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add integrity/crossorigin attributes to chart.js and html5-qrcode CDN scripts
- Replace md5() with hash('xxh128') for Meilisearch cache key generation (non-sensitive context)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reduce requireStripeReady() from 4 returns to 2 by combining conditions
- Extract SCANNER_PATH constant in ScannerController to avoid duplicated "/scanner/" literal
- Remove user-scalable=no from scanner viewport meta (WCAG accessibility)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create Attestation entity with reference, signature hash (HMAC-SHA256), event, user, payload
- Add migration Version20260326180000 for attestation table
- Save each attestation in DB with unique signature for tamper-proof verification
- Add public route /attestation/ventes/r/{reference} for QR code verification (short URL)
- Keep fallback /attestation/ventes/{hash} route for base64-signed verification
- Public page shows "Attestation conforme" with signature proof, no detailed data
- QR code on PDF now uses short reference URL instead of full base64 hash (scannable)
- Increase QR code resolution to 300px for better readability
- Display verification URL on PDF next to QR code
Attestation PDF improvements:
- Rename "ATTESTATION DE VENTES" to "ATTESTATION"
- Add two modes: "Attestation detaillee" (with ticket list) and "Attestation simple" (certification only)
- Simple mode: certifies figures are valid, only paid billets/votes confirmed by Stripe count
- Detailed mode: adds full ticket listing with reference, order number, billet name, buyer name
- No amounts displayed in either mode
- Gold color scheme (#fabf04) for headers, borders, table headers, summary box
- Larger text in QR verification box for readability
Scanner: ROLE_ROOT buyer tickets always validate at scan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SSO login button to scanner PWA with Keycloak redirect flow via session state
- Add manual scan mode via security key (16 chars) alongside QR camera scan
- Add audio feedback: good (accepted), warning (already scanned), refused sounds
- Add unique scan counter per reference (no double counting same ticket)
- Add order details display in scan results (order number, email, total, items)
- Add force validation button for refused tickets (organizer/ROLE_ROOT only), sends email notification
- Add already_scanned warning only for same-day scans, exit_definitive only same day
- Staff and exposant tickets always validate regardless of state
API: ROLE_ROOT access to all events, categories, billets, and scan endpoints
- ROLE_ROOT bypasses ownership checks on all /api/live/* endpoints
- ROLE_ROOT can login via API (email/password and SSO)
- Scan API accepts securityKey parameter in addition to reference
- Scan response includes billetType, buyerEmail, and full order details with items
Event management: tickets tab, staff/exposant accreditations, attestation PDF
- Add Tickets tab listing all sold tickets with search, download PDF, resend email, cancel actions
- Add Staff/Exposant accreditation form in Invitations tab, generates dedicated non-buyable billet
- Add Attestation tab to generate sales certificate PDF with category/billet selection
- PDF billet template shows STAFF/EXPOSANT badge with distinct colors (black/purple)
- Exclude invitations from all financial stats (event stats, admin dashboard, organizer finances)
- Fix sold counts to exclude invitations in categories recap
- Use actual Stripe fee parameters instead of hardcoded values in commission calculations
- Add commission detail breakdown (E-Ticket + Stripe) in categories and stats tabs
Admin: download tickets for orders
- Add download button on admin orders page (single PDF or ZIP for multiple tickets)
Scanner PWA fixes: CSP (unpkg -> jsdelivr), service worker scope (/scanner/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Labels and card text: text-gray-400 -> text-gray-600 on #fbfbfb bg
- Empty state message: text-gray-400 -> text-gray-600 on white bg
- Add explicit width/height to navbar logo to prevent CLS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- text-yellow-500 on white bg had ratio ~1.9 (need 4.5), now text-yellow-700
- text-indigo-600 links on white bg had ratio ~3.8, now text-indigo-800
with permanent underline for link visibility (WCAG 1.4.1)
- Cookie banner link also updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The endpoint path is now /t/<8-char hash of APP_SECRET> instead of
static /t. Token is injected via data-e attribute on body, read by JS.
Server validates token on every hit, returns 404 if invalid.
Changes with each APP_SECRET = impossible to hardcode in a blocker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bar chart: visitors per day
- Line chart: pageviews per day (with fill)
- Bounce rate KPI with color coding (green/yellow/red)
- Filter out self-referrers (ticket.e-cosplay.fr, esyweb.local)
- Uses Chart.js via cdn.jsdelivr.net (already in CSP)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bounce rate = visitors with only 1 pageview / total visitors.
Color-coded: green <40%, yellow <60%, red >=60%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Send confirmation email when no data found for access or deletion request
- Add DPO contact (DPO-167945, E-Cosplay) to both access and deletion PDFs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RGPD (/rgpd):
- Access form: search by IP, generate PDF with all visitor data, email it
- Deletion form: delete all visitor data by IP, generate attestation PDF
- Both forms pre-fill client IP, require email for response
- PDF templates with E-Cosplay branding, RGPD article references
Admin Analytics (/admin/analytics):
- KPIs: unique visitors, pageviews, pages/visitor
- Top pages and referrers tables
- Device type, browser, OS breakdowns
- Period filter: today, 7d, 30d, all
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core system:
- AnalyticsUniqId entity (visitor identity with device/os/browser parsing)
- AnalyticsEvent entity (page views linked to visitor)
- POST /t endpoint with AES-256-GCM encrypted payloads
- HMAC-SHA256 visitor hash for anti-tampering
- Async processing via Messenger
- JS module: auto page_view tracking, setAuth for logged users
- Encryption key shared via data-k attribute on body
- setAuth only triggers when cookie consent is accepted
- Clean CSP: remove old tracker domains (Cloudflare, Umami)
100% first-party, no cookies, invisible to adblockers, RGPD-friendly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /admin/infra page was slow because Docker stats API blocks per container.
Now a cron (every 5min) generates var/infra.json via app:infra:snapshot,
and the page reads the static JSON file instantly.
Mount Docker socket in cron container for snapshot access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete rewrite of /admin/infra into 4 columns:
- Col 1 (Serveur): CPU, RAM, Disk, System, Services (Caddy, Docker, SSL cert)
- Col 2 (Containers): All Docker containers with CPU%, RAM, state via Docker API
- Col 3 (Redis): Global stats + per-DB (Messenger, Sessions, Cache)
- Col 4 (PostgreSQL): Instance stats + PgBouncer pools/stats
Extract all infra logic into InfraService. Mount Docker socket (read-only)
in PHP container for container stats. Check SSL cert expiry and Caddy status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First row shows host-level stats from /proc: CPU model, cores, load
average with charge %, RAM total/used/available with usage %, disk
total/used/free with usage %, hostname, OS and uptime. All color-coded
green <70%, yellow <90%, red >=90%.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows per-container: hostname, PHP version, SAPI, uptime, CPU cores,
CPU usage % (sampled from cgroup), load averages (1/5/15m), RAM used/
total/free with usage %. Color-coded: green <70%, yellow <90%, red >=90%.
Reads from cgroup v2 (fallback v1) and /proc for container-level stats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add pgbouncer service to docker-compose-dev.yml with dev config
- Route DATABASE_URL through pgbouncer:6432 in dev (matches prod)
- Add PgBouncer pools and stats tables to /admin/infra with color-coded
avg query/xact times and client waiting indicators
- php, messenger, cron now depend on pgbouncer instead of database directly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows 3 Redis databases separately (Messenger, Sessions, Cache) with
key count and average TTL, alongside global Redis stats and PostgreSQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows real-time stats with color-coded indicators:
- Redis: version, memory, hit rate, ops/sec, evicted keys
- PostgreSQL: version, db size, connections, cache hit ratio, dead tuples
Uses MESSENGER_TRANSPORT_DSN for Redis auth (works in dev and prod).
Accessible via /admin/infra with nav link.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Standalone installable PWA with:
- JWT login via /api/auth/login
- Event list from /api/live/events
- QR code camera scanning (html5-qrcode library)
- Scan results with accepted/refused state and ticket details
- Auto token refresh on expiry
- Offline caching via service worker
- Dark theme optimized for outdoor scanning
- Vibration feedback on scan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin can now view the current logo and upload a new one via the
organizer edit form. Uses VichUploader with the existing organizer_logo
mapping. Adds test with fixture image.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show charges/payouts acceptance status and Stripe connection state
when an admin views an organizer's event. Pass owner to template
and use it for Stripe checks instead of app.user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add toggle online/offline and delete routes in AdminController
- Add action buttons (En ligne, Modifier, Supprimer) in admin events template
- Bypass requireEventOwnership and requireStripeReady for ROLE_ROOT so admin can edit any event
- Add Meilisearch healthcheck and depends_on in messenger service (prod + dev)
- Add tests for all new admin routes and ROLE_ROOT bypass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add nullable debt field to User entity with addDebt/reduceDebt helpers
- On refund webhook: add refunded amount to organizer debt
- On dispute webhook (charge.dispute.created): add disputed amount to debt
- OrderController: if organizer has debt > 0, payment goes to main Stripe
account instead of connected account, debt reduced on payment success
- Display debt amount on organizer dashboard with warning message
- Add dispute notification email template
- Migration for debt column on user table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add billing fields to User (isBilling, billingAmount, billingState,
billingStripeSubscriptionId) and OrganizerInvitation (billingAmount)
- Registration: organizer gets billingState="poor" (pending review)
- Admin approval: sets isBilling=true, billingAmount from form, state="good"
- Invitation: billingAmount from invitation, if 0 then isBilling=false
- ROLE_ROOT accounts: billing free (amount=0, state="good")
- Block Stripe Connect creation and all organizer features if state is
"poor" or "suspendu"
- Hide Stripe configuration section if billing not settled
- Add billing checkout via Stripe subscription with success route
- Webhooks: checkout.session.completed activates billing,
invoice.payment_failed and customer.subscription.deleted suspend
account and disable online events
- Show billing alert on /mon-compte with amount and subscribe button
- Display billing info in invitation email and landing page
- Add email templates for billing activated/failed/cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Hide organizer tabs (events, subaccounts, payouts) if Stripe not ready
- Redirect organizer tab content and all organizer routes to /mon-compte
- Add requireStripeReady() guard on all ROLE_ORGANIZER routes
- Force default tab to 'tickets' when Stripe is not validated
- Update test fixtures: approved organizers get Stripe enabled by default
- Add tests for blocked tabs and blocked event creation without Stripe
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Desktop: yellow "Inscription" button next to login icon (hidden on mobile)
- Mobile menu: yellow "Inscription" block after "Connexion" link
- Registration was already functional at /inscription, just missing from navbar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Insomnia export (/api/doc/insomnia.json):
- Generates Insomnia v4 export format with all API routes
- Workspace with environment variables (base_url, env, email, password, jwt_token)
- Folders per section (Auth, Events, Categories, Billets, Scanner)
- Each request with correct method, URL with Insomnia template vars, headers, body
- Auth routes use base_url directly, others use base_url/api/{env}/...
- Download button (indigo) next to Spec JSON button
Dynamic hostname:
- Insomnia export uses request.getSchemeAndHttpHost() (not hardcoded)
- Template passes host via data-host attribute
- JS env switcher reads host from data-host or falls back to location.origin
- Base URLs update dynamically when switching sandbox/live
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove POST /api/scan/verify (redundant with /api/scan)
- POST /api/scan now returns state: "accepted" or "refused" with reason
- Refused reasons: already_scanned, invalid, expired, exit_definitive, wrong_event
- Accepted response includes details object (for future additional data)
- Template: render extra section (refusal reasons table in red)
- Only 2 POST routes remain: /api/auth/login + /api/scan (all others are GET)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security:
- Move env switcher logic to assets/modules/api-env-switcher.js (no inline script)
- Register in app.js via initApiEnvSwitcher()
- Compliant with CSP script-src (no unsafe-inline needed for this page)
API doc:
- Add CSP policy section showing all authorized origins per directive
- Table: script-src, connect-src, style-src, img-src, font-src, frame-src, form-action, object-src, worker-src
- Note: inline scripts not allowed, must use nonce or external file
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Toggle switch (Sandbox orange / Live green) in header section
- Switches update in real-time: base URL, description, all endpoint path prefixes
- Sandbox: /api/sandbox (orange), Live: /api/live (green)
- Auth endpoints (/api/auth/*) are not affected by the toggle
- No page reload needed, pure JS DOM updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API doc:
- Add sandbox (/api/sandbox) and live (/api/live) environments with badges
- Auth (/api/auth/login) is shared between environments
- Endpoint paths show both prefixes: /api/sandbox|/api/live/...
- Auth endpoints show path without prefix
TASK_CHECKUP:
- Replace API key auth with JWT auth (ETicket-Email + ETicket-JWT headers)
- All routes use {env} prefix (sandbox/live)
- /mon-compte API tab redirects to /api/doc
- Sandbox: read-only mode (POST/PATCH/DELETE return result without DB modification)
- Mark documentation tasks as done
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Public event page:
- Share buttons: X (Twitter), Facebook, Instagram (copy link), TikTok (copy link), copy link
- Buttons use url_encode for share URLs with event title + URL
- Instagram/TikTok copy to clipboard (no direct share URL support)
- Consistent brutal design with aria-labels
Organizer dashboard:
- Share X, Facebook, copy link buttons per event in events list
- QR code download button per event
- Route /mon-compte/evenement/{id}/qrcode: generates 400px PNG QR code via Endroid
- QR code points to public event URL, downloaded as qrcode-{slug}.png
JS module:
- assets/modules/share.js: initShare() handles data-share-copy buttons
- Copies URL to clipboard, shows checkmark for 1.5s then restores icon
- 4 tests (no buttons, copy, checkmark restore, multiple buttons)
Social icons already displayed via _social_icons.html.twig component
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>