Commit Graph

85 Commits

Author SHA1 Message Date
Serreau Jovann
822bf8915f Scanner: SSO login, 2 scan modes (camera/security key), sound feedback, order details, force validation, staff/exposant badges
- 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>
2026-03-26 15:50:42 +01:00
Serreau Jovann
83583e0d3d Enable Doctrine L2 cache and add Redis cache pools
Doctrine Second Level Cache (NONSTRICT_READ_WRITE) on:
- Event, User, Category, Billet, BilletDesign
- Default region: 1h TTL, short_lived region: 5min TTL

Redis cache pools added:
- app.cache.events (30min) — for event listings
- app.cache.homepage (5min) — for homepage data
- doctrine.result_cache_pool — DQL result cache via Redis
- doctrine.system_cache_pool — metadata/query cache

All pools backed by Redis DB 2. Reduces DB queries significantly
for read-heavy pages (event listings, user profiles, categories).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:50:13 +01:00
Serreau Jovann
809a1055ec Restore Cloudflare CSP rules needed for WAF/Turnstile
Cloudflare WAF requires cloudflareinsights.com and challenges.cloudflare.com
in script-src, connect-src, frame-src and external_redirects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:56 +01:00
Serreau Jovann
6438afadbf Add first-party analytics tracker with encrypted transmissions
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>
2026-03-26 11:52:07 +01:00
Serreau Jovann
e4edc76f58 Add Redis cache for Meilisearch search results and admin dashboard stats
- Configure Redis DB 2 as Symfony cache adapter
- Cache Meilisearch search results for 5 minutes (invalidated on writes)
- Cache admin dashboard stats for 10 minutes
- Add invalidateSearchCache() called after each Meilisearch write
- Update tests to support cache mock injection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:24:35 +01:00
Serreau Jovann
b075209746 Add tools-security.esy-web.dev to script-src CSP directive
Direct script loading requires the domain in script-src,
not just connect-src. Added to both base and prod config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 10:01:47 +01:00
Serreau Jovann
d44e75e3fd Fix SonarQube issues, store sessions in Redis, use direct analytics URLs
- ApiSandboxController: reduce scan() returns from 4 to 3 via ternary
- ApiDocController: add MIME_JSON constant, extract buildInsomniaRequest()
  and buildInsomniaBody() to reduce cognitive complexity
- Store sessions in Redis to fix SSO disconnect with 2 PHP replicas
  (round-robin load balancing caused session loss on filesystem storage)
