/ app / db / schema.ts
schema.ts
  1  import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
  2  
  3  export enum UserRole {
  4    Student = "student",
  5    Instructor = "instructor",
  6    Admin = "admin",
  7  }
  8  
  9  export enum CourseStatus {
 10    Draft = "draft",
 11    Published = "published",
 12    Archived = "archived",
 13  }
 14  
 15  export enum LessonProgressStatus {
 16    NotStarted = "not_started",
 17    InProgress = "in_progress",
 18    Completed = "completed",
 19  }
 20  
 21  export enum QuestionType {
 22    MultipleChoice = "multiple_choice",
 23    TrueFalse = "true_false",
 24  }
 25  
 26  export enum TeamMemberRole {
 27    Admin = "admin",
 28    Member = "member",
 29  }
 30  
 31  // ─── Tables ───
 32  
 33  export const users = sqliteTable("users", {
 34    id: integer("id").primaryKey({ autoIncrement: true }),
 35    name: text("name").notNull(),
 36    email: text("email").notNull().unique(),
 37    role: text("role").notNull().$type<UserRole>(),
 38    avatarUrl: text("avatar_url"),
 39    bio: text("bio"),
 40    createdAt: text("created_at")
 41      .notNull()
 42      .$defaultFn(() => new Date().toISOString()),
 43  });
 44  
 45  export const categories = sqliteTable("categories", {
 46    id: integer("id").primaryKey({ autoIncrement: true }),
 47    name: text("name").notNull(),
 48    slug: text("slug").notNull().unique(),
 49  });
 50  
 51  export const courses = sqliteTable("courses", {
 52    id: integer("id").primaryKey({ autoIncrement: true }),
 53    title: text("title").notNull(),
 54    slug: text("slug").notNull().unique(),
 55    description: text("description").notNull(),
 56    salesCopy: text("sales_copy"),
 57    instructorId: integer("instructor_id")
 58      .notNull()
 59      .references(() => users.id),
 60    categoryId: integer("category_id")
 61      .notNull()
 62      .references(() => categories.id),
 63    status: text("status").notNull().$type<CourseStatus>(),
 64    coverImageUrl: text("cover_image_url"),
 65    price: integer("price").notNull().default(0),
 66    pppEnabled: integer("ppp_enabled", { mode: "boolean" })
 67      .notNull()
 68      .default(true),
 69    createdAt: text("created_at")
 70      .notNull()
 71      .$defaultFn(() => new Date().toISOString()),
 72    updatedAt: text("updated_at")
 73      .notNull()
 74      .$defaultFn(() => new Date().toISOString()),
 75  });
 76  
 77  export const modules = sqliteTable("modules", {
 78    id: integer("id").primaryKey({ autoIncrement: true }),
 79    courseId: integer("course_id")
 80      .notNull()
 81      .references(() => courses.id),
 82    title: text("title").notNull(),
 83    position: integer("position").notNull(),
 84    createdAt: text("created_at")
 85      .notNull()
 86      .$defaultFn(() => new Date().toISOString()),
 87  });
 88  
 89  export const lessons = sqliteTable("lessons", {
 90    id: integer("id").primaryKey({ autoIncrement: true }),
 91    moduleId: integer("module_id")
 92      .notNull()
 93      .references(() => modules.id),
 94    title: text("title").notNull(),
 95    content: text("content"),
 96    videoUrl: text("video_url"),
 97    githubRepoUrl: text("github_repo_url"),
 98    position: integer("position").notNull(),
 99    durationMinutes: integer("duration_minutes"),
100    createdAt: text("created_at")
101      .notNull()
102      .$defaultFn(() => new Date().toISOString()),
103  });
104  
105  export const enrollments = sqliteTable("enrollments", {
106    id: integer("id").primaryKey({ autoIncrement: true }),
107    userId: integer("user_id")
108      .notNull()
109      .references(() => users.id),
110    courseId: integer("course_id")
111      .notNull()
112      .references(() => courses.id),
113    enrolledAt: text("enrolled_at")
114      .notNull()
115      .$defaultFn(() => new Date().toISOString()),
116    completedAt: text("completed_at"),
117  });
118  
119  export const lessonProgress = sqliteTable("lesson_progress", {
120    id: integer("id").primaryKey({ autoIncrement: true }),
121    userId: integer("user_id")
122      .notNull()
123      .references(() => users.id),
124    lessonId: integer("lesson_id")
125      .notNull()
126      .references(() => lessons.id),
127    status: text("status").notNull().$type<LessonProgressStatus>(),
128    completedAt: text("completed_at"),
129  });
130  
131  export const quizzes = sqliteTable("quizzes", {
132    id: integer("id").primaryKey({ autoIncrement: true }),
133    lessonId: integer("lesson_id")
134      .notNull()
135      .references(() => lessons.id),
136    title: text("title").notNull(),
137    passingScore: real("passing_score").notNull(),
138  });
139  
140  export const quizQuestions = sqliteTable("quiz_questions", {
141    id: integer("id").primaryKey({ autoIncrement: true }),
142    quizId: integer("quiz_id")
143      .notNull()
144      .references(() => quizzes.id),
145    questionText: text("question_text").notNull(),
146    questionType: text("question_type").notNull().$type<QuestionType>(),
147    position: integer("position").notNull(),
148  });
149  
150  export const quizOptions = sqliteTable("quiz_options", {
151    id: integer("id").primaryKey({ autoIncrement: true }),
152    questionId: integer("question_id")
153      .notNull()
154      .references(() => quizQuestions.id),
155    optionText: text("option_text").notNull(),
156    isCorrect: integer("is_correct", { mode: "boolean" }).notNull(),
157  });
158  
159  export const quizAttempts = sqliteTable("quiz_attempts", {
160    id: integer("id").primaryKey({ autoIncrement: true }),
161    userId: integer("user_id")
162      .notNull()
163      .references(() => users.id),
164    quizId: integer("quiz_id")
165      .notNull()
166      .references(() => quizzes.id),
167    score: real("score").notNull(),
168    passed: integer("passed", { mode: "boolean" }).notNull(),
169    attemptedAt: text("attempted_at")
170      .notNull()
171      .$defaultFn(() => new Date().toISOString()),
172  });
173  
174  export const quizAnswers = sqliteTable("quiz_answers", {
175    id: integer("id").primaryKey({ autoIncrement: true }),
176    attemptId: integer("attempt_id")
177      .notNull()
178      .references(() => quizAttempts.id),
179    questionId: integer("question_id")
180      .notNull()
181      .references(() => quizQuestions.id),
182    selectedOptionId: integer("selected_option_id")
183      .notNull()
184      .references(() => quizOptions.id),
185  });
186  
187  export const purchases = sqliteTable("purchases", {
188    id: integer("id").primaryKey({ autoIncrement: true }),
189    userId: integer("user_id")
190      .notNull()
191      .references(() => users.id),
192    courseId: integer("course_id")
193      .notNull()
194      .references(() => courses.id),
195    pricePaid: integer("price_paid").notNull(),
196    country: text("country"),
197    createdAt: text("created_at")
198      .notNull()
199      .$defaultFn(() => new Date().toISOString()),
200  });
201  
202  export const teams = sqliteTable("teams", {
203    id: integer("id").primaryKey({ autoIncrement: true }),
204    createdAt: text("created_at")
205      .notNull()
206      .$defaultFn(() => new Date().toISOString()),
207  });
208  
209  export const teamMembers = sqliteTable("team_members", {
210    id: integer("id").primaryKey({ autoIncrement: true }),
211    teamId: integer("team_id")
212      .notNull()
213      .references(() => teams.id),
214    userId: integer("user_id")
215      .notNull()
216      .references(() => users.id),
217    role: text("role").notNull().$type<TeamMemberRole>(),
218    createdAt: text("created_at")
219      .notNull()
220      .$defaultFn(() => new Date().toISOString()),
221  });
222  
223  export const coupons = sqliteTable("coupons", {
224    id: integer("id").primaryKey({ autoIncrement: true }),
225    teamId: integer("team_id")
226      .notNull()
227      .references(() => teams.id),
228    courseId: integer("course_id")
229      .notNull()
230      .references(() => courses.id),
231    code: text("code").notNull().unique(),
232    purchaseId: integer("purchase_id")
233      .notNull()
234      .references(() => purchases.id),
235    redeemedByUserId: integer("redeemed_by_user_id").references(() => users.id),
236    redeemedAt: text("redeemed_at"),
237    createdAt: text("created_at")
238      .notNull()
239      .$defaultFn(() => new Date().toISOString()),
240  });
241  
242  export const videoWatchEvents = sqliteTable("video_watch_events", {
243    id: integer("id").primaryKey({ autoIncrement: true }),
244    userId: integer("user_id")
245      .notNull()
246      .references(() => users.id),
247    lessonId: integer("lesson_id")
248      .notNull()
249      .references(() => lessons.id),
250    eventType: text("event_type").notNull(),
251    positionSeconds: real("position_seconds").notNull(),
252    createdAt: text("created_at")
253      .notNull()
254      .$defaultFn(() => new Date().toISOString()),
255  });