Commit Graph

36 Commits

Author SHA1 Message Date
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
531c7da051 Add debt system: track refunds/disputes, redirect payments until cleared
- 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>
2026-03-25 10:15:01 +01:00
Serreau Jovann
e4c701456b Add billing system: subscription, webhooks, and access control
- 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>
2026-03-24 14:30:21 +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
372bf46136 Add organizer suspension: toggle, badge, block access, audit log
- User.isSuspended (nullable bool, null = not suspended)
- Admin: toggle suspend/reactivate button on organizers list
- SuspendedUserSubscriber: redirects suspended users to homepage with error
- Audit log: organizer_suspended / organizer_reactivated
- Badge 'Suspendu' (red) replaces offer badge when suspended

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:53:46 +01:00
Serreau Jovann
66ac2379ec Add audit trail: AuditLog entity, AuditService, admin logs page
- AuditLog entity: action, entityType, entityId, data (JSON), performedBy, ipAddress
- AuditService: logs actions with current user and IP
- Audit on: order_created, order_paid, order_cancelled, order_refunded
- Admin /admin/logs: paginated table with action badges, details, user, IP
- Navigation link 'Logs' in admin header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:48:10 +01:00
Serreau Jovann
ac65d4af64 Add offer and commission rate to organizer invitation
- offer (free/basic/custom) and commissionRate fields on OrganizerInvitation
- Admin form: select offer + commission rate input
- Invitation list: show offer badge + rate
- Email: gold banner with proposed offer and commission rate (hors Stripe)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 18:24:40 +01:00
Serreau Jovann
cca5575274 Add organizer invitation system: invite, accept, refuse
- OrganizerInvitation entity: companyName, firstName, lastName, email,
  message, status (sent/opened/accepted/refused), unique token (64 hex chars)
- Admin route /admin/organisateurs/inviter: form + invitation list with status
- Button "Inviter un organisateur" on admin organizers page
- Email with accept/refuse links using unique token
- Public route /invitation/{token}/{action}: accept or refuse without auth
- Response page: confirmation message for accept/refuse
- Migration, PHPStan config, 7 entity tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:41:31 +01:00
Serreau Jovann
be630e1c67 Add isInvitation field on BilletBuyer to distinguish invitations from free billets
- isInvitation (nullable bool) on BilletBuyer instead of checking totalHT == 0
- Set isInvitation=true when creating invitation in controller
- Email subject/content based on order.isInvitation
- Invoice shows organizer as buyer when order.isInvitation
- Invitations list filtered by isInvitation=true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:14:45 +01:00
Serreau Jovann
7b339a7a38 Make BilletBuyer firstName, lastName, email nullable for guest checkout flow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:55:04 +01:00
Serreau Jovann
7a29372b60 Save Stripe payment details on order confirmation, add arrive early tip
- Retrieve PaymentIntent on success redirect, save payment_method, card_brand, card_last4
- Display payment info on /ma-commande page (card type + last 4 digits)
- Add "Il est recommande d'arriver en avance" to practical info on ticket PDF
- Migration for payment_method, card_brand, card_last4 columns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:56:50 +01:00
Serreau Jovann
efe7f75994 Secure /ma-commande URLs with accessToken to prevent brute force
- Add accessToken (32 hex chars) to BilletBuyer, generated at creation
- URLs now: /ma-commande/{orderNumber}/{token} and /ma-commande/{orderNumber}/{token}/billet/{ref}
- Both orderNumber AND token must match to access order page
- Token is random, unpredictable, unique per order
- Migration generates tokens for existing rows

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:48:24 +01:00
Serreau Jovann
b0dead8120 Add security key to BilletOrder, QR code helper text
- securityKey: HMAC-SHA256(reference, APP_SECRET) truncated to 16 hex chars
- Generated automatically at ticket creation via BilletOrderService
- Deterministic: same reference + secret = same key, verifiable server-side
- Cannot be forged without knowing APP_SECRET
- PDF: "Presentez ce QR code pour valider votre ticket" under QR code
- PDF: "Cle de securite" displayed with letter-spacing
- Tests: generateSecurityKey determinism, uniqueness, format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:43:59 +01:00
Serreau Jovann
e1e98e752d Add isInvitation to BilletOrder, orga details in PDF footer, rename Sortie libre
- Add isInvitation (nullable bool) to BilletOrder: null=no badge, true=invitation
- PDF footer: add SIRET, email, phone of organizer
- PDF: show invitation badge based on ticket.isInvitation instead of design
- Rename "Sortie libre" to "Sortie - Entree illimitee"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:36:29 +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
2efb5f176a Replace isScanned with state (valid/invalid/expired) and firstScannedAt on BilletOrder
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:15:32 +01:00
Serreau Jovann
52cb19df8b Add BilletOrder entity, PDF generation, email with QR codes, public order page
- BilletOrder entity: individual tickets with unique ETICKET-XXXX reference,
  billetBuyer link, billet link, isScanned, scannedAt for entry control
