feat: certificates, landing pages, ai tutor, website update, compliance

This commit is contained in:
Rotimi Best
2026-05-18 03:47:22 +01:00
parent c31ad63955
commit ac3a2c14a7
644 changed files with 56050 additions and 5439 deletions
@@ -472,3 +472,94 @@ export async function listLatestComplianceRecordsForReminderScan(): Promise<Cour
);
}
}
export type OrgComplianceLearnerRow = {
courseId: string;
courseTitle: string;
groupMemberId: string;
profileId: string | null;
profile: CourseMemberProfile | null;
email: string | null;
status: TCourseCompletionRecord['status'] | 'no_record';
cycleNumber: number | null;
dueDate: string | null;
completedAt: string | null;
validUntil: string | null;
};
/**
* Latest compliance record per (course, learner) for every COMPLIANCE-type
* course in the org. One row per enrolled student × course.
*/
export async function getOrgComplianceLearnerRows(orgId: string): Promise<OrgComplianceLearnerRow[]> {
try {
const latestCycles = db
.select({
courseId: schema.courseCompletionRecord.courseId,
profileId: schema.courseCompletionRecord.profileId,
latestCycleNumber: sql<number>`max(${schema.courseCompletionRecord.cycleNumber})`.as('latest_cycle_number')
})
.from(schema.courseCompletionRecord)
.groupBy(schema.courseCompletionRecord.courseId, schema.courseCompletionRecord.profileId)
.as('latest_org_compliance_cycles');
const result = await db
.select({
courseId: schema.course.id,
courseTitle: schema.course.title,
member: schema.groupmember,
profile: {
id: schema.profile.id,
fullname: schema.profile.fullname,
username: schema.profile.username,
avatarUrl: schema.profile.avatarUrl,
email: schema.profile.email
},
record: schema.courseCompletionRecord
})
.from(schema.course)
.innerJoin(schema.group, eq(schema.group.id, schema.course.groupId))
.innerJoin(schema.groupmember, eq(schema.groupmember.groupId, schema.group.id))
.leftJoin(schema.profile, eq(schema.profile.id, schema.groupmember.profileId))
.leftJoin(
latestCycles,
and(eq(latestCycles.courseId, schema.course.id), eq(latestCycles.profileId, schema.groupmember.profileId))
)
.leftJoin(
schema.courseCompletionRecord,
and(
eq(schema.courseCompletionRecord.courseId, schema.course.id),
eq(schema.courseCompletionRecord.profileId, schema.groupmember.profileId),
eq(schema.courseCompletionRecord.cycleNumber, latestCycles.latestCycleNumber)
)
)
.where(
and(
eq(schema.group.organizationId, orgId),
eq(schema.course.type, 'COMPLIANCE'),
eq(schema.groupmember.roleId, ROLE.STUDENT),
isNotNull(schema.groupmember.profileId)
)
)
.orderBy(asc(schema.course.title), asc(schema.profile.fullname));
return result.map((row) => ({
courseId: row.courseId,
courseTitle: row.courseTitle,
groupMemberId: row.member.id,
profileId: row.member.profileId,
profile: row.profile ?? null,
email: row.profile?.email ?? row.member.email ?? null,
status: (row.record?.status as TCourseCompletionRecord['status'] | undefined) ?? 'no_record',
cycleNumber: row.record?.cycleNumber ?? null,
dueDate: row.record?.dueDate ?? null,
completedAt: row.record?.completedAt ?? null,
validUntil: row.record?.validUntil ?? null
}));
} catch (error) {
console.error('getOrgComplianceLearnerRows error:', error);
throw new Error(
`Failed to get org compliance learner rows: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}