The API documentation for "A booking Management System for "everything""
or
- Authentication APIs
- User APIs
- Bookable Item APIs
- Booking APIs
- Availability APIs
- Recommendation APIs
- Report APIs
- Private Bookable Items
- Conflict Detection
- Dynamic Availability Calculation
- Recommendation System (Content-Based)
- Dynamic Pricing (Demand-Based Tiers)
- Notification Orchestration (Outbox Pattern)
- API Rate Limiting
- Spring Boot: Framework for building robust, production-ready Spring applications.
- Spring Security: Comprehensive security services for Java EE-based enterprise software applications.
- Spring Data JPA / Hibernate: For database interaction and ORM.
- JWT (JSON Web Tokens): For stateless authentication.
- BCrypt: Secure password hashing.
- Lombok: Reduces boilerplate code (getters, setters, constructors).
- MySQL: Relational database.
- Maven: Build automation tool.
- Java 17+: Programming language.
- Jackson Datatype JSR310: For proper
java.time
(LocalDateTime) JSON serialization/deserialization. - Bucket4j: Java rate limiting library based on the token-bucket algorithm.
- Java Development Kit (JDK) 17 or higher
- Maven 3.6+
- A MySQL database instance
- Postman or a similar API testing tool
-
Clone the repository:
git clone <your-repo-url> cd <folder-name>
-
Configure
application.properties
(orapplication.yml
): Createsrc/main/resources/application.properties
and add your database, JWT, and new feature configurations.# Database Configuration (Example for MySQL) spring.datasource.url=jdbc:mysql://localhost:3306/welcome_db?createDatabaseIfNotExist=true spring.datasource.username=your_db_user spring.datasource.password=your_db_password spring.jpa.hibernate.ddl-auto=update # Recommended for development. Use 'none' or 'validate' for production. spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true # JWT Security Configuration application.security.jwt.secret-key=YOUR_SUPER_SECRET_KEY_AT_LEAST_256_BITS_LONG_AND_BASE64_ENCODED_FOR_PRODUCTION # Replace with a strong, base64-encoded key application.security.jwt.expiration=900000 # 15 minutes in milliseconds application.security.jwt.refresh-token.expiration=604800000 # 7 days in milliseconds # Outbox Processor Configuration outbox.processor.batch-size=10 outbox.processor.max-retries=5 # Rate Limiting Configuration app.rate-limit.enabled=true app.rate-limit.capacity=100 # Max requests in a burst app.rate-limit.refill-rate=10 # Tokens added per second app.rate-limit.duration-seconds=1 # The duration over which refill-rate applies
secret-key
Note: For production, generate a strong, random Base64-encoded key. You can generate one usingBase64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded())
in Java.
-
Enable Scheduling and Async: Ensure your main application class (
WelcomeApplication.java
) has the following annotations:import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling @EnableAsync public class WelcomeApplication { // ... }
-
Build the project:
mvn clean install
-
Run the application:
mvn spring-boot:run
The application will start on
http://localhost:8080
(or your configured port).
If spring.jpa.hibernate.ddl-auto
is set to update
, Hibernate will attempt to manage schema changes automatically. However, for specific column type changes (like JSON
for price_tiers
or TEXT
for payload
in outbox_events
), or if ddl-auto
is none
/validate
, you might need to run manual SQL commands:
- For
bookable_items
table (addis_private
andprice_tiers
):ALTER TABLE bookable_items ADD COLUMN is_private BOOLEAN DEFAULT FALSE NOT NULL, ADD COLUMN price_tiers JSON NULL; -- Use JSON type for MySQL 5.7+
- For
outbox_events
table (refactor columns):(Note:ALTER TABLE outbox_events DROP COLUMN IF EXISTS username, DROP COLUMN IF EXISTS email, ADD COLUMN payload TEXT NOT NULL, ADD COLUMN recipient_email VARCHAR(255) NULL, ADD COLUMN IF NOT EXISTS processed_at DATETIME NULL, ADD COLUMN IF NOT EXISTS error_message VARCHAR(255) NULL;
updated_at
andretry_count
should already exist from previous steps.)
The application uses JWT (JSON Web Tokens) for stateless authentication and Role-Based Access Control (RBAC) for authorization.
USER
: Standard application user. Can book items, view their own profile, and view public bookable items.EVENT_ORGANIZER
: Can create, update, and delete bookable items they own. Can view bookings for their own items.ADMIN
: Has full administrative privileges across all resources (users, bookable items, bookings, reports).
- Login (
POST /api/v1/auth/login
): Users provide username/password and receive anaccessToken
andrefreshToken
. - Access Protected Resources: The
accessToken
is sent in theAuthorization: Bearer <token>
header for subsequent requests to protected endpoints. - Token Expiration: The
accessToken
has a short lifespan. - Token Refresh (
POST /api/v1/auth/refresh
): When theaccessToken
expires, therefreshToken
can be used to obtain a newaccessToken
(and often a newrefreshToken
for rotation).
For Postman, it's recommended to set up an environment variable, e.g., baseUrl
with value http://localhost:8080
.
- Endpoint:
POST /api/v1/user
- Purpose: Creates a new user account. Triggers an asynchronous welcome email via the Outbox Pattern.
- Authentication: Public (No token required).
- Request Body:
User
object (password will be BCrypt encoded).{ "username": "newuser", "email": "newuser@example.com", "password": "securepassword123", "roles": ["USER"] }
- Success Response (201 Created):
UserResponse
DTO.{ "id": 1, "username": "newuser", "email": "newuser@example.com", "roles": ["USER"] }
- Example (Postman/cURL):
curl -X POST "{{baseUrl}}/api/v1/user" \ -H "Content-Type: application/json" \ -d '{ "username": "newuser", "email": "newuser@example.com", "password": "securepassword123", "roles": ["USER"] }'
- Endpoint:
POST /api/v1/auth/login
- Purpose: Authenticates a user and issues JWT access and refresh tokens.
- Authentication: Public.
- Request Body:
AuthRequest
DTO.{ "username": "testuser", "password": "password123" }
- Success Response (200 OK):
AuthResponse
DTO.{ "token": "eyJhbGciOiJIUzI1NiJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiJ9..." }
- Example (Postman/cURL):
curl -X POST "{{baseUrl}}/api/v1/auth/login" \ -H "Content-Type: application/json" \ -d '{ "username": "testuser", "password": "password123" }'
- Endpoint:
POST /api/v1/auth/refresh
- Purpose: Obtains a new access token (and refresh token) using a valid refresh token.
- Authentication: Public (refresh token in body).
- Request Body:
RefreshTokenRequest
DTO.{ "refreshToken": "eyJhbGciOiJIUzI1NiJ9..." }
- Success Response (200 OK):
AuthResponse
DTO with new tokens.{ "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwb3N0bWFudXNlciIsImlhdCI6MTc1MjI5OTExNCwiZXhwIjoxNzUyMzg1NTE0fQ.lqUcYreONhOcHHXQdkH4VTGLS-eiNr7WeQv-w6i3B08", "refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwb3N0bWFudXNlciIsImlhdCI6MTc1MjI5OTExNCwiZXhwIjoxNzUyOTAzOTE0fQ.r_4MSJEYQ0vFir38TKiegZU-uxcwyrkboL_MYFr2ZuI" }
- Example (Postman/cURL):
curl -X POST "{{baseUrl}}/api/v1/auth/refresh" \ -H "Content-Type: application/json" \ -d '{ "refreshToken": "eyJhbGciOiJIUzI1NiJ9..." }'
- Endpoint:
GET /api/v1/user/me
- Purpose: Retrieves the profile of the currently logged-in user.
- Authentication: Authenticated.
- Success Response (200 OK):
UserResponse
DTO.{ "id": 1, "username": "testuser", "email": "test@example.com", "roles": ["USER"] }
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/user/me" \ -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>"
- Endpoint:
GET /api/v1/user/{id}
- Purpose: Retrieves a user's profile by their ID.
- Authentication:
ADMIN
Role. - Success Response (200 OK):
UserResponse
DTO.{ "id": 2, "username": "event_organizer", "email": "organizer@example.com", "roles": ["EVENT_ORGANIZER"] }
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/user/2" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
- Endpoint:
GET /api/v1/user
- Purpose: Retrieves a list of all registered users.
- Authentication:
ADMIN
Role. - Success Response (200 OK): List of
UserResponse
DTOs.[ { "id": 1, "username": "testuser", "email": "test@example.com", "roles": ["USER"] }, { "id": 2, "username": "event_organizer", "email": "organizer@example.com", "roles": ["EVENT_ORGANIZER"] } ]
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/user" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
- Endpoint:
PUT /api/v1/user/{id}
- Purpose: Updates an existing user's profile.
- Authentication: User can update their own profile;
ADMIN
can update any profile. - Request Body:
UserUpdateRequest
DTO.{ "username": "updated_testuser", "email": "updated_test@example.com", "roles": ["USER", "EVENT_ORGANIZER", "ADMIN"] }
- Success Response (200 OK): Updated
UserResponse
DTO.{ "id": 1, "username": "updated_testuser", "email": "updated_test@example.com", "roles": ["USER"] }
- Example (Postman/cURL - User updating self):
curl -X PUT "{{baseUrl}}/api/v1/user/1" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \ -d '{ "username": "my_new_username", "email": "my_new_email@example.com" }'
- Endpoint:
DELETE /api/v1/user/{id}
- Purpose: Deletes a user account.
- Authentication:
ADMIN
Role. - Success Response (204 No Content): No body.
- Example (Postman/cURL):
curl -X DELETE "{{baseUrl}}/api/v1/user/3" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
These APIs manage the creation, retrieval, and modification of various bookable items (e.g., events, appointments, resources).
- Endpoint:
POST /api/v1/items
- Purpose: Creates a new bookable item. The logged-in user becomes the organizer. Supports optional
isPrivate
andpriceTiers
. - Authentication:
EVENT_ORGANIZER
orADMIN
Role. - Request Body:
BookableItemRequest
DTO.{ "name": "Spring Boot Workshop", "description": "An interactive workshop on Spring Boot basics.", "startTime": "2025-09-15T10:00:00", "endTime": "2025-09-15T12:00:00", "location": "Online via Zoom", "capacity": 50, "isPrivate": false, "priceTiers": [ {"tier": "Early Bird", "price": 49.99, "maxCapacity": 20}, {"tier": "Regular", "price": 59.99, "maxCapacity": 30} ] }
- Success Response (201 Created):
BookableItemResponse
DTO.{ "id": 101, "name": "Spring Boot Workshop", "description": "An interactive workshop on Spring Boot basics.", "startTime": "2025-09-15T10:00:00", "endTime": "2025-09-15T12:00:00", "location": "Online via Zoom", "capacity": 50, "organizerId": 2, "organizerUsername": "event_organizer", "isPrivate": false, "priceTiers": [ {"tier": "Early Bird", "price": 49.99, "maxCapacity": 20}, {"tier": "Regular", "price": 59.99, "maxCapacity": 30} ], "currentPrice": 49.99, "availableCapacity": 50, "createdAt": "2025-07-13T12:00:00", "updatedAt": "2025-07-13T12:00:00" }
- Example (Postman/cURL):
curl -X POST "{{baseUrl}}/api/v1/items" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <EVENT_ORGANIZER_ACCESS_TOKEN>" \ -d '{ "name": "Spring Boot Workshop", "description": "An interactive workshop on Spring Boot basics.", "startTime": "2025-09-15T10:00:00", "endTime": "2025-09-15T12:00:00", "location": "Online via Zoom", "capacity": 50, "isPrivate": false, "priceTiers": [{"tier": "Standard", "price": 59.99, "maxCapacity": 50}] }'
- Endpoint:
GET /api/v1/items/{id}
- Purpose: Retrieves details of a specific bookable item. Private items are only visible to the organizer or admin.
- Authentication: Public for public items. Authenticated (
EVENT_ORGANIZER
for their own private items,ADMIN
for any private item). - Success Response (200 OK):
BookableItemResponse
DTO.{ "id": 101, "name": "Spring Boot Workshop", "description": "An interactive workshop on Spring Boot basics.", "startTime": "2025-09-15T10:00:00", "endTime": "2025-09-15T12:00:00", "location": "Online via Zoom", "capacity": 50, "organizerId": 2, "organizerUsername": "event_organizer", "isPrivate": false, "priceTiers": [ {"tier": "Early Bird", "price": 49.99, "maxCapacity": 20}, {"tier": "Regular", "price": 59.99, "maxCapacity": 30} ], "currentPrice": 49.99, "availableCapacity": 50, "createdAt": "2025-07-13T12:00:00", "updatedAt": "2025-07-13T12:00:00" }
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/items/101"
- Endpoint:
GET /api/v1/items
- Purpose: Retrieves a list of all available public bookable items. Includes filtering by
organizerId
. - Authentication: Public.
- Query Parameters:
organizerId
(Optional): Filter items by organizer ID.onlyMyItems
(Optional): If true, returns only items organized by the authenticated user. Requires authentication.
- Success Response (200 OK): List of
BookableItemResponse
DTOs.[ { "id": 101, "name": "Spring Boot Workshop", "description": "An interactive workshop on Spring Boot basics.", "startTime": "2025-09-15T10:00:00", "endTime": "2025-09-15T12:00:00", "location": "Online via Zoom", "capacity": 50, "organizerId": 2, "organizerUsername": "event_organizer", "isPrivate": false, "priceTiers": [ {"tier": "Early Bird", "price": 49.99, "maxCapacity": 20}, {"tier": "Regular", "price": 59.99, "maxCapacity": 30} ], "currentPrice": 49.99, "availableCapacity": 50, "createdAt": "2025-07-13T12:00:00", "updatedAt": "2025-07-13T12:00:00" } ]
- Example (Postman/cURL - Get all public items):
curl -X GET "{{baseUrl}}/api/v1/items"
- Example (Postman/cURL - Get items by a specific organizer):
curl -X GET "{{baseUrl}}/api/v1/items?organizerId=2"
- Example (Postman/cURL - Get items organized by current user):
curl -X GET "{{baseUrl}}/api/v1/items?onlyMyItems=true" \ -H "Authorization: Bearer <EVENT_ORGANIZER_ACCESS_TOKEN>"
- Endpoint:
PUT /api/v1/items/{id}
- Purpose: Updates an existing bookable item's details. Can also modify
isPrivate
andpriceTiers
. - Authentication:
ADMIN
Role OR theEVENT_ORGANIZER
who created the item. - Request Body:
BookableItemRequest
DTO.{ "name": "Updated Spring Boot Workshop", "description": "New description for the updated workshop.", "startTime": "2025-09-20T14:00:00", "endTime": "2025-09-20T16:00:00", "location": "Hybrid - Online & Venue", "capacity": 60, "isPrivate": true, "priceTiers": [ {"tier": "Standard", "price": 65.00, "maxCapacity": 60} ] }
- Success Response (200 OK): Updated
BookableItemResponse
DTO. - Example (Postman/cURL - Organizer updating their item):
curl -X PUT "{{baseUrl}}/api/v1/items/101" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <EVENT_ORGANIZER_ACCESS_TOKEN>" \ -d '{ "name": "Updated Spring Boot Workshop", "description": "New description for the updated workshop.", "startTime": "2025-09-20T14:00:00", "endTime": "2025-09-20T16:00:00", "location": "Hybrid - Online & Venue", "capacity": 60, "isPrivate": true, "priceTiers": [{"tier": "Standard", "price": 65.00, "maxCapacity": 60}] }'
- Endpoint:
DELETE /api/v1/items/{id}
- Purpose: Deletes a bookable item.
- Authentication:
ADMIN
Role OR theEVENT_ORGANIZER
who created the item. - Success Response (204 No Content): No body.
- Example (Postman/cURL - Admin deleting any item):
curl -X DELETE "{{baseUrl}}/api/v1/items/101" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
These APIs handle the actual booking process and management of user bookings.
- Endpoint:
POST /api/v1/bookings/items/{itemId}
- Purpose: Creates a new booking for the current authenticated user for a specific bookable item. Checks for availability and conflicts.
- Authentication: Authenticated (
USER
,EVENT_ORGANIZER
,ADMIN
). - Request Body:
BookingRequest
DTO (optional if using default tier, required if specifying a price tier).{ "priceTier": "Early Bird" }
- Success Response (201 Created):
BookingResponse
DTO. Triggers asynchronous booking confirmation email.{ "id": 201, "userId": 1, "username": "testuser", "itemId": 101, "itemName": "Spring Boot Workshop", "bookingDate": "2025-07-13T15:30:00", "status": "CONFIRMED", "pricePaid": 49.99 }
- Example (Postman/cURL):
curl -X POST "{{baseUrl}}/api/v1/bookings/items/101" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>" \ -d '{ "priceTier": "Early Bird" }'
- Endpoint:
GET /api/v1/bookings/me
- Purpose: Retrieves all upcoming and past bookings for the current authenticated user.
- Authentication: Authenticated.
- Success Response (200 OK): List of
BookingResponse
DTOs.[ { "id": 201, "userId": 1, "username": "testuser", "itemId": 101, "itemName": "Spring Boot Workshop", "bookingDate": "2025-07-13T15:30:00", "status": "CONFIRMED", "pricePaid": 49.99 } ]
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/bookings/me" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>"
- Endpoint:
GET /api/v1/bookings/items/{itemId}
- Purpose: Retrieves all bookings for a specific bookable item.
- Authentication:
ADMIN
Role OR theEVENT_ORGANIZER
of that item. - Success Response (200 OK): List of
BookingResponse
DTOs.[ { "id": 201, "userId": 1, "username": "testuser", "itemId": 101, "itemName": "Spring Boot Workshop", "bookingDate": "2025-07-13T15:30:00", "status": "CONFIRMED", "pricePaid": 49.99 }, { "id": 202, "userId": 3, "username": "another_user", "itemId": 101, "itemName": "Spring Boot Workshop", "bookingDate": "2025-07-13T16:00:00", "status": "CONFIRMED", "pricePaid": 59.99 } ]
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/bookings/items/101" \ -H "Authorization: Bearer <EVENT_ORGANIZER_OR_ADMIN_ACCESS_TOKEN>"
- Endpoint:
PUT /api/v1/bookings/{bookingId}/cancel
- Purpose: Cancels an existing booking. Can be done by the user who made the booking or an admin. Triggers asynchronous cancellation email.
- Authentication: Authenticated (user must own the booking or have
ADMIN
role). - Success Response (200 OK): Updated
BookingResponse
DTO withCANCELLED
status. - Example (Postman/cURL):
curl -X PUT "{{baseUrl}}/api/v1/bookings/201/cancel" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>"
- Endpoint:
PUT /api/v1/bookings/{bookingId}/status
- Purpose: Allows an admin or organizer to manually update a booking's status.
- Authentication:
ADMIN
Role OR theEVENT_ORGANIZER
of the associated item. - Request Body:
BookingStatusUpdateRequest
DTO.{ "status": "COMPLETED" }
- Success Response (200 OK): Updated
BookingResponse
DTO. - Example (Postman/cURL):
curl -X PUT "{{baseUrl}}/api/v1/bookings/201/status" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>" \ -d '{ "status": "COMPLETED" }'
These APIs provide insights into the real-time availability of bookable items.
- Endpoint:
GET /api/v1/items/{itemId}/availability
- Purpose: Returns the current available capacity for a given bookable item, considering its total capacity and existing confirmed bookings.
- Authentication: Public.
- Success Response (200 OK):
AvailableCapacityResponse
DTO.{ "itemId": 101, "itemName": "Spring Boot Workshop", "totalCapacity": 50, "bookedSlots": 10, "availableSlots": 40 }
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/items/101/availability"
- Endpoint:
GET /api/v1/users/me/conflicts?startTime={startTime}&endTime={endTime}
- Purpose: Checks if the authenticated user has any existing bookings that conflict with a specified time range.
- Authentication: Authenticated.
- Query Parameters:
startTime
: Start time of the period to check (e.g.,2025-09-15T09:00:00
).endTime
: End time of the period to check (e.g.,2025-09-15T11:00:00
).
- Success Response (200 OK):
UserConflictCheckResponse
DTO.{ "userId": 1, "hasConflict": true, "conflictingBookings": [ { "id": 201, "itemName": "Another Meeting", "startTime": "2025-09-15T09:30:00", "endTime": "2025-09-15T10:30:00" } ] }
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/users/me/conflicts?startTime=2025-09-15T09:00:00&endTime=2025-09-15T11:00:00" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>"
This API provides recommendations for bookable items based on a content-based filtering approach.
- Endpoint:
GET /api/v1/recommendations/users/me
- Purpose: Returns a list of bookable items recommended for the authenticated user based on their past booking history and the characteristics of those items (e.g., location, type, organizer).
- Authentication: Authenticated.
- Query Parameters:
limit
(Optional): Maximum number of recommendations to return (default: 5).
- Success Response (200 OK): List of
BookableItemResponse
DTOs.[ { "id": 105, "name": "Advanced Spring Security Workshop", "description": "Deep dive into Spring Security.", "startTime": "2025-10-01T09:00:00", "endTime": "2025-10-01T17:00:00", "location": "Online via Zoom", "capacity": 30, "organizerId": 2, "organizerUsername": "event_organizer", "isPrivate": false, "priceTiers": [ {"tier": "Standard", "price": 99.99, "maxCapacity": 30} ], "currentPrice": 99.99, "availableCapacity": 25, "createdAt": "2025-07-10T10:00:00", "updatedAt": "2025-07-10T10:00:00" }, { "id": 106, "name": "Jakarta EE Basics", "description": "Introduction to Jakarta EE.", "startTime": "2025-09-25T13:00:00", "endTime": "2025-09-25T16:00:00", "location": "Conference Room A", "capacity": 20, "organizerId": 4, "organizerUsername": "another_organizer", "isPrivate": false, "priceTiers": [ {"tier": "Standard", "price": 75.00, "maxCapacity": 20} ], "currentPrice": 75.00, "availableCapacity": 18, "createdAt": "2025-07-11T11:00:00", "updatedAt": "2025-07-11T11:00:00" } ]
- Example (Postman/cURL):
curl -X GET "{{baseUrl}}/api/v1/recommendations/users/me?limit=3" \ -H "Authorization: Bearer <USER_ACCESS_TOKEN>"
These APIs provide administrative reporting capabilities.
- Endpoint:
GET /api/v1/reports/revenue
- Purpose: Generates a report on total revenue, optionally filtered by
itemId
and/or a date range. OnlyADMIN
can access this. - Authentication:
ADMIN
Role. - Query Parameters:
itemId
(Optional): Filter revenue for a specific bookable item.startDate
(Optional): Start date for the report (e.g.,2025-01-01
).endDate
(Optional): End date for the report (e.g.,2025-12-31
).
- Success Response (200 OK):
RevenueReportResponse
DTO.{ "totalRevenue": 1500.75, "reportDetails": [ { "itemId": 101, "itemName": "Spring Boot Workshop", "itemRevenue": 750.50, "numberOfBookings": 15 }, { "itemId": 102, "itemName": "Team Building Session", "itemRevenue": 750.25, "numberOfBookings": 10 } ] }
- Example (Postman/cURL - Total Revenue):
curl -X GET "{{baseUrl}}/api/v1/reports/revenue" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
- Example (Postman/cURL - Revenue for a specific item in a date range):
curl -X GET "{{baseUrl}}/api/v1/reports/revenue?itemId=101&startDate=2025-07-01&endDate=2025-09-30" \ -H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>"
This section details advanced features implemented and provides guidance on how to test them.
- Feature: Bookable items can be marked as
isPrivate=true
. Private items are only discoverable and viewable by theirorganizerId
or anADMIN
. They will not appear in the general/api/v1/items
listing for regular users. - Testing:
- Create a user with
EVENT_ORGANIZER
role (organizer1
). organizer1
creates a private item.- Attempt to
GET /api/v1/items/{privateItemId}
asorganizer1
(should succeed). - Attempt to
GET /api/v1/items/{privateItemId}
as aUSER
(should return 404 Not Found or 403 Forbidden). - Attempt to
GET /api/v1/items
as aUSER
(private item should not be in the list). - Attempt to
GET /api/v1/items/{privateItemId}
asADMIN
(should succeed). - Have a
USER
try to book a private item (should return an error indicating the item isn't found or accessible).
- Create a user with
- Feature: When a user attempts to create a booking, the system checks if the new booking's time (
startTime
toendTime
of theBookableItem
) overlaps with any of their existing confirmed bookings. If a conflict is detected, the booking is rejected. - Testing:
- Create a
USER
. - Create two public
BookableItem
s (itemA
,itemB
) with overlappingstartTime
andendTime
. - Have the
USER
successfully bookitemA
. - Have the
USER
attempt to bookitemB
. This request should result in a409 Conflict
error with a message indicating the time conflict. - Verify that booking an item that doesn't conflict with existing bookings succeeds.
- Create a
- Feature: The
availableCapacity
andcurrentPrice
for aBookableItem
are calculated dynamically at the time of retrieval (GET /api/v1/items/{id}
andGET /api/v1/items
).availableCapacity
istotalCapacity - confirmedBookingsCount
.currentPrice
is determined by thepriceTiers
based on how many slots are remaining. - Testing:
- Create a
BookableItem
withcapacity
= 50 and twopriceTiers
: "Early Bird" (price 49.99, maxCapacity 20) and "Regular" (price 59.99, maxCapacity 30). GET /api/v1/items/{itemId}
: VerifyavailableCapacity
is 50 andcurrentPrice
is 49.99.- Have 10
USER
s book this item (using "Early Bird" tier implicitly or explicitly). GET /api/v1/items/{itemId}
: VerifyavailableCapacity
is 40 andcurrentPrice
is still 49.99.- Have 15 more
USER
s book this item (total 25 confirmed bookings). GET /api/v1/items/{itemId}
: VerifyavailableCapacity
is 25 andcurrentPrice
is 59.99 (as "Early Bird" tier is now full, price moves to next tier).- Attempt to book the item once
availableCapacity
reaches 0 (should result in400 Bad Request
or similar indicating no more slots).
- Create a
- Feature: The
/api/v1/recommendations/users/me
endpoint provides personalized recommendations. It identifies categories or organizers from the user's past confirmed bookings and suggests other items within those categories/by those organizers that the user hasn't booked yet. - Testing:
- Create multiple
BookableItem
s with differentorganizerId
s and categories (e.g., "Tech Workshop" byorganizer1
, "Fitness Class" byorganizer2
, "Another Tech Workshop" byorganizer1
). - Have a
USER
book "Tech Workshop" byorganizer1
. GET /api/v1/recommendations/users/me
as thatUSER
. The response should prioritize "Another Tech Workshop" byorganizer1
and other tech-related items if they exist. Items byorganizer2
or in other categories should be less likely unless a user has booked from diverse categories.- Book more diverse items and observe how recommendations change.
- Create multiple
- Feature: Bookable items can have
priceTiers
, an array of objects specifyingtier
name,price
, andmaxCapacity
for that tier. ThecurrentPrice
of the item (and the price a user pays) is determined by the highestprice
tier for whichavailableCapacity
still exists within itsmaxCapacity
. - Testing: (Covered partly in Dynamic Availability Calculation)
- Create an item with tiers:
Total capacity = 50.
"priceTiers": [ {"tier": "Super Early Bird", "price": 29.99, "maxCapacity": 5}, {"tier": "Early Bird", "price": 49.99, "maxCapacity": 20}, {"tier": "Regular", "price": 69.99, "maxCapacity": 25} ]
- Initial:
currentPrice
should be 29.99. - Book 5 slots:
currentPrice
should become 49.99. - Book another 20 slots (total 25):
currentPrice
should become 69.99. - Attempt to book more than the item's total capacity (e.g., 51st booking) — should fail with an appropriate error (e.g., "Capacity exceeded").
- Verify that when a user books, the
pricePaid
in theBookingResponse
reflects thecurrentPrice
at the moment of booking.
- Create an item with tiers:
- Feature: Instead of directly sending emails, the application uses the Outbox Pattern. When a user registers or a booking is made/cancelled, a corresponding
OutboxEvent
is saved to the database within the same transaction. A scheduled job (OutboxProcessor
) then asynchronously picks up these events and simulates sending notifications (e.g., logging them) without blocking the main request thread. It includes retry logic. - Testing:
- Set
spring.jpa.hibernate.ddl-auto=update
or manually create theoutbox_events
table as specified in Database Migration. - Register a new user via
POST /api/v1/user
. - Immediately after the successful response, query the
outbox_events
table in your database. You should see a new entry witheventType='USER_REGISTERED'
andprocessed_at
as NULL. - Wait a few seconds (the scheduler interval).
- Query the
outbox_events
table again. Theprocessed_at
column for that event should now have a timestamp, indicating it was processed, and you should see a log message in the application console simulating the email sending. - Repeat for a successful
POST /api/v1/bookings/items/{itemId}
(event typeBOOKING_CONFIRMED
) and aPUT /api/v1/bookings/{bookingId}/cancel
(event typeBOOKING_CANCELLED
). - To test retry logic: Temporarily configure the email service (or mock it) to fail, then observe
retry_count
anderror_message
updating inoutbox_events
.
- Set
- Feature: Protects certain endpoints (e.g., login, registration) from excessive requests using the Bucket4j library to prevent brute-force attacks or denial-of-service. Configurable via
application.properties
. - Testing:
- Ensure
app.rate-limit.enabled=true
and setapp.rate-limit.capacity
to a low number (e.g., 5) andapp.rate-limit.refill-rate
to 1 token per second. - Rapidly send more than
capacity
(e.g., 6 or more)POST /api/v1/auth/login
requests from the same IP address/client. - The first few requests should succeed. Subsequent requests exceeding the limit within the defined period should receive a
429 Too Many Requests
HTTP status code. - Wait for the
refill-rate
duration, then try again. Requests should now succeed until the limit is hit again.
- Ensure
This section outlines the primary Data Transfer Objects (DTOs) used for API requests and responses.
-
AuthRequest
public record AuthRequest(String username, String password) {}
-
AuthResponse
public record AuthResponse(String token, String refreshToken) {}
-
RefreshTokenRequest
public record RefreshTokenRequest(String refreshToken) {}
-
UserResponse
public record UserResponse(Long id, String username, String email, Set<Role> roles) {}
-
UserUpdateRequest
public record UserUpdateRequest(String username, String email, Set<Role> roles) {}
-
PriceTierDTO
public record PriceTierDTO(String tier, double price, int maxCapacity) {}
-
BookableItemRequest
public record BookableItemRequest( String name, String description, LocalDateTime startTime, LocalDateTime endTime, String location, int capacity, boolean isPrivate, List<PriceTierDTO> priceTiers ) {}
-
BookableItemResponse
public record BookableItemResponse( Long id, String name, String description, LocalDateTime startTime, LocalDateTime endTime, String location, int capacity, Long organizerId, String organizerUsername, boolean isPrivate, List<PriceTierDTO> priceTiers, double currentPrice, // Dynamically calculated int availableCapacity, // Dynamically calculated LocalDateTime createdAt, LocalDateTime updatedAt ) {}
-
BookingRequest
public record BookingRequest(String priceTier) {} // Optional: specify preferred price tier
-
BookingResponse
public record BookingResponse( Long id, Long userId, String username, Long itemId, String itemName, LocalDateTime bookingDate, BookingStatus status, // e.g., PENDING, CONFIRMED, CANCELLED, COMPLETED double pricePaid ) {}
-
BookingStatusUpdateRequest
public record BookingStatusUpdateRequest(BookingStatus status) {}
-
AvailableCapacityResponse
public record AvailableCapacityResponse( Long itemId, String itemName, int totalCapacity, int bookedSlots, int availableSlots ) {}
-
UserConflictCheckResponse
public record UserConflictCheckResponse( Long userId, boolean hasConflict, List<ConflictingBookingDTO> conflictingBookings ) {}
ConflictingBookingDTO
(nested withinUserConflictCheckResponse
)public record ConflictingBookingDTO(Long id, String itemName, LocalDateTime startTime, LocalDateTime endTime) {}
-
RevenueReportResponse
public record RevenueReportResponse( double totalRevenue, List<ItemRevenueDetail> reportDetails ) {}
ItemRevenueDetail
(nested withinRevenueReportResponse
)public record ItemRevenueDetail(Long itemId, String itemName, double itemRevenue, long numberOfBookings) {}
The API provides consistent error responses through a GlobalExceptionHandler
:
400 Bad Request
: For invalid request body (validation errors) or business logic validation failures (e.g.,ValidationException
,CapacityExceededException
).401 Unauthorized
: For missing or invalid authentication tokens.403 Forbidden
: For valid tokens but insufficient permissions (AccessDeniedException
).404 Not Found
: For resources not found (ResourceNotFoundException
).409 Conflict
: For resource creation conflicts (e.g.,UserAlreadyExistsException
,TimeConflictException
).429 Too Many Requests
: When API rate limits are exceeded.500 Internal Server Error
: For unexpected server-side errors.