fix: replace stale program references (#749)

This commit is contained in:
Rotimi Best
2026-07-13 02:09:57 +01:00
committed by GitHub
parent 484e4fb674
commit 5e8188fbd3
57 changed files with 524 additions and 533 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ export const cohortMemberMiddleware = async (c: Context, next: Next) => {
return c.json(
{
success: false,
error: 'Program ID is required',
error: 'Cohort ID is required',
code: 'COHORT_ID_REQUIRED'
},
400
@@ -82,7 +82,7 @@ export const cohortNewsfeedCommentAuthorOrTeamMiddleware = async (c: Context, ne
return c.json(
{
success: false,
error: 'Only the author or program team members can perform this action',
error: 'Only the author or cohort team members can perform this action',
code: ErrorCodes.ORG_TEAM_NOT_AUTHORIZED
},
403
@@ -6,7 +6,7 @@ import { ROLE } from '@cio/utils/constants';
/**
* Middleware to check if the authenticated user is a cohort teacher or org admin.
* Mirrors the course-team-member pattern for program routes.
* Mirrors the course-team-member pattern for cohort routes.
*/
export const cohortTeamMemberMiddleware = async (c: Context, next: Next) => {
try {
@@ -27,7 +27,7 @@ export const cohortTeamMemberMiddleware = async (c: Context, next: Next) => {
return c.json(
{
success: false,
error: 'Program ID is required',
error: 'Cohort ID is required',
code: 'COHORT_ID_REQUIRED'
},
400
@@ -56,7 +56,7 @@ export const cohortTeamMemberMiddleware = async (c: Context, next: Next) => {
return c.json(
{
success: false,
error: 'Failed to verify program team membership',
error: 'Failed to verify cohort team membership',
code: 'COHORT_TEAM_MEMBER_CHECK_FAILED'
},
500
+15 -15
View File
@@ -82,29 +82,29 @@ export const cohortRouter = new Hono()
/**
* GET /cohort/enrolled
* Get programs the current user is enrolled in
* Get cohorts the current user is enrolled in
*/
.get('/enrolled', authMiddleware, async (c) => {
try {
const user = c.get('user')!;
const programs = await getEnrolledCohorts(user.id);
return c.json({ success: true, data: programs }, 200);
const cohorts = await getEnrolledCohorts(user.id);
return c.json({ success: true, data: cohorts }, 200);
} catch (error) {
return handleError(c, error, 'Failed to get enrolled cohorts');
}
})
// ── Programs ──────────────────────────────────────────────────────────────
// ── Cohorts ───────────────────────────────────────────────────────────────
/**
* GET /cohort?organizationId=...
* List all programs for an org
* List all cohorts for an org
*/
.get('/', authMiddleware, zValidator('query', ZOrgQuery), async (c) => {
try {
const { organizationId } = c.req.valid('query');
const programs = await listOrgCohorts(organizationId);
return c.json({ success: true, data: programs }, 200);
const cohorts = await listOrgCohorts(organizationId);
return c.json({ success: true, data: cohorts }, 200);
} catch (error) {
return handleError(c, error, 'Failed to list cohorts');
}
@@ -112,7 +112,7 @@ export const cohortRouter = new Hono()
/**
* POST /cohort
* Create a new program
* Create a new cohort
*/
.post(
'/',
@@ -255,7 +255,7 @@ export const cohortRouter = new Hono()
const member = await removeCohortMemberService(cohortId, memberId);
return c.json({ success: true, data: member }, 200);
} catch (error) {
return handleError(c, error, 'Failed to remove program member');
return handleError(c, error, 'Failed to remove cohort member');
}
}
)
@@ -267,7 +267,7 @@ export const cohortRouter = new Hono()
* Invite new students to a cohort by CSV emails. Issues org invites
* pre-tagged with this cohort so accept auto-enrolls them.
* Gated by cohort-team-member middleware — Cohort ADMINs/TUTORs can
* invite into their own program without org-admin rights.
* invite into their own cohort without org-admin rights.
*/
.post(
'/:cohortId/invite',
@@ -283,7 +283,7 @@ export const cohortRouter = new Hono()
const result = await inviteStudentsToCohort(cohortId, data, user.id);
return c.json({ success: true, data: result }, 201);
} catch (error) {
return handleError(c, error, 'Failed to invite students to program');
return handleError(c, error, 'Failed to invite students to cohort');
}
}
)
@@ -305,7 +305,7 @@ export const cohortRouter = new Hono()
const result = await assignExistingStudentsToCohort(cohortId, data);
return c.json({ success: true, data: result }, 200);
} catch (error) {
return handleError(c, error, 'Failed to assign students to program');
return handleError(c, error, 'Failed to assign students to cohort');
}
}
)
@@ -344,7 +344,7 @@ export const cohortRouter = new Hono()
const result = await addCourseToCohortService(cohortId, data);
return c.json({ success: true, data: result }, 201);
} catch (error) {
return handleError(c, error, 'Failed to add course to program');
return handleError(c, error, 'Failed to add course to cohort');
}
}
)
@@ -364,7 +364,7 @@ export const cohortRouter = new Hono()
const result = await removeCourseFromCohortService(cohortId, courseId);
return c.json({ success: true, data: result }, 200);
} catch (error) {
return handleError(c, error, 'Failed to remove course from program');
return handleError(c, error, 'Failed to remove course from cohort');
}
}
)
@@ -547,7 +547,7 @@ export const cohortRouter = new Hono()
/**
* GET /cohort/my/goals
* Goal assignments for the current authenticated user across all their programs.
* Goal assignments for the current authenticated user across all their cohorts.
* Used by the LMS "Your Goals" widget.
*/
.get('/my/goals', authMiddleware, async (c) => {
+10 -10
View File
@@ -105,11 +105,11 @@ async function enrollCohortStudentsInGroups(
return groupMemberRows.length;
}
// ─── Program CRUD ────────────────────────────────────────────────────────────
// ─── Cohort CRUD ─────────────────────────────────────────────────────────────
export async function createCohort(organizationId: string, profileId: string, data: TCreateCohort) {
try {
const description = data.description?.trim() || `A program for ${data.name}`;
const description = data.description?.trim() || `A cohort for ${data.name}`;
return createCohortWithCreatorMembership(
{
@@ -195,7 +195,7 @@ export async function deleteCohort(cohortId: string) {
}
}
// ─── Program Members ─────────────────────────────────────────────────────────
// ─── Cohort Members ──────────────────────────────────────────────────────────
export async function listCohortMembers(cohortId: string) {
try {
@@ -323,7 +323,7 @@ export async function removeCohortMemberService(_cohortId: string, memberId: str
} catch (error) {
if (error instanceof AppError) throw error;
throw new AppError(
error instanceof Error ? error.message : 'Failed to remove program member',
error instanceof Error ? error.message : 'Failed to remove cohort member',
ErrorCodes.INTERNAL_ERROR,
500
);
@@ -360,7 +360,7 @@ export async function getEnrolledCohorts(profileId: string) {
}
}
// ─── Program Courses ─────────────────────────────────────────────────────────
// ─── Cohort Courses ──────────────────────────────────────────────────────────
export async function listCohortCourses(cohortId: string, profileId: string) {
try {
@@ -411,7 +411,7 @@ export async function addCourseToCohortService(cohortId: string, data: TAddCours
} catch (error) {
if (error instanceof AppError) throw error;
throw new AppError(
error instanceof Error ? error.message : 'Failed to add course to program',
error instanceof Error ? error.message : 'Failed to add course to cohort',
ErrorCodes.INTERNAL_ERROR,
500
);
@@ -422,20 +422,20 @@ export async function removeCourseFromCohortService(cohortId: string, courseId:
try {
const deleted = await removeCourseFromCohort(cohortId, courseId);
if (!deleted) {
throw new AppError('Course not found in program', ErrorCodes.NOT_FOUND, 404);
throw new AppError('Course not found in cohort', ErrorCodes.NOT_FOUND, 404);
}
return deleted;
} catch (error) {
if (error instanceof AppError) throw error;
throw new AppError(
error instanceof Error ? error.message : 'Failed to remove course from program',
error instanceof Error ? error.message : 'Failed to remove course from cohort',
ErrorCodes.INTERNAL_ERROR,
500
);
}
}
// ─── Program Newsfeed ────────────────────────────────────────────────────────
// ─── Cohort Newsfeed ─────────────────────────────────────────────────────────
export async function listCohortNewsfeed(cohortId: string, options: { cursor?: string; limit: number }) {
try {
@@ -524,7 +524,7 @@ export async function deleteCohortNewsfeedService(feedId: string) {
}
}
// ─── Program Newsfeed Comments ────────────────────────────────────────────────
// ─── Cohort Newsfeed Comments ────────────────────────────────────────────────
export async function listCohortNewsfeedComments(feedId: string) {
try {
+3 -3
View File
@@ -221,7 +221,7 @@ function decideStatus(args: {
*
* Pulls all student cohort-members, computes a per-learner status, and upserts
* `program_goal_assignment`. Designed to be safe to run repeatedly (idempotent)
* and cheap enough for daily cron over hundreds of programs.
* and cheap enough for daily cron over hundreds of cohorts.
*/
export async function evaluateGoal(goalId: string): Promise<{ evaluated: number }> {
const goal = await getCohortGoalById(goalId);
@@ -316,7 +316,7 @@ export async function evaluateGoal(goalId: string): Promise<{ evaluated: number
}
/**
* Re-evaluate every active goal in a cohort. Used after material program
* Re-evaluate every active goal in a cohort. Used after material cohort
* changes (member added, course added) or by cron sweeps.
*/
export async function evaluateCohortGoals(cohortId: string): Promise<{ evaluated: number }> {
@@ -450,7 +450,7 @@ export async function getMyGoals(profileId: string) {
/**
* Cross-cohort goal roll-up for the org owner. Lists every active goal in the
* org with its per-status counts so the dashboard can group by program.
* org with its per-status counts so the dashboard can group by cohort.
*/
export async function getOrgGoalsOverview(organizationId: string) {
const goals = await getCohortGoalsByOrg(organizationId);
+4 -4
View File
@@ -4,11 +4,11 @@ import type { TInviteStudentsToCohort, TAssignExistingStudentsToCohort } from '@
import { assignAudienceToCourses, importAudienceMembers } from '@api/services/organization/audience';
/**
* Invite students to a single program by email CSV.
* Invite students to a single cohort by email CSV.
*
* Thin wrapper that hardcodes `cohortIds = [cohortId]` and delegates to
* `importAudienceMembers`. Lets a Cohort ADMIN/TUTOR (or org admin) issue org
* invites scoped to *their own* program, without granting org-wide invite rights.
* invites scoped to *their own* cohort, without granting org-wide invite rights.
*/
export async function inviteStudentsToCohort(
cohortId: string,
@@ -35,8 +35,8 @@ export async function inviteStudentsToCohort(
/**
* Assign existing student profiles (already members of the org audience) to a
* single program. Same scoping logic as `inviteStudentsToCohort` — restricted
* to one program at a time so the caller's program-admin permission is enough.
* single cohort. Same scoping logic as `inviteStudentsToCohort` — restricted
* to one cohort at a time so the caller's cohort-admin permission is enough.
*/
export async function assignExistingStudentsToCohort(cohortId: string, data: TAssignExistingStudentsToCohort) {
const cohort = await getCohortById(cohortId);