An Event Ticketing REST API built with Spring Boot, Supabase(as DB and AuthProvider), and Spring Security. It supports event publishing, ticket purchasing, QR code generation, and validation β all accessible via Swagger UI.
π This project is based on a solution originally developed by Devtiro. I've implemented my own version with some changes and improvements.
- ποΈ Event and Ticket Management
- π Purchase tickets
- π Role-based access control (Attendee, Organizer, Staff) via Supabase
- π² QR Code generation for purchased tickets
- β Validate ticket entry using QR codes
- π¦ DTO-based API layer
- π API documentation with Swagger UI
βββ config/
βββ controller/
βββ domain/
β βββ dto/
β β βββ request/
β β βββ response/
β βββ entities/
β βββ enums/
βββ exceptions/
βββ filters/
βββ mappers/
βββ repository/
βββ service/After starting backend go to http://localhost:8080/swagger-ui/index.html
Headers:
{
"apikey": "SUPABASE_PUBLISHABLE_KEY",
"Content-Type": "application/json"
}
Request Body (All Required):
{
"email": "user.organizer@gmail.com", // Required: Valid email format
"password": "Organizer@123" // Required: Minimum 8 characters
}Success Response (200):
{
"access_token": "eyJhbGciOiJFUzI1NiIs...", // JWT token for API authentication
"token_type": "bearer",
"expires_in": 3600, // Token expiry in seconds
"expires_at": 1758012705, // Unix timestamp of expiry
"refresh_token": "voqhpuwbj4ag", // Token for refreshing access_token
"user": {
"id": "7759aeee-eb03-483d-95e5-dba9c31399f4",
"aud": "authenticated",
"role": "authenticated",
"email": "mike.organizer@gmail.com",
"email_confirmed_at": "2025-09-02T11:22:37.016695Z",
"phone": "",
"confirmed_at": "2025-09-02T11:22:37.016695Z",
"last_sign_in_at": "2025-09-16T07:51:45.359186887Z",
"app_metadata": {
"provider": "email",
"providers": ["email"]
},
"user_metadata": {
"email": "mike.organizer@gmail.com",
"email_verified": true,
"name": "Mike Brown", // Full name from signup
"phone_verified": false,
"role": "Organizer", // User role for authorization
"sub": "7759aeee-eb03-483d-95e5-dba9c31399f4"
},
"identities": [
{
"identity_id": "8bed2a5b-9b97-4015-bfa3-c1c180a58fb4",
"id": "7759aeee-eb03-483d-95e5-dba9c31399f4",
"user_id": "7759aeee-eb03-483d-95e5-dba9c31399f4",
"identity_data": {
"email": "mike.organizer@gmail.com",
"email_verified": false,
"name": "Mike Brown",
"phone_verified": false,
"role": "Organizer",
"sub": "7759aeee-eb03-483d-95e5-dba9c31399f4"
},
"provider": "email",
"last_sign_in_at": "2025-09-02T11:22:37.013228Z",
"created_at": "2025-09-02T11:22:37.013284Z",
"updated_at": "2025-09-02T11:22:37.013284Z",
"email": "mike.organizer@gmail.com"
}
],
"created_at": "2025-09-02T11:22:37.008387Z",
"updated_at": "2025-09-16T07:51:45.417167Z",
"is_anonymous": false
},
"weak_password": null
}Headers:
{
"apikey": "SUPABASE_PUBLISHABLE_KEY",
"Content-Type": "application/json"
}
Request Body:
{
"email": "mike.organizer@gmail.com", // Required: Valid email format
"password": "Organizer@123", // Required: Minimum 8 characters
"data": { // Required: User metadata
"name": "Mike Brown", // Required: Full name
"role": "organizer" // Required: "attendee" | "organizer" | "staff"
}
}Success Response (200): Same structure as Login response above
Headers:
{
"apikey": "SUPABASE_PUBLISHABLE_KEY",
"Authorization": "Bearer ${access_token}" // Required: Valid JWT token
}Success Response (204): No content
{
"Authorization": "Bearer ${access_token}", // Required: JWT from Supabase auth
"Content-Type": "application/json" // Required for POST/PUT requests
}Description: Create a new event
Access: ORGANIZER
Request Body:
{
"name": "Spring Fest", // Required: 1-255 characters
"venue": "Main Hall", // Required: 1-255 characters
"description": "Amazing spring event", // Optional: Max 1000 characters
"starts_at": "2024-09-01T10:00:00", // Required: ISO 8601 datetime, future date
"ends_at": "2024-09-01T18:00:00", // Required: ISO 8601 datetime, after starts_at
"sales_starts_at": "2024-08-01T00:00:00", // Required: ISO 8601 datetime, before starts_at
"sales_ends_at": "2024-08-31T23:59:59", // Required: ISO 8601 datetime, before starts_at
"event_status": "DRAFT", // Required: "DRAFT" | "PUBLISHED" | "CANCELLED"
"ticket_types": [ // Required: Array with at least 1 ticket type
{
"name": "VIP", // Required: 1-100 characters
"description": "Front row seats", // Optional: Max 500 characters
"price": 100.0, // Required: Positive decimal (min 0.01)
"totalAvailable": 50 // Required: Positive integer (min 1)
}
]
}Success Response (201):
{
"id": "event-uuid",
"name": "Spring Fest",
"venue": "Main Hall",
"description": "Amazing spring event",
"starts_at": "2024-09-01T10:00:00",
"ends_at": "2024-09-01T18:00:00",
"sales_starts_at": "2024-08-01T00:00:00",
"sales_ends_at": "2024-08-31T23:59:59",
"event_status": "DRAFT",
"organizer_id": "user-uuid",
"created_at": "2024-01-15T12:00:00",
"ticket_types": [
{
"id": "ticket-type-uuid",
"name": "VIP",
"description": "Front row seats",
"price": 100.0,
"totalAvailable": 50,
"availableCount": 50
}
]
}Description: Get all events for authenticated organizer (paginated)
Access: ORGANIZER
Query Parameters:
page: 0 // Optional: Page number (default: 0)
size: 25 // Optional: Page size (default: 25, max: 100)
Success Response (200):
{
"content": [
{
"id": "event-uuid",
"name": "Spring Fest",
"venue": "Main Hall",
"starts_at": "2024-09-01T10:00:00",
"ends_at": "2024-09-01T18:00:00",
"event_status": "DRAFT",
"ticket_types_count": 2
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 25
},
"totalElements": 5,
"totalPages": 1,
"first": true,
"last": true
}Description: Get specific event by ID for authenticated organizer
Access: ORGANIZER
Path Parameters:
eventId: UUID // Required: Valid UUID of the event
Success Response (200): Same as POST response structure
Description: Update an event and its ticket types
Access: ORGANIZER
Path Parameters:
eventId: UUID // Required: Valid UUID of the event
Request Body:
{
"name": "Spring Fest Updated", // Required: 1-255 characters
"venue": "Main Hall", // Required: 1-255 characters
"description": "Updated description", // Optional: Max 1000 characters
"starts_at": "2024-09-01T10:00:00", // Required: ISO 8601 datetime
"ends_at": "2024-09-01T18:00:00", // Required: ISO 8601 datetime
"sales_starts_at": "2024-08-01T00:00:00", // Required: ISO 8601 datetime
"sales_ends_at": "2024-08-31T23:59:59", // Required: ISO 8601 datetime
"event_status": "PUBLISHED", // Required: "DRAFT" | "PUBLISHED" | "CANCELLED"
"ticket_types": [ // Required: Array with at least 1 ticket type
{
"ticket_type_id": "ticket-type-uuid", // Optional: Include for existing ticket types
"name": "VIP", // Required: 1-100 characters
"description": "Updated description", // Optional: Max 500 characters
"price": 120.0, // Required: Positive decimal
"totalAvailable": 60 // Required: Positive integer
}
]
}Description: Delete an event
Access: ORGANIZER
Path Parameters:
eventId: UUID // Required: Valid UUID of the event
Success Response (204): No content
Description: Get staff assigned to an event
Access: ORGANIZER
Success Response (200):
{
"user_id": "user-uuid",
"name": "John Staff",
"email": "john.staff@example.com",
"assigned_at": "2024-01-15T12:00:00"
}Description: Assign staff to an event
Access: ORGANIZER
Request Body:
{
"userIds": ["user-uuid-1", "user-uuid-2"] // Required: Array of valid user UUIDs with "staff" role
}Description: Get all published events (paginated, public)
Access: STAFF, ORGANIZER, ATTENDEE
Query Parameters:
page: 0 // Optional: Page number (default: 0)
size: 25 // Optional: Page size (default: 25, max: 100)
q: "search term" // Optional: Search query for event name/description
Description: Get details of a published event
Access: STAFF, ORGANIZER, ATTENDEE
Description: Purchase a ticket for a ticket type
Access: ATTENDEE
Request Body:
{
"ticketTypeId": "ticket-type-uuid" // Required: Valid UUID of available ticket type
}Success Response (201):
{
"id": "ticket-uuid",
"ticket_type": {
"id": "ticket-type-uuid",
"name": "VIP",
"price": 100.0,
"event": {
"id": "event-uuid",
"name": "Spring Fest",
"venue": "Main Hall",
"starts_at": "2024-09-01T10:00:00"
}
},
"purchaser_id": "user-uuid",
"purchased_at": "2024-01-15T12:00:00",
"status": "ACTIVE",
"qr_code_id": "qr-code-uuid"
}Description: Get all tickets for authenticated user (paginated)
Access: ATTENDEE
Query Parameters:
page: 0 // Optional: Page number (default: 0)
size: 25 // Optional: Page size (default: 25, max: 100)
Description: Get specific ticket for authenticated user
Access: ATTENDEE
Path Parameters:
id: UUID // Required: Valid UUID of user's ticket
Description: Get QR code image (PNG) for a ticket
Access: ATTENDEE
Response: Binary PNG image data
Content-Type: image/png
Description: Validate a ticket (by QR code or manually)
Access: STAFF
Request Body:
{
"targetId": "ticket-or-qrcode-uuid", // Required: UUID of ticket or QR code
"method": "QR_SCAN" // Required: "QR_SCAN" | "MANUAL"
}Success Response (200):
{
"id": "validation-uuid",
"ticket_id": "ticket-uuid",
"staff_id": "user-uuid",
"method": "QR_SCAN",
"validated_at": "2024-01-15T12:00:00",
"status": "VALID",
"ticket": {
"id": "ticket-uuid",
"status": "USED",
"event": {
"name": "Spring Fest",
"venue": "Main Hall"
}
}
}All API endpoints return errors in the following format:
{
"timestamp": "2024-01-15T12:00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed for field 'name'",
"path": "/api/v1/events"
}Common HTTP Status Codes:
200- Success201- Created204- No Content400- Bad Request (validation errors)401- Unauthorized (invalid/missing token)403- Forbidden (insufficient permissions)404- Not Found409- Conflict (e.g., duplicate email)500- Internal Server Error
Access Roles:
- ORGANIZER: Can manage events and assign staff
- ATTENDEE: Can purchase and view their tickets
- STAFF: Can validate tickets for assigned events
Data Validation Rules:
- All datetime fields must be in ISO 8601 format
- UUIDs must be valid UUID v4 format
- Email addresses must be valid format
- Prices must be positive decimals with max 2 decimal places
- Pagination: max size is 100, default is 25