Skip to main content

Overview

Once proposals are approved by administrators, they become Events visible to students. The event system supports browsing, registration, search, pagination, and organizer management.

Public Browsing

Students can browse all approved events

Search & Filter

Full-text search across event titles and descriptions

Pagination

Efficient pagination for large event lists

Organizer Dashboard

Organizers can view and manage their events

Event Lifecycle

1

Proposal Approved

Admin approves a proposal, creating an Event entity
2

Published

Event becomes visible to students with OPEN status
3

Registration Period

Students register until capacity is reached
4

Ongoing

Event status changes to ONGOING on event date
5

Completed

Event status changes to COMPLETED after end time

Event Entity

Database Structure

Event.java:16-85
@Entity
@Table(name = "events")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Event {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "event_id")
    private Long eventID;

    @Column(nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT")
    private String description;

    @Column(name = "event_date", nullable = false)
    private LocalDate eventDate;

    @Column(name = "start_time", nullable = false)
    private LocalTime startTime;

    @Column(name = "end_time", nullable = false)
    private LocalTime endTime;

    @Column(nullable = false)
    private String venue;

    @Column(nullable = false)
    private Integer capacity;

    @Column(name = "current_registrations")
    @Builder.Default
    private Integer currentRegistrations = 0;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private EventStatus status;

    @Enumerated(EnumType.STRING)
    @Column(name = "approval_status")
    @Builder.Default
    private ApprovalStatus approvalStatus = ApprovalStatus.APPROVED;

    @Enumerated(EnumType.STRING)
    @Column(name = "organization_type", nullable = false)
    private OrganizationType organizationType;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(
        name = "organizer_id",
        nullable = false,
        foreignKey = @ForeignKey(name = "fk_events_organizer")
    )
    private EventOrganizer organizer;

    @Column(name = "created_at")
    @Builder.Default
    private LocalDateTime createdAt = LocalDateTime.now();

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    // OPTIMISTIC LOCKING
    @Version
    private Long version;
}

Key Fields

eventID
Long
Primary key, auto-generated
title
String
Event title (required)
description
String
Detailed event description (TEXT column)
eventDate
LocalDate
Event date (required)
startTime
LocalTime
Event start time (required)
endTime
LocalTime
Event end time (required)
venue
String
Event location (required)
capacity
Integer
Maximum number of participants (required)
currentRegistrations
Integer
Current number of registrations (defaults to 0)
status
EventStatus
One of: OPEN, ONGOING, COMPLETED, CANCELLED
approvalStatus
ApprovalStatus
One of: PENDING, APPROVED, REJECTED (defaults to APPROVED)
organizationType
OrganizationType
One of: ACADEMIC, CULTURAL, SPORTS, SOCIAL, CAREER, VOLUNTEER
organizer
EventOrganizer
Foreign key to the organizer (lazy-loaded)
version
Long
Optimistic locking version for concurrency control
The version field is critical for preventing race conditions during registration. See Registrations for details.

Browse Events

Endpoint

GET /api/events?page=0&size=9&search=workshop

Query Parameters

page
integer
default:"0"
Page number (0-indexed)
size
integer
default:"9"
Number of events per page
Search query (searches title and description)

Implementation

EventController.java:27-45
@GetMapping
public ResponseEntity<Response<Page<EventDTO>>> getAllEvents(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "9") int size,
        @RequestParam(required = false) String search,
        @AuthenticationPrincipal CustomUserDetails userDetails) {

    Pageable pageable = PageRequest.of(page, size);
    Long currentUserId = userDetails != null ? userDetails.getUser().getUserID() : null;
    Page<EventDTO> events = eventService.getAllApprovedEvents(pageable, search, currentUserId);

    Response<Page<EventDTO>> response = Response.<Page<EventDTO>>builder()
            .statusCode(HttpStatus.OK.value())
            .message("Events retrieved successfully")
            .data(events)
            .build();

    return ResponseEntity.ok(response);
}
This endpoint is accessible to both authenticated and unauthenticated users. Authentication is optional but provides additional context (e.g., isRegistered field).