- Configure session cookie: 24h lifetime, secure auto, samesite lax
- Replace Caddy analytics proxies (/stats/*, /assets/perf.js, /sperf)
  with direct URLs to tools-security.esy-web.dev and cloudflareinsights.com
- Update JS tests for new direct analytics URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 09:21:19 +01:00
Serreau Jovann
04927ec988 Complete TASK_CHECKUP: security, UX, tests, coverage, accessibility, config externalization
Billetterie:
- Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration)
- Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction
- Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set
- Disable checkout when event ended (server 400 + template hide)
- Webhook deduplication via cache (24h TTL on stripe event.id)
- Email validation (filter_var) in OrderController guest flow
- JSON cart validation (structure check before processing)
- Invitation expiration after 7 days (isExpired method + landing page message)
- Stripe Checkout fallback when JS fails to load (noscript + redirect)

Config externalization:
- Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml
- Replace all hardcoded contact@e-cosplay.fr across 13 files
- MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin

UX & Accessibility:
- ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End)
- aria-label on cart +/- buttons and editor toolbar buttons
- tabindex=0 on editor toolbar buttons for keyboard access
- data-confirm handler in app.js (was only in admin.js)
- Cart error feedback on checkout failure
- Billet designer save feedback (loading/success/error states)
- Stock polling every 30s with rupture/low stock badges
- Back to event link on payment page

Security:
- HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed
- Stripe polling timeout (15s max) with fallback redirect
- Rate limiting on public order access (20/5min)
- .catch() on all fetch() calls (sortable, billet-designer)

Tests (92% PHP, 100% JS lines):
- PCOV added to dev Dockerfile
- Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key
- Rate limiter disabled in test env
- Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js
- New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4)
- New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest
- New tests: Stripe webhook payment_failed (6) + charge.refunded (6)
- New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration
- JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9)
- ESLint + PHP CS Fixer: 0 errors
- SonarQube exclusions aligned with vitest coverage config

Infra:
- Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am
- MeilisearchService: getAllDocumentIds(), listIndexes()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:14:06 +01:00
Serreau Jovann
61200adc74 Add stock management, order notifications, webhooks, expiration cron, and billet type validation
- Decrement billet quantity after purchase in BilletOrderService::generateOrderTickets
- Block purchase when stock is exhausted (quantity <= 0) in OrderController::buildOrderItems
- Add organizer email notification on new order (order_notification_orga template)
- Add organizer email notification on cancel/refund (order_cancelled_orga template)
- Add ExpirePendingOrdersCommand (app:orders:expire-pending) cron every 5min via Ansible
  - Cancels pending orders older than 30 minutes, restores stock, invalidates tickets
  - Includes BilletBuyerRepository::findExpiredPending query method
  - 3 unit tests covering: no expired orders, stock restoration, unlimited billets
- Add payment_intent.payment_failed webhook: cancels order, logs audit, emails buyer
- Add charge.refunded webhook: sets order to refunded, invalidates tickets, notifies orga and buyer
- Validate billet type (billet/reservation_brocante/vote) against organizer offer
  - getAllowedBilletTypes: gratuit=billet only, basic/sur-mesure=all types
  - Server-side validation in hydrateBilletFromRequest, UI filtering in templates
- Update TASK_CHECKUP.md: all Billetterie & Commandes items now complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:12:30 +01:00
Serreau Jovann
36456e8dfe Add rate limiting on login, order, invitation, contact routes
- Login: 5 attempts / 15 min (Symfony login_throttling)
- Order create: 10 / 5 min (sliding window)
- Invitation respond/register: 5 / 15 min
- Contact form: 3 / 10 min
- RateLimiterSubscriber with route-to-limiter mapping
- Returns 429 when rate limited

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:01:01 +01:00
Serreau Jovann
d0391e5fda Replace Stripe Checkout with Stripe Elements for in-page payment
- PaymentIntent instead of Checkout Session on connected account
- Stripe Elements Payment Element with neo-brutalist theme
- stripe-payment.js module with waitForStripe() for deferred loading
- No inline scripts (CSP compliant), data attributes on container
- Add order_number (YYYY-MM-DD-increment) to BilletBuyer
- Payment page redesign: full-width vertical layout with event info,
  buyer info, billet listing with images/descriptions, payment form
- CSP: add js.stripe.com to script-src, api.stripe.com to connect-src
- Add stripe_pk parameter in services.yaml
- Add head block to base.html.twig for page-specific scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:13:06 +01:00
Serreau Jovann
f0969972a2 Allow Stripe checkout in CSP form-action directive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:55:26 +01:00
Serreau Jovann
179a0703f8 Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
  isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
  stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
  billet-designer.test.js (7), commission-calculator.test.js (7),
  AccountControllerTest billet CRUD tests (11)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
Serreau Jovann
9290411652 Add isHidden to Category, category CRUD tests, coverage improvements
- Add isHidden field to Category entity with migration (DEFAULT false for existing rows)
- Add isHidden checkbox to edit category template and "Masquee" badge on category list
- Save isHidden in editCategory controller method
- Fix Category.isActive() indentation
- Create CategoryTest with full coverage (14 tests): defaults, setters, setEvent logic, isActive, isHidden
- Add category CRUD tests to AccountControllerTest: add/edit/delete/reorder categories with access control
- Add cookie-consent tests for dev env early return and Cloudflare tunnel script
- Exclude PayoutPdfService from phpunit coverage and SonarQube analysis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 23:35:42 +01:00
Serreau Jovann
57ea5c33ac Disable service worker in PWA config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:37:21 +01:00
Serreau Jovann
edd22462a6 fix error navbar 2026-03-20 21:12:34 +01:00
Serreau Jovann
f23bf28bf7 Add minimal serviceworker config with src only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:06:16 +01:00
Serreau Jovann
942dc62112 Remove serviceworker from PWA config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:05:27 +01:00
Serreau Jovann
b2c8ecf609 aa 2026-03-20 21:04:27 +01:00
Serreau Jovann
61ee9214dd Remove asset_cache, image_cache, font_cache from workbox config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:04:00 +01:00
Serreau Jovann
48da63625b Remove unsupported max_entries from asset_cache config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:02:57 +01:00
Serreau Jovann
5b3d9f96ab Add workbox clear_cache: true for cache cleanup on SW update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:02:30 +01:00
Serreau Jovann
a9e98faf39 Add service worker with workbox: asset/image/font cache, offline fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:01:56 +01:00
Serreau Jovann
dee2d18438 Restore full PWA manifest: icons, display_override, categories, shortcuts, favicons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:59:54 +01:00
Serreau Jovann
47cde556d6 Remove pwa:create:sw from Makefile, restore shortcuts without icons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:59:19 +01:00
Serreau Jovann
b10ac92b99 Simplify PWA config: manifest + favicons only, remove icons and image_processor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:56:21 +01:00
Serreau Jovann
e10b3bfa5c Remove service worker and workbox from PWA config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:50:43 +01:00
Serreau Jovann
16ddacff71 aa 2026-03-20 20:48:01 +01:00
Serreau Jovann
7cec1641e3 aa 2026-03-20 20:47:11 +01:00
Serreau Jovann
1b72fa8d2b Restore full PWA: service worker, workbox, icons, favicons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:42:21 +01:00
Serreau Jovann
ec0c95f696 Strip PWA to favicon only: remove icons, SW, workbox, shortcuts, display_override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:40:27 +01:00
Serreau Jovann
f023fe3922 Remove sizes from PWA icons, let bundle auto-detect from source image
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:36:13 +01:00
Serreau Jovann
a9ad279ae5 Remove all PWA shortcuts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:32:00 +01:00
Serreau Jovann
c7db9d7566 Remove shortcut icons to fix ApplicationIconCompiler null error in prod
The bundle crashes on shortcut->icons being null when icons lack sizes in prod env.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:30:16 +01:00
Serreau Jovann
1c1b6bed62 Fix PWA icons: add explicit sizes array to fix ApplicationIconCompiler null error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:20:25 +01:00
Serreau Jovann
9a73a235bc Fix PWA icons: remove sizes/type, let bundle auto-resize from source image
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:06:30 +01:00
Serreau Jovann
b4150c1248 Remove PWA screenshots from manifest config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:55:33 +01:00
Serreau Jovann
350a6be2e5 Move navigation_preload under workbox config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:46:16 +01:00
Serreau Jovann
99cacd529b Remove unsupported page_cache option from Workbox config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:45:23 +01:00
Serreau Jovann
d22a02f01e Add offline page, navigation preload, page cache, and pwa:create:sw commands
- Create /offline route and neo-brutalist offline page with retry button
- Add navigation_preload: true for faster SW navigation
- Add page_cache: 50 entries, 24h, 3s network timeout
- Add pwa_dev/pwa_prod Makefile commands using pwa:create:sw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:22:28 +01:00
Serreau Jovann
d150e2677f Add Workbox asset cache for CSS and JS files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:20:55 +01:00
Serreau Jovann
7669fbca8c Add service worker with Workbox: cache manifest, image cache, font cache, skip_waiting
- sw.js with network-first strategy, versioned cache, auto-purge old caches
- Workbox: cache_manifest, image_cache (30 days, 200 entries), font_cache (30 days, 10 entries)
- skip_waiting + clients.claim for instant updates
- CSP nonce for SW registration script
- Remove sw.js and workbox from .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:20:26 +01:00
Serreau Jovann
6fab96ab44 Full mobile responsive (320px) and tablet (768px) support across all templates
- All text-[20rem] background text → text-[8rem] md:text-[20rem]
- All text-8xl → text-5xl md:text-8xl
- All text-5xl emojis → text-3xl md:text-5xl
- edit_event: w-full md:w-[80%], poster column w-full lg:w-[350px]
- account/index: tab bar overflow-x-auto, events table overflow-x-auto
- admin/events: table overflow-x-auto
- register: tab buttons overflow-x-auto
- error 404/500: responsive padding p-6 md:p-12
- base footer: flex-col sm:flex-row for bottom bar
- Add PWA bundle (spomky-labs/pwa-bundle) with composer require files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:11:02 +01:00
Serreau Jovann
e101541185 Fix PWA screenshots config: use format instead of sizes/type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:07:07 +01:00
Serreau Jovann
44eea74d49 Use real screenshot for PWA desktop wide install UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:06:35 +01:00
Serreau Jovann
6dd1464e61 Add PWA screenshots for mobile and desktop (wide) richer install UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:05:01 +01:00
Serreau Jovann
0a83f77c25 Set PWA display-override and portrait orientation only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:04:29 +01:00
Serreau Jovann
e2aa30043c Fix PWA icons: separate any/maskable purpose, add manifest id, proper sizes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:04:06 +01:00
Serreau Jovann
237b3b23e2 Add 96x96 favicon icons to all PWA manifest shortcuts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:03:34 +01:00
Serreau Jovann
472d741f99 Complete PWA manifest: full name, description, lang, categories, shortcuts, orientation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:02:42 +01:00