- Move export, exportPdf, payoutPdf from AccountController to
AccountEventOperationsController (9 -> 12 methods)
- Remove getAllowedBilletTypes delegate from AccountController
- Update tests to reference AccountEventCatalogController for that method
- Remove unused DQL_EXCLUDE_INVITATIONS from AdminOrdersController
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MeilisearchServiceTest: add test for invalidateSearchCache()
- AnalyticsCryptoService: mark unreachable tryDecryptJsFormat guard
with @codeCoverageIgnore (decrypt already checks strlen >= 28)
- AccountControllerTest: add test for tickets search query (tq param)
- AdminControllerTest: add test for infra page with snapshot data file
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AttestationController: fix decodeAndVerifyHash to have max 3 returns, add 11 tests covering all routes (check, ventesRef, ventes) and all decodeAndVerifyHash branches (invalid base64, missing pipe, bad signature, bad JSON, valid hash with/without registered attestation), plus generateHash unit tests with unicode
- LegalController: add 6 tests for RGPD POST routes (rgpdAccess and rgpdDeletion) covering empty fields, data found, and no data found scenarios
- AdminController: add 10 tests for analytics page (all period filters + access denied) and orderTickets endpoint (single ticket PDF, multiple tickets ZIP, order not found, no tickets)
- AccountController: add 17 tests for downloadTicket (success/denied/404), resendTicket (success/denied/404), cancelTicket (success/denied/404), createAccreditation (staff/exposant/empty fields/no categories/invalid type), eventAttestation (with categories/billets/empty selection)
- AnalyticsEvent entity: new test file with 8 tests covering constructor defaults, all getters/setters, nullable fields, and fluent interface
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 retrievePaymentIntent() to StripeService
- StripeSyncCommand now checks pending orders against Stripe API:
- succeeded: generates tickets, sends emails, notifies organizer
- canceled: marks order as cancelled + audit log
- requires_payment_method: marks as cancelled + audit + failure email
- other statuses: logs as still pending
- Add 13 tests covering accounts sync + all pending order scenarios
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 'user-script' to ignored source files in CspReportController to filter
out false positive CSP violations triggered by browser extensions/userscripts.
Add corresponding test case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ApiAccountController::lookup: reduce from 4 to 3 returns via ternary
- AttestationController::ventes: reduce from 5 to 2 returns by extracting
decodeAndVerifyHash() helper; add TPL_NOT_FOUND_VENTES constant for the
template literal duplicated 5 times
- AnalyticsCryptoService::decrypt: reduce from 4 to 2 returns by extracting
tryDecryptJsFormat() helper
- InfraService::fmtDuration: reduce from 4 to 1 return using match(true)
- InfraService: replace nested ternary with match(true) for SSL status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The constant was incorrectly defined as self::NO_SNAPSHOT_MSG = self::NO_SNAPSHOT_MSG
causing a PHP fatal error. Replace with the actual string value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New API endpoint secured by X-App-Secret header (no JWT auth required).
Accepts an email in the request body and returns the user's id and
stripeAccountId if present. Includes 6 unit tests covering all cases
(success, missing secret, invalid secret, missing email, user not found).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AnalyticsController::track: extract handleTrackData(), reduce from 7 to 3 returns
- ApiAuthController::ssoValidate: extract ssoError/ssoSuccess helpers, reduce from 6 to 3 returns
- ApiLiveController::scan: extract findTicketFromRequest(), reduce from 4 to 3 returns
- ApiLiveController::scanForce: flatten logic, reduce from 6 to 3 returns
- ApiLiveController::processScan: extract isAlwaysValidTicket, checkRefusal,
markScannedAndRespond, reduce cognitive complexity from 16 to under 15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @param array<string, mixed> to AnalyticsController::createVisitor()
- Add @param/@return array<string, mixed> to AnalyticsCryptoService encrypt/decrypt
- Add @param array<string, mixed>|null to InfraService calcCpuPercent/calcMemory
- Merge duplicate docblocks on InfraService::calcMemory()
- Use intersection type CacheInterface&CacheItemPoolInterface for MeilisearchService cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add DQL_EXCLUDE_INVITATIONS, DQL_BB_EXCLUDE_INVITATIONS, CONTENT_TYPE_PDF constants
- Reduce createAccreditation from 4 to 3 returns by combining validations
- Extract collectAttestationBillets, buildAttestationStats, buildAttestationTicketDetails
from eventAttestation to reduce cognitive complexity from 18 to under 15
- Remove unused $totalRevenue, duplicate $label, and securityKey from attestation details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused $indexes variable (listIndexes() kept as health check)
- Make $fixMissing parameter required (always passed), remove && $fixMissing check
- Remove redundant array_values() on array_map result (already a list)
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>
Template accesses redis_global.error, postgres.error and pgbouncer.error
in the else branch when connected=false, but the key was not provided.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Template accesses server.hostname etc. but server was passed as empty array
when infra.json does not exist. Now provides all expected keys with '?' defaults.
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>
- Extract filterUsers() and createUserIndex() from checkUserIndex() to reduce cognitive complexity from 16 to under 15
- Add INDEXES_ENDPOINT constant to replace duplicated "/indexes" literal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace unpkg.com with cdn.jsdelivr.net in sw.js cache list
- Fix sw.js scope to /scanner/
- Remove unused $indexes parameter from checkAllIndexes()
- Extract duplicated " [%s] Index missing" literal to INDEX_MISSING_MSG constant
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>
Mentions legales, CGU, CGV, hebergement, cookies, RGPD, conformite
are now allowed for crawling instead of disallowed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New SECRET_ANALYTICS variable replaces kernel.secret for analytics
- Ansible generates a random 32-char secret at each deploy
- Endpoint token and encryption key change with every deployment
- Existing sessions will get new visitor_id after deploy (expected)
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>
SUBSTRING does not work on timestamp in PostgreSQL. Use native SQL
with CAST(created_at AS DATE) for daily chart aggregation.
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>
symfony/uid not installed, replace Uuid::v4() with random_int based
UUID v4 generation (RFC 4122 compliant).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web Crypto API AES-GCM outputs: iv + ciphertext + tag (tag appended)
PHP openssl was using: iv + tag + ciphertext (tag in middle)
Now both use the same format: iv (12 bytes) + ciphertext + tag (16 bytes).
Decrypt tries JS format first, falls back to PHP format for compatibility.
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>
If DATABASE_URL uses port 5432 (direct postgres), the PgBouncer stats
connection falls back to pgbouncer:6432. Fixes error when .env.local
overrides DATABASE_URL to bypass pgbouncer.
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>
PgBouncer admin console does not support extended query protocol
(prepared statements). Setting PDO::ATTR_EMULATE_PREPARES = true
forces PDO to use simple query protocol for SHOW commands.
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>