Skip to content

Commit a2239c1

Browse files
feat: implement Phase 1 LMS foundation (#810)
✅ Database & Schema - Add 11 LMS models (Course, Module, Lesson, Enrollment, Progress, Assignment, Submission, Certificate, Cohort, Bookmark, Note) - Migrate from SQLite to PostgreSQL (Neon) for production scalability - Create comprehensive seed script with test data (3 users, 1 course, 5 modules, 21 lessons) ✅ Authentication & Authorization - Implement RBAC middleware with role-based access control - Support 4 roles: ADMIN, INSTRUCTOR, MENTOR, STUDENT - Update NextAuth to include user roles in session ✅ API Infrastructure - Create test endpoints (/api/lms/health, /api/lms/test, /api/lms/courses) - Implement authentication-protected routes - Add public health check endpoint ✅ Production Ready - All builds passing - TypeScript compilation successful - Database migrations applied - Seed data verified (3 users, 1 course, 5 modules, 21 lessons)
1 parent 570ca0c commit a2239c1

File tree

15 files changed

+2538
-15
lines changed

15 files changed

+2538
-15
lines changed

docs/phase-1-foundation-plan.md

Lines changed: 1334 additions & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,8 @@
118118
"lint-staged": {
119119
"*.{js,jsx,ts,tsx}": "eslint --cache --fix",
120120
"*": "prettier --write --ignore-unknown"
121+
},
122+
"prisma": {
123+
"seed": "tsx prisma/seed.ts"
121124
}
122125
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
-- CreateEnum
2+
CREATE TYPE "UserRole" AS ENUM ('STUDENT', 'INSTRUCTOR', 'ADMIN', 'MENTOR');
3+
4+
-- CreateEnum
5+
CREATE TYPE "SkillLevel" AS ENUM ('NEWBIE', 'BEGINNER', 'JUNIOR', 'MID', 'SENIOR');
6+
7+
-- CreateEnum
8+
CREATE TYPE "Difficulty" AS ENUM ('BEGINNER', 'INTERMEDIATE', 'ADVANCED');
9+
10+
-- CreateEnum
11+
CREATE TYPE "LessonType" AS ENUM ('CONTENT', 'VIDEO', 'EXERCISE', 'QUIZ', 'PROJECT');
12+
13+
-- CreateEnum
14+
CREATE TYPE "EnrollmentStatus" AS ENUM ('ACTIVE', 'COMPLETED', 'DROPPED', 'PAUSED');
15+
16+
-- CreateEnum
17+
CREATE TYPE "AssignmentType" AS ENUM ('PROJECT', 'HOMEWORK', 'CAPSTONE', 'QUIZ');
18+
19+
-- CreateEnum
20+
CREATE TYPE "SubmissionStatus" AS ENUM ('DRAFT', 'SUBMITTED', 'GRADED', 'NEEDS_REVISION');
21+
22+
-- AlterTable
23+
ALTER TABLE "User" ADD COLUMN "assessmentDate" TIMESTAMP(3),
24+
ADD COLUMN "assessmentScore" INTEGER,
25+
ADD COLUMN "bio" TEXT,
26+
ADD COLUMN "branch" TEXT,
27+
ADD COLUMN "cohortId" TEXT,
28+
ADD COLUMN "deployments" TEXT,
29+
ADD COLUMN "githubUrl" TEXT,
30+
ADD COLUMN "graduationDate" TIMESTAMP(3),
31+
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
32+
ADD COLUMN "linkedinUrl" TEXT,
33+
ADD COLUMN "location" TEXT,
34+
ADD COLUMN "mos" TEXT,
35+
ADD COLUMN "rank" TEXT,
36+
ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'STUDENT',
37+
ADD COLUMN "skillLevel" "SkillLevel",
38+
ADD COLUMN "skills" TEXT,
39+
ADD COLUMN "title" TEXT,
40+
ADD COLUMN "websiteUrl" TEXT,
41+
ADD COLUMN "yearsServed" INTEGER;
42+
43+
-- CreateTable
44+
CREATE TABLE "Course" (
45+
"id" TEXT NOT NULL,
46+
"title" TEXT NOT NULL,
47+
"description" TEXT,
48+
"imageUrl" TEXT,
49+
"difficulty" "Difficulty" NOT NULL DEFAULT 'BEGINNER',
50+
"category" TEXT NOT NULL,
51+
"isPublished" BOOLEAN NOT NULL DEFAULT false,
52+
"estimatedHours" INTEGER,
53+
"prerequisites" TEXT[],
54+
"tags" TEXT[],
55+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
56+
"updatedAt" TIMESTAMP(3) NOT NULL,
57+
58+
CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
59+
);
60+
61+
-- CreateTable
62+
CREATE TABLE "Module" (
63+
"id" TEXT NOT NULL,
64+
"title" TEXT NOT NULL,
65+
"description" TEXT,
66+
"order" INTEGER NOT NULL,
67+
"courseId" TEXT NOT NULL,
68+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
69+
"updatedAt" TIMESTAMP(3) NOT NULL,
70+
71+
CONSTRAINT "Module_pkey" PRIMARY KEY ("id")
72+
);
73+
74+
-- CreateTable
75+
CREATE TABLE "Lesson" (
76+
"id" TEXT NOT NULL,
77+
"title" TEXT NOT NULL,
78+
"content" TEXT NOT NULL,
79+
"videoUrl" TEXT,
80+
"duration" INTEGER,
81+
"order" INTEGER NOT NULL,
82+
"type" "LessonType" NOT NULL DEFAULT 'CONTENT',
83+
"codeExample" TEXT,
84+
"resources" TEXT,
85+
"moduleId" TEXT NOT NULL,
86+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
87+
"updatedAt" TIMESTAMP(3) NOT NULL,
88+
89+
CONSTRAINT "Lesson_pkey" PRIMARY KEY ("id")
90+
);
91+
92+
-- CreateTable
93+
CREATE TABLE "Enrollment" (
94+
"id" TEXT NOT NULL,
95+
"userId" TEXT NOT NULL,
96+
"courseId" TEXT NOT NULL,
97+
"status" "EnrollmentStatus" NOT NULL DEFAULT 'ACTIVE',
98+
"progress" DOUBLE PRECISION NOT NULL DEFAULT 0,
99+
"enrolledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
100+
"completedAt" TIMESTAMP(3),
101+
"lastActivity" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
102+
103+
CONSTRAINT "Enrollment_pkey" PRIMARY KEY ("id")
104+
);
105+
106+
-- CreateTable
107+
CREATE TABLE "Progress" (
108+
"id" TEXT NOT NULL,
109+
"userId" TEXT NOT NULL,
110+
"lessonId" TEXT NOT NULL,
111+
"completed" BOOLEAN NOT NULL DEFAULT false,
112+
"timeSpent" INTEGER NOT NULL DEFAULT 0,
113+
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
114+
"completedAt" TIMESTAMP(3),
115+
116+
CONSTRAINT "Progress_pkey" PRIMARY KEY ("id")
117+
);
118+
119+
-- CreateTable
120+
CREATE TABLE "Assignment" (
121+
"id" TEXT NOT NULL,
122+
"title" TEXT NOT NULL,
123+
"description" TEXT NOT NULL,
124+
"instructions" TEXT NOT NULL,
125+
"dueDate" TIMESTAMP(3),
126+
"maxPoints" INTEGER NOT NULL DEFAULT 100,
127+
"type" "AssignmentType" NOT NULL DEFAULT 'PROJECT',
128+
"githubRepo" BOOLEAN NOT NULL DEFAULT false,
129+
"liveDemo" BOOLEAN NOT NULL DEFAULT false,
130+
"courseId" TEXT NOT NULL,
131+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
132+
"updatedAt" TIMESTAMP(3) NOT NULL,
133+
134+
CONSTRAINT "Assignment_pkey" PRIMARY KEY ("id")
135+
);
136+
137+
-- CreateTable
138+
CREATE TABLE "Submission" (
139+
"id" TEXT NOT NULL,
140+
"userId" TEXT NOT NULL,
141+
"assignmentId" TEXT NOT NULL,
142+
"githubUrl" TEXT,
143+
"liveUrl" TEXT,
144+
"notes" TEXT,
145+
"files" TEXT,
146+
"status" "SubmissionStatus" NOT NULL DEFAULT 'SUBMITTED',
147+
"score" INTEGER,
148+
"feedback" TEXT,
149+
"submittedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
150+
"gradedAt" TIMESTAMP(3),
151+
152+
CONSTRAINT "Submission_pkey" PRIMARY KEY ("id")
153+
);
154+
155+
-- CreateTable
156+
CREATE TABLE "Cohort" (
157+
"id" TEXT NOT NULL,
158+
"name" TEXT NOT NULL,
159+
"description" TEXT,
160+
"startDate" TIMESTAMP(3) NOT NULL,
161+
"endDate" TIMESTAMP(3),
162+
"isElite" BOOLEAN NOT NULL DEFAULT false,
163+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
164+
"updatedAt" TIMESTAMP(3) NOT NULL,
165+
166+
CONSTRAINT "Cohort_pkey" PRIMARY KEY ("id")
167+
);
168+
169+
-- CreateTable
170+
CREATE TABLE "Certificate" (
171+
"id" TEXT NOT NULL,
172+
"userId" TEXT NOT NULL,
173+
"courseId" TEXT NOT NULL,
174+
"certificateUrl" TEXT,
175+
"issuedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
176+
177+
CONSTRAINT "Certificate_pkey" PRIMARY KEY ("id")
178+
);
179+
180+
-- CreateTable
181+
CREATE TABLE "Bookmark" (
182+
"id" TEXT NOT NULL,
183+
"userId" TEXT NOT NULL,
184+
"lessonId" TEXT NOT NULL,
185+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
186+
187+
CONSTRAINT "Bookmark_pkey" PRIMARY KEY ("id")
188+
);
189+
190+
-- CreateTable
191+
CREATE TABLE "Note" (
192+
"id" TEXT NOT NULL,
193+
"userId" TEXT NOT NULL,
194+
"lessonId" TEXT NOT NULL,
195+
"content" TEXT NOT NULL,
196+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
197+
"updatedAt" TIMESTAMP(3) NOT NULL,
198+
199+
CONSTRAINT "Note_pkey" PRIMARY KEY ("id")
200+
);
201+
202+
-- CreateIndex
203+
CREATE UNIQUE INDEX "Module_courseId_order_key" ON "Module"("courseId", "order");
204+
205+
-- CreateIndex
206+
CREATE UNIQUE INDEX "Lesson_moduleId_order_key" ON "Lesson"("moduleId", "order");
207+
208+
-- CreateIndex
209+
CREATE UNIQUE INDEX "Enrollment_userId_courseId_key" ON "Enrollment"("userId", "courseId");
210+
211+
-- CreateIndex
212+
CREATE UNIQUE INDEX "Progress_userId_lessonId_key" ON "Progress"("userId", "lessonId");
213+
214+
-- CreateIndex
215+
CREATE UNIQUE INDEX "Submission_userId_assignmentId_key" ON "Submission"("userId", "assignmentId");
216+
217+
-- CreateIndex
218+
CREATE UNIQUE INDEX "Certificate_userId_courseId_key" ON "Certificate"("userId", "courseId");
219+
220+
-- CreateIndex
221+
CREATE UNIQUE INDEX "Bookmark_userId_lessonId_key" ON "Bookmark"("userId", "lessonId");
222+
223+
-- AddForeignKey
224+
ALTER TABLE "User" ADD CONSTRAINT "User_cohortId_fkey" FOREIGN KEY ("cohortId") REFERENCES "Cohort"("id") ON DELETE SET NULL ON UPDATE CASCADE;
225+
226+
-- AddForeignKey
227+
ALTER TABLE "Module" ADD CONSTRAINT "Module_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
228+
229+
-- AddForeignKey
230+
ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "Module"("id") ON DELETE CASCADE ON UPDATE CASCADE;
231+
232+
-- AddForeignKey
233+
ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
234+
235+
-- AddForeignKey
236+
ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
237+
238+
-- AddForeignKey
239+
ALTER TABLE "Progress" ADD CONSTRAINT "Progress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
240+
241+
-- AddForeignKey
242+
ALTER TABLE "Progress" ADD CONSTRAINT "Progress_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE CASCADE ON UPDATE CASCADE;
243+
244+
-- AddForeignKey
245+
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
246+
247+
-- AddForeignKey
248+
ALTER TABLE "Submission" ADD CONSTRAINT "Submission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
249+
250+
-- AddForeignKey
251+
ALTER TABLE "Submission" ADD CONSTRAINT "Submission_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
252+
253+
-- AddForeignKey
254+
ALTER TABLE "Certificate" ADD CONSTRAINT "Certificate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
255+
256+
-- AddForeignKey
257+
ALTER TABLE "Certificate" ADD CONSTRAINT "Certificate_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
258+
259+
-- AddForeignKey
260+
ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
261+
262+
-- AddForeignKey
263+
ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE CASCADE ON UPDATE CASCADE;
264+
265+
-- AddForeignKey
266+
ALTER TABLE "Note" ADD CONSTRAINT "Note_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
267+
268+
-- AddForeignKey
269+
ALTER TABLE "Note" ADD CONSTRAINT "Note_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE CASCADE ON UPDATE CASCADE;

0 commit comments

Comments
 (0)