Create API controllers structure with JWT auth and sandbox fixtures

Controllers:
- ApiAuthController: POST /api/auth/login with JWT generation (HS256, 24h TTL)
  - Validates email + password against DB
  - Returns JWT token with userId, email, roles, iat, exp
  - Static verifyJwt() for use by live/sandbox controllers
  - Only ROLE_ORGANIZER can authenticate
- ApiLiveController: empty shell at /api/live (routes to implement)
- ApiSandboxController: empty shell at /api/sandbox (routes to implement)

Auth is shared: one /api/auth/login for both environments using real credentials.

Sandbox fixtures (data/sandbox/fixtures.json):
- 2 events (Brocante + Convention Cosplay)
- 4 categories across events
- 6 billets with varied types (billet, reservation_brocante)
- 6 billet details with descriptions, images, categories, events
- 4 scan results (2 accepted, 2 refused with different reasons)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-23 19:12:28 +01:00
parent 8ab8efbf07
commit 40e89afc71
4 changed files with 436 additions and 0 deletions

296
data/sandbox/fixtures.json Normal file
View File

@@ -0,0 +1,296 @@
{
"events": [
{
"id": 1,
"title": "Brocante de Printemps 2026",
"description": "Grande brocante annuelle en plein air avec plus de 200 exposants.",
"startAt": "2026-06-15T08:00:00",
"endAt": "2026-06-15T18:00:00",
"address": "Place de la Republique",
"zipcode": "75003",
"city": "Paris",
"isOnline": true,
"isSecret": false,
"imageUrl": null
},
{
"id": 2,
"title": "Convention Cosplay Ete",
"description": "Convention cosplay avec concours, stands et animations.",
"startAt": "2026-08-20T10:00:00",
"endAt": "2026-08-21T19:00:00",
"address": "Parc des Expositions",
"zipcode": "02800",
"city": "Beautor",
"isOnline": true,
"isSecret": false,
"imageUrl": "https://ticket.e-cosplay.fr/demo/convention.jpg"
}
],
"categories": {
"1": [
{
"id": 1,
"name": "Entrees",
"position": 0,
"startAt": "2026-05-01T00:00:00",
"endAt": "2026-06-15T18:00:00",
"isHidden": false,
"isActive": true
},
{
"id": 2,
"name": "Reservations Brocante",
"position": 1,
"startAt": "2026-04-01T00:00:00",
"endAt": "2026-06-10T23:59:00",
"isHidden": false,
"isActive": true
}
],
"2": [
{
"id": 3,
"name": "General",
"position": 0,
"startAt": "2026-07-01T00:00:00",
"endAt": "2026-08-21T19:00:00",
"isHidden": false,
"isActive": true
},
{
"id": 4,
"name": "VIP",
"position": 1,
"startAt": "2026-07-01T00:00:00",
"endAt": "2026-08-15T23:59:00",
"isHidden": false,
"isActive": true
}
]
},
"billets": {
"1": [
{
"id": 1,
"name": "Entree Adulte",
"priceHT": 500,
"quantity": 500,
"sold": 312,
"type": "billet",
"isGeneratedBillet": true,
"notBuyable": false,
"position": 0
},
{
"id": 2,
"name": "Entree Enfant (-12 ans)",
"priceHT": 0,
"quantity": null,
"sold": 145,
"type": "billet",
"isGeneratedBillet": true,
"notBuyable": false,
"position": 1
}
],
"2": [
{
"id": 3,
"name": "Emplacement 3m",
"priceHT": 1500,
"quantity": 80,
"sold": 72,
"type": "reservation_brocante",
"isGeneratedBillet": false,
"notBuyable": false,
"position": 0
}
],
"3": [
{
"id": 4,
"name": "Pass 2 jours",
"priceHT": 2000,
"quantity": 300,
"sold": 189,
"type": "billet",
"isGeneratedBillet": true,
"notBuyable": false,
"position": 0
},
{
"id": 5,
"name": "Pass 1 jour Samedi",
"priceHT": 1200,
"quantity": 200,
"sold": 156,
"type": "billet",
"isGeneratedBillet": true,
"notBuyable": false,
"position": 1
}
],
"4": [
{
"id": 6,
"name": "Pass VIP 2 jours",
"priceHT": 5000,
"quantity": 30,
"sold": 28,
"type": "billet",
"isGeneratedBillet": true,
"notBuyable": false,
"position": 0
}
]
},
"billetDetails": {
"1": {
"id": 1,
"name": "Entree Adulte",
"description": "Entree standard pour les adultes. Billet electronique avec QR code.",
"priceHT": 500,
"quantity": 500,
"sold": 312,
"type": "billet",
"isGeneratedBillet": true,
"hasDefinedExit": false,
"notBuyable": false,
"position": 0,
"imageUrl": null,
"category": {"id": 1, "name": "Entrees"},
"event": {"id": 1, "title": "Brocante de Printemps 2026"}
},
"2": {
"id": 2,
"name": "Entree Enfant (-12 ans)",
"description": "Gratuit pour les enfants de moins de 12 ans.",
"priceHT": 0,
"quantity": null,
"sold": 145,
"type": "billet",
"isGeneratedBillet": true,
"hasDefinedExit": false,
"notBuyable": false,
"position": 1,
"imageUrl": null,
"category": {"id": 1, "name": "Entrees"},
"event": {"id": 1, "title": "Brocante de Printemps 2026"}
},
"3": {
"id": 3,
"name": "Emplacement 3m",
"description": "Emplacement de 3 metres lineaires pour exposant.",
"priceHT": 1500,
"quantity": 80,
"sold": 72,
"type": "reservation_brocante",
"isGeneratedBillet": false,
"hasDefinedExit": false,
"notBuyable": false,
"position": 0,
"imageUrl": null,
"category": {"id": 2, "name": "Reservations Brocante"},
"event": {"id": 1, "title": "Brocante de Printemps 2026"}
},
"4": {
"id": 4,
"name": "Pass 2 jours",
"description": "Acces aux 2 jours de la convention.",
"priceHT": 2000,
"quantity": 300,
"sold": 189,
"type": "billet",
"isGeneratedBillet": true,
"hasDefinedExit": true,
"notBuyable": false,
"position": 0,
"imageUrl": "https://ticket.e-cosplay.fr/demo/pass-2j.jpg",
"category": {"id": 3, "name": "General"},
"event": {"id": 2, "title": "Convention Cosplay Ete"}
},
"5": {
"id": 5,
"name": "Pass 1 jour Samedi",
"description": "Acces le samedi uniquement.",
"priceHT": 1200,
"quantity": 200,
"sold": 156,
"type": "billet",
"isGeneratedBillet": true,
"hasDefinedExit": true,
"notBuyable": false,
"position": 1,
"imageUrl": null,
"category": {"id": 3, "name": "General"},
"event": {"id": 2, "title": "Convention Cosplay Ete"}
},
"6": {
"id": 6,
"name": "Pass VIP 2 jours",
"description": "Acces VIP avec espace dedie, boissons et rencontre artistes.",
"priceHT": 5000,
"quantity": 30,
"sold": 28,
"type": "billet",
"isGeneratedBillet": true,
"hasDefinedExit": true,
"notBuyable": false,
"position": 0,
"imageUrl": "https://ticket.e-cosplay.fr/demo/vip.jpg",
"category": {"id": 4, "name": "VIP"},
"event": {"id": 2, "title": "Convention Cosplay Ete"}
}
},
"scan": {
"ETICKET-DEMO-0001-AAAA": {
"state": "accepted",
"reason": null,
"reference": "ETICKET-DEMO-0001-AAAA",
"billetName": "Pass 2 jours",
"buyerFirstName": "Jean",
"buyerLastName": "Dupont",
"isInvitation": false,
"firstScannedAt": null,
"hasDefinedExit": true,
"details": {}
},
"ETICKET-DEMO-0002-BBBB": {
"state": "refused",
"reason": "already_scanned",
"reference": "ETICKET-DEMO-0002-BBBB",
"billetName": "Entree Adulte",
"buyerFirstName": "Marie",
"buyerLastName": "Martin",
"isInvitation": false,
"firstScannedAt": "2026-06-15T09:30:00",
"hasDefinedExit": false,
"details": null
},
"ETICKET-DEMO-0003-CCCC": {
"state": "refused",
"reason": "invalid",
"reference": "ETICKET-DEMO-0003-CCCC",
"billetName": "Pass VIP 2 jours",
"buyerFirstName": "Pierre",
"buyerLastName": "Durand",
"isInvitation": false,
"firstScannedAt": null,
"hasDefinedExit": true,
"details": null
},
"ETICKET-DEMO-0004-DDDD": {
"state": "accepted",
"reason": null,
"reference": "ETICKET-DEMO-0004-DDDD",
"billetName": "Pass 1 jour Samedi",
"buyerFirstName": "Sophie",
"buyerLastName": "Bernard",
"isInvitation": true,
"firstScannedAt": null,
"hasDefinedExit": true,
"details": {}
}
}
}