- BilletOrderService: generates tickets after payment, creates A4 PDF with
  BilletDesign colors if present (default otherwise), real QR code via
  endroid/qr-code, event poster + org logo as base64, sends confirmation
  email with all ticket PDFs attached
- PDF template (pdf/billet.html.twig): A4 layout matching preview design,
  real QR code linking to /ticket/verify/{reference}
- Email template: order recap table, ticket references list, link to
  /ma-commande/{reference}
- Public order page /ma-commande/{reference}: no auth required, shows
  order details, ticket list with individual PDF download links
- Ticket verification page /ticket/verify/{reference}: shows valid/scanned
  status with ticket and event details
- Download route /ma-commande/{ref}/billet/{ticketRef}: generates PDF on-the-fly
- Migration for billet_order table with unique reference index
- BilletOrderTest: 8 tests, 24 assertions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:04:45 +01:00
Serreau Jovann
7167a58c7c Add reservation flow: BilletBuyer, guest checkout, Stripe payment
- Create BilletBuyer entity: event, user (nullable for guests), firstName,
  lastName, email, reference (ETICKET-XXXX-XXXX-XXXX), totalHT, status,
  stripeSessionId, paidAt, items (OneToMany)
- Create BilletBuyerItem entity: billet, billetName (snapshot), quantity,
  unitPriceHT, line total helpers
- OrderController with full checkout flow:
  - POST /evenement/{id}/commander: create order from cart JSON
  - GET/POST /commande/{id}/informations: guest form (name, email)
  - GET /commande/{id}/paiement: payment page with recap
  - POST /commande/{id}/stripe: Stripe Checkout on connected account
    with application_fee, productId, and quantities
  - GET /commande/{id}/confirmation: success page
- Cart JS: POST cart data on Commander click, redirect to guest/payment
- Templates: guest form, payment page, order summary partial, success page
- Stripe payment uses organizer connected account, application_fee based
  on commissionRate, existing productId when available
- Tests: BilletBuyerTest (12), BilletBuyerItemTest (6), cart.test.js (13)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:54:17 +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
42ded8fbbe Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
Serreau Jovann
38f03086d8 fix error navbar 2026-03-20 21:14:24 +01:00
Serreau Jovann
83a2c44d12 attemted fix error bg color footer 2026-03-20 21:12:01 +01:00
Serreau Jovann
3cd40f30c0 Add isSecret field to Event entity for hidden-when-online events
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 17:06:03 +01:00
Serreau Jovann
8b3b1dab13 Add Event entity, create event page, and custom WYSIWYG editor component
- Create Event entity with fields: account, title, description (text), startAt, endAt, address, zipcode, city, eventMainPicture (VichUploader), isOnline, createdAt, updatedAt
- Create EventRepository
- Add migration for event table with all columns
- Add "Creer un evenement" button on account events tab
- Add create event page (/mon-compte/evenement/creer) with full form
- Build custom web component <e-ticket-editor> WYSIWYG editor:
  - Toolbar: bold, italic, underline, paragraph, bullet list, remove formatting
  - contentEditable div with HTML sync to hidden textarea
  - HTML sanitizer (strips disallowed tags, XSS safe)
  - Neo-brutalist CSS styling
  - CSP compliant (no inline styles)
