Commit Graph

635 Commits

Author SHA1 Message Date
a5acd00637 Add admin-managed field options API for configurable dropdowns.
Store options in field_options with public /field-options and admin CRUD; validate learner profile values on update.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 09:21:36 -07:00
176f78515d Fix partial team member updates clearing team_role on invite accept.
Use nullable sqlc.narg fields so empty strings are not written to team_role and other optional columns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 07:54:44 -07:00
215a4bd1dc Simplify team invite to email and role; collect profile on accept
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 06:49:28 -07:00
0ad7f094cf Include access metadata for OPEN_LEARNER with is_accessible always true
Keeps the same response shape as STUDENT while skipping sequential locks; progress fields are still populated for completion UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 05:45:44 -07:00
31bd1e3814 Add team member email invitations for admin panel onboarding
Introduces invite, verify, accept, resend, and revoke flows using team_members and invitation tokens, sends the branded invitation template, and requires account activation before team login.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 03:43:00 -07:00
868e5ba001 Apply Yimaru Academy branding to email template seeds
Adds branded HTML layout matching the admin portal purple palette, updates 000066 seeds, and adds 000067 migration to refresh existing template rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 02:12:45 -07:00
5937c5505a Add admin-managed email templates and use them for OTP delivery
Adds CRUD and preview APIs, RBAC permissions, seeded system templates, and migrates OTP email/SMS to template rendering.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 01:28:48 -07:00
1f7b38861e Integrate Chapa for learner subscription payments
Add Chapa checkout, verify, webhook, and callback flows so subscriptions activate only after confirmed payment. Route subscription checkout through Chapa while keeping ArifPay for direct payments. Include integration docs and a Postman collection.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 03:35:57 -07:00
de8618191c Normalize broken FCM service account JSON (.env PEM newlines).
Repair multiline PEM inside private_key before Firebase init; add unit test; use normalized JSON for credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 08:57:26 -07:00
f7c9eddef5 Improve FCM service account loading and diagnostics.
Support FCM_SERVICE_ACCOUNT_KEY_FILE, clearer JSON parse errors for common .env mistakes, stop logging credential contents.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 08:41:27 -07:00
14d94ec723 Honor optional sort_order when creating exam-prep units.
Expose sort_order on CreateExamPrepUnitInput; insert applies explicit index with sibling shifting (aligned with LMS course create). Updated Swagger and LMS-Personas Postman collection.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 07:18:35 -07:00
5399d33af6 Add optional gender to LMS personas.
Migration 000065 adds nullable gender text column; persona API and Postman expose it alongside profile_picture.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 06:37:21 -07:00
9ff418247f Always include profile_picture in persona JSON responses.
Remove omitempty on LmsPersona.profile_picture so list/get return null when unset. Add LMS-Personas Postman collection matching current API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 06:25:55 -07:00
6ab077b53d Rename LMS persona image field to profile_picture.
Add migration 000064 renaming avatar_url column; expose profile_picture in API, sqlc, and Swagger.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 06:17:15 -07:00
873be1b482 Add LMS personas catalog and CRUD API.
Introduce lms_personas table, repoint practice persona_id FKs off users, validate persona refs on LMS and exam-prep practice flows, personas.* RBAC permissions, and OpenAPI docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 06:06:42 -07:00
71bc09a638 Make practice title optional on create.
POST /practices and exam-prep practice create accept missing or null title; persist as empty string. Refresh OpenAPI and document the behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 04:11:09 -07:00
bd1767d2a6 Add LMS lesson draft and publish visibility.
Migration 000062 adds lessons.publish_status (DRAFT default for new rows; existing rows published). Editors see all lessons; learners see published-only lists and GET by id. Sequential prerequisites and completion counts ignore drafts. Course lesson_count counts published lessons only. Swagger documents publish_status on create/update bodies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 02:16:42 -07:00
fffdff1031 Honor optional sort_order on lesson create under a module.
Accept sort_order on CreateLessonInput; SQL falls back to max+1. When set, shift sibling lessons and insert at that position (same pattern as module create). Regenerate sqlc and update Swagger.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 01:56:15 -07:00
7e61e34292 Add OPEN_LEARNER role without LMS sequential gating.
Migration 000061 inserts the RBAC role and demo user (openlearner@yimaru.com). STUDENT keeps sequential ApplyAccess and practice ordering; OPEN_LEARNER shares learner permissions and customer flows. Document the role in Swagger and point initial seed SQL at the migration for the demo account.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 10:26:25 -07:00
83db13bed0 Honor optional sort_order on module create under a course.
Accept sort_order in CreateModuleInput, shift siblings when set, and default to max+1 when omitted.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 04:15:18 -07:00
12ad59c409 Add draft vs published status for LMS and exam-prep practices.
Expose publish_status on create/update, filter learner-facing lists and gates, and add migration 000060.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 03:57:43 -07:00
37aef49e28 Honor optional sort_order on course create under a program.
Parses body sort_order, shifts sibling courses in-program, and inserts at the requested slot; omitting it keeps append-after-max behavior. Swagger/sqlc regenerated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 02:54:17 -07:00
1136a166f5 Shift sibling sort_order when creating or updating LMS hierarchy rows.
Sequential reorder uses a temporary negative id slot plus range shifts so UNIQUE constraints on programs, courses, modules, and lessons stay valid; replaces module pairwise swap-only behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 02:38:29 -07:00
d28bddace1 Accept optional sort_order when creating LMS programs.
Preserve append-after-max ordering when omitting sort_order and keep global uniqueness enforced by DB.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 02:10:49 -07:00
4a681265d7 Resolve bulk role path segment from RBAC roles.id.
Admin bulk deactivate/reactivate accepts decimal path segments matching GET /rbac/roles IDs, resolving RoleRecord.name to the platform key. Document 404 when id is unknown. Add Cursor rule: on push, commit dirty tree first without secrets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 01:16:28 -07:00
2f73b60122 Allow ADMIN users to bulk deactivate and reactivate by role.
Platform ADMIN callers no longer hit 403 on these endpoints; bulk changes to platform users.role ADMIN remain restricted to SUPER_ADMIN, while team_members.team_role ADMIN is still eligible under path ADMIN.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 01:09:42 -07:00
ecad91d89e Add SUPER_ADMIN bulk deactivate and reactivate by role.
Expose POST /admin/roles/:role/bulk-deactivate and bulk-reactivate for platform users and team_members, mirroring deactivate/reactivate semantics and optional team member exclusions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 00:52:14 -07:00
a80db8afd9 Add admin recent-activity timeline for learner profile UIs.
Expose GET /api/v1/admin/users/:user_id/recent-activity (progress.get_any_user) merging account creation and LMS completion milestones, with optional practice rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 01:13:21 -07:00
52effaa321 Add admin endpoint for nested user LMS completion activity.
Expose GET /api/v1/admin/users/:user_id/lms-learning-activity for progress.get_any_user so admins see program/course/module/lesson completions and practices from stored completion rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 00:58:49 -07:00
062b1f6151 Add country, region, and subscription_status filters to GET /users.
Filtering matches user profile country/region (case-insensitive trim) and derived subscription state in SQL so pagination totals stay correct.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 00:37:11 -07:00
49bcc22d0d Expose subscription_status on user profile responses instead of active_subscription.
Users see ACTIVE, PENDING, or Unsubscribed via new batch and single SQL helpers; Swagger refreshed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 00:28:19 -07:00
1e62510321 Always serialize active_subscription on profile responses.
Null encodes when there is no active plan so clients see explicit subscription state; Swagger regenerated and GET /users description updated accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 00:16:10 -07:00
f824c16c64 user list response fix 2026-05-18 00:09:26 -07:00
2883561525 Add monthly revenue trend for analytics when year is specified.
Exposes payments.revenue_monthly with Jan–Dec SUCCESS totals (UTC) per currency for dashboard charts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 23:32:36 -07:00
a1696bf1e0 Fix analytics dashboard course counts for LMS and exam_prep hierarchies.
Replace stub AnalyticsCourseCounts query and expose lms / exam_prep inventory in the courses section.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 22:34:25 -07:00
7f8ef3373c Add paginated Vimeo video list API (GET /me/videos).
Exposes the Vimeo account library for admin workflows and syncs swagger docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 22:23:50 -07:00
024a69b74b Add date-range filtering to analytics dashboard API.
Support all-time, year, year+month, and custom from/to query params with filtered metrics and time-series charts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 02:15:15 -07:00
8bba318372 Fix practice completion roll-up for non-module scopes.
Resolve practice hierarchy using module, lesson, or course linkage and only run module-level cascade when module context exists.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 03:58:39 -07:00
4ada908555 Allow completion for existing practice sets.
Treat existing PRACTICE sets as completable even when not published, while keeping sequence enforcement only for published practices.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 03:43:50 -07:00
86ab4e53d4 Fix practice completion lookup for progress endpoint.
Prioritize resolving lms_practices.id before falling back to question_set.id to avoid false 404 responses caused by cross-table ID collisions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 03:36:39 -07:00
c711df68b9 Fix practice completion lookup for progress endpoint.
Accept either question-set IDs or LMS practice IDs and recognize LMS owner types so valid practice completions no longer return practice-not-found responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 02:57:05 -07:00
eae87b40b5 Add practice completion progress endpoint.
Expose POST /progress/practices/:id/complete so practice completions are recorded through the existing CompletePractice flow and included in learner progress tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 11:20:33 -07:00
7e75d79dc8 Pass Firebase project_id explicitly from service account JSON.
Parse FCM_SERVICE_ACCOUNT_KEY, validate project_id, and provide firebase.Config{ProjectID} during FCM client initialization to prevent missing-project-id messaging failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 11:02:01 -07:00
4509fe2dc0 Initialize FCM client lazily during push send.
Add ensureFCMClient() so push APIs retry FCM initialization at request time and return actionable initialization errors when the service account key is empty or invalid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 10:58:42 -07:00
6f1cb24c63 Add runtime config debug logging for test push flow.
Log DB_URL alongside FCM_SERVICE_ACCOUNT_KEY during test-push requests and keep compose env-file wiring aligned with current local debug setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 10:25:53 -07:00
cd0ae19d03 Log FCM env value on test-push requests.
Add request-time logging in the test push endpoint so FCM_SERVICE_ACCOUNT_KEY can be verified during each API call, not only at service startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 09:56:45 -07:00
75353f8bdd Log FCM service account env value at startup.
Add a notification-service startup log to print FCM_SERVICE_ACCOUNT_KEY for runtime verification during push notification troubleshooting.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 09:54:22 -07:00
b2a72c2f6e Fix device registration error mapping for invalid user IDs.
Validate device registration input and translate devices_user_fk violations into a clear bad-request response so invalid auth contexts no longer return opaque 500 errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 08:32:42 -07:00
6a4fe68628 Add full FAQ management APIs and integration assets.
Implement public FAQ read endpoints and admin CRUD with RBAC, persistence, and migrations, then regenerate Swagger and add a complete Postman collection so frontend/admin teams can integrate and validate the feature end-to-end.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 07:58:17 -07:00
bc2357374b Add practice-existence flags and refresh API contracts.
Expose has_practice booleans for LMS and pre-exam hierarchy entities, wire SQL/repository mappings, and regenerate SQLC/Swagger artifacts. Also update the Resend sender display name for outbound emails.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 11:57:11 -07:00
9da9eb77e5 fix dynamic builder runtime mapping for option responses
Allow builder-native response kinds like OPTION to resolve to DYNAMIC so schema-driven definition creation succeeds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:56:41 -07:00
3d1b3ad9b8 dynamic question type builder completion 2026-05-08 10:12:02 -07:00
9a17f0b3c4 use descriptive top-level message for direct payments
Keep provider-specific details in data.message and return a stable, human-readable top-level success message for /payments/direct responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 09:26:07 -07:00
0983589e36 extend full-payload direct proxy flow to MPESA
Align MPESA direct payment with TELEBIRR_USSD by routing it through the provider's full checkout payload proxy endpoint for consistent gateway behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 09:21:43 -07:00
21ce61b910 telebirr-ussd direct payment fix 2026-05-07 09:08:43 -07:00
f906862676 partly implemented dynamic question builder + payment routes fix 2026-05-07 08:10:21 -07:00
73370633ce temporarily disabled subscription check 2026-05-06 05:10:36 -07:00
b62d89574e Include nested lesson and practice counts in exam-prep modules list response.
Return per-module lesson and practice aggregates under unit modules listing so clients can render module depth without additional queries.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 07:05:35 -07:00
16c3f6b613 Include nested module, lesson, and practice counts in exam-prep units list response.
Expose per-unit aggregate counts under catalog-course units listing so clients can display unit depth without extra chained requests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 06:35:13 -07:00
4124f98160 Include nested content counts in exam-prep catalog list response.
Add units, modules, and lessons aggregate counts per catalog course so clients can render hierarchy depth without extra API calls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 06:13:01 -07:00
10954d88b0 subscription management fix + duolingo hierarchy implementation 2026-05-04 10:44:18 -07:00
eba2b87ed6 Use initial assessment description as normalized level.
Made-with: Cursor
2026-04-29 08:12:09 -07:00
60290e5c34 Swap module sort_order on conflict during update.
When updating a module sort_order to an occupied position in the same course, perform an atomic swap in a transaction instead of failing with a unique constraint error.

