2025-11-01 15:49:29 +01:00
import {
bigint ,
boolean ,
doublePrecision ,
foreignKey ,
index ,
integer ,
json ,
jsonb ,
pgEnum ,
pgTable ,
pgView ,
serial ,
text ,
timestamp ,
unique ,
uuid ,
varchar
} from 'drizzle-orm/pg-core' ;
2026-03-09 11:18:22 +01:00
import type { AnswerData } from '@cio/question-types' ;
2025-11-01 15:49:29 +01:00
import { sql } from 'drizzle-orm' ;
export const courseType = pgEnum ( 'COURSE_TYPE' , [ 'SELF_PACED' , 'LIVE_CLASS' ]);
export const locale = pgEnum ( 'LOCALE' , [ 'en' , 'hi' , 'fr' , 'pt' , 'de' , 'vi' , 'ru' , 'es' , 'pl' , 'da' ]);
export const plan = pgEnum ( 'PLAN' , [ 'EARLY_ADOPTER' , 'ENTERPRISE' , 'BASIC' ]);
2026-03-09 11:18:22 +01:00
export const courseInviteEventType = pgEnum ( 'COURSE_INVITE_EVENT_TYPE' , [
'CREATED' ,
'REVOKED' ,
'PREVIEWED' ,
'ACCEPTED' ,
'EMAIL_SENT' ,
'EMAIL_FAILED' ,
'ABUSE_BLOCKED'
]);
export const organizationInviteEventType = pgEnum ( 'ORGANIZATION_INVITE_EVENT_TYPE' , [
'CREATED' ,
'REVOKED' ,
'PREVIEWED' ,
'ACCEPTED' ,
'EMAIL_SENT' ,
'EMAIL_FAILED' ,
'ABUSE_BLOCKED'
]);
2025-11-01 15:49:29 +01:00
2025-11-24 01:00:50 +01:00
export const user = pgTable ( 'user' , {
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-24 01:00:50 +01:00
. primaryKey ()
. notNull (),
name : text ( 'name' ). notNull (),
email : text ( 'email' ). notNull (). unique (),
emailVerified : boolean ( 'email_verified' ). default ( false ). notNull (),
image : text ( 'image' ),
createdAt : timestamp ( 'created_at' ). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' )
. defaultNow ()
. $onUpdate (() => /* @__PURE__ */ new Date ())
. notNull (),
role : text ( 'role' ),
banned : boolean ( 'banned' ). default ( false ),
banReason : text ( 'ban_reason' ),
banExpires : timestamp ( 'ban_expires' ),
isAnonymous : boolean ( 'is_anonymous' )
});
2026-03-09 11:18:22 +01:00
export const ssoProvider = pgTable ( 'sso_provider' , {
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
issuer : text ( 'issuer' ). notNull (),
oidcConfig : text ( 'oidc_config' ),
samlConfig : text ( 'saml_config' ),
userId : uuid ( 'user_id' )
. notNull ()
. references (() => user . id , { onDelete : 'cascade' }),
providerId : text ( 'provider_id' ). notNull (). unique (),
organizationId : text ( 'organization_id' ),
domain : text ( 'domain' ). notNull ()
});
2025-11-24 01:00:50 +01:00
export const session = pgTable ( 'session' , {
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-24 01:00:50 +01:00
. primaryKey ()
. notNull (),
expiresAt : timestamp ( 'expires_at' ). notNull (),
token : text ( 'token' ). notNull (). unique (),
createdAt : timestamp ( 'created_at' ). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' )
. $onUpdate (() => /* @__PURE__ */ new Date ())
. notNull (),
ipAddress : text ( 'ip_address' ),
userAgent : text ( 'user_agent' ),
userId : uuid ( 'user_id' )
. notNull ()
. references (() => user . id , { onDelete : 'cascade' }),
impersonatedBy : text ( 'impersonated_by' )
});
export const account = pgTable ( 'account' , {
id : uuid ( 'id' )
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-24 01:00:50 +01:00
. primaryKey ()
. notNull (),
accountId : text ( 'account_id' ). notNull (),
providerId : text ( 'provider_id' ). notNull (),
userId : uuid ( 'user_id' )
. notNull ()
. references (() => user . id , { onDelete : 'cascade' }),
accessToken : text ( 'access_token' ),
refreshToken : text ( 'refresh_token' ),
idToken : text ( 'id_token' ),
accessTokenExpiresAt : timestamp ( 'access_token_expires_at' ),
refreshTokenExpiresAt : timestamp ( 'refresh_token_expires_at' ),
scope : text ( 'scope' ),
password : text ( 'password' ),
createdAt : timestamp ( 'created_at' ). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' )
. $onUpdate (() => /* @__PURE__ */ new Date ())
. notNull ()
});
export const verification = pgTable ( 'verification' , {
id : uuid ( 'id' )
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-24 01:00:50 +01:00
. primaryKey ()
. notNull (),
identifier : text ( 'identifier' ). notNull (),
value : text ( 'value' ). notNull (),
expiresAt : timestamp ( 'expires_at' ). notNull (),
createdAt : timestamp ( 'created_at' ). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' )
. defaultNow ()
. $onUpdate (() => /* @__PURE__ */ new Date ())
. notNull ()
});
2025-11-01 15:49:29 +01:00
export const analyticsLoginEvents = pgTable (
'analytics_login_events' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
userId : uuid ( 'user_id' ). notNull (),
loggedInAt : timestamp ( 'logged_in_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
( table ) => [
index ( 'idx_analytics_login_events_logged_in_at' ). using (
'btree' ,
table . loggedInAt . asc (). nullsLast (). op ( 'timestamptz_ops' )
),
index ( 'idx_analytics_login_events_user_id' ). using ( 'btree' , table . userId . asc (). nullsLast (). op ( 'uuid_ops' )),
foreignKey ({
columns : [ table . userId ],
foreignColumns : [ user . id ],
name : 'analytics_login_events_user_id_fkey'
}),
2025-12-02 09:54:38 +01:00
unique ( 'analytics_login_events_user_id_unique' ). on ( table . userId )
2025-11-01 15:49:29 +01:00
]
);
2026-03-09 11:18:22 +01:00
export const courseSection = pgTable (
'course_section' ,
2025-11-01 15:49:29 +01:00
{
id : uuid (). defaultRandom (). primaryKey (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
title : varchar (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
order : bigint ({ mode : 'number' }). default ( sql `'0'` ),
courseId : uuid ( 'course_id' )
},
( table ) => [
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
2026-03-09 11:18:22 +01:00
name : 'public_course_section_course_id_fkey'
2025-11-01 15:49:29 +01:00
})
. onUpdate ( 'cascade' )
2025-12-02 09:54:38 +01:00
. onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const group = pgTable (
'group' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
name : varchar (). notNull (),
description : text (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
organizationId : uuid ( 'organization_id' )
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'group_organization_id_fkey'
2026-03-09 11:18:22 +01:00
}),
index ( 'idx_group_organization_id' ). on ( table . organizationId )
2025-11-01 15:49:29 +01:00
]
);
export const groupAttendance = pgTable (
'group_attendance' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'group_attendance_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
courseId : uuid ( 'course_id' ),
studentId : uuid ( 'student_id' ),
isPresent : boolean ( 'is_present' ). default ( false ),
lessonId : uuid ( 'lesson_id' ). notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'group_attendance_course_id_fkey'
}),
foreignKey ({
columns : [ table . studentId ],
foreignColumns : [ groupmember . id ],
name : 'group_attendance_student_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const currency = pgTable ( 'currency' , {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'currency_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
name : varchar ()
});
export const appsPollOption = pgTable (
'apps_poll_option' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'apps_poll_option_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }),
pollId : uuid ( 'poll_id' ),
label : varchar ()
},
( table ) => [
foreignKey ({
columns : [ table . pollId ],
foreignColumns : [ appsPoll . id ],
name : 'apps_poll_option_poll_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const profile = pgTable (
'profile' ,
{
id : uuid (). primaryKey (). notNull (),
fullname : text (). notNull (),
username : text (). notNull (),
avatarUrl : text ( 'avatar_url' ). default (
'https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/avatar.png'
),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
email : varchar (),
canAddCourse : boolean ( 'can_add_course' ). default ( true ),
role : varchar (),
goal : varchar (),
source : varchar (),
metadata : json (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
telegramChatId : bigint ( 'telegram_chat_id' , { mode : 'number' }),
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 ()
},
( table ) => [
foreignKey ({
columns : [ table . id ],
foreignColumns : [ user . id ],
name : 'profile_id_fkey'
}),
unique ( 'profile_username_key' ). on ( table . username ),
unique ( 'profile_email_key' ). on ( table . email ),
2025-11-24 01:00:50 +01:00
index ( 'idx_profile_id' ). on ( table . id ),
2025-12-02 09:54:38 +01:00
index ( 'idx_profile_email' ). on ( table . email )
2025-11-01 15:49:29 +01:00
]
);
export const option = pgTable (
'option' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'option_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
label : varchar (). notNull (),
isCorrect : boolean ( 'is_correct' ). default ( false ). notNull (),
2026-03-09 11:18:22 +01:00
settings : jsonb (). default ({}),
2025-11-01 15:49:29 +01:00
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
questionId : bigint ( 'question_id' , { mode : 'number' }). notNull (),
2025-12-02 09:54:38 +01:00
value : uuid (). default ( sql `gen_random_uuid()` ),
2025-11-01 15:49:29 +01:00
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
( table ) => [
foreignKey ({
columns : [ table . questionId ],
foreignColumns : [ question . id ],
name : 'option_question_id_fkey'
2025-12-02 09:54:38 +01:00
}). onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const quizPlay = pgTable (
'quiz_play' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'play_quiz_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
quizId : uuid ( 'quiz_id' ),
players : json (). default ([]),
started : boolean (). default ( false ),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
currentQuestionId : bigint ({ mode : 'number' }). default ( sql `'0'` ),
showCurrentQuestionAnswer : boolean (). default ( false ),
isLastQuestion : boolean (),
step : text (). default ( 'CONNECT_TO_PLAY' ),
studentStep : text (). default ( 'PIN_SETUP' ),
pin : text ()
},
( table ) => [
foreignKey ({
columns : [ table . quizId ],
foreignColumns : [ quiz . id ],
name : 'quiz_play_quiz_id_fkey'
}),
2025-12-02 09:54:38 +01:00
unique ( 'quiz_play_pin_key' ). on ( table . pin )
2025-11-01 15:49:29 +01:00
]
);
export const organizationContacts = pgTable (
'organization_contacts' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'organization_contacts_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
email : text (),
phone : text (),
name : text (),
message : text (),
organizationId : uuid ( 'organization_id' )
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'organization_contacts_organization_id_fkey'
})
]
);
export const lessonComment = pgTable (
'lesson_comment' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'lesson_comment_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
lessonId : uuid ( 'lesson_id' ),
groupmemberId : uuid ( 'groupmember_id' ),
comment : text ()
},
( table ) => [
foreignKey ({
columns : [ table . groupmemberId ],
foreignColumns : [ groupmember . id ],
name : 'lesson_comment_groupmember_id_fkey'
}),
foreignKey ({
columns : [ table . lessonId ],
foreignColumns : [ lesson . id ],
name : 'lesson_comment_lesson_id_fkey'
2025-12-02 09:54:38 +01:00
}). onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const quiz = pgTable (
'quiz' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
title : text (),
questions : json (),
timelimit : varchar (). default ( '10s' ),
theme : varchar (). default ( 'standard' ),
organizationId : uuid ( 'organization_id' ). notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'quiz_organization_id_fkey'
})
]
);
export const submission = pgTable (
'submission' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
reviewerId : bigint ( 'reviewer_id' , { mode : 'number' }),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
statusId : bigint ( 'status_id' , { mode : 'number' }). default ( sql `'1'` ),
2026-03-09 11:18:22 +01:00
gradingState : varchar ( 'grading_state' ). default ( 'queued' ). notNull (),
overallStatus : varchar ( 'overall_status' ). default ( 'manual_required' ). notNull (),
2025-11-01 15:49:29 +01:00
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
total : bigint ({ mode : 'number' }). default ( sql `'0'` ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
exerciseId : uuid ( 'exercise_id' ). notNull (),
submittedBy : uuid ( 'submitted_by' ),
courseId : uuid ( 'course_id' ),
feedback : text ()
},
( table ) => [
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'submission_course_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . exerciseId ],
foreignColumns : [ exercise . id ],
name : 'submission_exercise_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . statusId ],
foreignColumns : [ submissionstatus . id ],
name : 'submission_status_id_fkey'
}),
foreignKey ({
columns : [ table . submittedBy ],
foreignColumns : [ groupmember . id ],
name : 'submission_submitted_by_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const submissionstatus = pgTable (
'submissionstatus' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'submission_status_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
label : varchar (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
2025-12-02 09:54:38 +01:00
( table ) => []
2025-11-01 15:49:29 +01:00
);
export const course = pgTable (
'course' ,
{
title : varchar (). notNull (),
description : varchar (). notNull (),
overview : varchar (). default ( 'Welcome to this amazing course 🚀 ' ),
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
groupId : uuid ( 'group_id' ),
isTemplate : boolean ( 'is_template' ). default ( true ),
logo : text (). default ( '' ). notNull (),
slug : varchar (),
2026-03-09 11:18:22 +01:00
metadata : jsonb (). default ({ goals : '' , description : '' , requirements : '' }). notNull (). $type < {
requirements? : string ;
description? : string ;
goals? : string ;
videoUrl? : string ;
showDiscount? : boolean ;
discount? : number ;
paymentLink? : string ;
reward ?: {
show : boolean ;
description : string ;
};
instructor ?: {
name : string ;
role : string ;
coursesNo : number ;
description : string ;
imgUrl : string ;
};
certificate ?: {
templateUrl : string ;
};
reviews ?: {
id : number ;
hide : boolean ;
name : string ;
avatar_url : string ;
rating : number ;
created_at : number ;
description : string ;
}[];
lessonTabsOrder ?: {
id : 1 | 2 | 3 | 4 ;
name : string ;
}[];
grading? : boolean ;
lessonDownload? : boolean ;
allowNewStudent : boolean ;
sectionDisplay? : Record < string , boolean >;
isContentGroupingEnabled? : boolean ;
} > (),
2025-11-01 15:49:29 +01:00
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
cost : bigint ({ mode : 'number' }). default ( sql `'0'` ),
currency : varchar (). default ( 'USD' ). notNull (),
bannerImage : text ( 'banner_image' ),
isPublished : boolean ( 'is_published' ). default ( false ),
isCertificateDownloadable : boolean ( 'is_certificate_downloadable' ). default ( false ),
certificateTheme : text ( 'certificate_theme' ),
status : text (). default ( 'ACTIVE' ). notNull (),
2026-03-09 11:18:22 +01:00
type : courseType (). default ( 'LIVE_CLASS' )
2025-11-01 15:49:29 +01:00
},
( table ) => [
foreignKey ({
columns : [ table . groupId ],
foreignColumns : [ group . id ],
name : 'course_group_id_fkey'
}),
2026-03-09 11:18:22 +01:00
unique ( 'course_slug_key' ). on ( table . slug ),
index ( 'idx_course_group_id' ). on ( table . groupId )
2025-11-01 15:49:29 +01:00
]
);
export const appsPoll = pgTable (
'apps_poll' ,
{
id : uuid (). defaultRandom (). primaryKey (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
question : text (),
authorId : uuid (),
isPublic : boolean (),
status : varchar (). default ( 'draft' ),
expiration : timestamp ({ withTimezone : true , mode : 'string' }),
courseId : uuid ()
},
( table ) => [
foreignKey ({
columns : [ table . authorId ],
foreignColumns : [ groupmember . id ],
name : 'apps_poll_authorId_fkey'
}),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'apps_poll_courseId_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const videoTranscripts = pgTable ( 'video_transcripts' , {
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'video_transcripts_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
museSvid : text ( 'muse_svid' ),
transcript : text (),
downloaded : boolean (). default ( false ),
link : text ()
});
export const organizationEmaillist = pgTable (
'organization_emaillist' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'organization_emaillist_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
email : text (),
organizationId : uuid ( 'organization_id' )
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'organization_emaillist_organization_id_fkey'
})
]
);
export const lesson = pgTable (
'lesson' ,
{
note : varchar (),
videoUrl : varchar ( 'video_url' ),
slideUrl : varchar ( 'slide_url' ),
courseId : uuid ( 'course_id' ). notNull (),
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
title : varchar (). notNull (),
public : boolean (). default ( false ),
lessonAt : timestamp ( 'lesson_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
teacherId : uuid ( 'teacher_id' ),
isComplete : boolean ( 'is_complete' ). default ( false ),
callUrl : text ( 'call_url' ),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
order : bigint ({ mode : 'number' }),
isUnlocked : boolean ( 'is_unlocked' ). default ( false ),
2026-03-09 11:18:22 +01:00
videos : jsonb (). default ([]). $type <
{
type : 'youtube' | 'generic' | 'upload' ;
link : string ;
key? : string ;
assetId? : string ;
/** Display name; stored on add/upload. Fallback: derive from key or type. */
fileName? : string ;
metadata ?: {
svid? : string ;
title? : string ;
description? : string ;
thumbnailUrl? : string ;
duration? : number ;
aspectRatio? : string ;
createdAt? : string ;
};
}[]
> (),
documents : jsonb (). default ([]). $type <
{
type : string ;
name : string ;
link : string ;
size? : number ;
key : string ;
assetId? : string ;
}[]
> (),
sectionId : uuid ( 'section_id' )
2025-11-01 15:49:29 +01:00
},
( table ) => [
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'lesson_course_id_fkey'
}),
foreignKey ({
columns : [ table . teacherId ],
foreignColumns : [ profile . id ],
name : 'lesson_teacher_id_fkey'
}),
foreignKey ({
columns : [ table . sectionId ],
2026-03-09 11:18:22 +01:00
foreignColumns : [ courseSection . id ],
name : 'public_course_section_id_fkey'
2025-11-01 15:49:29 +01:00
})
. onUpdate ( 'cascade' )
2025-12-02 09:54:38 +01:00
. onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
2026-03-09 11:18:22 +01:00
export const asset = pgTable (
'assets' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
kind : varchar (). default ( 'video' ). notNull (),
provider : varchar (). default ( 'upload' ). notNull (),
storageProvider : varchar ( 'storage_provider' ). default ( 's3' ). notNull (),
storageKey : text ( 'storage_key' ),
sourceUrl : text ( 'source_url' ),
mimeType : text ( 'mime_type' ),
byteSize : bigint ( 'byte_size' , { mode : 'number' }),
checksum : text (),
title : text (),
description : text (),
thumbnailUrl : text ( 'thumbnail_url' ),
durationSeconds : integer ( 'duration_seconds' ),
aspectRatio : text ( 'aspect_ratio' ),
isExternal : boolean ( 'is_external' ). default ( false ). notNull (),
status : varchar (). default ( 'active' ). notNull (),
metadata : jsonb (). default ({}),
createdByProfileId : uuid ( 'created_by_profile_id' ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'assets_organization_id_fkey'
})
. onUpdate ( 'cascade' )
. onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . createdByProfileId ],
foreignColumns : [ profile . id ],
name : 'assets_created_by_profile_id_fkey'
})
. onUpdate ( 'cascade' )
. onDelete ( 'set null' ),
index ( 'idx_assets_organization_id' ). on ( table . organizationId ),
index ( 'idx_assets_organization_status' ). on ( table . organizationId , table . status ),
index ( 'idx_assets_organization_kind_status' ). on ( table . organizationId , table . kind , table . status ),
index ( 'idx_assets_organization_created_at' ). on ( table . organizationId , table . createdAt ),
index ( 'idx_assets_organization_byte_size' ). on ( table . organizationId , table . byteSize ),
unique ( 'assets_org_provider_storage_key_unique' ). on ( table . organizationId , table . provider , table . storageKey )
]
);
export const assetUsage = pgTable (
'asset_usages' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
assetId : uuid ( 'asset_id' ). notNull (),
targetType : varchar ( 'target_type' ). notNull (),
targetId : text ( 'target_id' ). notNull (),
slotType : varchar ( 'slot_type' ). default ( 'lesson_video' ). notNull (),
slotKey : text ( 'slot_key' ),
position : integer (),
createdByProfileId : uuid ( 'created_by_profile_id' ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'asset_usages_organization_id_fkey'
})
. onUpdate ( 'cascade' )
. onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . assetId ],
foreignColumns : [ asset . id ],
name : 'asset_usages_asset_id_fkey'
})
. onUpdate ( 'cascade' )
. onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . createdByProfileId ],
foreignColumns : [ profile . id ],
name : 'asset_usages_created_by_profile_id_fkey'
})
. onUpdate ( 'cascade' )
. onDelete ( 'set null' ),
index ( 'idx_asset_usages_org_id' ). on ( table . organizationId ),
index ( 'idx_asset_usages_asset_id' ). on ( table . assetId ),
index ( 'idx_asset_usages_target' ). on ( table . targetType , table . targetId ),
unique ( 'asset_usages_asset_target_slot_unique' ). on (
table . assetId ,
table . targetType ,
table . targetId ,
table . slotType ,
table . slotKey ,
table . position
)
]
);
2025-11-01 15:49:29 +01:00
export const appsPollSubmission = pgTable (
'apps_poll_submission' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'apps_poll_submision_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
pollOptionId : bigint ( 'poll_option_id' , { mode : 'number' }),
selectedById : uuid ( 'selected_by_id' ),
pollId : uuid ( 'poll_id' )
},
( table ) => [
foreignKey ({
columns : [ table . pollId ],
foreignColumns : [ appsPoll . id ],
name : 'apps_poll_submission_poll_id_fkey'
}),
foreignKey ({
columns : [ table . pollOptionId ],
foreignColumns : [ appsPollOption . id ],
name : 'apps_poll_submission_poll_option_id_fkey'
}),
foreignKey ({
columns : [ table . selectedById ],
foreignColumns : [ groupmember . id ],
name : 'apps_poll_submission_selected_by_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const exercise = pgTable (
'exercise' ,
{
title : varchar (). notNull (),
description : varchar (),
lessonId : uuid ( 'lesson_id' ),
2026-03-09 11:18:22 +01:00
courseId : uuid ( 'course_id' ),
sectionId : uuid ( 'section_id' ),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
order : bigint ({ mode : 'number' }),
2025-11-01 15:49:29 +01:00
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
2026-03-09 11:18:22 +01:00
isUnlocked : boolean ( 'is_unlocked' ). default ( true ),
2025-11-01 15:49:29 +01:00
dueBy : timestamp ( 'due_by' , { mode : 'string' })
},
( table ) => [
foreignKey ({
columns : [ table . lessonId ],
foreignColumns : [ lesson . id ],
name : 'exercise_lesson_id_fkey'
2026-03-09 11:18:22 +01:00
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'exercise_course_id_fkey'
}),
foreignKey ({
columns : [ table . sectionId ],
foreignColumns : [ courseSection . id ],
name : 'exercise_section_id_fkey'
})
2025-11-01 15:49:29 +01:00
]
);
export const groupmember = pgTable (
'groupmember' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
groupId : uuid ( 'group_id' ). notNull (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
roleId : bigint ( 'role_id' , { mode : 'number' }). notNull (),
profileId : uuid ( 'profile_id' ),
email : varchar (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
assignedStudentId : varchar ( 'assigned_student_id' )
},
( table ) => [
foreignKey ({
columns : [ table . groupId ],
foreignColumns : [ group . id ],
name : 'groupmember_group_id_fkey'
}),
foreignKey ({
columns : [ table . profileId ],
foreignColumns : [ profile . id ],
name : 'groupmember_profile_id_fkey'
}),
foreignKey ({
columns : [ table . roleId ],
foreignColumns : [ role . id ],
name : 'groupmember_role_id_fkey'
}),
unique ( 'unique_entries' ). on ( table . groupId , table . profileId , table . email ),
unique ( 'unique_group_email' ). on ( table . groupId , table . email ),
2025-12-02 09:54:38 +01:00
unique ( 'unique_group_profile' ). on ( table . groupId , table . profileId )
2025-11-01 15:49:29 +01:00
]
);
2026-03-09 11:18:22 +01:00
export const courseInvite = pgTable (
'course_invite' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
courseId : uuid ( 'course_id' ). notNull (),
// Reserved for future invite-role expansion (defaults to student invites)
roleId : bigint ( 'role_id' , { mode : 'number' })
. default ( sql `'3'` )
. notNull (),
tokenHash : text ( 'token_hash' ). notNull (),
createdByProfileId : uuid ( 'created_by_profile_id' ). notNull (),
revokedByProfileId : uuid ( 'revoked_by_profile_id' ),
revokedAt : timestamp ( 'revoked_at' , { withTimezone : true , mode : 'string' }),
expiresAt : timestamp ( 'expires_at' , { withTimezone : true , mode : 'string' }). notNull (),
maxUses : integer ( 'max_uses' ). default ( 1 ). notNull (),
usedCount : integer ( 'used_count' ). default ( 0 ). notNull (),
isRevoked : boolean ( 'is_revoked' ). default ( false ). notNull (),
allowedEmails : text ( 'allowed_emails' ). array (),
allowedDomains : text ( 'allowed_domains' ). array (),
metadata : jsonb (). default ({}). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
lastUsedAt : timestamp ( 'last_used_at' , { withTimezone : true , mode : 'string' })
},
( table ) => [
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'course_invite_course_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . createdByProfileId ],
foreignColumns : [ profile . id ],
name : 'course_invite_created_by_profile_id_fkey'
}),
foreignKey ({
columns : [ table . revokedByProfileId ],
foreignColumns : [ profile . id ],
name : 'course_invite_revoked_by_profile_id_fkey'
}),
foreignKey ({
columns : [ table . roleId ],
foreignColumns : [ role . id ],
name : 'course_invite_role_id_fkey'
}),
unique ( 'course_invite_token_hash_key' ). on ( table . tokenHash ),
index ( 'idx_course_invite_course_id' ). on ( table . courseId ),
index ( 'idx_course_invite_expires_at' ). on ( table . expiresAt ),
index ( 'idx_course_invite_created_by' ). on ( table . createdByProfileId ),
index ( 'idx_course_invite_revoked_by' ). on ( table . revokedByProfileId )
]
);
export const courseInviteAudit = pgTable (
'course_invite_audit' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
inviteId : uuid ( 'invite_id' ). notNull (),
courseId : uuid ( 'course_id' ). notNull (),
eventType : courseInviteEventType ( 'event_type' ). notNull (),
actorProfileId : uuid ( 'actor_profile_id' ),
targetEmail : varchar ( 'target_email' ),
ipAddress : text ( 'ip_address' ),
userAgent : text ( 'user_agent' ),
metadata : jsonb (). default ({}). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . inviteId ],
foreignColumns : [ courseInvite . id ],
name : 'course_invite_audit_invite_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'course_invite_audit_course_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . actorProfileId ],
foreignColumns : [ profile . id ],
name : 'course_invite_audit_actor_profile_id_fkey'
}),
index ( 'idx_course_invite_audit_invite_id' ). on ( table . inviteId ),
index ( 'idx_course_invite_audit_course_id' ). on ( table . courseId ),
index ( 'idx_course_invite_audit_event_type' ). on ( table . eventType ),
index ( 'idx_course_invite_audit_created_at' ). on ( table . createdAt )
]
);
export const organizationInvite = pgTable (
'organization_invite' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
// Role assigned when invite is accepted (e.g ADMIN, TUTOR)
roleId : bigint ( 'role_id' , { mode : 'number' }). notNull (),
email : text (). notNull (),
tokenHash : text ( 'token_hash' ). notNull (),
createdByProfileId : uuid ( 'created_by_profile_id' ). notNull (),
acceptedByProfileId : uuid ( 'accepted_by_profile_id' ),
acceptedAt : timestamp ( 'accepted_at' , { withTimezone : true , mode : 'string' }),
revokedByProfileId : uuid ( 'revoked_by_profile_id' ),
revokedAt : timestamp ( 'revoked_at' , { withTimezone : true , mode : 'string' }),
expiresAt : timestamp ( 'expires_at' , { withTimezone : true , mode : 'string' }). notNull (),
isRevoked : boolean ( 'is_revoked' ). default ( false ). notNull (),
metadata : jsonb (). default ({}). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'organization_invite_organization_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . roleId ],
foreignColumns : [ role . id ],
name : 'organization_invite_role_id_fkey'
}),
foreignKey ({
columns : [ table . createdByProfileId ],
foreignColumns : [ profile . id ],
name : 'organization_invite_created_by_profile_id_fkey'
}),
foreignKey ({
columns : [ table . acceptedByProfileId ],
foreignColumns : [ profile . id ],
name : 'organization_invite_accepted_by_profile_id_fkey'
}),
foreignKey ({
columns : [ table . revokedByProfileId ],
foreignColumns : [ profile . id ],
name : 'organization_invite_revoked_by_profile_id_fkey'
}),
unique ( 'organization_invite_token_hash_key' ). on ( table . tokenHash ),
index ( 'idx_organization_invite_organization_id' ). on ( table . organizationId ),
index ( 'idx_organization_invite_email_org' ). on ( table . email , table . organizationId ),
index ( 'idx_organization_invite_expires_at' ). on ( table . expiresAt ),
index ( 'idx_organization_invite_created_by' ). on ( table . createdByProfileId ),
index ( 'idx_organization_invite_accepted_by' ). on ( table . acceptedByProfileId ),
index ( 'idx_organization_invite_revoked_by' ). on ( table . revokedByProfileId )
]
);
export const organizationInviteAudit = pgTable (
'organization_invite_audit' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
inviteId : uuid ( 'invite_id' ). notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
eventType : organizationInviteEventType ( 'event_type' ). notNull (),
actorProfileId : uuid ( 'actor_profile_id' ),
targetEmail : varchar ( 'target_email' ),
ipAddress : text ( 'ip_address' ),
userAgent : text ( 'user_agent' ),
metadata : jsonb (). default ({}). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . inviteId ],
foreignColumns : [ organizationInvite . id ],
name : 'organization_invite_audit_invite_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'organization_invite_audit_organization_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . actorProfileId ],
foreignColumns : [ profile . id ],
name : 'organization_invite_audit_actor_profile_id_fkey'
}),
index ( 'idx_organization_invite_audit_invite_id' ). on ( table . inviteId ),
index ( 'idx_organization_invite_audit_org_id' ). on ( table . organizationId ),
index ( 'idx_organization_invite_audit_event_type' ). on ( table . eventType ),
index ( 'idx_organization_invite_audit_created_at' ). on ( table . createdAt )
]
);
2025-11-01 15:49:29 +01:00
export const lessonCompletion = pgTable (
'lesson_completion' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'lesson_completion_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
lessonId : uuid ( 'lesson_id' ),
profileId : uuid ( 'profile_id' ),
isComplete : boolean ( 'is_complete' ). default ( false ),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
( table ) => [
foreignKey ({
columns : [ table . lessonId ],
foreignColumns : [ lesson . id ],
name : 'lesson_completion_lesson_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . profileId ],
foreignColumns : [ profile . id ],
name : 'lesson_completion_profile_id_fkey'
}),
2025-12-02 09:54:38 +01:00
unique ( 'unique_lesson_profile' ). on ( table . lessonId , table . profileId )
2025-11-01 15:49:29 +01:00
]
);
export const communityAnswer = pgTable (
'community_answer' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
questionId : bigint ( 'question_id' , { mode : 'number' }),
body : varchar (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
authorId : bigint ( 'author_id' , { mode : 'number' }),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
votes : bigint ({ mode : 'number' }),
authorProfileId : uuid ( 'author_profile_id' )
},
( table ) => [
foreignKey ({
columns : [ table . authorId ],
foreignColumns : [ organizationmember . id ],
name : 'community_answer_author_id_fkey'
}),
foreignKey ({
columns : [ table . authorProfileId ],
foreignColumns : [ profile . id ],
name : 'community_answer_author_profile_id_fkey'
}),
foreignKey ({
columns : [ table . questionId ],
foreignColumns : [ communityQuestion . id ],
name : 'community_answer_question_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const communityQuestion = pgTable (
'community_question' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'community_question_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
title : varchar (),
body : text (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
authorId : bigint ( 'author_id' , { mode : 'number' }),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
votes : bigint ({ mode : 'number' }). default ( sql `'0'` ),
organizationId : uuid ( 'organization_id' ),
slug : text (),
authorProfileId : uuid ( 'author_profile_id' ),
courseId : uuid ( 'course_id' ). notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . authorId ],
foreignColumns : [ organizationmember . id ],
name : 'community_question_author_id_fkey'
}),
foreignKey ({
columns : [ table . authorProfileId ],
foreignColumns : [ profile . id ],
name : 'community_question_author_profile_id_fkey'
}),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'community_question_course_id_fkey'
}),
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'community_question_organization_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const courseNewsfeed = pgTable (
'course_newsfeed' ,
{
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
authorId : uuid ( 'author_id' ),
content : text (),
id : uuid (). defaultRandom (). primaryKey (). notNull (),
courseId : uuid ( 'course_id' ),
2026-03-09 11:18:22 +01:00
reaction : jsonb ()
. default ({ clap : [], smile : [], thumbsup : [], thumbsdown : [] })
. $type < { clap : string []; smile : string []; thumbsup : string []; thumbsdown : string [] } > (),
2025-11-01 15:49:29 +01:00
isPinned : boolean ( 'is_pinned' ). default ( false ). notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . authorId ],
foreignColumns : [ groupmember . id ],
name : 'course_newsfeed_author_id_fkey'
}),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'course_newsfeed_course_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const courseNewsfeedComment = pgTable (
'course_newsfeed_comment' ,
{
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
authorId : uuid ( 'author_id' ),
content : text (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'course_newsfeed_comment_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
courseNewsfeedId : uuid ( 'course_newsfeed_id' )
},
( table ) => [
foreignKey ({
columns : [ table . authorId ],
foreignColumns : [ groupmember . id ],
name : 'course_newsfeed_comment_author_id_fkey'
}),
foreignKey ({
columns : [ table . courseNewsfeedId ],
foreignColumns : [ courseNewsfeed . id ],
name : 'course_newsfeed_comment_course_newsfeed_id_fkey'
2025-12-25 05:09:27 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const testTenant = pgTable ( 'test_tenant' , {
id : serial (). primaryKey (). notNull (),
details : text ()
});
export const organizationPlan = pgTable (
'organization_plan' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'organization_plan_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
activatedAt : timestamp ( 'activated_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
orgId : uuid ( 'org_id' ),
planName : plan ( 'plan_name' ),
isActive : boolean ( 'is_active' ),
deactivatedAt : timestamp ( 'deactivated_at' , { withTimezone : true , mode : 'string' }),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
payload : jsonb (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
triggeredBy : bigint ( 'triggered_by' , { mode : 'number' }),
provider : text (). default ( 'lmz' ),
subscriptionId : text ( 'subscription_id' )
},
( table ) => [
foreignKey ({
columns : [ table . orgId ],
foreignColumns : [ organization . id ],
name : 'organization_plan_org_id_fkey'
}),
foreignKey ({
columns : [ table . triggeredBy ],
foreignColumns : [ organizationmember . id ],
name : 'organization_plan_triggered_by_fkey'
}),
unique ( 'organization_plan_subscription_id_key' ). on ( table . subscriptionId ),
2025-12-02 09:54:38 +01:00
index ( 'idx_organization_plan_org_id' ). on ( table . orgId )
2025-11-01 15:49:29 +01:00
]
);
export const lessonLanguage = pgTable (
'lesson_language' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'lesson_language_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
content : text (),
lessonId : uuid ( 'lesson_id' ). defaultRandom (),
locale : locale (). default ( 'en' )
},
( table ) => [
foreignKey ({
columns : [ table . lessonId ],
foreignColumns : [ lesson . id ],
name : 'public_lesson_language_lesson_id_fkey'
})
. onUpdate ( 'cascade' )
2025-12-02 09:54:38 +01:00
. onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const lessonLanguageHistory = pgTable (
'lesson_language_history' ,
{
id : serial (). primaryKey (). notNull (),
lessonLanguageId : integer ( 'lesson_language_id' ),
oldContent : text ( 'old_content' ),
newContent : text ( 'new_content' ),
timestamp : timestamp ({ mode : 'string' })
. default ( sql `CURRENT_TIMESTAMP` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . lessonLanguageId ],
foreignColumns : [ lessonLanguage . id ],
name : 'public_lesson_language_history_lesson_language_id_fkey'
})
. onUpdate ( 'cascade' )
2025-12-02 09:54:38 +01:00
. onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const organizationmember = pgTable (
'organizationmember' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'organizationmember_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
organizationId : uuid ( 'organization_id' ). notNull (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
roleId : bigint ( 'role_id' , { mode : 'number' }). notNull (),
profileId : uuid ( 'profile_id' ),
email : text (),
verified : boolean (). default ( false ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'organizationmember_organization_id_fkey'
}),
foreignKey ({
columns : [ table . profileId ],
foreignColumns : [ profile . id ],
name : 'organizationmember_profile_id_fkey'
}),
foreignKey ({
columns : [ table . roleId ],
foreignColumns : [ role . id ],
name : 'organizationmember_role_id_fkey'
}),
2025-11-24 01:00:50 +01:00
index ( 'idx_organizationmember_profile_id' ). on ( table . profileId ),
index ( 'idx_organizationmember_organization_id' ). on ( table . organizationId ),
2025-12-02 09:54:38 +01:00
index ( 'idx_organizationmember_profile_org' ). on ( table . profileId , table . organizationId )
2025-11-01 15:49:29 +01:00
]
);
export const question = pgTable (
'question' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'question_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
questionTypeId : bigint ( 'question_type_id' , { mode : 'number' }). notNull (),
title : varchar (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
exerciseId : uuid ( 'exercise_id' ). notNull (),
2025-12-02 09:54:38 +01:00
name : uuid (). default ( sql `gen_random_uuid()` ),
2025-11-01 15:49:29 +01:00
points : doublePrecision (),
2026-03-09 11:18:22 +01:00
settings : jsonb (). default ({}),
2025-11-01 15:49:29 +01:00
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
order : bigint ({ mode : 'number' })
},
( table ) => [
foreignKey ({
columns : [ table . exerciseId ],
foreignColumns : [ exercise . id ],
name : 'question_exercise_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . questionTypeId ],
foreignColumns : [ questionType . id ],
name : 'question_question_type_id_fkey'
2025-12-02 09:54:38 +01:00
})
2025-11-01 15:49:29 +01:00
]
);
export const questionAnswer = pgTable (
'question_answer' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'question_answer_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
2026-03-09 11:18:22 +01:00
answerData : jsonb ( 'answer_data' ). $type < AnswerData | null >(),
/** @deprecated Use answer_data instead. Kept for backwards compatibility. */
2025-11-01 15:49:29 +01:00
answers : varchar (). array (),
2026-03-09 11:18:22 +01:00
/** @deprecated Use answer_data instead. Kept for backwards compatibility. */
openAnswer : text ( 'open_answer' ),
2025-11-01 15:49:29 +01:00
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
questionId : bigint ( 'question_id' , { mode : 'number' }). notNull (),
groupMemberId : uuid ( 'group_member_id' ). notNull (),
submissionId : uuid ( 'submission_id' ),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
point : bigint ({ mode : 'number' }). default ( sql `'0'` )
},
( table ) => [
foreignKey ({
columns : [ table . groupMemberId ],
foreignColumns : [ groupmember . id ],
name : 'question_answer_group_member_id_fkey'
}),
foreignKey ({
columns : [ table . questionId ],
foreignColumns : [ question . id ],
name : 'question_answer_question_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . submissionId ],
foreignColumns : [ submission . id ],
name : 'question_answer_submission_id_fkey'
2025-12-02 09:54:38 +01:00
}). onDelete ( 'cascade' )
2025-11-01 15:49:29 +01:00
]
);
export const questionType = pgTable (
'question_type' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'question_type_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
label : varchar (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
typename : varchar ()
},
2025-12-02 09:54:38 +01:00
( table ) => []
2025-11-01 15:49:29 +01:00
);
export const role = pgTable (
'role' ,
{
type : varchar (). notNull (),
description : varchar (),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'role_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' }). defaultNow (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
2025-12-02 09:54:38 +01:00
( table ) => []
2025-11-01 15:49:29 +01:00
);
export const waitinglist = pgTable (
'waitinglist' ,
{
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'waitinglist_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
email : varchar (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow ()
},
( table ) => [ unique ( 'constraint_name' ). on ( table . email )]
);
export const organization = pgTable (
'organization' ,
{
id : uuid ()
2025-12-02 09:54:38 +01:00
. default ( sql `gen_random_uuid()` )
2025-11-01 15:49:29 +01:00
. primaryKey ()
. notNull (),
name : varchar (). notNull (),
siteName : text (),
avatarUrl : text ( 'avatar_url' ),
2026-03-09 11:18:22 +01:00
settings : jsonb (). default ({}). $type < {
signup ?: {
inviteOnly? : boolean ;
};
} > (),
2025-12-02 09:54:38 +01:00
landingpage : jsonb (). default ({}). $type < {
header ?: {
title : string ;
titleHighlight : string ;
subtitle : string ;
action : {
label : string ;
link : string ;
redirect : boolean ;
};
banner : {
video : string ;
image : string ;
type : string ;
show : boolean ;
};
background : {
image : string ;
show : boolean ;
};
show : boolean ;
};
aboutUs ?: {
title : string ;
content : string ;
imageUrl : string ;
show : boolean ;
};
courses ?: {
title : string ;
titleHighlight : string ;
subtitle : string ;
show : boolean ;
};
faq ?: {
title : string ;
questions : Array < {
id : number ;
title : string ;
content : string ;
} > ;
show : boolean ;
};
contact ?: {
title : string ;
titleHighlight : string ;
subtitle : string ;
address : string ;
phone : string ;
email : string ;
show : boolean ;
};
mailinglist ?: {
title : string ;
subtitle : string ;
buttonLabel : string ;
show : boolean ;
};
customLinks ?: {
show : boolean ;
links : Array < {
id : number ;
label : string ;
url : string ;
openInNewTab : boolean ;
} > ;
};
footer ?: {
facebook : string ;
instagram : string ;
twitter : string ;
linkedin : string ;
show : boolean ;
};
} > (),
2026-03-09 11:18:22 +01:00
theme : text (). default ( 'blue' ),
2025-11-01 15:49:29 +01:00
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
customization : json ()
. default ({
apps : { poll : true , comments : true },
course : { grading : true , newsfeed : true },
dashboard : { exercise : true , community : true , bannerText : '' , bannerImage : '' }
})
2025-12-02 09:54:38 +01:00
. $type < {
dashboard : {
community : boolean ;
exercise : boolean ;
bannerImage : string ;
bannerText : string ;
};
course : {
newsfeed : boolean ;
grading : boolean ;
};
apps : {
poll : boolean ;
comments : boolean ;
};
} > ()
2025-11-01 15:49:29 +01:00
. notNull (),
isRestricted : boolean ( 'is_restricted' ). default ( false ). notNull (),
customCode : text (),
customDomain : text (),
favicon : text (),
2026-03-09 11:18:22 +01:00
isCustomDomainVerified : boolean (). default ( false ),
disableSignup : boolean ( 'disable_signup' ). default ( false ),
disableSignupMessage : text ( 'disable_signup_message' ),
disableEmailPassword : boolean ( 'disable_email_password' ). default ( false ),
disableGoogleAuth : boolean ( 'disable_google_auth' ). default ( false )
2025-11-01 15:49:29 +01:00
},
( table ) => [
unique ( 'organization_siteName_key' ). on ( table . siteName ),
2025-12-02 09:54:38 +01:00
unique ( 'organization_customDomain_key' ). on ( table . customDomain )
2025-11-01 15:49:29 +01:00
]
);
2025-12-02 09:54:38 +01:00
2026-03-09 11:18:22 +01:00
export const tagGroup = pgTable (
'tag_group' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
name : varchar (). notNull (),
slug : text (). notNull (),
description : text (),
order : integer (). default ( 0 ). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'tag_group_organization_id_fkey'
}). onDelete ( 'cascade' ),
index ( 'idx_tag_group_organization_id' ). on ( table . organizationId ),
unique ( 'tag_group_org_slug_key' ). on ( table . organizationId , table . slug )
]
);
export const tag = pgTable (
'tag' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' ). notNull (),
groupId : uuid ( 'group_id' ). notNull (),
name : varchar (). notNull (),
slug : text (). notNull (),
description : text (),
color : varchar (). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . organizationId ],
foreignColumns : [ organization . id ],
name : 'tag_organization_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . groupId ],
foreignColumns : [ tagGroup . id ],
name : 'tag_group_id_fkey'
}). onDelete ( 'cascade' ),
index ( 'idx_tag_organization_id' ). on ( table . organizationId ),
index ( 'idx_tag_group_id' ). on ( table . groupId ),
unique ( 'tag_org_slug_key' ). on ( table . organizationId , table . slug )
]
);
export const tagAssignment = pgTable (
'tag_assignment' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
tagId : uuid ( 'tag_id' ). notNull (),
courseId : uuid ( 'course_id' ). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . tagId ],
foreignColumns : [ tag . id ],
name : 'tag_assignment_tag_id_fkey'
}). onDelete ( 'cascade' ),
foreignKey ({
columns : [ table . courseId ],
foreignColumns : [ course . id ],
name : 'tag_assignment_course_id_fkey'
}). onDelete ( 'cascade' ),
index ( 'idx_tag_assignment_tag_id' ). on ( table . tagId ),
index ( 'idx_tag_assignment_course_id' ). on ( table . courseId ),
unique ( 'tag_assignment_tag_course_key' ). on ( table . tagId , table . courseId )
]
);
2025-11-01 15:49:29 +01:00
export const dashOrgStats = pgView ( 'dash_org_stats' , {
orgId : uuid ( 'org_id' ),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
noOfCourses : bigint ( 'no_of_courses' , { mode : 'number' }),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
enrolledStudents : bigint ( 'enrolled_students' , { mode : 'number' })
}). as (
sql `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`
);
export const lessonVersions = pgView ( 'lesson_versions' , {
oldContent : text ( 'old_content' ),
newContent : text ( 'new_content' ),
timestamp : timestamp ({ mode : 'string' }),
locale : locale (),
lessonId : uuid ( 'lesson_id' )
}). as (
sql `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`
);
2025-12-25 05:09:27 +01:00
export const exerciseTemplate = pgTable ( 'exercise_template' , {
id : bigint ({ mode : 'number' }). primaryKey (). generatedByDefaultAsIdentity ({
name : 'exercise_template_id_seq' ,
startWith : 1 ,
increment : 1 ,
minValue : 1 ,
cache : 1
}),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' }). defaultNow (). notNull (),
title : text (),
description : text (),
tag : text (),
questionnaire : jsonb (). default ({}). $type < {
questions : [
{
title : string ;
name : string ;
points : number ;
order : number ;
question_type : {
id : number ;
label : string ;
};
options : [
{
label : string ;
is_correct : boolean ;
},
{
label : string ;
is_correct : boolean ;
},
{
label : string ;
is_correct : boolean ;
}
];
}
];
} > ()
});
2026-03-09 11:18:22 +01:00
// Organization SSO provider type (our app enum for display/categorization)
export const organizationSsoProviderType = pgEnum ( 'SSO_PROVIDER' , [ 'OKTA' , 'GOOGLE_WORKSPACE' , 'AUTH0' ]);
// Organization Auth Policy - stores SSO policies per org
export const organizationAuthPolicy = pgTable (
'organization_auth_policy' ,
{
organizationId : uuid ( 'organization_id' )
. notNull ()
. primaryKey ()
. references (() => organization . id , { onDelete : 'cascade' }),
forceSso : boolean ( 'force_sso' ). default ( false ). notNull (),
autoJoinSsoDomains : boolean ( 'auto_join_sso_domains' ). default ( false ). notNull (),
breakGlassEnabled : boolean ( 'break_glass_enabled' ). default ( true ). notNull (),
defaultRoleId : bigint ( 'default_role_id' , { mode : 'number' })
. default ( sql `'3'` )
. notNull (),
roleMapping : jsonb ( 'role_mapping' ). default ({}). notNull (),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
foreignKey ({
columns : [ table . defaultRoleId ],
foreignColumns : [ role . id ],
name : 'organization_auth_policy_default_role_id_fkey'
}),
index ( 'idx_organization_auth_policy_org_id' ). on ( table . organizationId )
]
);
// Organization SSO Config - metadata for SSO connections (Better Auth stores the actual config)
export const organizationSsoConfig = pgTable (
'organization_sso_config' ,
{
id : uuid ()
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' )
. notNull ()
. unique ()
. references (() => organization . id , { onDelete : 'cascade' }),
betterAuthProviderId : text ( 'better_auth_provider_id' ). notNull (),
provider : organizationSsoProviderType (). notNull (),
displayName : text ( 'display_name' ). notNull (),
domain : text (). notNull (),
isActive : boolean ( 'is_active' ). default ( false ). notNull (),
createdByProfileId : uuid ( 'created_by_profile_id' )
. notNull ()
. references (() => profile . id ),
updatedByProfileId : uuid ( 'updated_by_profile_id' ). references (() => profile . id ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [
index ( 'idx_organization_sso_config_org_id' ). on ( table . organizationId ),
index ( 'idx_organization_sso_config_domain' ). on ( table . domain ),
index ( 'idx_organization_sso_config_provider_id' ). on ( table . betterAuthProviderId )
]
);
export const organizationTokenAuth = pgTable (
'organization_token_auth' ,
{
id : uuid ( 'id' )
. default ( sql `gen_random_uuid()` )
. primaryKey ()
. notNull (),
organizationId : uuid ( 'organization_id' )
. notNull ()
. unique ()
. references (() => organization . id , { onDelete : 'cascade' }),
signingSecret : text ( 'signing_secret' ). notNull (),
isActive : boolean ( 'is_active' ). default ( false ). notNull (),
createdByProfileId : uuid ( 'created_by_profile_id' )
. notNull ()
. references (() => profile . id ),
createdAt : timestamp ( 'created_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull (),
updatedAt : timestamp ( 'updated_at' , { withTimezone : true , mode : 'string' })
. default ( sql `timezone('utc'::text, now())` )
. notNull ()
},
( table ) => [ index ( 'idx_organization_token_auth_org_id' ). on ( table . organizationId )]
);