feat(cohorts): rename Programs to Cohorts with data migration (#694)
* feat(cohorts): rename Programs to Cohorts with data migration - Add cohort database tables and Drizzle migration alongside legacy program tables - Introduce migrate-programs-to-cohorts script to copy existing program data safely - Rename API routes (/cohort), services, queries, validation, and error codes - Rename dashboard feature module, routes (/cohorts), navigation, and translations - Update docs, emails, and internal compliance cron endpoints for cohort terminology - Keep legacy program tables in place for rollback until explicitly removed Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): restore migrate:answer-data script alongside cohort migration Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): cast program enums when migrating data to cohort tables Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): truncate cohort tables before program-to-cohort copy Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(db): report accurate row counts in programs-to-cohorts migration Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
fe9f8c441f
commit
ec61bfe7d9
+5
-5
@@ -4,12 +4,12 @@ import { defineEmail } from '../send';
|
||||
import { getDefaultTemplate } from '../templates';
|
||||
import { ZEmailBranding } from '../core/branding';
|
||||
|
||||
export const programGoalReminderEmail = defineEmail({
|
||||
id: 'programGoalReminder',
|
||||
subject: 'Reminder: a program goal is due soon',
|
||||
export const cohortGoalReminderEmail = defineEmail({
|
||||
id: 'cohortGoalReminder',
|
||||
subject: 'Reminder: a cohort goal is due soon',
|
||||
schema: z.object({
|
||||
orgName: z.string().min(1),
|
||||
programName: z.string().min(1),
|
||||
cohortName: z.string().min(1),
|
||||
goalTitle: z.string().min(1),
|
||||
daysUntilDue: z.number().int(),
|
||||
completedCount: z.number().int(),
|
||||
@@ -29,7 +29,7 @@ export const programGoalReminderEmail = defineEmail({
|
||||
|
||||
const content = `
|
||||
<p>Hi there,</p>
|
||||
<p>This is a reminder that the goal <strong>${fields.goalTitle}</strong> in your program <strong>${fields.programName}</strong> at ${fields.orgName} needs your attention.</p>
|
||||
<p>This is a reminder that the goal <strong>${fields.goalTitle}</strong> in your program <strong>${fields.cohortName}</strong> at ${fields.orgName} needs your attention.</p>
|
||||
${dueLine}
|
||||
<p>Your progress so far: <strong>${progress}</strong>.</p>
|
||||
<p><a href="${fields.loginUrl}">Open the LMS</a> to keep going.</p>
|
||||
@@ -20,8 +20,8 @@ export * from './teacher-student-buy-request';
|
||||
export * from './student-prove-payment';
|
||||
export * from './teacher-course-welcome';
|
||||
export * from './student-org-invite';
|
||||
export * from './student-program-welcome';
|
||||
export * from './program-goal-reminder';
|
||||
export * from './student-cohort-welcome';
|
||||
export * from './cohort-goal-reminder';
|
||||
export * from './quiz-assigned';
|
||||
export * from './session';
|
||||
export * from './submission-received';
|
||||
@@ -38,8 +38,8 @@ import type { studentCourseCompletionEmail } from './student-course-completion';
|
||||
import type { studentCourseWelcomeEmail } from './student-course-welcome';
|
||||
import type { studentProvePaymentEmail } from './student-prove-payment';
|
||||
import type { studentOrgInviteEmail } from './student-org-invite';
|
||||
import type { studentProgramWelcomeEmail } from './student-program-welcome';
|
||||
import type { programGoalReminderEmail } from './program-goal-reminder';
|
||||
import type { studentCohortWelcomeEmail } from './student-cohort-welcome';
|
||||
import type { cohortGoalReminderEmail } from './cohort-goal-reminder';
|
||||
import type { quizAssignedEmail } from './quiz-assigned';
|
||||
import type { sessionReminderEmail, sessionUpdatedEmail } from './session';
|
||||
import type { submissionReceivedEmail } from './submission-received';
|
||||
@@ -72,8 +72,8 @@ export type EmailSchemas = {
|
||||
studentProvePayment: typeof studentProvePaymentEmail.template.schema;
|
||||
teacherCourseWelcome: typeof teacherCourseWelcomeEmail.template.schema;
|
||||
studentOrgInvite: typeof studentOrgInviteEmail.template.schema;
|
||||
studentProgramWelcome: typeof studentProgramWelcomeEmail.template.schema;
|
||||
programGoalReminder: typeof programGoalReminderEmail.template.schema;
|
||||
studentCohortWelcome: typeof studentCohortWelcomeEmail.template.schema;
|
||||
cohortGoalReminder: typeof cohortGoalReminderEmail.template.schema;
|
||||
quizAssigned: typeof quizAssignedEmail.template.schema;
|
||||
sessionReminder: typeof sessionReminderEmail.template.schema;
|
||||
sessionUpdated: typeof sessionUpdatedEmail.template.schema;
|
||||
|
||||
+6
-6
@@ -4,20 +4,20 @@ import { defineEmail } from '../send';
|
||||
import { getDefaultTemplate } from '../templates';
|
||||
import { ZEmailBranding } from '../core/branding';
|
||||
|
||||
export const studentProgramWelcomeEmail = defineEmail({
|
||||
id: 'studentProgramWelcome',
|
||||
subject: 'You have access to a program — sign in to get started',
|
||||
export const studentCohortWelcomeEmail = defineEmail({
|
||||
id: 'studentCohortWelcome',
|
||||
subject: 'You have access to a cohort — sign in to get started',
|
||||
schema: z.object({
|
||||
orgName: z.string().min(1),
|
||||
programName: z.string().min(1),
|
||||
cohortName: z.string().min(1),
|
||||
loginUrl: z.string().min(1),
|
||||
branding: ZEmailBranding
|
||||
}),
|
||||
render: (fields) => {
|
||||
const content = `
|
||||
<p>Hi there,</p>
|
||||
<p>You now have access to <strong>${fields.programName}</strong> in <strong>${fields.orgName}</strong>.</p>
|
||||
<p><a href="${fields.loginUrl}">Sign in to the LMS</a> to open the program and start learning.</p>
|
||||
<p>You now have access to <strong>${fields.cohortName}</strong> in <strong>${fields.orgName}</strong>.</p>
|
||||
<p><a href="${fields.loginUrl}">Sign in to the LMS</a> to open the cohort and start learning.</p>
|
||||
<p>If you run into any issues, reach out to your instructor(s).</p>
|
||||
<p>Cheers,</p>
|
||||
<p>${fields.orgName}</p>
|
||||
Reference in New Issue
Block a user