Made-with: Cursor
2026-04-29 03:36:45 -07:00
8430b82687 Fix partial question-set updates preserving existing values.
When PUT payload omits title, status, or shuffle_questions, reuse current persisted values so updates do not write invalid empty status values.

Made-with: Cursor
2026-04-29 03:02:13 -07:00
cdb0fa1bb3 Enforce strict initial assessment set validation.
Require INITIAL_ASSESSMENT titles to follow the Level Test A1/A2/B1/B2 format and ensure passing_score is always present on create and update.

Made-with: Cursor
2026-04-29 02:47:21 -07:00
9027b65011 Require lesson and practice completion for LMS rollups.
Update lesson and practice completion flows to cascade module/course/program progress only when both lesson completion and related published practice completion criteria are met, and align progress counters with the new rule.

Made-with: Cursor
2026-04-28 09:56:53 -07:00
8c116f4a0b GET question sets API fix 2026-04-28 09:41:09 -07:00
87bf2ed609 data loss fix 2026-04-28 09:30:48 -07:00
9cfd6c524e Allow INITIAL_ASSESSMENT question sets without owner_type and owner_id
Made-with: Cursor
2026-04-27 10:46:33 -07:00
0d02eb1a24 add MinIO media URL refresh endpoint
Add POST /api/v1/files/refresh-url to issue fresh presigned URLs from object keys, minio:// references, or stale presigned URLs so clients can refresh media links before render.

