Academic Calendar API
This document describes the Academic Calendar endpoints implemented in ILS-dev/backend/routes/academicCalendar.js. It is a read-only extraction of the current server behaviour used by admin and scheduling flows.
Overview
- Purpose: create, update, list and soft-delete academic calendar events (holidays, breaks, exam periods, semester boundaries, registration windows).
- Core model:
academic_calendar_eventswith fields includingevent_type,title,description,starts_on,ends_on,institution_id,academic_year,period_id, and soft-deletedeleted_at. - Valid
event_typevalues:holiday,exam_period,semester_start,semester_end,break,registration.
Key behaviours
- The list endpoint expands multi-day events into one entry per calendar date within the requested range. Each expanded event has an id of the form
academic-<eventId>-<date>and a type prefixed withacademic_(e.g.,academic_holiday). - Date arithmetic uses a midday anchor (
T12:00:00) to avoid DST edge cases, and the expansion loop includes a guard (max 400 iterations) to prevent runaway loops. - Create/modify/delete endpoints require an authenticated user (
req.user.id) and perform write operations againstacademic_calendar_events. - Deletion is a soft delete implemented by setting
deleted_at = now().
Endpoints
- GET /api/academic-calendar?from=&to=&institutionId=
- Parameters:
from(required, ISO date),to(required, ISO date),institutionId(optional). - Behaviour: returns expanded daily events for all matching
academic_calendar_eventsthat intersect the[from,to]range. IfinstitutionIdis provided, the query includes events whereinstitution_idmatches or is NULL (global events). -
Response: array of objects with keys:
id,type,title,date,starts_at,ends_at,all_day,entity_type,entity_id,description,institution_name,academic_year,period_id,period_name. -
POST /api/academic-calendar
- Auth: required.
- Body:
{ event_type, title, description?, starts_on, ends_on?, institution_id?, academic_year?, period_id? }. - Validation:
titleandstarts_onrequired;event_typemust be one of the valid types. -
Response:
201with the createdacademic_calendar_eventsrow. -
PATCH /api/academic-calendar/:id
- Auth: required.
- Body: any of
{ title, description, starts_on, ends_on, event_type, academic_year, period_id }. -
Behaviour: updates provided fields and sets
updated_at = now(); returns404if not found or already deleted. -
DELETE /api/academic-calendar/:id
- Auth: required.
- Behaviour: soft-deletes the event by setting
deleted_at = now(); returns204on success.
Examples
Fetch academic events for a date range:
curl "http://localhost:4000/api/academic-calendar?from=2026-08-01&to=2026-09-30"
Create a holiday event:
curl -X POST "http://localhost:4000/api/academic-calendar" \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{"event_type":"holiday","title":"Founders Day","starts_on":"2026-09-15","ends_on":"2026-09-15"}'
Edit an event:
curl -X PATCH "http://localhost:4000/api/academic-calendar/42" \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{"title":"Founders Week","ends_on":"2026-09-18"}'
Delete (soft) an event:
curl -X DELETE "http://localhost:4000/api/academic-calendar/42" \
-H "Authorization: Bearer <JWT>"
Implementation notes & cautions
- The list query filters
deleted_at IS NULLand expands multi-day rows into daily entries; consumers should treat the returned objects as calendar-day entries rather than raw DB rows. - The server-side expansion uses a
whileloop bounded by 400 iterations to prevent accidental infinite loops for malformed data. - Global academic events have
institution_id = NULLand are included when aninstitutionIdfilter is provided.
Source (read-only): ILS-dev/backend/routes/academicCalendar.js
Last updated: 2026-07-08