From fa060f5f6d53c1b4a4f36b6ae8e41bc10246d393 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Mon, 13 Jul 2026 10:33:19 +0100 Subject: [PATCH] feat(notifications): email notification preferences (#747) * feat(notifications): add email notification preferences Implement the email notification preferences PRD with org-level defaults and personal opt-outs enforced at the email enqueue chokepoint. - Add toggle map, profile.settings jsonb, and shouldSendEmail resolver - Extend profile and organization update APIs with emailNotifications - Pass preference context through all toggleable email send call sites - Add Settings > Notifications page with personal and org admin sections Co-authored-by: Rotimi Best * feat(notifications): group email toggles into course, cohort, and enrollment sections Organize personal and org notification settings into scannable sections so users can find course- or cohort-related emails quickly. Co-authored-by: Rotimi Best * fix(notifications): use Exercise instead of Quiz in toggle copy Co-authored-by: Rotimi Best * fix(notifications): only mark certification email sent when enqueued Skip setMemberCertificationEmailSent when notification preferences or missing Redis prevent the completion email from being queued, so the email can still send if preferences change later. Co-authored-by: Rotimi Best * fix(notifications): address PR review feedback - Add EmailPreferenceLookupCache to avoid N+1 settings lookups in batch processors and multi-recipient email enqueue paths - Replace $effect store hydration in notifications settings with onMount and explicit sync after save - Remove thin api email-preferences re-export; import from db queries - Hide tutor-only personal toggles (newStudent, newSubmission) for students Co-authored-by: Rotimi Best * fix(notifications): hydrate toggles after store load and add profile settings migration - One-time store hydration via onMount plus guarded effect when profile/org load after mount, without clobbering in-progress edits - Add drizzle migration for profile.settings jsonb column Co-authored-by: Rotimi Best --------- --- apps/api/src/services/account/profile.ts | 17 +- apps/api/src/services/cohort/goal.ts | 3 +- apps/api/src/services/course/invite.ts | 11 +- .../src/services/course/notify-exercise.ts | 1 + apps/api/src/services/course/people.ts | 9 +- apps/api/src/services/jobs/email-jobs.ts | 19 + apps/api/src/services/newsfeed/newsfeed.ts | 6 +- .../api/src/services/organization/audience.ts | 6 +- .../api/src/services/submission/submission.ts | 9 +- apps/api/src/utils/course-completion.ts | 9 +- .../lib/features/auth/api/profile.svelte.ts | 24 + .../src/lib/features/org/api/org.svelte.ts | 2 +- .../src/lib/features/settings/pages/index.ts | 1 + .../settings/pages/notifications.svelte | 264 + .../src/lib/features/settings/utils/types.ts | 4 + .../features/ui/navigation/org-navigation.ts | 9 + .../src/lib/utils/translations/da.json | 72 +- .../src/lib/utils/translations/de.json | 72 +- .../src/lib/utils/translations/en.json | 70 + .../src/lib/utils/translations/es.json | 72 +- .../src/lib/utils/translations/fr.json | 72 +- .../src/lib/utils/translations/hi.json | 72 +- .../src/lib/utils/translations/pl.json | 72 +- .../src/lib/utils/translations/pt.json | 72 +- .../src/lib/utils/translations/ru.json | 72 +- .../src/lib/utils/translations/vi.json | 72 +- .../settings/notifications/+page.svelte | 21 + .../notifications/notify-course-exercise.ts | 29 +- .../notifications/notify-session-update.ts | 29 +- .../notifications/session-reminder-scan.ts | 13 + .../src/migrations/0003_profile_settings.sql | 1 + .../db/src/migrations/meta/0003_snapshot.json | 13108 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 9 +- packages/db/src/queries/auth/profile.ts | 38 +- packages/db/src/queries/cohort/goal.ts | 3 + packages/db/src/queries/course/course.ts | 4 + packages/db/src/queries/course/session.ts | 3 + packages/db/src/queries/newsfeed/newsfeed.ts | 2 + .../notifications/email-preferences.ts | 118 + .../db/src/queries/notifications/index.ts | 1 + packages/db/src/schema.ts | 25 +- packages/jobs/src/payloads/notifications.ts | 1 + packages/utils/package.json | 4 + .../utils/src/notifications/email-toggles.ts | 117 + packages/utils/src/notifications/index.ts | 1 + .../utils/src/validation/account/account.ts | 14 +- .../validation/organization/organization.ts | 15 +- 47 files changed, 14622 insertions(+), 46 deletions(-) create mode 100644 apps/dashboard/src/lib/features/settings/pages/notifications.svelte create mode 100644 apps/dashboard/src/routes/(app)/org/[slug]/settings/notifications/+page.svelte create mode 100644 packages/db/src/migrations/0003_profile_settings.sql create mode 100644 packages/db/src/migrations/meta/0003_snapshot.json create mode 100644 packages/db/src/queries/notifications/email-preferences.ts create mode 100644 packages/db/src/queries/notifications/index.ts create mode 100644 packages/utils/src/notifications/email-toggles.ts create mode 100644 packages/utils/src/notifications/index.ts diff --git a/apps/api/src/services/account/profile.ts b/apps/api/src/services/account/profile.ts index c3604016..dd9050c7 100644 --- a/apps/api/src/services/account/profile.ts +++ b/apps/api/src/services/account/profile.ts @@ -17,6 +17,7 @@ import { import type { OrganizationWithMemberAndPlans } from '@cio/db/queries/organization/types'; import { ROLE } from '@cio/utils/constants'; import type { TProfile } from '@cio/db/types'; +import type { TUpdateProfile } from '@cio/utils/validation/account'; import { env } from '@cio/core/config/env'; import { getLicenseStatus } from '@api/services/license'; @@ -97,12 +98,18 @@ export async function getAccountData(userId: string): Promise> -) { +export async function updateUser(userId: string, data: TUpdateProfile) { try { - const updatedProfile = await updateProfile(userId, data); + const { emailNotifications, ...profileFields } = data; + const updatePayload: Partial> = { + ...profileFields + }; + + if (emailNotifications !== undefined) { + updatePayload.settings = { emailNotifications }; + } + + const updatedProfile = await updateProfile(userId, updatePayload); if (!updatedProfile) { throw new AppError(`Profile not found for user ID: ${userId}`, ErrorCodes.PROFILE_NOT_FOUND, 404); diff --git a/apps/api/src/services/cohort/goal.ts b/apps/api/src/services/cohort/goal.ts index df3620cc..b4102720 100644 --- a/apps/api/src/services/cohort/goal.ts +++ b/apps/api/src/services/cohort/goal.ts @@ -429,7 +429,8 @@ export async function runCohortGoalReminderScan(): Promise<{ theme: row.organizationTheme }) }, - idempotencyKey: `cohort-goal-reminder:${row.assignment.id}:${daysUntilDue}:${dayKey}` + idempotencyKey: `cohort-goal-reminder:${row.assignment.id}:${daysUntilDue}:${dayKey}`, + preference: { organizationId: row.organizationId, recipientProfileId: row.profileId } }); remindersEnqueued += 1; } catch (error) { diff --git a/apps/api/src/services/course/invite.ts b/apps/api/src/services/course/invite.ts index 23991fd8..b150bf3d 100644 --- a/apps/api/src/services/course/invite.ts +++ b/apps/api/src/services/course/invite.ts @@ -359,6 +359,7 @@ async function sendStudentJoinEmails(input: { courseId: string; courseName: string; orgName: string; + organizationId: string; org: OrgUrlInfo; branding: EmailBranding; studentId: string; @@ -379,7 +380,8 @@ async function sendStudentJoinEmails(input: { }, from: buildEmailFromName(`${input.orgName} (via ClassroomIO.com)`), idempotencyKey: `course-welcome:${input.courseId}:${input.studentId}`, - ics + ics, + preference: { organizationId: input.organizationId, recipientProfileId: input.studentId } }); } catch (error) { console.error('Failed to enqueue student welcome email:', error); @@ -407,7 +409,8 @@ async function sendStudentJoinEmails(input: { branding: input.branding }, from: buildEmailFromName('ClassroomIO'), - idempotencyKey: `teacher-student-joined:${input.courseId}:${input.studentId}` + idempotencyKey: `teacher-student-joined:${input.courseId}:${input.studentId}`, + preference: { organizationId: input.organizationId } }); } catch (error) { console.error('Failed to enqueue teacher join notification emails:', error); @@ -696,6 +699,7 @@ export async function enrollInCourse( courseId, courseName: title, orgName: org.name, + organizationId: org.id, org: { siteName: org.siteName, customDomain: org.customDomain, isCustomDomainVerified: org.isCustomDomainVerified }, branding: buildEmailBranding({ name: org.name, avatarUrl: org.avatarUrl, theme: org.theme }), studentId: user.id, @@ -964,6 +968,7 @@ export async function acceptStudentInvite(token: string, user: TAuthUser, contex courseId: course.id, courseName: course.title, orgName: organization.name, + organizationId: organization.id, orgSiteName: organization.siteName, orgCustomDomain: organization.customDomain, orgIsCustomDomainVerified: organization.isCustomDomainVerified, @@ -1049,6 +1054,7 @@ export async function acceptStudentInvite(token: string, user: TAuthUser, contex courseId: course.id, courseName: course.title, orgName: organization.name, + organizationId: organization.id, orgSiteName: organization.siteName, orgCustomDomain: organization.customDomain, orgIsCustomDomainVerified: organization.isCustomDomainVerified, @@ -1066,6 +1072,7 @@ export async function acceptStudentInvite(token: string, user: TAuthUser, contex courseId: result.courseId, courseName: result.courseName, orgName: result.orgName, + organizationId: result.organizationId, org: { siteName: result.orgSiteName, customDomain: result.orgCustomDomain, diff --git a/apps/api/src/services/course/notify-exercise.ts b/apps/api/src/services/course/notify-exercise.ts index e52b223d..da2d5fbe 100644 --- a/apps/api/src/services/course/notify-exercise.ts +++ b/apps/api/src/services/course/notify-exercise.ts @@ -59,6 +59,7 @@ export async function notifyCourseExerciseService(courseId: string, exerciseId: exerciseTitle: exercise.title, courseName: course.title, orgName, + organizationId: orgData.orgId, fromName: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), quizUrl, branding: buildEmailBranding({ diff --git a/apps/api/src/services/course/people.ts b/apps/api/src/services/course/people.ts index 100ef299..01e88b5e 100644 --- a/apps/api/src/services/course/people.ts +++ b/apps/api/src/services/course/people.ts @@ -110,7 +110,8 @@ export async function addMember( }, from: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), idempotencyKey: `course-people-student-welcome:${courseId}:${studentEmail}`, - ics: await getWelcomeSessionIcs(courseId) + ics: await getWelcomeSessionIcs(courseId), + preference: { organizationId: courseOrgData.orgId, recipientProfileId: data.profileId } }); } catch (emailError) { console.error('Failed to enqueue student welcome email:', emailError); @@ -133,7 +134,8 @@ export async function addMember( branding }, from: buildEmailFromName('ClassroomIO'), - idempotencyKey: `course-people-teacher-joined:${courseId}:${studentEmail}` + idempotencyKey: `course-people-teacher-joined:${courseId}:${studentEmail}`, + preference: { organizationId: courseOrgData.orgId } }); } catch (emailError) { console.error('Failed to enqueue teacher notification email:', emailError); @@ -237,7 +239,8 @@ export async function addMembers(courseId: string, members: TAddCourseMembers) { branding }, from: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), - idempotencyKey: `teacher-course-welcome:${courseId}:${email}` + idempotencyKey: `teacher-course-welcome:${courseId}:${email}`, + preference: { organizationId: courseOrgData.orgId } }).catch((emailError) => { console.error(`Failed to enqueue welcome email to ${email}:`, emailError); return []; diff --git a/apps/api/src/services/jobs/email-jobs.ts b/apps/api/src/services/jobs/email-jobs.ts index 4e45ea2b..eb14f51c 100644 --- a/apps/api/src/services/jobs/email-jobs.ts +++ b/apps/api/src/services/jobs/email-jobs.ts @@ -1,5 +1,6 @@ import { enqueueEmailSend, isRedisConfigured } from '@cio/jobs'; import { EmailRegistry, type EmailId, type EmailSchemaFor } from '@cio/email'; +import { EmailPreferenceLookupCache } from '@cio/db/queries/notifications'; import * as z from 'zod'; import { logRedisUnavailableOnce } from '@cio/core/utils/redis/redis'; @@ -24,6 +25,10 @@ export interface EnqueueTemplateEmailInput extends CommonOp subject?: string; /** Optional iCalendar (.ics) body attached as a text/calendar part. */ ics?: string; + preference?: { + organizationId?: string; + recipientProfileId?: string; + }; } export interface EnqueueRawEmailInput extends CommonOptions { @@ -76,8 +81,22 @@ export async function enqueueTransactionalEmail( const recipients = toRecipientArray(input.to); const jobIds: string[] = []; + const preferenceCache = input.preference ? new EmailPreferenceLookupCache() : null; for (const recipient of recipients) { + if (preferenceCache && input.preference) { + const allowed = await preferenceCache.shouldSend({ + emailId: template, + organizationId: input.preference.organizationId, + recipientEmail: recipient, + recipientProfileId: input.preference.recipientProfileId + }); + + if (!allowed) { + continue; + } + } + const jobId = await enqueueEmailSend( { kind: 'template', diff --git a/apps/api/src/services/newsfeed/newsfeed.ts b/apps/api/src/services/newsfeed/newsfeed.ts index 61bc7618..2dfeaf34 100644 --- a/apps/api/src/services/newsfeed/newsfeed.ts +++ b/apps/api/src/services/newsfeed/newsfeed.ts @@ -384,7 +384,8 @@ async function sendNewsfeedPostEmail(feedId: string, authorId: string) { }, from: buildEmailFromName(`${orgName} - ClassroomIO`), replyTo: feedData.author?.email || 'noreply@classroomio.com', - idempotencyKey: `newsfeed:post:${feedId}` + idempotencyKey: `newsfeed:post:${feedId}`, + preference: { organizationId: feedData.organization.id } }); } catch (error) { console.error('Error sending newsfeed post email:', error); @@ -426,7 +427,8 @@ async function sendNewsfeedCommentEmail(feedId: string, commentContent: string) branding }, from: buildEmailFromName(`${orgName} - ClassroomIO`), - replyTo: 'noreply@classroomio.com' + replyTo: 'noreply@classroomio.com', + preference: { organizationId: feedData.organization.id } }); } catch (error) { console.error('Error sending newsfeed comment email:', error); diff --git a/apps/api/src/services/organization/audience.ts b/apps/api/src/services/organization/audience.ts index 2fa8c2ee..6df6a609 100644 --- a/apps/api/src/services/organization/audience.ts +++ b/apps/api/src/services/organization/audience.ts @@ -224,7 +224,8 @@ async function enrollAudienceStudentProfilesInCourses( }, from: buildEmailFromName(`${organization.name} (via ClassroomIO.com)`), idempotencyKey: `audience-course-welcome:${p.groupId}:${p.profileId}`, - ics: icsByGroupId.get(p.groupId) + ics: icsByGroupId.get(p.groupId), + preference: { organizationId: orgId, recipientProfileId: p.profileId } }); emailsSent++; } catch (emailError) { @@ -307,7 +308,8 @@ async function enrollAudienceStudentProfilesInCohorts( branding: buildEmailBranding(organization) }, from: buildEmailFromName(`${organization.name} (via ClassroomIO.com)`), - idempotencyKey: `audience-cohort-welcome:${pair.cohortId}:${pair.profileId}` + idempotencyKey: `audience-cohort-welcome:${pair.cohortId}:${pair.profileId}`, + preference: { organizationId: orgId, recipientProfileId: pair.profileId } }); emailsSent++; } catch (emailError) { diff --git a/apps/api/src/services/submission/submission.ts b/apps/api/src/services/submission/submission.ts index ef2aa6ad..bbf164eb 100644 --- a/apps/api/src/services/submission/submission.ts +++ b/apps/api/src/services/submission/submission.ts @@ -1005,7 +1005,11 @@ async function sendSubmissionUpdateEmail(submissionId: string, newStatusId: numb }) }, from: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), - idempotencyKey: `submission-update:${submissionId}:${newStatusId}` + idempotencyKey: `submission-update:${submissionId}:${newStatusId}`, + preference: { + organizationId: orgResult?.orgId, + recipientProfileId: fullSubmission.groupmember.profile.id + } }); } catch (error) { console.error('Failed to enqueue submission graded email:', error); @@ -1076,7 +1080,8 @@ async function sendExerciseSubmissionUpdateEmail(courseId: string, exerciseId: s }) }, from: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), - idempotencyKey: `exercise-submitted:${courseId}:${exerciseId}:${submittedBy}` + idempotencyKey: `exercise-submitted:${courseId}:${exerciseId}:${submittedBy}`, + preference: { organizationId: orgResult?.orgId } }); } catch (error) { console.error('Failed to enqueue submission received email:', error); diff --git a/apps/api/src/utils/course-completion.ts b/apps/api/src/utils/course-completion.ts index eccd579b..d8a3f9eb 100644 --- a/apps/api/src/utils/course-completion.ts +++ b/apps/api/src/utils/course-completion.ts @@ -74,7 +74,7 @@ export function scheduleCertificationCompletionWork(params: { }); const certificateUrl = `${base}/courses/${courseId}/certificates`; - await enqueueTransactionalEmail('studentCourseCompletion', { + const enqueueResult = await enqueueTransactionalEmail('studentCourseCompletion', { to: studentEmail, fields: { orgName: courseRow.orgName, @@ -89,9 +89,14 @@ export function scheduleCertificationCompletionWork(params: { }) }, from: buildEmailFromName(`${courseRow.orgName} (via ClassroomIO.com)`), - idempotencyKey: `course-completion:${groupMemberId}` + idempotencyKey: `course-completion:${groupMemberId}`, + preference: { organizationId: courseRow.orgId, recipientProfileId: profileId } }); + if (enqueueResult.jobIds.length === 0) { + return; + } + await setMemberCertificationEmailSent(groupMemberId, new Date().toISOString()); } catch (error) { console.error('scheduleCertificationCompletionWork: failed:', error); diff --git a/apps/dashboard/src/lib/features/auth/api/profile.svelte.ts b/apps/dashboard/src/lib/features/auth/api/profile.svelte.ts index fbe7ec7e..9e111add 100644 --- a/apps/dashboard/src/lib/features/auth/api/profile.svelte.ts +++ b/apps/dashboard/src/lib/features/auth/api/profile.svelte.ts @@ -137,6 +137,30 @@ export class ProfileApi extends BaseApiWithErrors { } } + async updateEmailNotifications(emailNotifications: Record) { + try { + this.isLoading = true; + this.errors = {}; + this.success = false; + + await this.execute({ + requestFn: () => classroomio.account.profile.$put({ json: { emailNotifications } }), + logContext: 'updating email notification preferences', + onSuccess: (response) => { + profileStore.update((_profile) => ({ + ..._profile, + ...response.profile + })); + this.success = true; + } + }); + } catch (error) { + this.handleError(error); + } finally { + this.isLoading = false; + } + } + async changeEmail(fields: TChangeEmailForm) { // Validate email format const result = ZChangeEmail.safeParse({ newEmail: fields.newEmail }); diff --git a/apps/dashboard/src/lib/features/org/api/org.svelte.ts b/apps/dashboard/src/lib/features/org/api/org.svelte.ts index f8bd8ea4..1675a55f 100644 --- a/apps/dashboard/src/lib/features/org/api/org.svelte.ts +++ b/apps/dashboard/src/lib/features/org/api/org.svelte.ts @@ -59,7 +59,7 @@ export interface TOrgUpdateForm { disableSignupMessage?: string; disableEmailPassword?: boolean; disableGoogleAuth?: boolean; - settings?: { signup?: { inviteOnly?: boolean } }; + settings?: { signup?: { inviteOnly?: boolean }; emailNotifications?: Record }; } /** diff --git a/apps/dashboard/src/lib/features/settings/pages/index.ts b/apps/dashboard/src/lib/features/settings/pages/index.ts index 6615556c..4485c92f 100644 --- a/apps/dashboard/src/lib/features/settings/pages/index.ts +++ b/apps/dashboard/src/lib/features/settings/pages/index.ts @@ -9,6 +9,7 @@ export { default as BillingPage } from './billing.svelte'; export { default as AiCreditsPage } from './ai-credits.svelte'; export { default as IntegrationsPage } from './integrations.svelte'; export { default as AuthPage } from './auth.svelte'; +export { default as NotificationsPage } from './notifications.svelte'; // Keep old exports for backward compatibility export { default as SsoPage } from './sso.svelte'; export { default as TokenAuthPage } from './token-auth.svelte'; diff --git a/apps/dashboard/src/lib/features/settings/pages/notifications.svelte b/apps/dashboard/src/lib/features/settings/pages/notifications.svelte new file mode 100644 index 00000000..406a138b --- /dev/null +++ b/apps/dashboard/src/lib/features/settings/pages/notifications.svelte @@ -0,0 +1,264 @@ + + + + + + + {$t('settings.notifications.personal.heading')} + + + {$t('settings.notifications.personal.description')} + + +
+ {#each personalNotificationSections as section, sectionIndex (section.id)} + {#if sectionIndex > 0} + + {/if} + + + {$t(`settings.notifications.sections.${section.id}.heading`)} + + {$t(`settings.notifications.sections.${section.id}.description`)} + + +
+ {#each section.keys as key (key)} + + +
+ {$t(`settings.notifications.toggles.${key}.label`)} + + {$t(`settings.notifications.toggles.${key}.description`)} + +
+
+ {/each} +
+
+ {/each} +
+ +
+ +
+
+ + {#if $isOrgAdmin} + + + + + + {$t('settings.notifications.org.heading')} + + + {$t('settings.notifications.org.description')} + + +
+ {#each ORG_EMAIL_NOTIFICATION_SECTIONS as section, sectionIndex (section.id)} + {#if sectionIndex > 0} + + {/if} + + + {$t(`settings.notifications.sections.${section.id}.heading`)} + + {$t(`settings.notifications.sections.${section.id}.description`)} + + +
+ {#each section.keys as key (key)} + + +
+ {$t(`settings.notifications.toggles.${key}.label`)} + + {$t(`settings.notifications.toggles.${key}.description`)} + +
+
+ {/each} +
+
+ {/each} +
+ +
+ +
+
+ {/if} +
diff --git a/apps/dashboard/src/lib/features/settings/utils/types.ts b/apps/dashboard/src/lib/features/settings/utils/types.ts index e0db4914..5306a68b 100644 --- a/apps/dashboard/src/lib/features/settings/utils/types.ts +++ b/apps/dashboard/src/lib/features/settings/utils/types.ts @@ -1,5 +1,9 @@ import { classroomio, type InferResponseType } from '$lib/utils/services/api'; +export type UpdateProfileRequest = typeof classroomio.account.profile.$put; +export type UpdateProfileSuccess = Extract, { success: true }>; +export type UpdateProfileResponseProfile = UpdateProfileSuccess['profile']; + export type GetUsageRequest = typeof classroomio.agent.usage.$get; export type GetPurchasedRequest = (typeof classroomio.agent.usage)['purchased']['$get']; export type GetLeaderboardRequest = (typeof classroomio.agent.usage)['leaderboard']['$get']; diff --git a/apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts b/apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts index 60bf5205..0c6a3910 100644 --- a/apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts +++ b/apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts @@ -210,6 +210,11 @@ export const baseNavConfig: NavItemConfig[] = [ path: '/settings', matchPattern: '^/org/[^/]+/settings/?$' }, + { + titleKey: 'settings.tabs.notifications_tab', + path: '/settings/notifications', + matchPattern: '^/org/[^/]+/settings/notifications/?$' + }, { titleKey: 'settings.tabs.organization_tab', path: '/settings/org', @@ -253,6 +258,10 @@ export const baseNavConfig: NavItemConfig[] = [ } ], nestedRoutes: [ + { + path: 'notifications', + titleKey: 'settings.tabs.notifications_tab' + }, { path: 'billing', titleKey: 'settings.tabs.billing_tab' diff --git a/apps/dashboard/src/lib/utils/translations/da.json b/apps/dashboard/src/lib/utils/translations/da.json index fab4b9f4..0885451e 100644 --- a/apps/dashboard/src/lib/utils/translations/da.json +++ b/apps/dashboard/src/lib/utils/translations/da.json @@ -3395,7 +3395,8 @@ "domains_tab": "Domæner", "teams_tab": "Hold", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "AI underviser" + "ai_tutor_tab": "AI underviser", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Åbent Akademi" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Køb for ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/de.json b/apps/dashboard/src/lib/utils/translations/de.json index c79b2c7a..4dcb7390 100644 --- a/apps/dashboard/src/lib/utils/translations/de.json +++ b/apps/dashboard/src/lib/utils/translations/de.json @@ -3395,7 +3395,8 @@ "domains_tab": "Domänen", "teams_tab": "Mannschaften", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "KI-Tutor" + "ai_tutor_tab": "KI-Tutor", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Offene Akademie" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Für ${dollars} kaufen" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/en.json b/apps/dashboard/src/lib/utils/translations/en.json index 7c23d061..233a7c2f 100644 --- a/apps/dashboard/src/lib/utils/translations/en.json +++ b/apps/dashboard/src/lib/utils/translations/en.json @@ -398,6 +398,7 @@ }, "tabs": { "profile_tab": "Profile", + "notifications_tab": "Notifications", "organization_tab": "Organization", "landing_page_tab": "Landing Page", "billing_tab": "Billing", @@ -435,6 +436,75 @@ "update_profile": "Update Profile", "logout": "Log out" }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "sections": { + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + } + }, + "toggles": { + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + } + } + }, "organization": { "page_subtitle": "Set the name, logo, and theme students see for this organization.", "organization_profile": { diff --git a/apps/dashboard/src/lib/utils/translations/es.json b/apps/dashboard/src/lib/utils/translations/es.json index aab37867..ddaaf2c3 100644 --- a/apps/dashboard/src/lib/utils/translations/es.json +++ b/apps/dashboard/src/lib/utils/translations/es.json @@ -3395,7 +3395,8 @@ "domains_tab": "Dominios", "teams_tab": "equipos", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Tutor de IA" + "ai_tutor_tab": "Tutor de IA", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Academia Abierta" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Comprar por ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/fr.json b/apps/dashboard/src/lib/utils/translations/fr.json index c6b4f721..f859fca9 100644 --- a/apps/dashboard/src/lib/utils/translations/fr.json +++ b/apps/dashboard/src/lib/utils/translations/fr.json @@ -3395,7 +3395,8 @@ "domains_tab": "Domaines", "teams_tab": "Équipes", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Tuteur IA" + "ai_tutor_tab": "Tuteur IA", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Académie ouverte" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Acheter pour ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/hi.json b/apps/dashboard/src/lib/utils/translations/hi.json index b8fb285c..d6b20714 100644 --- a/apps/dashboard/src/lib/utils/translations/hi.json +++ b/apps/dashboard/src/lib/utils/translations/hi.json @@ -3395,7 +3395,8 @@ "domains_tab": "डोमेन", "teams_tab": "टीमें", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "एआई ट्यूटर" + "ai_tutor_tab": "एआई ट्यूटर", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "अकादमी खोलें" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "${dollars} में खरीदें" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/pl.json b/apps/dashboard/src/lib/utils/translations/pl.json index d0df1b86..df68c6c5 100644 --- a/apps/dashboard/src/lib/utils/translations/pl.json +++ b/apps/dashboard/src/lib/utils/translations/pl.json @@ -3395,7 +3395,8 @@ "domains_tab": "Domeny", "teams_tab": "Zespoły", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Nauczyciel AI" + "ai_tutor_tab": "Nauczyciel AI", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Otwarta Akademia" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Kup za ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/pt.json b/apps/dashboard/src/lib/utils/translations/pt.json index 0d1bc60d..f03a3d0c 100644 --- a/apps/dashboard/src/lib/utils/translations/pt.json +++ b/apps/dashboard/src/lib/utils/translations/pt.json @@ -3395,7 +3395,8 @@ "domains_tab": "Domínios", "teams_tab": "Equipes", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Tutor de IA" + "ai_tutor_tab": "Tutor de IA", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Academia Aberta" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Comprar por ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/ru.json b/apps/dashboard/src/lib/utils/translations/ru.json index d3f192b2..6ac2fbb0 100644 --- a/apps/dashboard/src/lib/utils/translations/ru.json +++ b/apps/dashboard/src/lib/utils/translations/ru.json @@ -3395,7 +3395,8 @@ "domains_tab": "Домены", "teams_tab": "Команды", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Репетитор по искусственному интеллекту" + "ai_tutor_tab": "Репетитор по искусственному интеллекту", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Открытая Академия" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Купить за ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/lib/utils/translations/vi.json b/apps/dashboard/src/lib/utils/translations/vi.json index 7feb8f5b..fa8d9f43 100644 --- a/apps/dashboard/src/lib/utils/translations/vi.json +++ b/apps/dashboard/src/lib/utils/translations/vi.json @@ -3395,7 +3395,8 @@ "domains_tab": "Tên miền", "teams_tab": "Đội", "ai_credits_tab": "AI Credits", - "ai_tutor_tab": "Gia sư AI" + "ai_tutor_tab": "Gia sư AI", + "notifications_tab": "Notifications" }, "subheadings": { "view_site": "Học viện mở" @@ -3569,6 +3570,75 @@ "preview": "{quantity} pack(s) · {tokens} credits · ${dollars}", "buy_button": "Mua với ${dollars}" } + }, + "notifications": { + "heading": "Notifications", + "page_title": "Notifications", + "page_subtitle": "Choose which email notifications you receive and, as an admin, which emails your organization sends.", + "toggles": { + "courseCompletion": { + "label": "Course completion and certificates", + "description": "Email students when they complete a course and earn a certificate." + }, + "enrollmentWelcome": { + "label": "Enrollment welcome emails", + "description": "Send welcome emails when staff enroll students in courses or cohorts." + }, + "session": { + "label": "Live session reminders and updates", + "description": "Email students before live sessions and when session details change." + }, + "cohortReminder": { + "label": "Cohort goal reminders", + "description": "Email students about upcoming or overdue cohort learning goals." + }, + "quizAssigned": { + "label": "Exercise assigned", + "description": "Email students when an exercise is assigned or nudged." + }, + "newsfeed": { + "label": "Course announcements and comments", + "description": "Email course members about new posts and comments on the newsfeed." + }, + "gradingResult": { + "label": "Grading results", + "description": "Email students when their submission is graded or updated." + }, + "newSubmission": { + "label": "New submissions to grade", + "description": "Email tutors when a student submits work that needs grading." + }, + "newStudent": { + "label": "New student joins", + "description": "Email tutors when a student enrolls in a course." + } + }, + "org": { + "heading": "Organization defaults", + "description": "When disabled here, nobody in your organization receives that email. Members can still opt out of emails you leave enabled.", + "save": "Save organization defaults", + "save_success": "Organization notification defaults saved" + }, + "personal": { + "heading": "Your email preferences", + "description": "These apply to emails sent to you across every organization you belong to. Your organization may still disable certain emails for everyone.", + "save": "Save personal preferences", + "save_success": "Personal notification preferences saved" + }, + "sections": { + "enrollment": { + "heading": "Enrollment", + "description": "Welcome emails sent when staff add members to courses or cohorts." + }, + "cohort": { + "heading": "Cohort", + "description": "Reminders about cohort learning goals and deadlines." + }, + "course": { + "heading": "Course", + "description": "Enrollments, submissions, lessons, announcements, and completions in your courses." + } + } } }, "profileMenu": { diff --git a/apps/dashboard/src/routes/(app)/org/[slug]/settings/notifications/+page.svelte b/apps/dashboard/src/routes/(app)/org/[slug]/settings/notifications/+page.svelte new file mode 100644 index 00000000..b0d392e8 --- /dev/null +++ b/apps/dashboard/src/routes/(app)/org/[slug]/settings/notifications/+page.svelte @@ -0,0 +1,21 @@ + + + + {t.get('settings.notifications.page_title')} - ClassroomIO + + + + + {$t('settings.notifications.heading')} + {$t('settings.notifications.page_subtitle')} + + + + {#snippet child()} + + {/snippet} + diff --git a/apps/jobs/src/processors/notifications/notify-course-exercise.ts b/apps/jobs/src/processors/notifications/notify-course-exercise.ts index 3dc255af..c1656772 100644 --- a/apps/jobs/src/processors/notifications/notify-course-exercise.ts +++ b/apps/jobs/src/processors/notifications/notify-course-exercise.ts @@ -1,4 +1,5 @@ import { getCourseMembers } from '@cio/db/queries/course/people'; +import { EmailPreferenceLookupCache } from '@cio/db/queries/notifications'; import { ZNotifyCourseExercisePayload, enqueueEmailSend } from '@cio/jobs'; import { ROLE } from '@cio/utils/constants'; @@ -20,17 +21,33 @@ export async function processNotifyCourseExercise(rawPayload: unknown, jobId: st const members = await getCourseMembers(payload.courseId); const recipients = members .filter((member) => member.roleId === ROLE.STUDENT) - .map((member) => member.profile?.email) - .filter((email): email is string => !!email); + .map((member) => ({ + email: member.profile?.email, + profileId: member.profile?.id + })) + .filter((recipient): recipient is { email: string; profileId: string | undefined } => !!recipient.email); let notified = 0; - for (const email of recipients) { + const preferenceCache = new EmailPreferenceLookupCache(); + + for (const recipient of recipients) { try { + const allowed = await preferenceCache.shouldSend({ + emailId: 'quizAssigned', + organizationId: payload.organizationId, + recipientEmail: recipient.email, + recipientProfileId: recipient.profileId + }); + + if (!allowed) { + continue; + } + await enqueueEmailSend( { kind: 'template', template: 'quizAssigned', - to: email, + to: recipient.email, fields: { orgName: payload.orgName, courseName: payload.courseName, @@ -41,11 +58,11 @@ export async function processNotifyCourseExercise(rawPayload: unknown, jobId: st from: payload.fromName }, // Vary by the notify job id so re-running the nudge re-sends. - { idempotencyKey: `quiz-assigned:${jobId}:${email}` } + { idempotencyKey: `quiz-assigned:${jobId}:${recipient.email}` } ); notified += 1; } catch (error) { - log.error('notify-course-exercise-enqueue-failed', { jobId, recipient: email }); + log.error('notify-course-exercise-enqueue-failed', { jobId, recipient: recipient.email }); throw error; } } diff --git a/apps/jobs/src/processors/notifications/notify-session-update.ts b/apps/jobs/src/processors/notifications/notify-session-update.ts index fa24e0a2..492fc1b8 100644 --- a/apps/jobs/src/processors/notifications/notify-session-update.ts +++ b/apps/jobs/src/processors/notifications/notify-session-update.ts @@ -1,6 +1,7 @@ import { getCourseById, getCourseWithOrgData } from '@cio/db/queries/course'; import { getCourseMembers } from '@cio/db/queries/course/people'; import { getLessonById } from '@cio/db/queries/lesson'; +import { EmailPreferenceLookupCache } from '@cio/db/queries/notifications'; import { ROLE } from '@cio/utils/constants'; import { buildEmailBranding, buildEmailFromName, buildSessionIcs } from '@cio/email'; import { ZNotifyCourseSessionUpdatePayload, enqueueEmailSend } from '@cio/jobs'; @@ -68,17 +69,33 @@ export async function processNotifyCourseSessionUpdate(rawPayload: unknown): Pro const members = await getCourseMembers(courseId); const recipients = members .filter((member) => member.roleId === ROLE.STUDENT) - .map((member) => member.profile?.email) - .filter((email): email is string => !!email); + .map((member) => ({ + email: member.profile?.email, + profileId: member.profile?.id + })) + .filter((recipient): recipient is { email: string; profileId: string | undefined } => !!recipient.email); let notified = 0; - for (const email of recipients) { + const preferenceCache = new EmailPreferenceLookupCache(); + + for (const recipient of recipients) { try { + const allowed = await preferenceCache.shouldSend({ + emailId: 'sessionUpdated', + organizationId: orgData.orgId, + recipientEmail: recipient.email, + recipientProfileId: recipient.profileId + }); + + if (!allowed) { + continue; + } + await enqueueEmailSend( { kind: 'template', template: 'sessionUpdated', - to: email, + to: recipient.email, fields: { orgName, courseName: course.title, @@ -90,11 +107,11 @@ export async function processNotifyCourseSessionUpdate(rawPayload: unknown): Pro from: buildEmailFromName(`${orgName} (via ClassroomIO.com)`), ics }, - { idempotencyKey: `session-updated:${lesson.id}:${email}:${sequence}` } + { idempotencyKey: `session-updated:${lesson.id}:${recipient.email}:${sequence}` } ); notified += 1; } catch (error) { - log.error('notify-session-update-enqueue-failed', { lessonId, recipient: email }); + log.error('notify-session-update-enqueue-failed', { lessonId, recipient: recipient.email }); } } diff --git a/apps/jobs/src/processors/notifications/session-reminder-scan.ts b/apps/jobs/src/processors/notifications/session-reminder-scan.ts index 9ee12671..a3d812db 100644 --- a/apps/jobs/src/processors/notifications/session-reminder-scan.ts +++ b/apps/jobs/src/processors/notifications/session-reminder-scan.ts @@ -1,4 +1,5 @@ import { listUpcomingSessionsForReminderScan } from '@cio/db/queries/course'; +import { EmailPreferenceLookupCache } from '@cio/db/queries/notifications'; import { buildEmailBranding, buildEmailFromName, buildSessionIcs } from '@cio/email'; import { enqueueEmailSend } from '@cio/jobs'; @@ -34,6 +35,7 @@ export async function processSessionReminderScan(): Promise { const now = Date.now(); let remindersEnqueued = 0; + const preferenceCache = new EmailPreferenceLookupCache(); for (const row of rows) { if (!row.email) continue; @@ -70,6 +72,17 @@ export async function processSessionReminderScan(): Promise { : undefined; try { + const allowed = await preferenceCache.shouldSend({ + emailId: 'sessionReminder', + organizationId: row.organizationId, + recipientEmail: row.email, + recipientProfileId: row.profileId + }); + + if (!allowed) { + continue; + } + await enqueueEmailSend( { kind: 'template', diff --git a/packages/db/src/migrations/0003_profile_settings.sql b/packages/db/src/migrations/0003_profile_settings.sql new file mode 100644 index 00000000..258d620b --- /dev/null +++ b/packages/db/src/migrations/0003_profile_settings.sql @@ -0,0 +1 @@ +ALTER TABLE "profile" ADD COLUMN "settings" jsonb DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0003_snapshot.json b/packages/db/src/migrations/meta/0003_snapshot.json new file mode 100644 index 00000000..f7204641 --- /dev/null +++ b/packages/db/src/migrations/meta/0003_snapshot.json @@ -0,0 +1,13108 @@ +{ + "id": "17f4e73c-bf2c-4202-a0ec-0f3191ebdf8c", + "prevId": "0655b405-657c-4e96-a92a-156024ed5399", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_agent_run": { + "name": "ai_agent_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "AI_AGENT_RUN_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "phase": { + "name": "phase", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_step_key": { + "name": "current_step_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "approved_plan": { + "name": "approved_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_cursor": { + "name": "execution_cursor", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "source_ids": { + "name": "source_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "model_summary": { + "name": "model_summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "queued_instructions": { + "name": "queued_instructions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "worker_id": { + "name": "worker_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancel_requested_at": { + "name": "cancel_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_agent_run_course_user": { + "name": "idx_ai_agent_run_course_user", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_agent_run_org_status": { + "name": "idx_ai_agent_run_org_status", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_agent_run_conversation": { + "name": "idx_ai_agent_run_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_agent_run_updated_at": { + "name": "idx_ai_agent_run_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_agent_run_org_id_fkey": { + "name": "ai_agent_run_org_id_fkey", + "tableFrom": "ai_agent_run", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_agent_run_course_id_fkey": { + "name": "ai_agent_run_course_id_fkey", + "tableFrom": "ai_agent_run", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_agent_run_conversation_id_fkey": { + "name": "ai_agent_run_conversation_id_fkey", + "tableFrom": "ai_agent_run", + "tableTo": "ai_chat_conversation", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "ai_agent_run_user_id_fkey": { + "name": "ai_agent_run_user_id_fkey", + "tableFrom": "ai_agent_run", + "tableTo": "profile", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_agent_run_event": { + "name": "ai_agent_run_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_agent_run_event_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_agent_run_event_run_created": { + "name": "idx_ai_agent_run_event_run_created", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_agent_run_event_run_id_fkey": { + "name": "ai_agent_run_event_run_id_fkey", + "tableFrom": "ai_agent_run_event", + "tableTo": "ai_agent_run", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_agent_run_step": { + "name": "ai_agent_run_step", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_key": { + "name": "step_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "step_type": { + "name": "step_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "AI_AGENT_RUN_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "input_hash": { + "name": "input_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output_ids": { + "name": "output_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_agent_run_step_run_status": { + "name": "idx_ai_agent_run_step_run_status", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_agent_run_step_run_id_fkey": { + "name": "ai_agent_run_step_run_id_fkey", + "tableFrom": "ai_agent_run_step", + "tableTo": "ai_agent_run", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_agent_run_step_run_step_unique": { + "name": "ai_agent_run_step_run_step_unique", + "nullsNotDistinct": false, + "columns": [ + "run_id", + "step_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_chat_conversation": { + "name": "ai_chat_conversation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'New conversation'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_chat_conversation_course_user": { + "name": "idx_ai_chat_conversation_course_user", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_chat_conversation_course_id_fkey": { + "name": "ai_chat_conversation_course_id_fkey", + "tableFrom": "ai_chat_conversation", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_chat_document": { + "name": "ai_chat_document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "word_count": { + "name": "word_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "page_count": { + "name": "page_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_chat_document_conversation": { + "name": "idx_ai_chat_document_conversation", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_chat_document_conversation_id_fkey": { + "name": "ai_chat_document_conversation_id_fkey", + "tableFrom": "ai_chat_document", + "tableTo": "ai_chat_conversation", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_chat_model_context": { + "name": "ai_chat_model_context", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_summary": { + "name": "model_summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "compacted_through_message_id": { + "name": "compacted_through_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ids": { + "name": "source_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_chat_model_context_course_user": { + "name": "idx_ai_chat_model_context_course_user", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_chat_model_context_conversation_id_fkey": { + "name": "ai_chat_model_context_conversation_id_fkey", + "tableFrom": "ai_chat_model_context", + "tableTo": "ai_chat_conversation", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_chat_model_context_course_id_fkey": { + "name": "ai_chat_model_context_course_id_fkey", + "tableFrom": "ai_chat_model_context", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ai_chat_model_context_user_id_fkey": { + "name": "ai_chat_model_context_user_id_fkey", + "tableFrom": "ai_chat_model_context", + "tableTo": "profile", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_credit_balance": { + "name": "ai_credit_balance", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_credit_balance_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_credit_balance_org_id_fkey": { + "name": "ai_credit_balance_org_id_fkey", + "tableFrom": "ai_credit_balance", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_credit_balance_org_id_key": { + "name": "ai_credit_balance_org_id_key", + "nullsNotDistinct": false, + "columns": [ + "org_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_credit_purchase": { + "name": "ai_credit_purchase", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_credit_purchase_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "triggered_by": { + "name": "triggered_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'polar'" + }, + "provider_order_id": { + "name": "provider_order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "unit_price_cents": { + "name": "unit_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_credit_purchase_org_created": { + "name": "idx_ai_credit_purchase_org_created", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_credit_purchase_org_id_fkey": { + "name": "ai_credit_purchase_org_id_fkey", + "tableFrom": "ai_credit_purchase", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ai_credit_purchase_triggered_by_fkey": { + "name": "ai_credit_purchase_triggered_by_fkey", + "tableFrom": "ai_credit_purchase", + "tableTo": "profile", + "columnsFrom": [ + "triggered_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_credit_purchase_provider_order_id_key": { + "name": "ai_credit_purchase_provider_order_id_key", + "nullsNotDistinct": false, + "columns": [ + "provider_order_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_token_usage": { + "name": "ai_token_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_token_usage_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cost_units": { + "name": "cost_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_token_usage_org_month": { + "name": "idx_ai_token_usage_org_month", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_token_usage_org_id_fkey": { + "name": "ai_token_usage_org_id_fkey", + "tableFrom": "ai_token_usage", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_tutor_cap_event": { + "name": "ai_tutor_cap_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_tutor_cap_event_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_tutor_cap_event_org_occurred": { + "name": "idx_ai_tutor_cap_event_org_occurred", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_tutor_cap_event_user": { + "name": "idx_ai_tutor_cap_event_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_tutor_cap_event_org_id_fkey": { + "name": "ai_tutor_cap_event_org_id_fkey", + "tableFrom": "ai_tutor_cap_event", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_tutor_cap_event_user_id_fkey": { + "name": "ai_tutor_cap_event_user_id_fkey", + "tableFrom": "ai_tutor_cap_event", + "tableTo": "profile", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_tutor_message_count": { + "name": "ai_tutor_message_count", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "ai_tutor_message_count_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cap_hit_at": { + "name": "cap_hit_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_tutor_message_count_org_period": { + "name": "idx_ai_tutor_message_count_org_period", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_tutor_message_count_user": { + "name": "idx_ai_tutor_message_count_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_tutor_message_count_org_id_fkey": { + "name": "ai_tutor_message_count_org_id_fkey", + "tableFrom": "ai_tutor_message_count", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_tutor_message_count_user_id_fkey": { + "name": "ai_tutor_message_count_user_id_fkey", + "tableFrom": "ai_tutor_message_count", + "tableTo": "profile", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_tutor_message_count_org_user_period_key": { + "name": "ai_tutor_message_count_org_user_period_key", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "user_id", + "period_start" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_country_daily": { + "name": "analytics_country_daily", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "varchar(2)", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enrollments": { + "name": "enrollments", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_acntd_org_date": { + "name": "idx_acntd_org_date", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "analytics_country_daily_org_date_country_unique": { + "name": "analytics_country_daily_org_date_country_unique", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "date", + "country" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_course_daily": { + "name": "analytics_course_daily", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unique_visitors": { + "name": "unique_visitors", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enrollments": { + "name": "enrollments", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avg_time_to_enroll_seconds": { + "name": "avg_time_to_enroll_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_acd_org_date": { + "name": "idx_acd_org_date", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_acd_course_date": { + "name": "idx_acd_course_date", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "analytics_course_daily_course_date_unique": { + "name": "analytics_course_daily_course_date_unique", + "nullsNotDistinct": false, + "columns": [ + "course_id", + "date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_login_events": { + "name": "analytics_login_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "logged_in_at": { + "name": "logged_in_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "logged_in_date": { + "name": "logged_in_date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_DATE" + } + }, + "indexes": { + "idx_analytics_login_events_logged_in_at": { + "name": "idx_analytics_login_events_logged_in_at", + "columns": [ + { + "expression": "logged_in_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analytics_login_events_user_id": { + "name": "idx_analytics_login_events_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analytics_login_events_logged_in_date": { + "name": "idx_analytics_login_events_logged_in_date", + "columns": [ + { + "expression": "logged_in_date", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "date_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analytics_login_events_user_id_fkey": { + "name": "analytics_login_events_user_id_fkey", + "tableFrom": "analytics_login_events", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "analytics_login_events_user_id_date_unique": { + "name": "analytics_login_events_user_id_date_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "logged_in_date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_org_daily": { + "name": "analytics_org_daily", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "landing_views": { + "name": "landing_views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unique_visitors": { + "name": "unique_visitors", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "course_page_views": { + "name": "course_page_views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enrollments": { + "name": "enrollments", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completions": { + "name": "completions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_aod_org_date": { + "name": "idx_aod_org_date", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "analytics_org_daily_org_date_unique": { + "name": "analytics_org_daily_org_date_unique", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_page_event": { + "name": "analytics_page_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "referrer_host": { + "name": "referrer_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_source": { + "name": "utm_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_medium": { + "name": "utm_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_campaign": { + "name": "utm_campaign", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "varchar(2)", + "primaryKey": false, + "notNull": false + }, + "device_type": { + "name": "device_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "varchar(8)", + "primaryKey": false, + "notNull": false + }, + "props": { + "name": "props", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_ape_org_occurred": { + "name": "idx_ape_org_occurred", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ape_course_occurred": { + "name": "idx_ape_course_occurred", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ape_session": { + "name": "idx_ape_session", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ape_event_type": { + "name": "idx_ape_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ape_user": { + "name": "idx_ape_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apps_poll": { + "name": "apps_poll", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorId": { + "name": "authorId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'draft'" + }, + "expiration": { + "name": "expiration", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "courseId": { + "name": "courseId", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apps_poll_authorId_fkey": { + "name": "apps_poll_authorId_fkey", + "tableFrom": "apps_poll", + "tableTo": "groupmember", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "apps_poll_courseId_fkey": { + "name": "apps_poll_courseId_fkey", + "tableFrom": "apps_poll", + "tableTo": "course", + "columnsFrom": [ + "courseId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apps_poll_option": { + "name": "apps_poll_option", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "apps_poll_option_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apps_poll_option_poll_id_fkey": { + "name": "apps_poll_option_poll_id_fkey", + "tableFrom": "apps_poll_option", + "tableTo": "apps_poll", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apps_poll_submission": { + "name": "apps_poll_submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "apps_poll_submision_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "poll_option_id": { + "name": "poll_option_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "selected_by_id": { + "name": "selected_by_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apps_poll_submission_poll_id_fkey": { + "name": "apps_poll_submission_poll_id_fkey", + "tableFrom": "apps_poll_submission", + "tableTo": "apps_poll", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "apps_poll_submission_poll_option_id_fkey": { + "name": "apps_poll_submission_poll_option_id_fkey", + "tableFrom": "apps_poll_submission", + "tableTo": "apps_poll_option", + "columnsFrom": [ + "poll_option_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "apps_poll_submission_selected_by_id_fkey": { + "name": "apps_poll_submission_selected_by_id_fkey", + "tableFrom": "apps_poll_submission", + "tableTo": "groupmember", + "columnsFrom": [ + "selected_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'video'" + }, + "provider": { + "name": "provider", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'upload'" + }, + "storage_provider": { + "name": "storage_provider", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'s3'" + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hls_manifest_key": { + "name": "hls_manifest_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hls_audio_key": { + "name": "hls_audio_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumbnail_url": { + "name": "thumbnail_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumbnail_candidates": { + "name": "thumbnail_candidates", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "aspect_ratio": { + "name": "aspect_ratio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_external": { + "name": "is_external", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_assets_organization_id": { + "name": "idx_assets_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_assets_organization_status": { + "name": "idx_assets_organization_status", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_assets_organization_kind_status": { + "name": "idx_assets_organization_kind_status", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_assets_organization_created_at": { + "name": "idx_assets_organization_created_at", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_assets_organization_byte_size": { + "name": "idx_assets_organization_byte_size", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "byte_size", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_organization_id_fkey": { + "name": "assets_organization_id_fkey", + "tableFrom": "assets", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "assets_created_by_profile_id_fkey": { + "name": "assets_created_by_profile_id_fkey", + "tableFrom": "assets", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "assets_org_provider_storage_key_unique": { + "name": "assets_org_provider_storage_key_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider", + "storage_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset_usages": { + "name": "asset_usages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slot_type": { + "name": "slot_type", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'lesson_video'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_asset_usages_org_id": { + "name": "idx_asset_usages_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_asset_usages_asset_id": { + "name": "idx_asset_usages_asset_id", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_asset_usages_target": { + "name": "idx_asset_usages_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "asset_usages_organization_id_fkey": { + "name": "asset_usages_organization_id_fkey", + "tableFrom": "asset_usages", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "asset_usages_asset_id_fkey": { + "name": "asset_usages_asset_id_fkey", + "tableFrom": "asset_usages", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "asset_usages_created_by_profile_id_fkey": { + "name": "asset_usages_created_by_profile_id_fkey", + "tableFrom": "asset_usages", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "asset_usages_asset_target_slot_unique": { + "name": "asset_usages_asset_target_slot_unique", + "nullsNotDistinct": false, + "columns": [ + "asset_id", + "target_type", + "target_id", + "slot_type", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort": { + "name": "cohort", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_image": { + "name": "cover_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "COHORT_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_cohort_organization_id": { + "name": "idx_cohort_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_organization_id_fkey": { + "name": "cohort_organization_id_fkey", + "tableFrom": "cohort", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cohort_created_by_profile_id_fkey": { + "name": "cohort_created_by_profile_id_fkey", + "tableFrom": "cohort", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_course": { + "name": "cohort_course", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cohort_id": { + "name": "cohort_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cohort_course_cohort_id": { + "name": "idx_cohort_course_cohort_id", + "columns": [ + { + "expression": "cohort_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_course_cohort_id_fkey": { + "name": "cohort_course_cohort_id_fkey", + "tableFrom": "cohort_course", + "tableTo": "cohort", + "columnsFrom": [ + "cohort_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cohort_course_course_id_fkey": { + "name": "cohort_course_course_id_fkey", + "tableFrom": "cohort_course", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cohort_course_cohort_id_course_id_unique": { + "name": "cohort_course_cohort_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "cohort_id", + "course_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_goal": { + "name": "cohort_goal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cohort_id": { + "name": "cohort_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "COHORT_GOAL_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "course_ids": { + "name": "course_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "required_count": { + "name": "required_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "score_threshold": { + "name": "score_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_pass_rate_threshold": { + "name": "team_pass_rate_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deadline_kind": { + "name": "deadline_kind", + "type": "COHORT_GOAL_DEADLINE_KIND", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "deadline_date": { + "name": "deadline_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "relative_days": { + "name": "relative_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recurring_months": { + "name": "recurring_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reminder_days_before": { + "name": "reminder_days_before", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[7,1]'::jsonb" + }, + "status": { + "name": "status", + "type": "COHORT_GOAL_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cohort_goal_cohort_id": { + "name": "idx_cohort_goal_cohort_id", + "columns": [ + { + "expression": "cohort_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cohort_goal_status": { + "name": "idx_cohort_goal_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_goal_cohort_id_fkey": { + "name": "cohort_goal_cohort_id_fkey", + "tableFrom": "cohort_goal", + "tableTo": "cohort", + "columnsFrom": [ + "cohort_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cohort_goal_created_by_profile_id_fkey": { + "name": "cohort_goal_created_by_profile_id_fkey", + "tableFrom": "cohort_goal", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_goal_assignment": { + "name": "cohort_goal_assignment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cohort_member_id": { + "name": "cohort_member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "COHORT_GOAL_ASSIGNMENT_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'not_started'" + }, + "completed_count": { + "name": "completed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "required_count": { + "name": "required_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_evaluated_at": { + "name": "last_evaluated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cohort_goal_assignment_goal_id": { + "name": "idx_cohort_goal_assignment_goal_id", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cohort_goal_assignment_cohort_member_id": { + "name": "idx_cohort_goal_assignment_cohort_member_id", + "columns": [ + { + "expression": "cohort_member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cohort_goal_assignment_due_date": { + "name": "idx_cohort_goal_assignment_due_date", + "columns": [ + { + "expression": "due_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_goal_assignment_goal_id_fkey": { + "name": "cohort_goal_assignment_goal_id_fkey", + "tableFrom": "cohort_goal_assignment", + "tableTo": "cohort_goal", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cohort_goal_assignment_cohort_member_id_fkey": { + "name": "cohort_goal_assignment_cohort_member_id_fkey", + "tableFrom": "cohort_goal_assignment", + "tableTo": "cohort_member", + "columnsFrom": [ + "cohort_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cohort_goal_assignment_goal_id_cohort_member_id_unique": { + "name": "cohort_goal_assignment_goal_id_cohort_member_id_unique", + "nullsNotDistinct": false, + "columns": [ + "goal_id", + "cohort_member_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_member": { + "name": "cohort_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cohort_id": { + "name": "cohort_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cohort_member_cohort_id": { + "name": "idx_cohort_member_cohort_id", + "columns": [ + { + "expression": "cohort_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cohort_member_profile_id": { + "name": "idx_cohort_member_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_member_cohort_id_fkey": { + "name": "cohort_member_cohort_id_fkey", + "tableFrom": "cohort_member", + "tableTo": "cohort", + "columnsFrom": [ + "cohort_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cohort_member_profile_id_fkey": { + "name": "cohort_member_profile_id_fkey", + "tableFrom": "cohort_member", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cohort_member_role_id_fkey": { + "name": "cohort_member_role_id_fkey", + "tableFrom": "cohort_member", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cohort_member_cohort_id_profile_id_unique": { + "name": "cohort_member_cohort_id_profile_id_unique", + "nullsNotDistinct": false, + "columns": [ + "cohort_id", + "profile_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_newsfeed": { + "name": "cohort_newsfeed", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cohort_id": { + "name": "cohort_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reaction": { + "name": "reaction", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"clap\":[],\"smile\":[],\"thumbsup\":[],\"thumbsdown\":[]}'::jsonb" + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_cohort_newsfeed_cohort_id": { + "name": "idx_cohort_newsfeed_cohort_id", + "columns": [ + { + "expression": "cohort_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cohort_newsfeed_author_id_fkey": { + "name": "cohort_newsfeed_author_id_fkey", + "tableFrom": "cohort_newsfeed", + "tableTo": "cohort_member", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cohort_newsfeed_cohort_id_fkey": { + "name": "cohort_newsfeed_cohort_id_fkey", + "tableFrom": "cohort_newsfeed", + "tableTo": "cohort", + "columnsFrom": [ + "cohort_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cohort_newsfeed_comment": { + "name": "cohort_newsfeed_comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "cohort_newsfeed_comment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cohort_newsfeed_id": { + "name": "cohort_newsfeed_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "cohort_newsfeed_comment_author_id_fkey": { + "name": "cohort_newsfeed_comment_author_id_fkey", + "tableFrom": "cohort_newsfeed_comment", + "tableTo": "cohort_member", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cohort_newsfeed_comment_cohort_newsfeed_id_fkey": { + "name": "cohort_newsfeed_comment_cohort_newsfeed_id_fkey", + "tableFrom": "cohort_newsfeed_comment", + "tableTo": "cohort_newsfeed", + "columnsFrom": [ + "cohort_newsfeed_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.community_answer": { + "name": "community_answer", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "question_id": { + "name": "question_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "votes": { + "name": "votes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_profile_id": { + "name": "author_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "community_answer_author_id_fkey": { + "name": "community_answer_author_id_fkey", + "tableFrom": "community_answer", + "tableTo": "organizationmember", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "community_answer_author_profile_id_fkey": { + "name": "community_answer_author_profile_id_fkey", + "tableFrom": "community_answer", + "tableTo": "profile", + "columnsFrom": [ + "author_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "community_answer_question_id_fkey": { + "name": "community_answer_question_id_fkey", + "tableFrom": "community_answer", + "tableTo": "community_question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.community_question": { + "name": "community_question", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "community_question_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "votes": { + "name": "votes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_profile_id": { + "name": "author_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "community_question_author_id_fkey": { + "name": "community_question_author_id_fkey", + "tableFrom": "community_question", + "tableTo": "organizationmember", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "community_question_author_profile_id_fkey": { + "name": "community_question_author_profile_id_fkey", + "tableFrom": "community_question", + "tableTo": "profile", + "columnsFrom": [ + "author_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "community_question_course_id_fkey": { + "name": "community_question_course_id_fkey", + "tableFrom": "community_question", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "community_question_organization_id_fkey": { + "name": "community_question_organization_id_fkey", + "tableFrom": "community_question", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course": { + "name": "course", + "schema": "", + "columns": { + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "overview": { + "name": "overview", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Welcome to this amazing course 🚀 '" + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_template": { + "name": "is_template", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"goals\":\"\",\"description\":\"\",\"requirements\":\"\"}'::jsonb" + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "currency": { + "name": "currency", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "banner_image": { + "name": "banner_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "certificate": { + "name": "certificate", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "compliance": { + "name": "compliance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "type": { + "name": "type", + "type": "COURSE_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'SELF_PACED'" + }, + "callout": { + "name": "callout", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_course_group_id": { + "name": "idx_course_group_id", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_group_id_fkey": { + "name": "course_group_id_fkey", + "tableFrom": "course", + "tableTo": "group", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_slug_key": { + "name": "course_slug_key", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_certificate_issue": { + "name": "course_certificate_issue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_completion_record_id": { + "name": "course_completion_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cycle_number": { + "name": "cycle_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'valid'" + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_course_certificate_issue_course_id": { + "name": "idx_course_certificate_issue_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_certificate_issue_profile_id": { + "name": "idx_course_certificate_issue_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_certificate_issue_status": { + "name": "idx_course_certificate_issue_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_certificate_issue_course_id_fkey": { + "name": "course_certificate_issue_course_id_fkey", + "tableFrom": "course_certificate_issue", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_certificate_issue_profile_id_fkey": { + "name": "course_certificate_issue_profile_id_fkey", + "tableFrom": "course_certificate_issue", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_certificate_issue_record_id_fkey": { + "name": "course_certificate_issue_record_id_fkey", + "tableFrom": "course_certificate_issue", + "tableTo": "course_completion_record", + "columnsFrom": [ + "course_completion_record_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_certificate_issue_record_id_key": { + "name": "course_certificate_issue_record_id_key", + "nullsNotDistinct": false, + "columns": [ + "course_completion_record_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_completion_notification_event": { + "name": "course_completion_notification_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_completion_record_id": { + "name": "course_completion_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "course_completion_notification_event_record_id_fkey": { + "name": "course_completion_notification_event_record_id_fkey", + "tableFrom": "course_completion_notification_event", + "tableTo": "course_completion_record", + "columnsFrom": [ + "course_completion_record_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_completion_notification_event_record_channel_type_key": { + "name": "course_completion_notification_event_record_channel_type_key", + "nullsNotDistinct": false, + "columns": [ + "course_completion_record_id", + "channel", + "event_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_completion_record": { + "name": "course_completion_record", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_member_id": { + "name": "group_member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cycle_number": { + "name": "cycle_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'not_started'" + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "valid_until": { + "name": "valid_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "time_spent_minutes": { + "name": "time_spent_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "waived_by": { + "name": "waived_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "waiver_reason": { + "name": "waiver_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "waiver_expires_at": { + "name": "waiver_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_course_completion_record_course_id": { + "name": "idx_course_completion_record_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_completion_record_profile_id": { + "name": "idx_course_completion_record_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_completion_record_status": { + "name": "idx_course_completion_record_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_completion_record_due_date": { + "name": "idx_course_completion_record_due_date", + "columns": [ + { + "expression": "due_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_completion_record_valid_until": { + "name": "idx_course_completion_record_valid_until", + "columns": [ + { + "expression": "valid_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_completion_record_course_id_fkey": { + "name": "course_completion_record_course_id_fkey", + "tableFrom": "course_completion_record", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_completion_record_group_member_id_fkey": { + "name": "course_completion_record_group_member_id_fkey", + "tableFrom": "course_completion_record", + "tableTo": "groupmember", + "columnsFrom": [ + "group_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_completion_record_profile_id_fkey": { + "name": "course_completion_record_profile_id_fkey", + "tableFrom": "course_completion_record", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_completion_record_waived_by_fkey": { + "name": "course_completion_record_waived_by_fkey", + "tableFrom": "course_completion_record", + "tableTo": "profile", + "columnsFrom": [ + "waived_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_completion_record_course_profile_cycle_key": { + "name": "course_completion_record_course_profile_cycle_key", + "nullsNotDistinct": false, + "columns": [ + "course_id", + "profile_id", + "cycle_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_import_draft": { + "name": "course_import_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "COURSE_IMPORT_SOURCE_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "COURSE_IMPORT_DRAFT_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "locale": { + "name": "locale", + "type": "LOCALE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'en'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "draft": { + "name": "draft", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "warnings": { + "name": "warnings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "sourceArtifacts": { + "name": "sourceArtifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "published_course_id": { + "name": "published_course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_course_import_draft_org_id": { + "name": "idx_course_import_draft_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_import_draft_status": { + "name": "idx_course_import_draft_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "course_import_draft_org_idempotency_key": { + "name": "course_import_draft_org_idempotency_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"course_import_draft\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_import_draft_organization_id_organization_id_fk": { + "name": "course_import_draft_organization_id_organization_id_fk", + "tableFrom": "course_import_draft", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_import_draft_created_by_profile_id_profile_id_fk": { + "name": "course_import_draft_created_by_profile_id_profile_id_fk", + "tableFrom": "course_import_draft", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_import_draft_published_course_id_course_id_fk": { + "name": "course_import_draft_published_course_id_course_id_fk", + "tableFrom": "course_import_draft", + "tableTo": "course", + "columnsFrom": [ + "published_course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_invite": { + "name": "course_invite", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'3'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revoked_by_profile_id": { + "name": "revoked_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "used_count": { + "name": "used_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "allowed_domains": { + "name": "allowed_domains", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_course_invite_course_id": { + "name": "idx_course_invite_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_expires_at": { + "name": "idx_course_invite_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_created_by": { + "name": "idx_course_invite_created_by", + "columns": [ + { + "expression": "created_by_profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_revoked_by": { + "name": "idx_course_invite_revoked_by", + "columns": [ + { + "expression": "revoked_by_profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_invite_course_id_fkey": { + "name": "course_invite_course_id_fkey", + "tableFrom": "course_invite", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_invite_created_by_profile_id_fkey": { + "name": "course_invite_created_by_profile_id_fkey", + "tableFrom": "course_invite", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_invite_revoked_by_profile_id_fkey": { + "name": "course_invite_revoked_by_profile_id_fkey", + "tableFrom": "course_invite", + "tableTo": "profile", + "columnsFrom": [ + "revoked_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_invite_role_id_fkey": { + "name": "course_invite_role_id_fkey", + "tableFrom": "course_invite", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "course_invite_token_hash_key": { + "name": "course_invite_token_hash_key", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_invite_audit": { + "name": "course_invite_audit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "COURSE_INVITE_EVENT_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "actor_profile_id": { + "name": "actor_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_email": { + "name": "target_email", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_course_invite_audit_invite_id": { + "name": "idx_course_invite_audit_invite_id", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_audit_course_id": { + "name": "idx_course_invite_audit_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_audit_event_type": { + "name": "idx_course_invite_audit_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_course_invite_audit_created_at": { + "name": "idx_course_invite_audit_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "course_invite_audit_invite_id_fkey": { + "name": "course_invite_audit_invite_id_fkey", + "tableFrom": "course_invite_audit", + "tableTo": "course_invite", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_invite_audit_course_id_fkey": { + "name": "course_invite_audit_course_id_fkey", + "tableFrom": "course_invite_audit", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "course_invite_audit_actor_profile_id_fkey": { + "name": "course_invite_audit_actor_profile_id_fkey", + "tableFrom": "course_invite_audit", + "tableTo": "profile", + "columnsFrom": [ + "actor_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_newsfeed": { + "name": "course_newsfeed", + "schema": "", + "columns": { + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reaction": { + "name": "reaction", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"clap\":[],\"smile\":[],\"thumbsup\":[],\"thumbsdown\":[]}'::jsonb" + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "course_newsfeed_author_id_fkey": { + "name": "course_newsfeed_author_id_fkey", + "tableFrom": "course_newsfeed", + "tableTo": "groupmember", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_newsfeed_course_id_fkey": { + "name": "course_newsfeed_course_id_fkey", + "tableFrom": "course_newsfeed", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_newsfeed_comment": { + "name": "course_newsfeed_comment", + "schema": "", + "columns": { + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "course_newsfeed_comment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "course_newsfeed_id": { + "name": "course_newsfeed_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "course_newsfeed_comment_author_id_fkey": { + "name": "course_newsfeed_comment_author_id_fkey", + "tableFrom": "course_newsfeed_comment", + "tableTo": "groupmember", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "course_newsfeed_comment_course_newsfeed_id_fkey": { + "name": "course_newsfeed_comment_course_newsfeed_id_fkey", + "tableFrom": "course_newsfeed_comment", + "tableTo": "course_newsfeed", + "columnsFrom": [ + "course_newsfeed_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.course_section": { + "name": "course_section", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "course_section_course_id_course_id_fk": { + "name": "course_section_course_id_course_id_fk", + "tableFrom": "course_section", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency": { + "name": "currency", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "currency_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dead_letter_job": { + "name": "dead_letter_job", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "queue_name": { + "name": "queue_name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "job_name": { + "name": "job_name", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "bullmq_job_id": { + "name": "bullmq_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "replayed_at": { + "name": "replayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_dead_letter_job_domain_created": { + "name": "idx_dead_letter_job_domain_created", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_dead_letter_job_org_created": { + "name": "idx_dead_letter_job_org_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exercise": { + "name": "exercise", + "schema": "", + "columns": { + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "is_unlocked": { + "name": "is_unlocked", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "due_by": { + "name": "due_by", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "allow_multiple_attempts": { + "name": "allow_multiple_attempts", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "section_display_mode": { + "name": "section_display_mode", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'one_question'" + }, + "completion_policy": { + "name": "completion_policy", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'submitted'" + }, + "pass_threshold": { + "name": "pass_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_exercise_course_slug": { + "name": "idx_exercise_course_slug", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "exercise_lesson_id_fkey": { + "name": "exercise_lesson_id_fkey", + "tableFrom": "exercise", + "tableTo": "lesson", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "exercise_course_id_fkey": { + "name": "exercise_course_id_fkey", + "tableFrom": "exercise", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "exercise_section_id_fkey": { + "name": "exercise_section_id_fkey", + "tableFrom": "exercise", + "tableTo": "course_section", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exercise_section": { + "name": "exercise_section", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "exercise_id": { + "name": "exercise_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'Untitled Section'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "color_theme": { + "name": "color_theme", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'blue'" + }, + "after_behavior": { + "name": "after_behavior", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"action\":\"continue\"}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_exercise_section_exercise_id": { + "name": "idx_exercise_section_exercise_id", + "columns": [ + { + "expression": "exercise_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "exercise_section_exercise_id_fkey": { + "name": "exercise_section_exercise_id_fkey", + "tableFrom": "exercise_section", + "tableTo": "exercise", + "columnsFrom": [ + "exercise_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exercise_template": { + "name": "exercise_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "exercise_template_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag": { + "name": "tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "questionnaire": { + "name": "questionnaire", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group": { + "name": "group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_group_organization_id": { + "name": "idx_group_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_organization_id_fkey": { + "name": "group_organization_id_fkey", + "tableFrom": "group", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_attendance": { + "name": "group_attendance", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "group_attendance_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "student_id": { + "name": "student_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_present": { + "name": "is_present", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "group_attendance_course_id_fkey": { + "name": "group_attendance_course_id_fkey", + "tableFrom": "group_attendance", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "group_attendance_student_id_fkey": { + "name": "group_attendance_student_id_fkey", + "tableFrom": "group_attendance", + "tableTo": "groupmember", + "columnsFrom": [ + "student_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.groupmember": { + "name": "groupmember", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "assigned_student_id": { + "name": "assigned_student_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "certificate_earned_at": { + "name": "certificate_earned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "certification_email_sent_at": { + "name": "certification_email_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupmember_group_id_fkey": { + "name": "groupmember_group_id_fkey", + "tableFrom": "groupmember", + "tableTo": "group", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "groupmember_profile_id_fkey": { + "name": "groupmember_profile_id_fkey", + "tableFrom": "groupmember", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "groupmember_role_id_fkey": { + "name": "groupmember_role_id_fkey", + "tableFrom": "groupmember", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_entries": { + "name": "unique_entries", + "nullsNotDistinct": false, + "columns": [ + "group_id", + "profile_id", + "email" + ] + }, + "unique_group_email": { + "name": "unique_group_email", + "nullsNotDistinct": false, + "columns": [ + "group_id", + "email" + ] + }, + "unique_group_profile": { + "name": "unique_group_profile", + "nullsNotDistinct": false, + "columns": [ + "group_id", + "profile_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_step": { + "name": "job_step", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "domain": { + "name": "domain", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_key": { + "name": "step_key", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "JOB_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "input_hash": { + "name": "input_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_job_step_run": { + "name": "idx_job_step_run", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "job_step_run_step_unique": { + "name": "job_step_run_step_unique", + "nullsNotDistinct": false, + "columns": [ + "domain", + "run_id", + "step_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson": { + "name": "lesson", + "schema": "", + "columns": { + "note": { + "name": "note", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "video_url": { + "name": "video_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "slide_url": { + "name": "slide_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "lesson_at": { + "name": "lesson_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "teacher_id": { + "name": "teacher_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "call_url": { + "name": "call_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_unlocked": { + "name": "is_unlocked", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "video_watch_threshold": { + "name": "video_watch_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 95 + }, + "videos": { + "name": "videos", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "documents": { + "name": "documents", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_lesson_course_slug": { + "name": "idx_lesson_course_slug", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "lesson_section_id_course_section_id_fk": { + "name": "lesson_section_id_course_section_id_fk", + "tableFrom": "lesson", + "tableTo": "course_section", + "columnsFrom": [ + "section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lesson_course_id_fkey": { + "name": "lesson_course_id_fkey", + "tableFrom": "lesson", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "lesson_teacher_id_fkey": { + "name": "lesson_teacher_id_fkey", + "tableFrom": "lesson", + "tableTo": "profile", + "columnsFrom": [ + "teacher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson_comment": { + "name": "lesson_comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "lesson_comment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "groupmember_id": { + "name": "groupmember_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "lesson_comment_groupmember_id_fkey": { + "name": "lesson_comment_groupmember_id_fkey", + "tableFrom": "lesson_comment", + "tableTo": "groupmember", + "columnsFrom": [ + "groupmember_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "lesson_comment_lesson_id_fkey": { + "name": "lesson_comment_lesson_id_fkey", + "tableFrom": "lesson_comment", + "tableTo": "lesson", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson_completion": { + "name": "lesson_completion", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "lesson_completion_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "lesson_completion_lesson_id_fkey": { + "name": "lesson_completion_lesson_id_fkey", + "tableFrom": "lesson_completion", + "tableTo": "lesson", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "lesson_completion_profile_id_fkey": { + "name": "lesson_completion_profile_id_fkey", + "tableFrom": "lesson_completion", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_lesson_profile": { + "name": "unique_lesson_profile", + "nullsNotDistinct": false, + "columns": [ + "lesson_id", + "profile_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson_language": { + "name": "lesson_language", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "lesson_language_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "locale": { + "name": "locale", + "type": "LOCALE", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'en'" + } + }, + "indexes": {}, + "foreignKeys": { + "public_lesson_language_lesson_id_fkey": { + "name": "public_lesson_language_lesson_id_fkey", + "tableFrom": "lesson_language", + "tableTo": "lesson", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson_language_history": { + "name": "lesson_language_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "lesson_language_id": { + "name": "lesson_language_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "old_content": { + "name": "old_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_content": { + "name": "new_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "public_lesson_language_history_lesson_language_id_fkey": { + "name": "public_lesson_language_history_lesson_language_id_fkey", + "tableFrom": "lesson_language_history", + "tableTo": "lesson_language", + "columnsFrom": [ + "lesson_language_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lesson_video_progress": { + "name": "lesson_video_progress", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "watched_seconds": { + "name": "watched_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "furthest_seconds": { + "name": "furthest_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_position_seconds": { + "name": "last_position_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "lesson_video_progress_lesson_id_fkey": { + "name": "lesson_video_progress_lesson_id_fkey", + "tableFrom": "lesson_video_progress", + "tableTo": "lesson", + "columnsFrom": [ + "lesson_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "lesson_video_progress_profile_id_fkey": { + "name": "lesson_video_progress_profile_id_fkey", + "tableFrom": "lesson_video_progress", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "lesson_video_progress_lesson_profile_asset_unique": { + "name": "lesson_video_progress_lesson_profile_asset_unique", + "nullsNotDistinct": false, + "columns": [ + "lesson_id", + "profile_id", + "asset_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_job": { + "name": "media_job", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "triggered_by_profile_id": { + "name": "triggered_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "JOB_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "stage": { + "name": "stage", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "root_job_id": { + "name": "root_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_ids": { + "name": "job_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "cancel_requested_at": { + "name": "cancel_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "warnings": { + "name": "warnings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_media_job_asset_id": { + "name": "idx_media_job_asset_id", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_job_org_status": { + "name": "idx_media_job_org_status", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_job_updated_at": { + "name": "idx_media_job_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_job_organization_id_fkey": { + "name": "media_job_organization_id_fkey", + "tableFrom": "media_job", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "media_job_asset_id_fkey": { + "name": "media_job_asset_id_fkey", + "tableFrom": "media_job", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "media_job_triggered_by_profile_id_fkey": { + "name": "media_job_triggered_by_profile_id_fkey", + "tableFrom": "media_job", + "tableTo": "profile", + "columnsFrom": [ + "triggered_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_transcript": { + "name": "media_transcript", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "media_job_id": { + "name": "media_job_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'openai'" + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "default": "'whisper-1'" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "segments": { + "name": "segments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vtt_storage_key": { + "name": "vtt_storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vtt_bucket": { + "name": "vtt_bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_media_transcript_asset": { + "name": "idx_media_transcript_asset", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_transcript_organization_id_fkey": { + "name": "media_transcript_organization_id_fkey", + "tableFrom": "media_transcript", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "media_transcript_asset_id_fkey": { + "name": "media_transcript_asset_id_fkey", + "tableFrom": "media_transcript", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "media_transcript_media_job_id_fkey": { + "name": "media_transcript_media_job_id_fkey", + "tableFrom": "media_transcript", + "tableTo": "media_job", + "columnsFrom": [ + "media_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "media_transcript_asset_id_unique": { + "name": "media_transcript_asset_id_unique", + "nullsNotDistinct": false, + "columns": [ + "asset_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.option": { + "name": "option", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "option_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_correct": { + "name": "is_correct", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "question_id": { + "name": "question_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "option_question_id_fkey": { + "name": "option_question_id_fkey", + "tableFrom": "option", + "tableTo": "question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "siteName": { + "name": "siteName", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "landingpage": { + "name": "landingpage", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'blue'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "customization": { + "name": "customization", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"apps\":{\"poll\":true,\"comments\":true},\"course\":{\"grading\":true,\"newsfeed\":true},\"dashboard\":{\"exercise\":true,\"community\":true,\"bannerText\":\"\",\"bannerImage\":\"\"}}'::json" + }, + "is_restricted": { + "name": "is_restricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customCode": { + "name": "customCode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customDomain": { + "name": "customDomain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon": { + "name": "favicon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isCustomDomainVerified": { + "name": "isCustomDomainVerified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "disable_signup": { + "name": "disable_signup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "disable_signup_message": { + "name": "disable_signup_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disable_email_password": { + "name": "disable_email_password", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "disable_google_auth": { + "name": "disable_google_auth", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "parent_organization_id": { + "name": "parent_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "read_only_until": { + "name": "read_only_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ai_tutor_settings": { + "name": "ai_tutor_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":true,\"persona\":\"encouraging\",\"customPersona\":\"\",\"responseLength\":\"medium\",\"welcomeMessage\":\"\",\"disclaimerFooter\":\"I am an AI tutor, not your instructor.\",\"thingsToSay\":\"\",\"thingsNotToSay\":\"\",\"forbiddenTopics\":[],\"groundingScope\":\"course\",\"requireCitations\":true,\"assessmentMode\":\"hint_only\",\"revealSolutionsAfterAttempts\":3,\"codePolicy\":\"hints_only\",\"blockOffTopic\":true,\"profanityFilter\":true,\"escalation\":{\"enabled\":false,\"email\":\"\"}}'::jsonb" + } + }, + "indexes": { + "idx_organization_parent_id": { + "name": "idx_organization_parent_id", + "columns": [ + { + "expression": "parent_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_parent_organization_id_fkey": { + "name": "organization_parent_organization_id_fkey", + "tableFrom": "organization", + "tableTo": "organization", + "columnsFrom": [ + "parent_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_siteName_key": { + "name": "organization_siteName_key", + "nullsNotDistinct": false, + "columns": [ + "siteName" + ] + }, + "organization_customDomain_key": { + "name": "organization_customDomain_key", + "nullsNotDistinct": false, + "columns": [ + "customDomain" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_api_key": { + "name": "organization_api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "ORGANIZATION_API_KEY_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "secret_prefix": { + "name": "secret_prefix", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_api_key_org_id": { + "name": "idx_organization_api_key_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_api_key_type": { + "name": "idx_organization_api_key_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_api_key_secret_hash_unique": { + "name": "organization_api_key_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_api_key_organization_id_organization_id_fk": { + "name": "organization_api_key_organization_id_organization_id_fk", + "tableFrom": "organization_api_key", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_api_key_created_by_profile_id_profile_id_fk": { + "name": "organization_api_key_created_by_profile_id_profile_id_fk", + "tableFrom": "organization_api_key", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_auth_policy": { + "name": "organization_auth_policy", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "force_sso": { + "name": "force_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_join_sso_domains": { + "name": "auto_join_sso_domains", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "break_glass_enabled": { + "name": "break_glass_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "default_role_id": { + "name": "default_role_id", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'3'" + }, + "role_mapping": { + "name": "role_mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_auth_policy_org_id": { + "name": "idx_organization_auth_policy_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_auth_policy_organization_id_organization_id_fk": { + "name": "organization_auth_policy_organization_id_organization_id_fk", + "tableFrom": "organization_auth_policy", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_auth_policy_default_role_id_fkey": { + "name": "organization_auth_policy_default_role_id_fkey", + "tableFrom": "organization_auth_policy", + "tableTo": "role", + "columnsFrom": [ + "default_role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_automation_usage": { + "name": "organization_automation_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_api_key_id": { + "name": "organization_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "ORGANIZATION_API_KEY_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "AUTOMATION_USAGE_CATEGORY", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "credits_consumed": { + "name": "credits_consumed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_org_automation_usage_org_created_at": { + "name": "idx_org_automation_usage_org_created_at", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_org_automation_usage_key_created_at": { + "name": "idx_org_automation_usage_key_created_at", + "columns": [ + { + "expression": "organization_api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_org_automation_usage_type_created_at": { + "name": "idx_org_automation_usage_type_created_at", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_automation_usage_organization_id_organization_id_fk": { + "name": "organization_automation_usage_organization_id_organization_id_fk", + "tableFrom": "organization_automation_usage", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_automation_usage_organization_api_key_id_organization_api_key_id_fk": { + "name": "organization_automation_usage_organization_api_key_id_organization_api_key_id_fk", + "tableFrom": "organization_automation_usage", + "tableTo": "organization_api_key", + "columnsFrom": [ + "organization_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_contacts": { + "name": "organization_contacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "organization_contacts_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_contacts_organization_id_fkey": { + "name": "organization_contacts_organization_id_fkey", + "tableFrom": "organization_contacts", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_emaillist": { + "name": "organization_emaillist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "organization_emaillist_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_emaillist_organization_id_fkey": { + "name": "organization_emaillist_organization_id_fkey", + "tableFrom": "organization_emaillist", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_invite": { + "name": "organization_invite", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "ORGANIZATION_INVITE_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'EMAIL'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_by_profile_id": { + "name": "accepted_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_profile_id": { + "name": "revoked_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_revoked": { + "name": "is_revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_invite_organization_id": { + "name": "idx_organization_invite_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_email_org": { + "name": "idx_organization_invite_email_org", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_expires_at": { + "name": "idx_organization_invite_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_created_by": { + "name": "idx_organization_invite_created_by", + "columns": [ + { + "expression": "created_by_profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_accepted_by": { + "name": "idx_organization_invite_accepted_by", + "columns": [ + { + "expression": "accepted_by_profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_revoked_by": { + "name": "idx_organization_invite_revoked_by", + "columns": [ + { + "expression": "revoked_by_profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_invite_organization_id_fkey": { + "name": "organization_invite_organization_id_fkey", + "tableFrom": "organization_invite", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_invite_role_id_fkey": { + "name": "organization_invite_role_id_fkey", + "tableFrom": "organization_invite", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_invite_created_by_profile_id_fkey": { + "name": "organization_invite_created_by_profile_id_fkey", + "tableFrom": "organization_invite", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_invite_accepted_by_profile_id_fkey": { + "name": "organization_invite_accepted_by_profile_id_fkey", + "tableFrom": "organization_invite", + "tableTo": "profile", + "columnsFrom": [ + "accepted_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_invite_revoked_by_profile_id_fkey": { + "name": "organization_invite_revoked_by_profile_id_fkey", + "tableFrom": "organization_invite", + "tableTo": "profile", + "columnsFrom": [ + "revoked_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_invite_token_hash_key": { + "name": "organization_invite_token_hash_key", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_invite_audit": { + "name": "organization_invite_audit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "ORGANIZATION_INVITE_EVENT_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "actor_profile_id": { + "name": "actor_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_email": { + "name": "target_email", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_invite_audit_invite_id": { + "name": "idx_organization_invite_audit_invite_id", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_audit_org_id": { + "name": "idx_organization_invite_audit_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_audit_event_type": { + "name": "idx_organization_invite_audit_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_invite_audit_created_at": { + "name": "idx_organization_invite_audit_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_invite_audit_invite_id_fkey": { + "name": "organization_invite_audit_invite_id_fkey", + "tableFrom": "organization_invite_audit", + "tableTo": "organization_invite", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_invite_audit_organization_id_fkey": { + "name": "organization_invite_audit_organization_id_fkey", + "tableFrom": "organization_invite_audit", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_invite_audit_actor_profile_id_fkey": { + "name": "organization_invite_audit_actor_profile_id_fkey", + "tableFrom": "organization_invite_audit", + "tableTo": "profile", + "columnsFrom": [ + "actor_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_plan": { + "name": "organization_plan", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "organization_plan_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "activated_at": { + "name": "activated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plan_name": { + "name": "plan_name", + "type": "PLAN", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "deactivated_at": { + "name": "deactivated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "triggered_by": { + "name": "triggered_by", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'lmz'" + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_organization_plan_org_id": { + "name": "idx_organization_plan_org_id", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_plan_org_id_fkey": { + "name": "organization_plan_org_id_fkey", + "tableFrom": "organization_plan", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_plan_triggered_by_fkey": { + "name": "organization_plan_triggered_by_fkey", + "tableFrom": "organization_plan", + "tableTo": "organizationmember", + "columnsFrom": [ + "triggered_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_plan_subscription_id_key": { + "name": "organization_plan_subscription_id_key", + "nullsNotDistinct": false, + "columns": [ + "subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_sso_config": { + "name": "organization_sso_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "better_auth_provider_id": { + "name": "better_auth_provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "SSO_PROVIDER", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by_profile_id": { + "name": "updated_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_sso_config_org_id": { + "name": "idx_organization_sso_config_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_sso_config_domain": { + "name": "idx_organization_sso_config_domain", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organization_sso_config_provider_id": { + "name": "idx_organization_sso_config_provider_id", + "columns": [ + { + "expression": "better_auth_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_sso_config_organization_id_organization_id_fk": { + "name": "organization_sso_config_organization_id_organization_id_fk", + "tableFrom": "organization_sso_config", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_sso_config_created_by_profile_id_profile_id_fk": { + "name": "organization_sso_config_created_by_profile_id_profile_id_fk", + "tableFrom": "organization_sso_config", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organization_sso_config_updated_by_profile_id_profile_id_fk": { + "name": "organization_sso_config_updated_by_profile_id_profile_id_fk", + "tableFrom": "organization_sso_config", + "tableTo": "profile", + "columnsFrom": [ + "updated_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_sso_config_organization_id_unique": { + "name": "organization_sso_config_organization_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_token_auth": { + "name": "organization_token_auth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "signing_secret": { + "name": "signing_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organization_token_auth_org_id": { + "name": "idx_organization_token_auth_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_token_auth_organization_id_organization_id_fk": { + "name": "organization_token_auth_organization_id_organization_id_fk", + "tableFrom": "organization_token_auth", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_token_auth_created_by_profile_id_profile_id_fk": { + "name": "organization_token_auth_created_by_profile_id_profile_id_fk", + "tableFrom": "organization_token_auth", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_token_auth_organization_id_unique": { + "name": "organization_token_auth_organization_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizationmember": { + "name": "organizationmember", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "organizationmember_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_organizationmember_profile_id": { + "name": "idx_organizationmember_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organizationmember_organization_id": { + "name": "idx_organizationmember_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_organizationmember_profile_org": { + "name": "idx_organizationmember_profile_org", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizationmember_org_profile_unique": { + "name": "organizationmember_org_profile_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organizationmember\".\"profile_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizationmember_org_email_unique": { + "name": "organizationmember_org_email_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organizationmember\".\"email\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organizationmember_organization_id_fkey": { + "name": "organizationmember_organization_id_fkey", + "tableFrom": "organizationmember", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organizationmember_profile_id_fkey": { + "name": "organizationmember_profile_id_fkey", + "tableFrom": "organizationmember", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organizationmember_role_id_fkey": { + "name": "organizationmember_role_id_fkey", + "tableFrom": "organizationmember", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "can_add_course": { + "name": "can_add_course", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "goal": { + "name": "goal", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "LOCALE", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'en'" + }, + "is_restricted": { + "name": "is_restricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_profile_id": { + "name": "idx_profile_id", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_profile_email": { + "name": "idx_profile_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "profile_id_fkey": { + "name": "profile_id_fkey", + "tableFrom": "profile", + "tableTo": "user", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_username_key": { + "name": "profile_username_key", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "profile_email_key": { + "name": "profile_email_key", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program": { + "name": "program", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_image": { + "name": "cover_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "PROGRAM_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ACTIVE'" + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_program_organization_id": { + "name": "idx_program_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_organization_id_fkey": { + "name": "program_organization_id_fkey", + "tableFrom": "program", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_created_by_profile_id_fkey": { + "name": "program_created_by_profile_id_fkey", + "tableFrom": "program", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_course": { + "name": "program_course", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "program_id": { + "name": "program_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_program_course_program_id": { + "name": "idx_program_course_program_id", + "columns": [ + { + "expression": "program_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_course_program_id_fkey": { + "name": "program_course_program_id_fkey", + "tableFrom": "program_course", + "tableTo": "program", + "columnsFrom": [ + "program_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_course_course_id_fkey": { + "name": "program_course_course_id_fkey", + "tableFrom": "program_course", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_course_program_id_course_id_unique": { + "name": "program_course_program_id_course_id_unique", + "nullsNotDistinct": false, + "columns": [ + "program_id", + "course_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_goal": { + "name": "program_goal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "program_id": { + "name": "program_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "PROGRAM_GOAL_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "course_ids": { + "name": "course_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "required_count": { + "name": "required_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "score_threshold": { + "name": "score_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "team_pass_rate_threshold": { + "name": "team_pass_rate_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deadline_kind": { + "name": "deadline_kind", + "type": "PROGRAM_GOAL_DEADLINE_KIND", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "deadline_date": { + "name": "deadline_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "relative_days": { + "name": "relative_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "recurring_months": { + "name": "recurring_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reminder_days_before": { + "name": "reminder_days_before", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[7,1]'::jsonb" + }, + "status": { + "name": "status", + "type": "PROGRAM_GOAL_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_profile_id": { + "name": "created_by_profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_program_goal_program_id": { + "name": "idx_program_goal_program_id", + "columns": [ + { + "expression": "program_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_program_goal_status": { + "name": "idx_program_goal_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_goal_program_id_fkey": { + "name": "program_goal_program_id_fkey", + "tableFrom": "program_goal", + "tableTo": "program", + "columnsFrom": [ + "program_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_goal_created_by_profile_id_fkey": { + "name": "program_goal_created_by_profile_id_fkey", + "tableFrom": "program_goal", + "tableTo": "profile", + "columnsFrom": [ + "created_by_profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_goal_assignment": { + "name": "program_goal_assignment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "program_member_id": { + "name": "program_member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "PROGRAM_GOAL_ASSIGNMENT_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'not_started'" + }, + "completed_count": { + "name": "completed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "required_count": { + "name": "required_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_evaluated_at": { + "name": "last_evaluated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_program_goal_assignment_goal_id": { + "name": "idx_program_goal_assignment_goal_id", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_program_goal_assignment_program_member_id": { + "name": "idx_program_goal_assignment_program_member_id", + "columns": [ + { + "expression": "program_member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_program_goal_assignment_due_date": { + "name": "idx_program_goal_assignment_due_date", + "columns": [ + { + "expression": "due_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_goal_assignment_goal_id_fkey": { + "name": "program_goal_assignment_goal_id_fkey", + "tableFrom": "program_goal_assignment", + "tableTo": "program_goal", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_goal_assignment_program_member_id_fkey": { + "name": "program_goal_assignment_program_member_id_fkey", + "tableFrom": "program_goal_assignment", + "tableTo": "program_member", + "columnsFrom": [ + "program_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_goal_assignment_goal_id_program_member_id_unique": { + "name": "program_goal_assignment_goal_id_program_member_id_unique", + "nullsNotDistinct": false, + "columns": [ + "goal_id", + "program_member_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_member": { + "name": "program_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "program_id": { + "name": "program_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_program_member_program_id": { + "name": "idx_program_member_program_id", + "columns": [ + { + "expression": "program_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_program_member_profile_id": { + "name": "idx_program_member_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_member_program_id_fkey": { + "name": "program_member_program_id_fkey", + "tableFrom": "program_member", + "tableTo": "program", + "columnsFrom": [ + "program_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "program_member_profile_id_fkey": { + "name": "program_member_profile_id_fkey", + "tableFrom": "program_member", + "tableTo": "profile", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_member_role_id_fkey": { + "name": "program_member_role_id_fkey", + "tableFrom": "program_member", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_member_program_id_profile_id_unique": { + "name": "program_member_program_id_profile_id_unique", + "nullsNotDistinct": false, + "columns": [ + "program_id", + "profile_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_newsfeed": { + "name": "program_newsfeed", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "program_id": { + "name": "program_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reaction": { + "name": "reaction", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"clap\":[],\"smile\":[],\"thumbsup\":[],\"thumbsdown\":[]}'::jsonb" + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_program_newsfeed_program_id": { + "name": "idx_program_newsfeed_program_id", + "columns": [ + { + "expression": "program_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "program_newsfeed_author_id_fkey": { + "name": "program_newsfeed_author_id_fkey", + "tableFrom": "program_newsfeed", + "tableTo": "program_member", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_newsfeed_program_id_fkey": { + "name": "program_newsfeed_program_id_fkey", + "tableFrom": "program_newsfeed", + "tableTo": "program", + "columnsFrom": [ + "program_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_newsfeed_comment": { + "name": "program_newsfeed_comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "program_newsfeed_comment_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "program_newsfeed_id": { + "name": "program_newsfeed_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "program_newsfeed_comment_author_id_fkey": { + "name": "program_newsfeed_comment_author_id_fkey", + "tableFrom": "program_newsfeed_comment", + "tableTo": "program_member", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_newsfeed_comment_program_newsfeed_id_fkey": { + "name": "program_newsfeed_comment_program_newsfeed_id_fkey", + "tableFrom": "program_newsfeed_comment", + "tableTo": "program_newsfeed", + "columnsFrom": [ + "program_newsfeed_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.question": { + "name": "question", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "question_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "question_type_id": { + "name": "question_type_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "exercise_id": { + "name": "exercise_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "exercise_section_id": { + "name": "exercise_section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "gen_random_uuid()" + }, + "points": { + "name": "points", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "order": { + "name": "order", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "question_exercise_id_fkey": { + "name": "question_exercise_id_fkey", + "tableFrom": "question", + "tableTo": "exercise", + "columnsFrom": [ + "exercise_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "question_exercise_section_id_fkey": { + "name": "question_exercise_section_id_fkey", + "tableFrom": "question", + "tableTo": "exercise_section", + "columnsFrom": [ + "exercise_section_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "question_question_type_id_fkey": { + "name": "question_question_type_id_fkey", + "tableFrom": "question", + "tableTo": "question_type", + "columnsFrom": [ + "question_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.question_answer": { + "name": "question_answer", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "question_answer_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "answer_data": { + "name": "answer_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "answers": { + "name": "answers", + "type": "varchar[]", + "primaryKey": false, + "notNull": false + }, + "open_answer": { + "name": "open_answer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "question_id": { + "name": "question_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "group_member_id": { + "name": "group_member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "point": { + "name": "point", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + } + }, + "indexes": {}, + "foreignKeys": { + "question_answer_group_member_id_fkey": { + "name": "question_answer_group_member_id_fkey", + "tableFrom": "question_answer", + "tableTo": "groupmember", + "columnsFrom": [ + "group_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "question_answer_question_id_fkey": { + "name": "question_answer_question_id_fkey", + "tableFrom": "question_answer", + "tableTo": "question", + "columnsFrom": [ + "question_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "question_answer_submission_id_fkey": { + "name": "question_answer_submission_id_fkey", + "tableFrom": "question_answer", + "tableTo": "submission", + "columnsFrom": [ + "submission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.question_type": { + "name": "question_type", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "question_type_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "typename": { + "name": "typename", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.quiz": { + "name": "quiz", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "questions": { + "name": "questions", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "timelimit": { + "name": "timelimit", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'10s'" + }, + "theme": { + "name": "theme", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'standard'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "quiz_organization_id_fkey": { + "name": "quiz_organization_id_fkey", + "tableFrom": "quiz", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.quiz_play": { + "name": "quiz_play", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "play_quiz_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "quiz_id": { + "name": "quiz_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "players": { + "name": "players", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'::json" + }, + "started": { + "name": "started", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "currentQuestionId": { + "name": "currentQuestionId", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "showCurrentQuestionAnswer": { + "name": "showCurrentQuestionAnswer", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "isLastQuestion": { + "name": "isLastQuestion", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "step": { + "name": "step", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'CONNECT_TO_PLAY'" + }, + "studentStep": { + "name": "studentStep", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'PIN_SETUP'" + }, + "pin": { + "name": "pin", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "quiz_play_quiz_id_fkey": { + "name": "quiz_play_quiz_id_fkey", + "tableFrom": "quiz_play", + "tableTo": "quiz", + "columnsFrom": [ + "quiz_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "quiz_play_pin_key": { + "name": "quiz_play_pin_key", + "nullsNotDistinct": false, + "columns": [ + "pin" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "type": { + "name": "type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "role_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": [ + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.submission": { + "name": "submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "reviewer_id": { + "name": "reviewer_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'1'" + }, + "grading_state": { + "name": "grading_state", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "overall_status": { + "name": "overall_status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'manual_required'" + }, + "total": { + "name": "total", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "exercise_id": { + "name": "exercise_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "submitted_by": { + "name": "submitted_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "submission_course_id_fkey": { + "name": "submission_course_id_fkey", + "tableFrom": "submission", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "submission_exercise_id_fkey": { + "name": "submission_exercise_id_fkey", + "tableFrom": "submission", + "tableTo": "exercise", + "columnsFrom": [ + "exercise_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "submission_status_id_fkey": { + "name": "submission_status_id_fkey", + "tableFrom": "submission", + "tableTo": "submissionstatus", + "columnsFrom": [ + "status_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "submission_submitted_by_fkey": { + "name": "submission_submitted_by_fkey", + "tableFrom": "submission", + "tableTo": "groupmember", + "columnsFrom": [ + "submitted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.submissionstatus": { + "name": "submissionstatus", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "submission_status_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag": { + "name": "tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_tag_organization_id": { + "name": "idx_tag_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_tag_group_id": { + "name": "idx_tag_group_id", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tag_organization_id_fkey": { + "name": "tag_organization_id_fkey", + "tableFrom": "tag", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tag_group_id_fkey": { + "name": "tag_group_id_fkey", + "tableFrom": "tag", + "tableTo": "tag_group", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tag_org_slug_key": { + "name": "tag_org_slug_key", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag_assignment": { + "name": "tag_assignment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tag_id": { + "name": "tag_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_tag_assignment_tag_id": { + "name": "idx_tag_assignment_tag_id", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_tag_assignment_course_id": { + "name": "idx_tag_assignment_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tag_assignment_tag_id_fkey": { + "name": "tag_assignment_tag_id_fkey", + "tableFrom": "tag_assignment", + "tableTo": "tag", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tag_assignment_course_id_fkey": { + "name": "tag_assignment_course_id_fkey", + "tableFrom": "tag_assignment", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tag_assignment_tag_course_key": { + "name": "tag_assignment_tag_course_key", + "nullsNotDistinct": false, + "columns": [ + "tag_id", + "course_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag_group": { + "name": "tag_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_tag_group_organization_id": { + "name": "idx_tag_group_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tag_group_organization_id_fkey": { + "name": "tag_group_organization_id_fkey", + "tableFrom": "tag_group", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tag_group_org_slug_key": { + "name": "tag_group_org_slug_key", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.test_tenant": { + "name": "test_tenant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_anonymous": { + "name": "is_anonymous", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.video_transcripts": { + "name": "video_transcripts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "video_transcripts_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "muse_svid": { + "name": "muse_svid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transcript": { + "name": "transcript", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "downloaded": { + "name": "downloaded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitinglist": { + "name": "waitinglist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "waitinglist_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "constraint_name": { + "name": "constraint_name", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.widget": { + "name": "widget", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "WIDGET_STATUS", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'DRAFT'" + }, + "layout_type": { + "name": "layout_type", + "type": "WIDGET_LAYOUT_TYPE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'card_grid'" + }, + "selection_mode": { + "name": "selection_mode", + "type": "WIDGET_SELECTION_MODE", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "public_key": { + "name": "public_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "has_unpublished_changes": { + "name": "has_unpublished_changes", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "latest_published_version_id": { + "name": "latest_published_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_widget_organization_id": { + "name": "idx_widget_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_widget_status": { + "name": "idx_widget_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "widget_organization_id_fkey": { + "name": "widget_organization_id_fkey", + "tableFrom": "widget", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "widget_created_by_user_id_fkey": { + "name": "widget_created_by_user_id_fkey", + "tableFrom": "widget", + "tableTo": "user", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "widget_updated_by_user_id_fkey": { + "name": "widget_updated_by_user_id_fkey", + "tableFrom": "widget", + "tableTo": "user", + "columnsFrom": [ + "updated_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "widget_public_key_key": { + "name": "widget_public_key_key", + "nullsNotDistinct": false, + "columns": [ + "public_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.widget_course": { + "name": "widget_course", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "widget_id": { + "name": "widget_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_widget_course_widget_id": { + "name": "idx_widget_course_widget_id", + "columns": [ + { + "expression": "widget_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_widget_course_course_id": { + "name": "idx_widget_course_course_id", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "widget_course_widget_id_fkey": { + "name": "widget_course_widget_id_fkey", + "tableFrom": "widget_course", + "tableTo": "widget", + "columnsFrom": [ + "widget_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "widget_course_course_id_fkey": { + "name": "widget_course_course_id_fkey", + "tableFrom": "widget_course", + "tableTo": "course", + "columnsFrom": [ + "course_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "widget_course_widget_course_key": { + "name": "widget_course_widget_course_key", + "nullsNotDistinct": false, + "columns": [ + "widget_id", + "course_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.widget_version": { + "name": "widget_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "widget_id": { + "name": "widget_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config_snapshot": { + "name": "config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_manifest": { + "name": "runtime_manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "rolled_back_from_version_id": { + "name": "rolled_back_from_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "timezone('utc'::text, now())" + } + }, + "indexes": { + "idx_widget_version_widget_id": { + "name": "idx_widget_version_widget_id", + "columns": [ + { + "expression": "widget_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "widget_version_widget_id_fkey": { + "name": "widget_version_widget_id_fkey", + "tableFrom": "widget_version", + "tableTo": "widget", + "columnsFrom": [ + "widget_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "widget_version_published_by_user_id_fkey": { + "name": "widget_version_published_by_user_id_fkey", + "tableFrom": "widget_version", + "tableTo": "user", + "columnsFrom": [ + "published_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "widget_version_rolled_back_from_version_id_fkey": { + "name": "widget_version_rolled_back_from_version_id_fkey", + "tableFrom": "widget_version", + "tableTo": "widget_version", + "columnsFrom": [ + "rolled_back_from_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "widget_version_widget_version_key": { + "name": "widget_version_widget_version_key", + "nullsNotDistinct": false, + "columns": [ + "widget_id", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.AI_AGENT_RUN_STATUS": { + "name": "AI_AGENT_RUN_STATUS", + "schema": "public", + "values": [ + "queued", + "running", + "waiting_for_input", + "paused", + "completed", + "failed", + "canceled" + ] + }, + "public.AUTOMATION_USAGE_CATEGORY": { + "name": "AUTOMATION_USAGE_CATEGORY", + "schema": "public", + "values": [ + "read", + "write", + "publish" + ] + }, + "public.COHORT_GOAL_ASSIGNMENT_STATUS": { + "name": "COHORT_GOAL_ASSIGNMENT_STATUS", + "schema": "public", + "values": [ + "not_started", + "in_progress", + "completed", + "at_risk", + "overdue", + "waived" + ] + }, + "public.COHORT_GOAL_DEADLINE_KIND": { + "name": "COHORT_GOAL_DEADLINE_KIND", + "schema": "public", + "values": [ + "absolute", + "relative_to_join", + "recurring", + "none" + ] + }, + "public.COHORT_GOAL_STATUS": { + "name": "COHORT_GOAL_STATUS", + "schema": "public", + "values": [ + "active", + "archived" + ] + }, + "public.COHORT_GOAL_TYPE": { + "name": "COHORT_GOAL_TYPE", + "schema": "public", + "values": [ + "complete_all", + "n_of_m", + "score", + "pass_rate", + "readiness" + ] + }, + "public.COHORT_STATUS": { + "name": "COHORT_STATUS", + "schema": "public", + "values": [ + "ACTIVE", + "INACTIVE", + "ARCHIVED" + ] + }, + "public.COURSE_IMPORT_DRAFT_STATUS": { + "name": "COURSE_IMPORT_DRAFT_STATUS", + "schema": "public", + "values": [ + "DRAFT", + "PUBLISHED", + "ARCHIVED" + ] + }, + "public.COURSE_IMPORT_SOURCE_TYPE": { + "name": "COURSE_IMPORT_SOURCE_TYPE", + "schema": "public", + "values": [ + "prompt", + "pdf", + "course" + ] + }, + "public.COURSE_INVITE_EVENT_TYPE": { + "name": "COURSE_INVITE_EVENT_TYPE", + "schema": "public", + "values": [ + "CREATED", + "REVOKED", + "PREVIEWED", + "ACCEPTED", + "EMAIL_SENT", + "EMAIL_FAILED", + "ABUSE_BLOCKED" + ] + }, + "public.COURSE_TYPE": { + "name": "COURSE_TYPE", + "schema": "public", + "values": [ + "SELF_PACED", + "LIVE_CLASS", + "COMPLIANCE", + "PUBLIC" + ] + }, + "public.JOB_STATUS": { + "name": "JOB_STATUS", + "schema": "public", + "values": [ + "queued", + "running", + "completed", + "failed", + "canceled" + ] + }, + "public.LOCALE": { + "name": "LOCALE", + "schema": "public", + "values": [ + "en", + "hi", + "fr", + "pt", + "de", + "vi", + "ru", + "es", + "pl", + "da" + ] + }, + "public.ORGANIZATION_API_KEY_TYPE": { + "name": "ORGANIZATION_API_KEY_TYPE", + "schema": "public", + "values": [ + "mcp", + "api", + "zapier" + ] + }, + "public.ORGANIZATION_INVITE_EVENT_TYPE": { + "name": "ORGANIZATION_INVITE_EVENT_TYPE", + "schema": "public", + "values": [ + "CREATED", + "REVOKED", + "PREVIEWED", + "ACCEPTED", + "EMAIL_SENT", + "EMAIL_FAILED", + "ABUSE_BLOCKED" + ] + }, + "public.ORGANIZATION_INVITE_TYPE": { + "name": "ORGANIZATION_INVITE_TYPE", + "schema": "public", + "values": [ + "EMAIL", + "LINK" + ] + }, + "public.SSO_PROVIDER": { + "name": "SSO_PROVIDER", + "schema": "public", + "values": [ + "OKTA", + "GOOGLE_WORKSPACE", + "AUTH0" + ] + }, + "public.PLAN": { + "name": "PLAN", + "schema": "public", + "values": [ + "EARLY_ADOPTER", + "ENTERPRISE", + "BASIC" + ] + }, + "public.PROGRAM_GOAL_ASSIGNMENT_STATUS": { + "name": "PROGRAM_GOAL_ASSIGNMENT_STATUS", + "schema": "public", + "values": [ + "not_started", + "in_progress", + "completed", + "at_risk", + "overdue", + "waived" + ] + }, + "public.PROGRAM_GOAL_DEADLINE_KIND": { + "name": "PROGRAM_GOAL_DEADLINE_KIND", + "schema": "public", + "values": [ + "absolute", + "relative_to_join", + "recurring", + "none" + ] + }, + "public.PROGRAM_GOAL_STATUS": { + "name": "PROGRAM_GOAL_STATUS", + "schema": "public", + "values": [ + "active", + "archived" + ] + }, + "public.PROGRAM_GOAL_TYPE": { + "name": "PROGRAM_GOAL_TYPE", + "schema": "public", + "values": [ + "complete_all", + "n_of_m", + "score", + "pass_rate", + "readiness" + ] + }, + "public.PROGRAM_STATUS": { + "name": "PROGRAM_STATUS", + "schema": "public", + "values": [ + "ACTIVE", + "INACTIVE", + "ARCHIVED" + ] + }, + "public.WIDGET_LAYOUT_TYPE": { + "name": "WIDGET_LAYOUT_TYPE", + "schema": "public", + "values": [ + "card_grid", + "tag_filter", + "carousel", + "primary_course", + "compact_list", + "editorial_spotlight", + "category_shelf" + ] + }, + "public.WIDGET_SELECTION_MODE": { + "name": "WIDGET_SELECTION_MODE", + "schema": "public", + "values": [ + "manual", + "published" + ] + }, + "public.WIDGET_STATUS": { + "name": "WIDGET_STATUS", + "schema": "public", + "values": [ + "DRAFT", + "PUBLISHED", + "ARCHIVED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.dash_org_stats": { + "columns": { + "org_id": { + "name": "org_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "no_of_courses": { + "name": "no_of_courses", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "enrolled_students": { + "name": "enrolled_students", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "definition": "SELECT gp.organization_id AS org_id, count(DISTINCT course.id) AS no_of_courses, count(DISTINCT gm.profile_id) AS enrolled_students FROM course JOIN \"group\" gp ON gp.id = course.group_id LEFT JOIN groupmember gm ON gm.group_id = gp.id AND gm.role_id = 3 WHERE course.status = 'ACTIVE'::text GROUP BY gp.organization_id", + "name": "dash_org_stats", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.lesson_versions": { + "columns": { + "old_content": { + "name": "old_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "new_content": { + "name": "new_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "LOCALE", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "lesson_id": { + "name": "lesson_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "definition": "SELECT llh.old_content, llh.new_content, llh.\"timestamp\", ll.locale, ll.lesson_id FROM lesson_language_history llh JOIN lesson_language ll ON ll.id = llh.lesson_language_id", + "name": "lesson_versions", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 7d1e9221..b08364af 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1782660458258, "tag": "0002_majestic_masked_marvel", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1783909129074, + "tag": "0003_profile_settings", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/src/queries/auth/profile.ts b/packages/db/src/queries/auth/profile.ts index d7ec2665..8e033be9 100644 --- a/packages/db/src/queries/auth/profile.ts +++ b/packages/db/src/queries/auth/profile.ts @@ -108,7 +108,43 @@ export async function getProfilesByEmails(emails: string[]) { } export const updateProfile = async (id: string, data: Partial>) => { - const [updatedProfile] = await db.update(schema.profile).set(data).where(eq(schema.profile.id, id)).returning(); + let setData = { ...data }; + + if (data.settings !== undefined) { + const [existing] = await db + .select({ settings: schema.profile.settings }) + .from(schema.profile) + .where(eq(schema.profile.id, id)) + .limit(1); + + const existingSettings = (existing?.settings as Record) ?? {}; + const newSettings = data.settings as Record; + const mergedSettings: Record = { ...existingSettings }; + + for (const key of Object.keys(newSettings ?? {})) { + const existingVal = existingSettings[key]; + const newVal = newSettings[key]; + + if (newVal !== undefined) { + if ( + existingVal && + typeof existingVal === 'object' && + !Array.isArray(existingVal) && + newVal && + typeof newVal === 'object' && + !Array.isArray(newVal) + ) { + mergedSettings[key] = { ...(existingVal as Record), ...(newVal as Record) }; + } else { + mergedSettings[key] = newVal; + } + } + } + + setData = { ...setData, settings: mergedSettings as TProfile['settings'] }; + } + + const [updatedProfile] = await db.update(schema.profile).set(setData).where(eq(schema.profile.id, id)).returning(); return updatedProfile; }; diff --git a/packages/db/src/queries/cohort/goal.ts b/packages/db/src/queries/cohort/goal.ts index 9c6b3237..c8dd6842 100644 --- a/packages/db/src/queries/cohort/goal.ts +++ b/packages/db/src/queries/cohort/goal.ts @@ -102,6 +102,7 @@ export async function listAssignmentsForReminderScan(): Promise< email: string | null; cohortId: string; cohortName: string; + organizationId: string; organizationName: string; organizationSiteName: string | null; organizationCustomDomain: string | null; @@ -119,6 +120,7 @@ export async function listAssignmentsForReminderScan(): Promise< email: schema.cohortMember.email, cohortId: schema.cohort.id, cohortName: schema.cohort.name, + organizationId: schema.organization.id, organizationName: schema.organization.name, organizationSiteName: schema.organization.siteName, organizationCustomDomain: schema.organization.customDomain, @@ -147,6 +149,7 @@ export async function listAssignmentsForReminderScan(): Promise< email: row.email, cohortId: row.cohortId, cohortName: row.cohortName, + organizationId: row.organizationId, organizationName: row.organizationName, organizationSiteName: row.organizationSiteName, organizationCustomDomain: row.organizationCustomDomain, diff --git a/packages/db/src/queries/course/course.ts b/packages/db/src/queries/course/course.ts index 7c3d7642..3631a7ec 100644 --- a/packages/db/src/queries/course/course.ts +++ b/packages/db/src/queries/course/course.ts @@ -675,6 +675,7 @@ export type TCourseCertificationRow = { emailMessage?: string | null; } | null; title: string; + orgId: string; orgSiteName: string | null; orgCustomDomain: string | null; orgIsCustomDomainVerified: boolean | null; @@ -691,6 +692,7 @@ export async function getCourseCertificationRow(courseId: string): Promise { + try { + const [organization] = await db + .select({ settings: schema.organization.settings }) + .from(schema.organization) + .where(eq(schema.organization.id, orgId)) + .limit(1); + + return organization?.settings?.emailNotifications; + } catch (error) { + console.error('getOrganizationEmailNotificationSettings error:', error); + throw new Error('Failed to get organization email notification settings'); + } +} + +export async function getProfileEmailNotificationSettingsById( + profileId: string +): Promise { + try { + const profile = await getProfileById(profileId); + + return profile?.settings?.emailNotifications; + } catch (error) { + console.error('getProfileEmailNotificationSettingsById error:', error); + throw new Error('Failed to get profile email notification settings'); + } +} + +export async function getProfileEmailNotificationSettingsByEmail( + email: string +): Promise { + try { + const profile = await getProfileByEmail(email); + + return profile?.settings?.emailNotifications; + } catch (error) { + console.error('getProfileEmailNotificationSettingsByEmail error:', error); + throw new Error('Failed to get profile email notification settings'); + } +} + +export async function shouldSendEmail(input: { + emailId: EmailId; + organizationId?: string; + recipientEmail?: string; + recipientProfileId?: string; +}): Promise { + return new EmailPreferenceLookupCache().shouldSend(input); +} + +/** + * Reuses org/profile settings lookups across many recipients in batch processors. + */ +export class EmailPreferenceLookupCache { + private orgSettingsById = new Map(); + private profileSettingsById = new Map(); + private profileIdByEmail = new Map(); + + async shouldSend(input: { + emailId: EmailId; + organizationId?: string; + recipientEmail?: string; + recipientProfileId?: string; + }): Promise { + const orgSettings = input.organizationId ? await this.getOrganizationSettings(input.organizationId) : undefined; + + let profileId = input.recipientProfileId; + + if (!profileId && input.recipientEmail) { + profileId = await this.getProfileIdByEmail(input.recipientEmail); + } + + const personalSettings = profileId ? await this.getProfileSettings(profileId) : undefined; + + return resolveEmailDelivery(input.emailId, orgSettings, personalSettings); + } + + private async getOrganizationSettings(orgId: string): Promise { + if (!this.orgSettingsById.has(orgId)) { + const settings = await getOrganizationEmailNotificationSettings(orgId); + this.orgSettingsById.set(orgId, settings); + } + + return this.orgSettingsById.get(orgId); + } + + private async getProfileIdByEmail(email: string): Promise { + if (!this.profileIdByEmail.has(email)) { + const profile = await getProfileByEmail(email); + this.profileIdByEmail.set(email, profile?.id); + } + + return this.profileIdByEmail.get(email); + } + + private async getProfileSettings(profileId: string): Promise { + if (!this.profileSettingsById.has(profileId)) { + const settings = await getProfileEmailNotificationSettingsById(profileId); + this.profileSettingsById.set(profileId, settings); + } + + return this.profileSettingsById.get(profileId); + } +} diff --git a/packages/db/src/queries/notifications/index.ts b/packages/db/src/queries/notifications/index.ts new file mode 100644 index 00000000..3443a91d --- /dev/null +++ b/packages/db/src/queries/notifications/index.ts @@ -0,0 +1 @@ +export * from './email-preferences'; diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index dad8a257..e3c57c93 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -409,7 +409,19 @@ export const profile = pgTable( isEmailVerified: boolean('is_email_verified').default(false), verifiedAt: timestamp('verified_at', { withTimezone: true, mode: 'string' }), locale: locale().default('en'), - isRestricted: boolean('is_restricted').default(false).notNull() + isRestricted: boolean('is_restricted').default(false).notNull(), + settings: jsonb().default({}).$type<{ + emailNotifications?: { + newStudent?: boolean; + newSubmission?: boolean; + gradingResult?: boolean; + newsfeed?: boolean; + quizAssigned?: boolean; + cohortReminder?: boolean; + session?: boolean; + courseCompletion?: boolean; + }; + }>() }, (table) => [ foreignKey({ @@ -2013,6 +2025,17 @@ export const organization = pgTable( half?: boolean; reached?: boolean; }; + emailNotifications?: { + newStudent?: boolean; + newSubmission?: boolean; + gradingResult?: boolean; + newsfeed?: boolean; + quizAssigned?: boolean; + cohortReminder?: boolean; + session?: boolean; + enrollmentWelcome?: boolean; + courseCompletion?: boolean; + }; }>(), landingpage: jsonb().default({}).$type<{ header?: { diff --git a/packages/jobs/src/payloads/notifications.ts b/packages/jobs/src/payloads/notifications.ts index 6b3b5c50..4ae0e653 100644 --- a/packages/jobs/src/payloads/notifications.ts +++ b/packages/jobs/src/payloads/notifications.ts @@ -27,6 +27,7 @@ export const ZNotifyCourseExercisePayload = z.object({ exerciseTitle: z.string().min(1), courseName: z.string().min(1), orgName: z.string().min(1), + organizationId: z.string().min(1), fromName: z.string().min(1), quizUrl: z.string().min(1), branding: ZNotifyBranding diff --git a/packages/utils/package.json b/packages/utils/package.json index 95c888e3..9d43650f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -64,6 +64,10 @@ "./org-site/og-public-url": { "types": "./dist/org-site/og-public-url.d.ts", "default": "./dist/org-site/og-public-url.js" + }, + "./notifications": { + "types": "./dist/notifications/index.d.ts", + "default": "./dist/notifications/index.js" } }, "scripts": { diff --git a/packages/utils/src/notifications/email-toggles.ts b/packages/utils/src/notifications/email-toggles.ts new file mode 100644 index 00000000..07b33aa9 --- /dev/null +++ b/packages/utils/src/notifications/email-toggles.ts @@ -0,0 +1,117 @@ +export const EMAIL_NOTIFICATION_TOGGLE_KEYS = [ + 'newStudent', + 'newSubmission', + 'gradingResult', + 'newsfeed', + 'quizAssigned', + 'cohortReminder', + 'session', + 'enrollmentWelcome', + 'courseCompletion' +] as const; + +export type EmailNotificationToggleKey = (typeof EMAIL_NOTIFICATION_TOGGLE_KEYS)[number]; + +export type PersonalEmailNotificationToggleKey = Exclude; + +export const PERSONAL_EMAIL_NOTIFICATION_TOGGLE_KEYS = EMAIL_NOTIFICATION_TOGGLE_KEYS.filter( + (key): key is PersonalEmailNotificationToggleKey => key !== 'enrollmentWelcome' +); + +/** Personal toggles that only apply to tutors/admins — hidden from pure students in the UI. */ +export const TUTOR_ONLY_PERSONAL_EMAIL_NOTIFICATION_TOGGLE_KEYS = ['newStudent', 'newSubmission'] as const; + +export type TutorOnlyPersonalEmailNotificationToggleKey = + (typeof TUTOR_ONLY_PERSONAL_EMAIL_NOTIFICATION_TOGGLE_KEYS)[number]; + +export type EmailNotificationSectionId = 'course' | 'cohort' | 'enrollment'; + +export type EmailNotificationSection = { + id: EmailNotificationSectionId; + keys: readonly TKey[]; +}; + +export const PERSONAL_EMAIL_NOTIFICATION_SECTIONS: EmailNotificationSection[] = [ + { + id: 'course', + keys: ['newStudent', 'newSubmission', 'gradingResult', 'newsfeed', 'quizAssigned', 'session', 'courseCompletion'] + }, + { + id: 'cohort', + keys: ['cohortReminder'] + } +]; + +export const ORG_EMAIL_NOTIFICATION_SECTIONS: EmailNotificationSection[] = [ + { + id: 'course', + keys: ['newStudent', 'newSubmission', 'gradingResult', 'newsfeed', 'quizAssigned', 'session', 'courseCompletion'] + }, + { + id: 'cohort', + keys: ['cohortReminder'] + }, + { + id: 'enrollment', + keys: ['enrollmentWelcome'] + } +]; + +export type EmailNotificationSettings = Partial>; + +export type PersonalEmailNotificationSettings = Partial>; + +export const EMAIL_TOGGLE_MAP = { + teacherStudentJoined: 'newStudent', + submissionReceived: 'newSubmission', + submissionGraded: 'gradingResult', + newsfeedPost: 'newsfeed', + newsfeedComment: 'newsfeed', + quizAssigned: 'quizAssigned', + cohortGoalReminder: 'cohortReminder', + sessionReminder: 'session', + sessionUpdated: 'session', + studentCourseWelcome: 'enrollmentWelcome', + studentCohortWelcome: 'enrollmentWelcome', + teacherCourseWelcome: 'enrollmentWelcome', + studentCourseCompletion: 'courseCompletion' +} as const satisfies Record; + +export type ToggleableEmailId = keyof typeof EMAIL_TOGGLE_MAP; + +export function getToggleKeyForEmail(emailId: string): EmailNotificationToggleKey | undefined { + return EMAIL_TOGGLE_MAP[emailId as ToggleableEmailId]; +} + +export function isMandatoryEmail(emailId: string): boolean { + return getToggleKeyForEmail(emailId) === undefined; +} + +/** + * Pure preference resolution. Absent keys resolve to allowed (opt-out default). + */ +export function resolveEmailDelivery( + emailId: string, + orgSettings?: EmailNotificationSettings | null, + personalSettings?: PersonalEmailNotificationSettings | null +): boolean { + const toggleKey = getToggleKeyForEmail(emailId); + + if (!toggleKey) { + return true; + } + + if (orgSettings?.[toggleKey] === false) { + return false; + } + + if (toggleKey === 'enrollmentWelcome') { + return true; + } + + if (personalSettings?.[toggleKey] === false) { + return false; + } + + return true; +} diff --git a/packages/utils/src/notifications/index.ts b/packages/utils/src/notifications/index.ts new file mode 100644 index 00000000..bd9dd795 --- /dev/null +++ b/packages/utils/src/notifications/index.ts @@ -0,0 +1 @@ +export * from './email-toggles'; diff --git a/packages/utils/src/validation/account/account.ts b/packages/utils/src/validation/account/account.ts index 8903b655..c9accc07 100644 --- a/packages/utils/src/validation/account/account.ts +++ b/packages/utils/src/validation/account/account.ts @@ -1,10 +1,22 @@ import * as z from 'zod'; +const ZEmailNotificationToggleFields = { + newStudent: z.boolean().optional(), + newSubmission: z.boolean().optional(), + gradingResult: z.boolean().optional(), + newsfeed: z.boolean().optional(), + quizAssigned: z.boolean().optional(), + cohortReminder: z.boolean().optional(), + session: z.boolean().optional(), + courseCompletion: z.boolean().optional() +}; + export const ZUpdateProfile = z.object({ fullname: z.string().min(3).optional(), username: z.string().min(3).optional(), locale: z.enum(['en', 'hi', 'fr', 'pt', 'de', 'vi', 'ru', 'es', 'pl', 'da']).optional(), - avatarUrl: z.url().optional() + avatarUrl: z.url().optional(), + emailNotifications: z.object(ZEmailNotificationToggleFields).optional() }); export type TUpdateProfile = z.infer; diff --git a/packages/utils/src/validation/organization/organization.ts b/packages/utils/src/validation/organization/organization.ts index 22037c86..d43bf2c0 100644 --- a/packages/utils/src/validation/organization/organization.ts +++ b/packages/utils/src/validation/organization/organization.ts @@ -115,7 +115,20 @@ export const ZUpdateOrganization = z.object({ inviteOnly: z.boolean().optional() }) .optional(), - internalEnrollmentOnly: z.boolean().optional() + internalEnrollmentOnly: z.boolean().optional(), + emailNotifications: z + .object({ + newStudent: z.boolean().optional(), + newSubmission: z.boolean().optional(), + gradingResult: z.boolean().optional(), + newsfeed: z.boolean().optional(), + quizAssigned: z.boolean().optional(), + cohortReminder: z.boolean().optional(), + session: z.boolean().optional(), + enrollmentWelcome: z.boolean().optional(), + courseCompletion: z.boolean().optional() + }) + .optional() }) .optional() });