Made-with: Cursor
2026-04-27 05:25:16 -07:00
78f231f222 fix OTP verification by submitted code
Resolve false OTP already used/expired responses during registration by loading OTP rows using user_id plus submitted otp code and validating usage/expiry on the matched row.

Made-with: Cursor
2026-04-25 05:07:19 -07:00
526426d9f9 course practice count fix 2026-04-25 02:41:34 -07:00
5857fce9a0 count data for course 2026-04-25 02:36:52 -07:00
7e26f15bed early otp expiration fix 2026-04-25 00:16:05 -07:00
bc68326a66 fix: return sample_answer_voice_prompt and audio_correct_answer_text in question set items list
Extend GetQuestionSetItems and GetPublishedQuestionsInSet queries to match
paginated fields; map audio answer join in repository instead of nils.

Made-with: Cursor
2026-04-24 03:11:47 -07:00
33d34f0dd2 fix: map default CEFR courses to Beginner/Intermediate/Advanced programs
Seed A1-A2, B1-B2, and C1-C2 only on their matching programs; add migration
000050 for existing databases. Document mapping in domain.

Made-with: Cursor
2026-04-24 01:14:50 -07:00
5b53929d92 learning progress implementation 2026-04-23 03:58:27 -07:00
6c672c4b20 static data for Courses 2026-04-23 02:07:32 -07:00
9db9c9899a module+lesson+practice implementations 2026-04-23 01:59:20 -07:00
152478a96c added program 2026-04-23 00:59:01 -07:00
9154dec067 fix: course-management practice detail without sub_module_practices row
- resolveCourseManagementPractice falls back to SUB_MODULE PRACTICE question_sets
- Synthetic practice uses id 0 and question_set_id for orphan sets
- Align GET practice and /detail with resolver; sync question_count after load

