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:
296
data/sandbox/fixtures.json
Normal file
296
data/sandbox/fixtures.json
Normal 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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user