Response Example

{
  "statusCode": 200,
  "message": "Events retrieved successfully",
  "data": {
    "content": [
      {
        "eventID": 456,
        "title": "React Workshop",
        "description": "Learn React fundamentals",
        "eventDate": "2024-04-15",
        "startTime": "14:00:00",
        "endTime": "16:00:00",
        "venue": "Room 301",
        "capacity": 50,
        "currentRegistrations": 32,
        "status": "OPEN",
        "organizationType": "ACADEMIC",
        "organizerName": "Computer Science Club",
        "organizationName": "CS Club",
        "isRegistered": true,
        "isOwner": false,
        "createdAt": "2024-03-15T10:30:00"
      }
    ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 9,
      "sort": { "sorted": false },
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "totalPages": 3,
    "totalElements": 25,
    "last": false,
    "first": true,
    "number": 0,
    "numberOfElements": 9,
    "size": 9,
    "empty": false
  },
  "timestamp": "2024-03-15T10:35:00"
}

Pagination Structure

content
Array<EventDTO>
Array of events for the current page
totalPages
integer
Total number of pages
totalElements
integer
Total number of events across all pages
number
integer
Current page number (0-indexed)
size
integer
Number of items per page
first
boolean
True if this is the first page
last
boolean
True if this is the last page

Get Event by ID

Endpoint

GET /api/events/{eventID}

Implementation

EventController.java:47-60
@GetMapping("/{eventID}")
public ResponseEntity<Response<EventDTO>> getEventByID(@PathVariable Long eventID,
                                                       @AuthenticationPrincipal CustomUserDetails userDetails) {
    Long currentUserId = userDetails != null ? userDetails.getUser().getUserID() : null;
    EventDTO event = eventService.getEventByID(eventID, currentUserId);

    Response<EventDTO> response = Response.<EventDTO>builder()
            .statusCode(HttpStatus.OK.value())
            .message("Event retrieved successfully")
            .data(event)
            .build();

    return ResponseEntity.ok(response);
}

Response Example

{
  "statusCode": 200,
  "message": "Event retrieved successfully",
  "data": {
    "eventID": 456,
    "title": "React Workshop",
    "description": "Learn React fundamentals including hooks, state management, and best practices.",
    "eventDate": "2024-04-15",
    "startTime": "14:00:00",
    "endTime": "16:00:00",
    "venue": "Room 301, Engineering Building",
    "capacity": 50,
    "currentRegistrations": 32,
    "status": "OPEN",
    "approvalStatus": "APPROVED",
    "organizationType": "ACADEMIC",
    "organizerID": 789,
    "organizerName": "John Smith",
    "organizationName": "Computer Science Club",
    "isRegistered": false,
    "isOwner": false,
    "createdAt": "2024-03-15T10:30:00",
    "updatedAt": "2024-03-16T09:15:00"
  },
  "timestamp": "2024-03-15T10:35:00"
}

EventDTO Fields

isRegistered
boolean
True if the current user is registered for this event (requires authentication)
isOwner
boolean
True if the current user is the organizer of this event
The isRegistered and isOwner fields provide client-side context for displaying appropriate UI (e.g., “Register” vs “Already Registered” buttons).

Get Managed Events (Organizers)

Endpoint

GET /api/events/managed

Authorization

EventController.java:62-63
@GetMapping("/managed")
@PreAuthorize("hasRole('ORGANIZER')")
Only users with ORGANIZER role can access this endpoint.

Implementation

EventController.java:64-77
public ResponseEntity<Response<List<EventDTO>>> getManagedEvents(
        @AuthenticationPrincipal CustomUserDetails userDetails) {

    Long organizerId = userDetails.getUser().getUserID();
    List<EventDTO> managedEvents = eventService.getEventsByOrganizer(organizerId);

    Response<List<EventDTO>> response = Response.<List<EventDTO>>builder()
            .statusCode(HttpStatus.OK.value())
            .message("Managed events retrieved successfully")
            .data(managedEvents)
            .build();

    return ResponseEntity.ok(response);
}

Response Example

{
  "statusCode": 200,
  "message": "Managed events retrieved successfully",
  "data": [
    {
      "eventID": 456,
      "title": "React Workshop",
      "status": "OPEN",
      "currentRegistrations": 32,
      "capacity": 50,
      "eventDate": "2024-04-15",
      "isOwner": true
    },
    {
      "eventID": 457,
      "title": "Career Fair 2024",
      "status": "ONGOING",
      "currentRegistrations": 150,
      "capacity": 200,
      "eventDate": "2024-03-20",
      "isOwner": true
    }
  ],
  "timestamp": "2024-03-15T10:35:00"
}
This endpoint returns all events organized by the authenticated user, regardless of status (OPEN, ONGOING, COMPLETED, CANCELLED).

Event Statuses

Description: Event is accepting registrationsConditions:
  • Event date is in the future
  • Current registrations < capacity
Actions Available:
  • Students can register
  • Organizer can post updates
  • Admin can cancel event

Search Functionality

The search parameter performs full-text search across:
  • Event title
  • Event description
// Example: Search for "workshop"
GET /api/events?search=workshop

// Example: Search with pagination
GET /api/events?search=react&page=0&size=12

Case-Insensitive

Search is case-insensitive

Partial Matching

Supports partial word matching

Multiple Words

Searches for any matching word

Fast Performance

Database-level search with indexes

Optimistic Locking

Concurrency Control

The Event entity uses optimistic locking to prevent race conditions:
Event.java:82-84
// OPTIMISTIC LOCKING
@Version
private Long version;
Optimistic locking is critical for preventing overbooking during high-traffic registration periods.

How It Works

1

Read Event

Client reads event with version = 5
2

Modify Event

Client increments currentRegistrations
3

Save with Version Check

JPA checks if database version is still 5
4

Success or Retry

  • If version matches: Save succeeds, version incremented to 6
  • If version doesn’t match: ObjectOptimisticLockingFailureException thrown
See Registrations for a detailed example of optimistic locking in action.

Organization Types

Events are categorized by organization type:

ACADEMIC

Workshops, seminars, lectures

CULTURAL

Art shows, performances, exhibitions

SPORTS

Tournaments, fitness activities

SOCIAL

Networking, social gatherings

CAREER

Career fairs, recruitment events

VOLUNTEER

Community service, volunteer work

The Wrapper Rule

All event endpoints follow the Wrapper Rule: ResponseEntity<Response<T>>

Consistent Structure

Response<List<EventDTO>> response = Response.<List<EventDTO>>builder()
        .statusCode(HttpStatus.OK.value())
        .message("Events retrieved successfully")
        .data(managedEvents)
        .build();

return ResponseEntity.ok(response);
This ensures:
  • Consistent error handling
  • Standardized response format
  • Client-friendly error messages
  • Automatic timestamp inclusion

Error Handling

{
  "statusCode": 404,
  "message": "Event not found with ID: 456",
  "timestamp": "2024-03-15T10:30:00"
}
{
  "statusCode": 403,
  "message": "Access Denied",
  "timestamp": "2024-03-15T10:30:00"
}
{
  "statusCode": 400,
  "message": "Invalid page number",
  "timestamp": "2024-03-15T10:30:00"
}

Best Practices

1

Use Pagination

Always use pagination for event lists to improve performance and user experience.
2

Implement Search

Use the search parameter for client-side search functionality.
3

Check isRegistered

Use the isRegistered field to prevent duplicate registrations on the client side.
4

Handle Empty Results

Check the empty field in pagination response to handle empty result sets.

Proposals

Learn about proposal submission before events

Registrations

Understand how students register for events

Build docs developers (and LLMs) love