Overview
Event Organizers submit proposals to request permission to host events. The proposal system supports multipart file uploads, approval workflows, and resubmission after rejection.
Multipart Upload Attach supporting documents to proposals
Approval Workflow Admin review and approval process
Resubmission Update and resubmit rejected proposals
Status Tracking Track proposal through pending, approved, rejected states
Proposal Lifecycle
Submission
Organizer creates a proposal with event details and optional document attachments
Pending Review
Proposal enters the queue for admin review
Admin Decision
Admin approves or rejects the proposal with comments
Approved
Event is created and becomes visible to students
Rejected (Optional)
Organizer can update and resubmit with changes
Create Proposal
Endpoint
POST / api / proposals
Content - Type : multipart / form - data
Authorization
Only users with ORGANIZER role can create proposals.
ProposalController.java:48-49
@ PostMapping ( consumes = MediaType . MULTIPART_FORM_DATA_VALUE )
@ PreAuthorize ( "hasRole('ORGANIZER')" )
Request Structure
The request must be sent as multipart/form-data with two parts:
JSON string containing the proposal details
Array of document files (optional)
Proposal JSON Structure
Detailed event description
Proposed event date (must be in the future)
Maximum number of participants (minimum 1)
One of: ACADEMIC, CULTURAL, SPORTS, SOCIAL, CAREER, VOLUNTEER
Implementation
The controller handles multipart requests by deserializing JSON and processing files:
ProposalController.java:50-72
public ResponseEntity < Response < ProposalDTO >> createProposal (
@ RequestPart ( "proposal" ) String proposalJson,
@ RequestPart ( value = "files" , required = false ) MultipartFile [] files,
@ AuthenticationPrincipal CustomUserDetails userDetails) throws Exception {
ProposalDTO proposalDTO = objectMapper . readValue (proposalJson, ProposalDTO . class );
var violations = validator . validate (proposalDTO);
if ( ! violations . isEmpty ()) {
throw new ConstraintViolationException (violations);
}
Long organizerID = userDetails . getUser (). getUserID ();
ProposalDTO createdProposal = proposalService . createProposal (proposalDTO, files, organizerID);
Response < ProposalDTO > response = Response. < ProposalDTO > builder ()
. statusCode ( HttpStatus . CREATED . value ())
. message ( "Proposal submitted successfully and is pending approval" )
. data (createdProposal)
. build ();
return ResponseEntity . status ( HttpStatus . CREATED ). body (response);
}
The proposalJson must be valid JSON. Use JSON.stringify() when sending from JavaScript.
Example Request
curl -X POST https://api.ems.edu/api/proposals \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F 'proposal={"title":"Tech Workshop","description":"Learn React","proposedDate":"2024-04-15","startTime":"14:00:00","endTime":"16:00:00","venue":"Room 301","capacity":50,"organizationType":"ACADEMIC"}' \
-F '[email protected] ' \
-F '[email protected] '
Response Example
{
"statusCode" : 201 ,
"message" : "Proposal submitted successfully and is pending approval" ,
"data" : {
"proposalID" : 123 ,
"title" : "Tech Workshop" ,
"description" : "Learn React" ,
"proposedDate" : "2024-04-15" ,
"startTime" : "14:00:00" ,
"endTime" : "16:00:00" ,
"venue" : "Room 301" ,
"capacity" : 50 ,
"organizationType" : "ACADEMIC" ,
"status" : "PENDING" ,
"submittedAt" : "2024-03-15T10:30:00" ,
"organizerID" : 456 ,
"organizerName" : "Computer Science Club" ,
"attachmentsJson" : "[ \" proposal-doc.pdf \" , \" budget.xlsx \" ]"
},
"timestamp" : "2024-03-15T10:30:00"
}
Get My Proposals
Endpoint
Authorization
ProposalController.java:31-32
@ GetMapping
@ PreAuthorize ( "hasRole('ORGANIZER')" )
Implementation
ProposalController.java:33-46
public ResponseEntity < Response < List < ProposalDTO >>> getMyProposals (
@ AuthenticationPrincipal CustomUserDetails userDetails) {
Long organizerID = userDetails . getUser (). getUserID ();
List < ProposalDTO > proposals = proposalService . getProposalsByOrganizer (organizerID);
Response < List < ProposalDTO >> response = Response. < List < ProposalDTO >> builder ()
. statusCode ( HttpStatus . OK . value ())
. message ( "Proposals fetched successfully" )
. data (proposals)
. build ();
return ResponseEntity . ok (response);
}
This endpoint automatically filters proposals to only show those created by the authenticated organizer.
Response Example
{
"statusCode" : 200 ,
"message" : "Proposals fetched successfully" ,
"data" : [
{
"proposalID" : 123 ,
"title" : "Tech Workshop" ,
"status" : "PENDING" ,
"submittedAt" : "2024-03-15T10:30:00"
},
{
"proposalID" : 122 ,
"title" : "Career Fair" ,
"status" : "APPROVED" ,
"submittedAt" : "2024-03-10T09:00:00" ,
"reviewedAt" : "2024-03-12T14:30:00"
},
{
"proposalID" : 121 ,
"title" : "Networking Event" ,
"status" : "REJECTED" ,
"submittedAt" : "2024-03-05T11:00:00" ,
"reviewedAt" : "2024-03-07T16:00:00" ,
"rejectionReason" : "Venue not available on proposed date"
}
],
"timestamp" : "2024-03-15T10:35:00"
}
Resubmit Proposal
Endpoint
PUT / api / proposals / {proposalID} / resubmit
Content - Type : multipart / form - data
Authorization
ProposalController.java:74-75
@ PutMapping ( value = "/{proposalID}/resubmit" , consumes = { "multipart/form-data" })
@ PreAuthorize ( "hasRole('ORGANIZER')" )
Use Case
After a proposal is rejected, the organizer can update the details and resubmit:
Review Rejection Reason
Read the rejectionReason from the proposal
Update Proposal
Modify the proposal details to address the concerns
Resubmit
Send the updated proposal with new or additional files
Implementation
ProposalController.java:76-93
public ResponseEntity < Response < ProposalDTO >> resubmit (
@ PathVariable Long proposalID,
@ RequestPart ( "proposal" ) String proposalJson,
@ RequestPart ( value = "files" , required = false ) MultipartFile [] files,
@ AuthenticationPrincipal CustomUserDetails userDetails) throws Exception {
Long organizerID = userDetails . getUser (). getUserID ();
ProposalDTO dto = objectMapper . readValue (proposalJson, ProposalDTO . class );
ProposalDTO updated = proposalService . updateAndResubmit (proposalID, dto, files, organizerID);
Response < ProposalDTO > response = Response. < ProposalDTO > builder ()
. statusCode ( HttpStatus . OK . value ())
. message ( "Proposal resubmitted successfully" )
. data (updated)
. build ();
return ResponseEntity . ok (response);
}
Example Request
curl -X PUT https://api.ems.edu/api/proposals/123/resubmit \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F 'proposal={"title":"Tech Workshop - Updated","proposedDate":"2024-05-15","venue":"Room 401"}' \
-F '[email protected] '
Response Example
{
"statusCode" : 200 ,
"message" : "Proposal resubmitted successfully" ,
"data" : {
"proposalID" : 123 ,
"title" : "Tech Workshop - Updated" ,
"status" : "PENDING" ,
"submittedAt" : "2024-03-15T14:00:00" ,
"rejectionReason" : null
},
"timestamp" : "2024-03-15T14:00:00"
}
Resubmitting a proposal resets its status to PENDING and clears the previous rejectionReason.
Proposal Statuses
PENDING
APPROVED
REJECTED
Description: Proposal is awaiting admin reviewActions Available:
Admin can approve or reject
Organizer can view details
Next States:
APPROVED (admin approves)
REJECTED (admin rejects)
Description: Proposal has been approved and event is createdActions Available:
Event becomes visible to students
Organizer can manage the event
Next States: Description: Proposal was rejected with a reasonActions Available:
Organizer can resubmit with changes
Organizer can view rejection reason
Next States:
PENDING (organizer resubmits)
Document Attachments
File Handling
Proposals support multiple file attachments:
@ RequestPart ( value = "files" , required = false ) MultipartFile [] files
Optional Files Files are optional - proposals can be submitted without attachments
Multiple Files Multiple files can be attached in a single request
Storage File paths are stored as JSON in the attachmentsJson field
Retrieval Files can be retrieved through the file service
Supported File Types
Always validate file types and sizes on the server to prevent security vulnerabilities.
Common file types for proposals:
PDF documents (.pdf)
Word documents (.doc, .docx)
Excel spreadsheets (.xls, .xlsx)
Images (.jpg, .png)
Presentations (.ppt, .pptx)
Validation Rules
Field Validation
@ NotBlank ( message = "Title is required" )
private String title ;
@ NotNull ( message = "Proposed date is required" )
@ Future ( message = "Proposed date must be in the future" )
private LocalDate proposedDate ;
@ NotNull ( message = "Start time is required" )
private LocalTime startTime ;
@ NotNull ( message = "End time is required" )
private LocalTime endTime ;
@ NotBlank ( message = "Venue is required" )
private String venue ;
@ NotNull ( message = "Capacity is required" )
@ Min ( value = 1 , message = "Capacity must be at least 1" )
private Integer capacity ;
@ NotNull ( message = "Organization type is required" )
private OrganizationType organizationType ;
Manual Validation
The controller performs manual validation before processing:
ProposalController.java:57-60
var violations = validator . validate (proposalDTO);
if ( ! violations . isEmpty ()) {
throw new ConstraintViolationException (violations);
}
Validation errors are automatically caught by the global exception handler and returned as structured error responses.
The Wrapper Rule
All API responses follow the Wrapper Rule: ResponseEntity<Response<T>>
Response Structure
@ Data
@ Builder
@ NoArgsConstructor
@ AllArgsConstructor
public class Response < T > {
private int statusCode ;
private String message ;
private T data ;
@ Builder . Default
private LocalDateTime timestamp = LocalDateTime . now ();
private List < String > errors ;
}
Example Responses
{
"statusCode" : 201 ,
"message" : "Proposal submitted successfully" ,
"data" : { /* ProposalDTO */ },
"timestamp" : "2024-03-15T10:30:00" ,
"errors" : null
}
{
"statusCode" : 400 ,
"message" : "Validation failed" ,
"data" : null ,
"timestamp" : "2024-03-15T10:30:00" ,
"errors" : [
"Title is required" ,
"Proposed date must be in the future"
]
}
{
"statusCode" : 403 ,
"message" : "Access Denied" ,
"data" : null ,
"timestamp" : "2024-03-15T10:30:00" ,
"errors" : null
}
Admin Review Process
For admin-side endpoints (approve/reject), see the Admin Operations documentation.
Admins can:
View all pending proposals
Filter by department
Approve proposals (creates events)
Reject proposals with reasons
View approval history
Best Practices
Validate Before Upload
Validate proposal data on the client side before sending files to reduce server load.
Handle File Size Limits
Implement client-side file size checks to prevent upload failures.
Provide Clear Rejection Reasons
When rejecting proposals, provide specific, actionable feedback.
Check Status Before Actions
Verify proposal status before attempting to resubmit or modify.
Error Handling
{
"statusCode" : 404 ,
"message" : "Proposal not found with ID: 123" ,
"timestamp" : "2024-03-15T10:30:00"
}
{
"statusCode" : 403 ,
"message" : "You can only access your own proposals" ,
"timestamp" : "2024-03-15T10:30:00"
}
Invalid Status Transition
{
"statusCode" : 400 ,
"message" : "Cannot resubmit an approved proposal" ,
"timestamp" : "2024-03-15T10:30:00"
}
{
"statusCode" : 500 ,
"message" : "Failed to upload file: document.pdf" ,
"timestamp" : "2024-03-15T10:30:00"
}
Events Learn about event management after approval
Roles & Permissions Understand organizer permissions