From 4f50e26fdbd58090576812496f3521ba992c23fd Mon Sep 17 00:00:00 2001 From: rxmox Date: Mon, 2 Mar 2026 12:01:41 -0700 Subject: [PATCH 1/2] Add gameType, eventImg fields and status transition API to Event model - Add gameType (enum: 'Name Bingo', required) and eventImg (optional) to Event schema - Change currentState from free-form string to validated enum ('Upcoming', 'In Progress', 'Completed') with default 'Upcoming' - Add PUT /api/events/:eventId/status endpoint for host-only status transitions - Emit Pusher event-started/event-ended events on status changes - Update API reference, database schema, event lifecycle, and real-time events docs --- shatter-backend/docs/API_REFERENCE.md | 74 +++++++- shatter-backend/docs/DATABASE_SCHEMA.md | 30 +-- shatter-backend/docs/EVENT_LIFECYCLE.md | 176 ++++++++++-------- shatter-backend/docs/REALTIME_EVENTS_GUIDE.md | 44 +++-- .../src/controllers/event_controller.ts | 71 +++++++ shatter-backend/src/models/event_model.ts | 15 +- shatter-backend/src/routes/event_routes.ts | 3 +- 7 files changed, 293 insertions(+), 120 deletions(-) diff --git a/shatter-backend/docs/API_REFERENCE.md b/shatter-backend/docs/API_REFERENCE.md index a28569b..b1a11a4 100644 --- a/shatter-backend/docs/API_REFERENCE.md +++ b/shatter-backend/docs/API_REFERENCE.md @@ -26,6 +26,7 @@ - [POST /api/events/createEvent](#post-apieventscreateevent) - [GET /api/events/event/:joinCode](#get-apieventseventjoincode) - [GET /api/events/:eventId](#get-apieventseventid) + - [PUT /api/events/:eventId/status](#put-apieventseventidstatus) - [POST /api/events/:eventId/join/user](#post-apieventseventиdjoinuser) - [POST /api/events/:eventId/join/guest](#post-apieventseventиdjoinguest) - [GET /api/events/createdEvents/user/:userId](#get-apieventscreatedeventsuseriduserid) @@ -64,6 +65,7 @@ Quick reference of all implemented endpoints. See detailed sections below for re | POST | `/api/events/createEvent` | Protected | Create a new event | | GET | `/api/events/event/:joinCode` | Public | Get event by join code | | GET | `/api/events/:eventId` | Public | Get event by ID | +| PUT | `/api/events/:eventId/status` | Protected | Update event lifecycle status (host-only) | | POST | `/api/events/:eventId/join/user` | Protected | Join event as authenticated user | | POST | `/api/events/:eventId/join/guest` | Public | Join event as guest | | GET | `/api/events/createdEvents/user/:userId` | Protected | Get events created by user | @@ -416,7 +418,7 @@ Get all events a user has joined (populates event details). "joinCode": "12345678", "startDate": "2025-02-01T18:00:00.000Z", "endDate": "2025-02-01T21:00:00.000Z", - "currentState": "active" + "currentState": "In Progress" } ] } @@ -492,10 +494,12 @@ Create a new event. |------------------|--------|----------|-------| | `name` | string | Yes | | | `description` | string | Yes | Required by schema | +| `gameType` | string | Yes | Must be `"Name Bingo"` | | `startDate` | string | Yes | ISO 8601 date | | `endDate` | string | Yes | Must be after `startDate` | | `maxParticipant` | number | Yes | | -| `currentState` | string | Yes | Free-form string (no enum) | +| `currentState` | string | No | One of: `"Upcoming"`, `"In Progress"`, `"Completed"`. Defaults to `"Upcoming"` | +| `eventImg` | string | No | URL for event image | **Success Response (201):** @@ -506,12 +510,14 @@ Create a new event. "_id": "665a...", "name": "Tech Meetup", "description": "Monthly networking event", + "gameType": "Name Bingo", "joinCode": "48291037", "startDate": "2025-02-01T18:00:00.000Z", "endDate": "2025-02-01T21:00:00.000Z", "maxParticipant": 50, "participantIds": [], - "currentState": "pending", + "currentState": "Upcoming", + "eventImg": "https://example.com/event-image.jpg", "createdBy": "664f...", "createdAt": "2025-01-20T12:00:00.000Z", "updatedAt": "2025-01-20T12:00:00.000Z" @@ -555,7 +561,8 @@ Get event details by join code. "participantIds": [ { "_id": "666b...", "name": "John Doe", "userId": "664f..." } ], - "currentState": "active", + "currentState": "Upcoming", + "gameType": "Name Bingo", "createdBy": "664f...", ... } @@ -610,6 +617,60 @@ Get event details by event ID. --- +### PUT `/api/events/:eventId/status` + +Update an event's lifecycle status. Only the event host (creator) can change the status. + +- **Auth:** Protected (host-only — `event.createdBy` must match authenticated user) + +**URL Params:** + +| Param | Type | Required | +|-----------|----------|----------| +| `eventId` | ObjectId | Yes | + +**Request Body:** + +| Field | Type | Required | Notes | +|----------|--------|----------|-------| +| `status` | string | Yes | Target status. One of: `"In Progress"`, `"Completed"` | + +**Valid Transitions:** + +| From | To | +|---------------|----------------| +| `Upcoming` | `In Progress` | +| `In Progress` | `Completed` | + +**Success Response (200):** + +```json +{ + "success": true, + "event": { + "_id": "665a...", + "name": "Tech Meetup", + "currentState": "In Progress", + ... + } +} +``` + +**Error Responses:** + +| Status | Error | +|--------|-------| +| 400 | `"Status is required"` | +| 400 | `"Invalid status transition from to "` | +| 403 | `"Only the event host can update the event status"` | +| 404 | `"Event not found"` | + +**Side Effects:** +- **Upcoming → In Progress:** Emits Pusher event `event-started` on channel `event-{eventId}` with payload `{ status: 'In Progress' }` +- **In Progress → Completed:** Emits Pusher event `event-ended` on channel `event-{eventId}` with payload `{ status: 'Completed' }` + +--- + ### POST `/api/events/:eventId/join/user` Join an event as a registered (authenticated) user. @@ -1064,7 +1125,6 @@ These endpoints are **not yet implemented**. Do not depend on them. | Method | Endpoint | Description | |--------|----------|-------------| -| PUT | `/api/events/:eventId/status` | Update event lifecycle state (host-only) | | POST | `/api/events/:eventId/leave` | Leave an event | | DELETE | `/api/events/:eventId` | Cancel/delete an event | | GET | `/api/events/:eventId/participants` | Search/list participants | @@ -1110,10 +1170,10 @@ curl -X POST http://localhost:4000/api/events/createEvent \ -d '{ "name": "Tech Meetup", "description": "Monthly networking event", + "gameType": "Name Bingo", "startDate": "2025-02-01T18:00:00.000Z", "endDate": "2025-02-01T21:00:00.000Z", - "maxParticipant": 50, - "currentState": "pending" + "maxParticipant": 50 }' ``` diff --git a/shatter-backend/docs/DATABASE_SCHEMA.md b/shatter-backend/docs/DATABASE_SCHEMA.md index 30c2c7a..97b840b 100644 --- a/shatter-backend/docs/DATABASE_SCHEMA.md +++ b/shatter-backend/docs/DATABASE_SCHEMA.md @@ -102,20 +102,22 @@ ### Fields -| Field | Type | Required | Default | Notes | -|------------------|------------|----------|---------|-------| -| `_id` | ObjectId | Auto | Auto | | -| `name` | String | Yes | — | | -| `description` | String | Yes | — | | -| `joinCode` | String | Yes | — | Unique, auto-generated 8-digit number | -| `startDate` | Date | Yes | — | | -| `endDate` | Date | Yes | — | Must be after `startDate` | -| `maxParticipant` | Number | Yes | — | | -| `participantIds` | [ObjectId] | No | `[]` | Refs `Participant` | -| `currentState` | String | Yes | — | Free-form string (no enum validation) | -| `createdBy` | ObjectId | Yes | — | User who created the event (no ref set) | -| `createdAt` | Date | Auto | Auto | Mongoose timestamps | -| `updatedAt` | Date | Auto | Auto | Mongoose timestamps | +| Field | Type | Required | Default | Notes | +|------------------|---------------|----------|--------------|-------| +| `_id` | ObjectId | Auto | Auto | | +| `name` | String | Yes | — | | +| `description` | String | Yes | — | | +| `joinCode` | String | Yes | — | Unique, auto-generated 8-digit number | +| `gameType` | String (enum) | Yes | — | One of: `'Name Bingo'` | +| `eventImg` | String | No | — | URL for event image | +| `startDate` | Date | Yes | — | | +| `endDate` | Date | Yes | — | Must be after `startDate` | +| `maxParticipant` | Number | Yes | — | | +| `participantIds` | [ObjectId] | No | `[]` | Refs `Participant` | +| `currentState` | String (enum) | Yes | `'Upcoming'` | One of: `'Upcoming'`, `'In Progress'`, `'Completed'` | +| `createdBy` | ObjectId | Yes | — | User who created the event (no ref set) | +| `createdAt` | Date | Auto | Auto | Mongoose timestamps | +| `updatedAt` | Date | Auto | Auto | Mongoose timestamps | ### Indexes diff --git a/shatter-backend/docs/EVENT_LIFECYCLE.md b/shatter-backend/docs/EVENT_LIFECYCLE.md index 438661a..f4164ae 100644 --- a/shatter-backend/docs/EVENT_LIFECYCLE.md +++ b/shatter-backend/docs/EVENT_LIFECYCLE.md @@ -6,112 +6,139 @@ ## Table of Contents -- [Current State](#current-state-) - - [How `currentState` Works Today](#how-currentstate-works-today) - - [Current Event Endpoints and State](#current-event-endpoints-and-state) -- [Planned State Machine](#planned-state-machine-) - - [States](#states) - - [Transition Rules](#transition-rules) - - [Planned Endpoint](#planned-endpoint-put-apieventseventidstatus) +- [Event States](#event-states) + - [State Enum](#state-enum) + - [State Diagram](#state-diagram) +- [Transition Rules](#transition-rules) + - [Valid Transitions](#valid-transitions) + - [Transition Endpoint](#transition-endpoint-put-apieventseventidstatus) - [Side Effects Per Transition](#side-effects-per-transition) +- [Current Event Endpoints and State](#current-event-endpoints-and-state) - [What's Allowed in Each State (Planned)](#whats-allowed-in-each-state-planned) - [Frontend Integration Notes](#frontend-integration-notes) - [UI State Mapping](#ui-state-mapping) - [Subscribing to State Changes](#subscribing-to-state-changes) - - [Polling Fallback](#polling-fallback-current-workaround) --- -## Current State ✅ +## Event States -### How `currentState` Works Today +### State Enum -The `currentState` field on the Event model is a **free-form string** with no enum, no validation, and no transition enforcement. +The `currentState` field on the Event model is a **validated enum** with three possible values: ```js // event_model.ts -currentState: { type: String, required: true } +currentState: { + type: String, + enum: ['Upcoming', 'In Progress', 'Completed'], + default: 'Upcoming', + required: true +} ``` -- Any string value is accepted when creating an event -- There is no endpoint to update the state after creation -- No logic gates behavior based on state (joining, games, etc. work regardless of state value) -- The backend does not enforce any state machine — the frontend can pass whatever string it wants +| State | Description | +|---------------|-------------| +| `Upcoming` | Event created, waiting for host to start. Participants can join. | +| `In Progress` | Event is live. Games/activities are in progress. Participants can still join (unless at capacity). | +| `Completed` | Event is over. No new joins. Results are finalized. | -### Current Event Endpoints and State +These values match the mobile app's `EventState` enum exactly (title case with spaces). -| Endpoint | State Behavior | -|----------|---------------| -| `POST /api/events/createEvent` | Sets `currentState` from request body (any string) | -| `GET /api/events/:eventId` | Returns `currentState` as-is | -| `GET /api/events/event/:joinCode` | Returns `currentState` as-is | -| `POST /api/events/:eventId/join/user` | Does **not** check `currentState` | -| `POST /api/events/:eventId/join/guest` | Does **not** check `currentState` | +### State Diagram -**In practice**, frontends have been passing values like `"pending"`, `"active"`, or similar, but the backend does not enforce these. +``` +Upcoming ──► In Progress ──► Completed +``` ---- +- Only forward transitions are allowed +- There is no way to revert a state (e.g., `Completed` cannot go back to `In Progress`) +- Events are created with `currentState: 'Upcoming'` by default -## Planned State Machine ⏳ +--- -> **This section describes planned functionality that is NOT yet implemented.** +## Transition Rules -### States +### Valid Transitions -``` -pending ──► active ──► ended -``` +| From | To | Who Can Trigger | Endpoint | +|---------------|----------------|--------------------|----------| +| `Upcoming` | `In Progress` | Event creator only | `PUT /api/events/:eventId/status` | +| `In Progress` | `Completed` | Event creator only | `PUT /api/events/:eventId/status` | -| State | Description | -|-----------|-------------| -| `pending` | Event created, waiting for host to start. Participants can join. | -| `active` | Event is live. Games/activities are in progress. Participants can still join (unless at capacity). | -| `ended` | Event is over. No new joins. Results are finalized. | +Invalid transitions (e.g., `Completed` → `In Progress`, `Upcoming` → `Completed`) are rejected with a `400` error. -### Transition Rules +### Transition Endpoint: `PUT /api/events/:eventId/status` -| From | To | Who Can Trigger | Endpoint | -|-----------|----------|-----------------|----------| -| `pending` | `active` | Event creator only | `PUT /api/events/:eventId/status` | -| `active` | `ended` | Event creator only | `PUT /api/events/:eventId/status` | +**Auth:** Protected (event creator only — `event.createdBy === req.user.userId`) -Invalid transitions (e.g., `ended` → `active`, `pending` → `ended`) will be rejected. +**Request Body:** -### Planned Endpoint: `PUT /api/events/:eventId/status` +```json +{ + "status": "In Progress" +} +``` -**Auth:** Protected (event creator only) +**Validation:** +- Only the user in `createdBy` can change the state (403 for non-host) +- Only valid transitions are allowed (400 for invalid transitions) +- The `status` field is required (400 if missing) -**Request Body:** +**Success Response (200):** ```json { - "status": "active" + "success": true, + "event": { + "_id": "665a...", + "name": "Tech Meetup", + "currentState": "In Progress", + ... + } } ``` -**Validation:** -- Only the user in `createdBy` can change the state -- Only valid transitions are allowed (`pending` → `active`, `active` → `ended`) +**Error Responses:** + +| Status | Error | +|--------|-------| +| 400 | `"Status is required"` | +| 400 | `"Invalid status transition from to "` | +| 403 | `"Only the event host can update the event status"` | +| 404 | `"Event not found"` | ### Side Effects Per Transition -#### `pending` → `active` -- Trigger Pusher event `event-started` on channel `event-{eventId}` -- Payload: `{ eventId, state: "active", startedAt: }` +#### `Upcoming` → `In Progress` +- Triggers Pusher event `event-started` on channel `event-{eventId}` +- Payload: `{ status: 'In Progress' }` - Frontend should transition from lobby/waiting UI to active game UI -#### `active` → `ended` -- Trigger Pusher event `event-ended` on channel `event-{eventId}` -- Payload: `{ eventId, state: "ended", endedAt: }` -- Lock bingo state (no more updates to player grids) +#### `In Progress` → `Completed` +- Triggers Pusher event `event-ended` on channel `event-{eventId}` +- Payload: `{ status: 'Completed' }` - Frontend should show results/summary screen --- +## Current Event Endpoints and State + +| Endpoint | State Behavior | +|----------|---------------| +| `POST /api/events/createEvent` | Sets `currentState` to `'Upcoming'` by default (can be overridden with a valid enum value) | +| `GET /api/events/:eventId` | Returns `currentState` as-is | +| `GET /api/events/event/:joinCode` | Returns `currentState` as-is | +| `PUT /api/events/:eventId/status` | Validates and transitions `currentState` (host-only) | +| `POST /api/events/:eventId/join/user` | Does **not** check `currentState` | +| `POST /api/events/:eventId/join/guest` | Does **not** check `currentState` | + +--- + ## What's Allowed in Each State (Planned) -| Action | `pending` | `active` | `ended` | -|--------|-----------|----------|---------| +| Action | `Upcoming` | `In Progress` | `Completed` | +|--------|-----------|----------------|-------------| | Join event | Yes | Yes (if not full) | No | | Leave event | Yes | Yes | No | | View participants | Yes | Yes | Yes | @@ -128,41 +155,26 @@ Invalid transitions (e.g., `ended` → `active`, `pending` → `ended`) will be | `currentState` | Suggested UI | |----------------|-------------| -| `pending` | Lobby / waiting room. Show participant list, join code, QR code. "Waiting for host to start..." | -| `active` | Game screen. Show bingo grid, active activities, connection creation. | -| `ended` | Results screen. Show final scores, connections made, event summary. | +| `Upcoming` | Lobby / waiting room. Show participant list, join code, QR code. "Waiting for host to start..." | +| `In Progress` | Game screen. Show bingo grid, active activities, connection creation. | +| `Completed` | Results screen. Show final scores, connections made, event summary. | ### Subscribing to State Changes -Once the state transition endpoint and Pusher events are implemented, subscribe to state changes: +Subscribe to Pusher events on the event channel to react to state transitions in real time: ```js const channel = pusher.subscribe(`event-${eventId}`); channel.bind('event-started', (data) => { + // data.status === 'In Progress' // Transition UI from lobby to active game - setEventState('active'); + setEventState('In Progress'); }); channel.bind('event-ended', (data) => { + // data.status === 'Completed' // Transition UI from active game to results - setEventState('ended'); + setEventState('Completed'); }); ``` - -### Polling Fallback (Current Workaround) - -Since state change events are not yet implemented, frontends can poll the event endpoint: - -```js -// Poll every 5 seconds for state changes -const interval = setInterval(async () => { - const res = await fetch(`/api/events/${eventId}`); - const { event } = await res.json(); - if (event.currentState !== currentState) { - setCurrentState(event.currentState); - } -}, 5000); -``` - -This is a temporary approach and should be replaced with Pusher events once implemented. diff --git a/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md b/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md index 7b5f074..987aeea 100644 --- a/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md +++ b/shatter-backend/docs/REALTIME_EVENTS_GUIDE.md @@ -11,9 +11,9 @@ - [Channel Naming Convention](#channel-naming-convention) - [Implemented Events](#implemented-events-) - [`participant-joined`](#participant-joined) -- [Planned Events](#planned-events-) - [`event-started`](#event-started) - [`event-ended`](#event-ended) +- [Planned Events](#planned-events-) - [`bingo-achieved`](#bingo-achieved) - [Client Integration Examples](#client-integration-examples) - [React (Web Dashboard)](#react-web-dashboard) @@ -112,42 +112,56 @@ Each event has its own channel. Subscribe when a user enters an event, unsubscri --- -## Planned Events ⏳ - -These events are **not yet implemented**. Do not depend on them. - ### `event-started` **Channel:** `event-{eventId}` -Triggered when the host starts the event (transitions state to `active`). +**Triggered when:** +- The event host transitions the event status from `Upcoming` to `In Progress` (`PUT /api/events/:eventId/status`) -**Expected payload:** +**Payload:** ```json { - "eventId": "665a...", - "state": "active", - "startedAt": "2025-02-01T18:00:00.000Z" + "status": "In Progress" } ``` +| Field | Type | Description | +|----------|--------|-------------| +| `status` | string | The new event status (`"In Progress"`) | + +**Use case:** Transition the UI from the lobby/waiting room to the active game screen. + +--- + ### `event-ended` **Channel:** `event-{eventId}` -Triggered when the host ends the event (transitions state to `ended`). +**Triggered when:** +- The event host transitions the event status from `In Progress` to `Completed` (`PUT /api/events/:eventId/status`) -**Expected payload:** +**Payload:** ```json { - "eventId": "665a...", - "state": "ended", - "endedAt": "2025-02-01T21:00:00.000Z" + "status": "Completed" } ``` +| Field | Type | Description | +|----------|--------|-------------| +| `status` | string | The new event status (`"Completed"`) | + +**Use case:** Transition the UI from the active game screen to the results/summary screen. + +--- + +## Planned Events ⏳ + +These events are **not yet implemented**. Do not depend on them. + ### `bingo-achieved` **Channel:** `event-{eventId}` diff --git a/shatter-backend/src/controllers/event_controller.ts b/shatter-backend/src/controllers/event_controller.ts index f884d3a..ad29ad6 100644 --- a/shatter-backend/src/controllers/event_controller.ts +++ b/shatter-backend/src/controllers/event_controller.ts @@ -35,6 +35,8 @@ export async function createEvent(req: Request, res: Response) { endDate, maxParticipant, currentState, + gameType, + eventImg, } = req.body; const createdBy = req.user!.userId; @@ -64,6 +66,8 @@ export async function createEvent(req: Request, res: Response) { maxParticipant, participantIds: [], currentState, + gameType, + eventImg, createdBy, // user id }); @@ -355,6 +359,73 @@ export async function getEventById(req: Request, res: Response) { * @returns 400 if userId is missing * @returns 404 if no events are found for the user */ +/** + * PUT /api/events/:eventId/status + * Update event status (host only) + * + * @param req.params.eventId - Event ID (required) + * @param req.body.status - New status: "In Progress" or "Completed" (required) + * @param req.user.userId - Authenticated user ID (from access token) + * + * @returns 200 with updated event on success + * @returns 400 if status is invalid or transition is not allowed + * @returns 403 if user is not the event host + * @returns 404 if event is not found + */ +export async function updateEventStatus(req: Request, res: Response) { + try { + const { eventId } = req.params; + const { status } = req.body; + + const validStatuses = ['In Progress', 'Completed']; + if (!status || !validStatuses.includes(status)) { + return res.status(400).json({ + success: false, + error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`, + }); + } + + const event = await Event.findById(eventId); + if (!event) { + return res.status(404).json({ success: false, error: "Event not found" }); + } + + // Only the host can change event status + if (event.createdBy.toString() !== req.user!.userId) { + return res.status(403).json({ + success: false, + error: "Only the event host can update the event status", + }); + } + + // Validate allowed transitions + const allowedTransitions: Record = { + 'Upcoming': 'In Progress', + 'In Progress': 'Completed', + }; + + if (allowedTransitions[event.currentState] !== status) { + return res.status(400).json({ + success: false, + error: `Cannot transition from "${event.currentState}" to "${status}"`, + }); + } + + event.currentState = status; + const updatedEvent = await event.save(); + + // Emit Pusher events for real-time updates + const pusherEvent = status === 'In Progress' ? 'event-started' : 'event-ended'; + await pusher.trigger(`event-${eventId}`, pusherEvent, { + status, + }); + + return res.status(200).json({ success: true, event: updatedEvent }); + } catch (err: any) { + return res.status(500).json({ success: false, error: err.message }); + } +} + export async function getEventsByUserId(req: Request, res: Response) { try { const { userId } = req.params; diff --git a/shatter-backend/src/models/event_model.ts b/shatter-backend/src/models/event_model.ts index 04dc55f..28aec64 100644 --- a/shatter-backend/src/models/event_model.ts +++ b/shatter-backend/src/models/event_model.ts @@ -12,6 +12,8 @@ export interface IEvent extends Document { maxParticipant: number; participantIds: Schema.Types.ObjectId[]; currentState: string; + gameType: string; + eventImg?: string; createdBy: Schema.Types.ObjectId; } @@ -24,7 +26,18 @@ const EventSchema = new Schema( endDate: { type: Date, required: true }, maxParticipant: { type: Number, required: true }, participantIds: [{ type: Schema.Types.ObjectId, ref: "Participant" }], - currentState: { type: String, required: true }, + currentState: { + type: String, + enum: ['Upcoming', 'In Progress', 'Completed'], + default: 'Upcoming', + required: true, + }, + gameType: { + type: String, + enum: ['Name Bingo'], + required: true, + }, + eventImg: { type: String, required: false }, createdBy: { type: Schema.Types.ObjectId, required: true, diff --git a/shatter-backend/src/routes/event_routes.ts b/shatter-backend/src/routes/event_routes.ts index cf03550..03e2bf9 100644 --- a/shatter-backend/src/routes/event_routes.ts +++ b/shatter-backend/src/routes/event_routes.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -import { createEvent, getEventByJoinCode, getEventById, joinEventAsUser, joinEventAsGuest, getEventsByUserId } from '../controllers/event_controller'; +import { createEvent, getEventByJoinCode, getEventById, joinEventAsUser, joinEventAsGuest, getEventsByUserId, updateEventStatus } from '../controllers/event_controller'; import { authMiddleware } from '../middleware/auth_middleware'; const router = Router(); @@ -7,6 +7,7 @@ const router = Router(); router.post("/createEvent", authMiddleware, createEvent); router.get("/event/:joinCode", getEventByJoinCode); +router.put("/:eventId/status", authMiddleware, updateEventStatus); router.get("/:eventId", getEventById); router.post("/:eventId/join/user", authMiddleware, joinEventAsUser); router.post("/:eventId/join/guest", joinEventAsGuest); From 15b36309ab1e88df3c25dd6b24135fb1e471049f Mon Sep 17 00:00:00 2001 From: rxmox Date: Mon, 2 Mar 2026 12:16:42 -0700 Subject: [PATCH 2/2] Return 400 for Mongoose validation errors in createEvent The catch block was returning 500 for all errors, including Mongoose ValidationError (e.g., missing or invalid gameType). Now checks for ValidationError by name and returns 400 instead. --- shatter-backend/src/controllers/event_controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shatter-backend/src/controllers/event_controller.ts b/shatter-backend/src/controllers/event_controller.ts index ad29ad6..5466b73 100644 --- a/shatter-backend/src/controllers/event_controller.ts +++ b/shatter-backend/src/controllers/event_controller.ts @@ -75,6 +75,9 @@ export async function createEvent(req: Request, res: Response) { res.status(201).json({ success: true, event: savedEvent }); } catch (err: any) { + if (err.name === 'ValidationError') { + return res.status(400).json({ success: false, error: err.message }); + } res.status(500).json({ success: false, error: err.message }); } }