Made-with: Cursor
2026-04-21 09:59:41 -07:00
5fbca53534 fix: resolve practice by question set id; set Response flags on question-sets by-owner
- GetSubModulePracticeByID matches sub_module_practices.id or question_set_id
- Prefer primary id when both could match (ORDER BY + LIMIT 1)
- Set Success/StatusCode on practice GET/detail and GetQuestionSetsByOwner

Made-with: Cursor
2026-04-21 09:55:11 -07:00
6839d1aa0d fix: sub-module practices list excludes non-PRACTICE sets and bad Response flags
- Drop question_sets.set_type = PRACTICE filter so sub_module_practices rows list correctly
- Set Success and StatusCode on GET sub-modules/:id/practices response
- Return empty JSON array instead of null for no practices

Made-with: Cursor
2026-04-21 09:31:22 -07:00
72d1a0c3ed feat: list sub-categories by course category ID
- GET /api/v1/course-management/categories/:categoryId/sub-categories

- SQL GetCourseSubCategoriesByCategoryID; swagger refresh

Made-with: Cursor
2026-04-20 08:32:19 -07:00
de95c4d0d2 feat: practice detail API, inactive purge tracking, and related plumbing
- Add GET /api/v1/course-management/practices/:practiceId/detail with full question items

- Add migration 000040 for sub-module content inactive purge tracking