- Register editor in app.js via customElements.define
- Add editor CSS in app.scss
- Add 16 Event entity tests (all fields + isOnline + picture upload + updatedAt)
- Add 16 editor JS tests (sanitizer + custom element lifecycle)
- Add 3 AccountController tests (create event page, submit, access control)
- Update placeholders to generic examples (no association-specific data)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:49:24 +01:00
Serreau Jovann
198d684fb8 Add organizer pages, SEO breadcrumbs, Open Graph, homepage redesign, and infrastructure updates
- Add public organizers list page (/organisateurs) with neo-brutalist card grid, social icons, and logo display
- Add organizer detail page (/organisateur/{id}-{slug}) with company info, SIRET, email, address, social links, and events placeholder
- Add slug-based URLs with 301 redirect on wrong slug, getSlug() method on User entity
- Add "Voir les evenements" button on organizer cards linking to detail page
- Add JSON-LD BreadcrumbList to all 17 pages that were missing breadcrumbs (login, forgot_password, register_success, email_verified, legal/*, attestation/*, account/*)
- Add Open Graph meta tags (og:title, og:description, og:image, og:type, og:locale, og:site_name) in base.html.twig with automatic inheritance from title/description blocks
- Add og:image with organizer logo on detail page
- Update sitemap: add /organisateurs to sitemap-main, generate organizer detail URLs in sitemap-orgas with logo images
- Update navbar to highlight "Organisateurs" on detail pages
- Redesign homepage with hero section, marquee, stats counters, how-it-works, and CTA sections
- Add Tailwind v4 @source "../templates" directive to app.scss and admin.scss
- Migrate Flysystem from S3 to local storage (uploads/events, uploads/logos)
- Update Liip Imagine config with FormatExtensionResolver for webp conversion
- Add User entity social fields (website, facebook, instagram, twitter, tiktok), logo upload (Vich), __serialize/__unserialize for session safety
- Add account page settings tab with profile, logo upload, and social media for organizers
- Add Stripe Connect status display and sub-account management in account page
- Delete WebpExtensionSubscriber (replaced by FormatExtensionResolver)
- Add migration for social fields and logo columns
- Add deploy.yml chmod tasks for uploads directories
- Add HomeController tests (detail success, wrong slug redirect, 404 cases)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:44:31 +01:00
Serreau Jovann
ab52a8d02f Add payouts, PDF attestations, sub-accounts, and webhook improvements
Payout system:
- Create Payout entity (stripePayoutId, status, amount, currency, destination, arrivalDate)
- Webhook handles payout.created/updated/paid/failed/canceled with email notification
- Payout list in /mon-compte virements tab with status badges
- PDF attestation on paid payouts with email attachment

PDF attestation:
- dompdf with DejaVu Sans font, yellow-orange gradient background
- Orange centered title bar, E-Cosplay logo, emitter/beneficiary info blocks
- QR code linking to /attestation/check/{payoutId} for authenticity verification
- Public verification page: shows payout details if valid, error if altered
- Legal disclaimer and CGV reference
- Button visible only when status is paid, opens in new tab

Sub-accounts:
- Add parentOrganizer (self-referencing ManyToOne) and subAccountPermissions (JSON) to User
- Permissions: scanner (validate tickets), events (CRUD), tickets (free invitations)
- Create sub-account with random password, send email with credentials
- Edit page with name/email/permissions checkboxes
- Delete with confirmation
- hasPermission() helper method

Account improvements:
- Block entire page for unapproved organizers with validation pending message
- Display stripeStatus in Stripe Connect banners
- Remove test payout button

Webhook v2 Connect events:
- v2.core.account.created/updated/closed → update stripeStatus
- capability_status_updated → sync charges/payouts enabled from capabilities
- PayoutPdfService for reusable PDF generation

Migrations: stripeStatus, Payout table, sub-account fields, drop pdfPath

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 23:49:48 +01:00
Serreau Jovann
c5e5f81fe8 Add Stripe Connect account support and account.updated webhook handler
- Add stripeChargesEnabled and stripePayoutsEnabled fields to User entity + migration
- Handle account.updated webhook: sync charges_enabled and payouts_enabled from Stripe
- Add createAccountConnect() and createAccountLink() to StripeService
- Update organizer approved email with Stripe verification notice
- Tests: webhook account.updated with flags, unknown account, User stripe fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:46:55 +01:00
Serreau Jovann
65887fb3da Add Stripe integration: webhook controller, service, sync command, Connect account
- Create StripeService: webhook sync, signature verification, save secret to .env.local
- Create StripeWebhookController with signature verification (400 on invalid)
- Create stripe:sync command to auto-create webhook endpoint via Stripe API
- Webhook listens to all events (configurable later)
- Save webhook secret automatically to .env.local on creation
- Add stripeAccountId field to User entity for Stripe Connect + migration
- Tests: StripeServiceTest (5), StripeWebhookControllerTest (2), StripeSyncCommandTest (1), UserTest updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:37:16 +01:00
Serreau Jovann
100ff96c70 Add SIRET/RNA verification, organizer management, registration flow pages
SIRET/RNA verification:
- Create SiretService with API gouv lookup + JOAFE RNA lookup + cache pool (24h)
- Verification page: declared info vs API data side by side
- Display NAF code + label (from naf.json), nature juridique code + label
- Association/Entreprise/EI badges, ESS badge, RNA, coordonnees lat/long
- JOAFE section: objet, regime, domaine, dates, lieu, PDF download link
- Tranche effectif with readable labels
- Refresh cache button
- Page restricted to non-approved organizers only

Organizer approval flow:
- Approval form with offer (free/basic/custom) and commission rate (default 3%)
- Add commissionRate field to User entity + migration
- Rejection form with required reason textarea, sent in email
- Edit page for approved organizers: all fields modifiable
- Modify button in approved organizers table

Registration flow pages:
- Post-registration success page with email verification message
- Organizer gets additional 48h staff review notice
- Post-email-verification page: confirmed for buyers, 48h notice for organizers

Dashboard:
- Simplified Meilisearch sync to single button

Tests: SiretServiceTest (9), AdminControllerTest (31), RegistrationControllerTest updated, UserTest updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:25:04 +01:00
Serreau Jovann
82829f6240 Add organizer logo upload, Meilisearch organizer search, and webp URL rewriting
VichUploader organizer logo:
- Add organizer_logo mapping with local Flysystem storage
- Add logoFile, logoName, updatedAt fields to User entity
- Use Vich Attribute (not deprecated Annotation)
- Add migration for logo_name and updated_at columns

Meilisearch organizer search:
- Add search bar on /admin/organisateurs page (hides tabs during search)
- Index organizers in Meilisearch on approval
- Sync button on dashboard now syncs both buyers and organizers
- Add tests: search query, search error

Liip Imagine webp:
- Add format filter to all filter_sets for explicit webp conversion
- Add organizer_logo filter_set (400x400, webp)
- Create WebpExtensionSubscriber to rewrite image URLs to .webp extension
- 8 tests for subscriber (png, jpg, jpeg, gif, webp passthrough, case insensitive, null)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:46:34 +01:00
Serreau Jovann
52e6e2c14c Add email verification, organizer approval, and forgot password features
- Add isVerified, emailVerificationToken, emailVerifiedAt fields to User entity
- Send verification email on registration with token link
- Add /verification-email/{token} route to confirm email
- Send notification emails to organizer and staff on organizer email verification
- Add isApproved and offer fields to User entity for organizer approval workflow
- Auto-verify and auto-approve SSO Keycloak users with offer='custom'
- Add resetCode and resetCodeExpiresAt fields to User entity
- Create ForgotPasswordController with 2-step flow (email -> code + new password)
- Block forgot password for SSO users (no local password)
- Add "Mot de passe oublie" link on login page
- Create email templates: verification, reset_code, organizer_pending, organizer_request
- Add migrations for all new fields
- Add tests: ForgotPasswordControllerTest (9 tests), update RegistrationControllerTest,
  update UserTest with verification, approval, offer, and reset code fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:13:32 +01:00
Serreau Jovann
e46b8572f2 Add registration page with buyer/organizer tabs
- Add organizer fields to User entity: companyName, siret, address, postalCode, city, phone
- Update RegistrationController to handle buyer and organizer registration types
- Create register template with tab selector (acheteur/organisateur)
- Organizer registration assigns ROLE_ORGANIZER
- Add migration for new User fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:31:22 +01:00
Serreau Jovann
2405fcc2da Add SSO E-Cosplay (Keycloak OIDC) and dynamic navbar active state
- Install knpuniversity/oauth2-client-bundle and stevenmaguire/oauth2-keycloak
- Register KnpUOAuth2ClientBundle in bundles.php
- Configure Keycloak OIDC client (realm e-cosplay, auth.esy-web.dev)
- Add keycloakId field to User entity with migration
- Create KeycloakAuthenticator with group-to-role mapping (/superadmin -> ROLE_ROOT)
- Create OAuthController with SSO routes (/connection/sso/login, logout, check)
- Add custom_authenticator to security firewall with form_login entry point
- Add auth.esy-web.dev to nelmio external_redirects whitelist and CSP form-action
- Add SSO button and error flash messages to login page
- Make navbar active state dynamic based on current route (desktop + mobile)
- Add Keycloak env vars to .env, .env.local, and ansible/env.local.j2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:38:19 +01:00
Serreau Jovann
04becc238b Add MessengerLog, async mailer, doctrine fixes
- MessengerLog entity: store all messenger failures with full details
- MessengerFailureSubscriber: log errors + send alert email synchronously
- MailerService: dispatch emails via Messenger bus (async)
- Makefile: add entity command
- Doctrine: enable Second Level Cache in prod, remove deprecated config
- Liip Imagine: set twig mode to lazy
- Fix app.scss @use/@import

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 21:43:10 +01:00
Serreau Jovann
9341647acf Add webapp packages 2026-03-04 21:52:29 +01:00