14 KiB
Backend (Postman) vs Frontend — fields & endpoints to decide on
Source of truth: the Postman collection covering Terms, Courses, Sessions, Exams, Homeworks, Media (2026-05). This doc lists every place where backend and FE disagree on shape, plus FE-side concepts the backend doc has no slot for. Each item needs a product/design call before we wire it up.
Conventions in the rest of this doc:
- B → FE means the backend exposes a field/endpoint that the FE doesn't surface yet.
- FE → B means the FE shows/sends a field the backend doc doesn't accept.
- shape diff means both sides handle the concept but in different shapes (enum values, nesting, naming).
Terms
Endpoints
| Backend | FE today | Status |
|---|---|---|
GET /terms?per_page&active_only |
getTermsList |
aligned |
GET /terms/:id |
showTerm |
aligned |
POST /terms |
addNewTerm |
aligned |
PATCH /terms/:id |
updateTerm |
aligned |
DELETE /terms/:id |
deleteTerm |
aligned |
| — | cloneTerm (POST /admin/terms/:id/clone) |
FE-only; backend has nothing. Decide: drop the clone button, or ask backend to add it. |
| — | changeStatusTerm (POST /admin/terms/:id/status) |
Use PATCH /terms/:id with is_active — dedicated status endpoint dropped. |
| — | listUserTerm, addUserTerm, removeUserTerm, changeLeaveStatus |
FE-only. Term-students subtab + leave toggle. Backend exposes nothing equivalent. Keep mock-only until backend adds. |
| — | listCourseTerm, addCourseTerm, removeCourseTerm |
FE-only. Can be replaced by GET /courses?term_id= for the list; the attach/detach side has no backend equivalent (course term_id is set at create-time). |
Fields
| Backend → FE | FE has it as | Notes |
|---|---|---|
starts_at, ends_at (ISO datetime) |
startDate, endDate |
Naming diff; we send starts_at/ends_at in the mock already — confirm the FE form should rename or keep an adapter. |
cover_url (read) + cover_media_id (write) |
coverUrl / coverMediaId |
aligned in shape, just camelCase. |
created_at |
createdAt |
aligned. |
is_active |
isActive |
aligned. |
description |
description |
aligned. |
FE → B (need decision):
studentsCount,coursesCount— computed UI counts. Backend response doesn't include them. Either compute client-side or ask backend for?include=counts.image(UI alias of cover) — FE keeps this for a fallback. Backend response only hascover_url.
Courses
Endpoints
| Backend | FE today | Status |
|---|---|---|
GET /courses?term_id&per_page |
getCoursesList |
aligned (drop /admin/ prefix in endpoints.js). |
GET /courses/:id |
showCourse |
aligned. Backend response includes nested term and teacher — FE already reads course.term / course.teacher, good. |
POST /courses |
addNewCourse |
aligned. Backend currently requires term_id (422 example), but per user instruction the standalone (template-tab) flow must allow term_id: null. Decide: ask backend to make term_id nullable, or refuse to submit until a term is picked. |
PATCH /courses/:id |
updateCourse |
aligned. Used now for status toggle too (drops dedicated /status endpoint). |
DELETE /courses/:id |
deleteCourse |
aligned. |
| — | changeStatusCourse (/admin/courses/:id/status) |
Dropped. Use PATCH /courses/:id { is_active }. |
| — | listCourseStudents, addCourseStudent, removeCourseStudent |
FE-only. CourseDetailsModal "students" tab + AddCourseStudentModal. Backend doesn't expose course-students; either add a sub-resource or remove the UI. Kept mock-only for now. |
| — | listCourseSessions, attachCourseSession, detachCourseSession |
FE-only. CourseDetailsModal "sessions" tab can be served by GET /sessions?course_id=. The attach/detach side (M:N) has no backend equivalent — backend uses session.course_id (1:N). Kept mock-only for now; recommend swapping the list tab to the regular sessions query. |
Fields
| Backend ↔ FE | Notes |
|---|---|
term_id ⇄ termId |
aligned. Nullable in CourseFormPage, required in AddOfferedCourseModal — schema reflects this. |
teacher_id ⇄ teacherId |
aligned. |
capacity ⇄ capacity |
aligned. |
is_active ⇄ isActive |
aligned. |
cover_media_id (write) / cover_url (read) ⇄ coverMediaId / coverUrl |
aligned. |
description ⇄ description |
aligned. |
Nested term, teacher on show response |
FE already reads. |
FE → B (need decision):
sessionsCount— number-of-sessions field on the create form. Backend has nothing. Either compute server-side from related sessions, or drop the field.prerequisites(array of { courseId, course }) — backend has no prerequisite relation. Drop or ask backend for it.contentType(video/voice/text) +contentMediaId— single course-level content file. Backend treats files only as session media. Decide whether course-level content should move to "intro session" or stay a course concept.image(UI alias of cover_url) — kept as a fallback alongsidecoverUrl.
Sessions
Endpoints
| Backend | FE today | Status |
|---|---|---|
GET /sessions?course_id&per_page |
getSessionsList |
aligned. |
GET /sessions/:id |
showSession |
aligned. |
POST /sessions |
addNewSession |
aligned. |
PATCH /sessions/:id |
updateSession |
aligned. Used for status toggle now. |
DELETE /sessions/:id |
deleteSession |
aligned. |
| — | changeStatusSession (/admin/sessions/:id/toggle-status) |
Dropped. Use PATCH with is_active. |
| — | getSessionsAttendance (/admin/sessions/:sessionId/attendances) |
FE-only. SessionAttendanceModal depends on this. Kept mock-only. |
Fields — biggest gap of all five resources
| Backend | FE today | Notes |
|---|---|---|
type enum: online | offline | content |
sessionType enum: in_person, online, video, audio, text, slide, pdf |
Shape diff. FE has 7 values; backend has 3. There's an existing SESSION_TYPE_TO_SPEC mapper in services/mock/fixtures/admin-sessions.js. Decide whether the FE keeps the richer 7-value enum (and we map down to backend's 3) or collapses. |
starts_at, location, link |
All three live inside form.sessionConfig.* plus also derived to top-level startsAt / location / link in the mock |
Structural diff. Backend wants flat fields; FE form nests them under sessionConfig keyed by sessionType. The mock derives top-level from sessionConfig.* for show payloads. Decide whether the FE form should flatten the schema to match backend or keep the conditional-by-type config UI. |
media_ids[] (write) / media[] (read with collection_name, file_name, mime_type, file_size, url, download_url) |
materials[] with { fileId, isRequired, type, title, order } |
Shape diff. Backend's media rows are typed by upload-purpose (video/voice/pdf/slide/attachment); FE has its own type enum. Decide which shape the FE keeps. |
| — | durationMinutes, order, sessionConfig.minWatchedPercent, sessionConfig.minReadPercent, sessionConfig.mustCompleteBeforeNext, sessionConfig.platform |
FE → B, all UI-only fields. Backend has nothing equivalent. Drop, move into a metadata JSON, or ask backend to add. |
| — | usedInTerms |
UI-only count, no backend. |
| — | image (vs media) |
UI-only thumbnail; backend doesn't separate. |
Exams
Endpoints
| Backend | FE today | Status |
|---|---|---|
GET /exams/:id (with questions+options) |
showExam |
aligned. |
POST /exams |
addNewExam |
aligned. |
PATCH /exams/:id |
updateExam |
aligned. |
DELETE /exams/:id |
deleteExam |
aligned. |
POST /exams/:examId/questions |
— | Backend → FE. New endpoint. Today the FE submits the whole question list inside the exam create/update payload. Decide whether to keep "all-in-one" submission (and ask backend to accept it) or switch to add-questions-after-create. |
POST /questions/:questionId/options |
— | Backend → FE. Same as above — backend lets you add options one at a time. FE today bundles all options with the question. |
POST /exams/:examId/submit |
— (student-side feature) | Backend → FE. Student-side; not in current admin UI. |
| — | getExamsList (/admin/exams) |
FE-only. ExamsListPage depends on it. Kept mock-only; ask backend to add a list endpoint. |
| — | getExamParticipants, showExamParticipant |
FE-only. ExamParticipantsModal + ExamParticipantDetailsModal depend on these. Kept mock-only. |
Fields
| Backend ↔ FE | Notes | |
|---|---|---|
session_id ⇄ sessionId |
aligned. | |
title ⇄ title |
aligned. | |
description ⇄ description |
aligned. | |
pass_score ⇄ passingScore |
aligned (naming diff). | |
is_active |
Backend → FE. Exam form has no active toggle. Decide whether to add it. | |
Backend question shape: { question_text, position, options: [{ option_text, is_correct }] } |
FE: { title, score, correctAnswerId, answers: [{ id, title }] } |
Shape diff. Backend hides is_correct from public reads (only on add). FE concept of score (per-question weighting) has no backend slot. Decide: keep FE scoring (ask backend to store) or drop. |
FE → B (need decision):
durationMinutes— no backend slot.randomize— no backend slot.endDate/startDate— exam validity window, no backend slot.usedInTerms— derived UI count.
Homeworks (FE calls them "assignments")
Endpoints
| Backend | FE today | Status |
|---|---|---|
POST /homeworks |
addNewAssignment |
aligned (URL renamed to /homeworks; FE key name kept). |
PATCH /homeworks/:id |
updateAssignment |
aligned. |
DELETE /homeworks/:id |
deleteAssignment |
aligned. |
POST /homeworks/:homeworkId/submit (student) |
— | Backend → FE. Student submit, not in admin UI yet. |
PATCH /homework-submissions/:submissionId/review |
reviewAssignmentSubmission |
aligned (URL renamed). |
| — | getAssignmentsList |
FE-only. AssignmentsListPage depends on it. Kept mock-only. |
| — | showAssignment, getAssignmentSubmissions, showAssignmentSubmission |
FE-only. Detail + submissions list — kept mock-only. |
Fields
| Backend ↔ FE | Notes | |
|---|---|---|
session_id ⇄ sessionId |
aligned. | |
title, description |
aligned. | |
deadline (single datetime) |
FE: startDate + endDate + computed durationDays |
Shape diff. Backend has one deadline; FE has a window. Decide: drop start/end and use single deadline, or ask backend to add a window. |
is_active |
Backend → FE. FE form has no active toggle. | |
Submission status: accepted | denied |
FE: pending | approved | rejected | needs_revision |
Shape diff. Backend has two states; FE has four. The FE pending/needs_revision have no backend slot. |
Submission media_id (single) |
FE attachments[] (multiple) |
Shape diff. Backend allows one file per submission; FE expects many. |
Submission teacher_feedback ⇄ reviewerNote |
naming diff. | |
Submission reviewed_at (read) |
— | Backend provides; FE doesn't surface. |
FE → B (need decision):
priorityenum (mandatory \| optional) — no backend slot.submissionsCount— derived count, no backend slot.
Media
Endpoints
| Backend | FE today | Status |
|---|---|---|
POST /media (multipart with purpose + file) |
uploadMedia |
aligned URL & method. |
GET /media/:id/download |
— | Backend → FE. Add as downloadMedia. |
DELETE /media/:id |
— | Backend → FE. Add as deleteMedia. |
Fields
- Backend response:
{ id, collection_name, file_name, mime_type, file_size, url, download_url }. FE today readspayload.id,payload.url, sometimespayload.uploadId(a now-stale field). Cleanup needed: dropuploadId, useideverywhere. - Backend
purposeenum:avatar | cover | video | voice | pdf | slide | attachment | homework_file. FE upload calls currently hardcodepurpose: 'cover'orpurpose: 'content'.contentis not in the backend enum. Decide which of the backend purposes each FE uploader should send (e.g., session video →video, course PDF →pdf, homework upload →homework_file). - Pending media TTL: backend deletes unreferenced pending uploads after 24h. FE doesn't track this; if a user uploads a cover, abandons the form, and comes back next day, the
cover_media_idreference will 404 on submit. Document the failure mode.
Cross-cutting
Snake_case vs camelCase
Backend wire format is snake_case throughout. FE today reads camelCase (e.g., the mock returns coverUrl, isActive). When the real backend lands, the FE will either need:
- a HTTP-layer transformer (camelize on response, snake_case on request), or
- camelCase field aliases on the backend serializer.
Decide before swapping the mock for the real API. Affects every screen.
/admin/ URL prefix
Backend doc has none — endpoints live at /terms, /courses, etc. The FE previously had /admin/courses, /admin/sessions, /admin/exams, /admin/assignments. Aligned to backend (prefix dropped). Auth context (admin role) is implicit in the token, not the URL.
Sub-features that depend on missing backend endpoints
UI screens that work today against the mock but have no backend equivalent in the current doc (kept mock-only with TODOs in endpoints.js):
- Term: clone term, term status toggle, students-in-term subtab, leave toggle, courses-in-term subtab (could swap to
GET /courses?term_id=) - Course: status toggle (swapped to PATCH), students-in-course subtab, attach/detach sessions (M:N),
AddCourseStudentModal,AddSessionToCourseModal - Session: status toggle (swapped to PATCH), attendance roster
- Exam: list page, participants list, participant detail
- Assignment/Homework: list page, detail show, submissions list, submission detail
For each of these we need to either (a) ask backend to expose the endpoint, or (b) drop the UI surface.