- Hierarchy queries, sqlc gen, config/app purge job, swagger refresh

Made-with: Cursor
2026-04-20 08:24:59 -07:00
90baa582be fix: load sub-module lesson by ID regardless of active flag
Course-management GET/PUT used GetSubModuleLessonByID with is_active=TRUE,
which returned no row for inactive lessons. Align with other ByID lookups
and allow admins to view and edit inactive lessons.

Made-with: Cursor
2026-04-20 00:48:13 -07:00
bbd919ca12 feat: optional include_inactive for sub-module lessons list
GET .../sub-modules/:id/lessons?include_inactive=true returns all lessons;
default remains active-only.

Made-with: Cursor
2026-04-18 03:25:28 -07:00
3e54b5039d fix: surface DB error when team login refresh token issuance fails
Return err.Error() in the response so operators see e.g. missing
team_refresh_tokens table instead of a generic message.

Made-with: Cursor
2026-04-18 03:13:34 -07:00
24f1aca97a fix: return updated lesson from UpdateSubModuleLesson after is_active false
GetSubModuleLessonByID filters is_active=true, so refetch failed with 500
after soft-deactivating. Use RETURNING row from the update instead.

Made-with: Cursor
2026-04-18 02:54:47 -07:00
ce1b827768 refresh token fix 2026-04-17 10:16:25 -07:00
886b62ed68 feat(levels): flexible cefr_level codes up to 64 chars
- Migration 000038 drops fixed A1-C3 check and widens cefr_level column
- CreateLevel validates length and NUL only; preserve client casing
- Regenerate Swagger docs

Made-with: Cursor
2026-04-17 09:24:34 -07:00
7ff0b639cf added more structure to submodules 2026-04-17 09:07:25 -07:00
c5d3935062 added more structure to levels 2026-04-17 08:33:58 -07:00
518c3ee751 added more structure to lessons 2026-04-17 08:27:40 -07:00
1026354c24 Expand course hierarchy read APIs and practice retrieval.
Add list/detail endpoints for courses, levels, modules, submodules, and submodule practices; extend course listing queries; add lesson update support and clean up removed route paths.

Made-with: Cursor
2026-04-17 07:52:22 -07:00
343ce470cc add lesson and subcategory retrieval/update endpoints
Introduce dedicated APIs for submodule lesson detail/update and subcategory listing (including Human Language), with SQL/query wiring and handler routing updates.

Made-with: Cursor
2026-04-17 01:40:47 -07:00
01914cb81e Add lesson detail retrieval endpoints.
Expose APIs to list lessons by submodule and fetch a single lesson by ID, including title, description, intro video URL, and question count.

Made-with: Cursor
2026-04-16 02:42:21 -07:00
a9c6966820 add legacy learning-path GET endpoint for flows compatibility
Expose GET /course-management/courses/:courseId/learning-path and build response from unified hierarchy tables so first-time Flows tab loads no longer fail with Cannot GET.

Made-with: Cursor
2026-04-14 10:05:53 -07:00
57f0db269a add legacy category courses GET endpoint for compatibility
Expose GET /course-management/categories/:categoryId/courses so legacy tab/API callers no longer fail with Cannot GET during initial content load.

Made-with: Cursor
2026-04-14 09:21:13 -07:00
83f5541650 add course category deletion endpoint
Expose delete support for top-level course categories and cascade removal of linked sub-categories/courses for content-management cleanup.

Made-with: Cursor
2026-04-14 06:22:36 -07:00