Initial commit

This commit is contained in:
twotalesanimation
2026-06-11 10:46:09 +02:00
commit 81ad7e4ea9
223 changed files with 39530 additions and 0 deletions
@@ -0,0 +1,173 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"image" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Course" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Enrollment" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"courseId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Enrollment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Playlist" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"courseId" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Playlist_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Video" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"playlistId" TEXT NOT NULL,
"durationSec" INTEGER,
"url" TEXT NOT NULL,
"thumbnail" TEXT,
"index" INTEGER NOT NULL DEFAULT 0,
"locked" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Video_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VideoProgress" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"seconds" INTEGER NOT NULL DEFAULT 0,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "VideoProgress_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Course_code_key" ON "Course"("code");
-- CreateIndex
CREATE INDEX "Enrollment_courseId_idx" ON "Enrollment"("courseId");
-- CreateIndex
CREATE UNIQUE INDEX "Enrollment_userId_courseId_key" ON "Enrollment"("userId", "courseId");
-- CreateIndex
CREATE INDEX "Playlist_courseId_idx" ON "Playlist"("courseId");
-- CreateIndex
CREATE INDEX "Video_playlistId_idx" ON "Video"("playlistId");
-- CreateIndex
CREATE UNIQUE INDEX "Video_playlistId_index_key" ON "Video"("playlistId", "index");
-- CreateIndex
CREATE INDEX "VideoProgress_videoId_idx" ON "VideoProgress"("videoId");
-- CreateIndex
CREATE UNIQUE INDEX "VideoProgress_userId_videoId_key" ON "VideoProgress"("userId", "videoId");
-- CreateIndex
CREATE INDEX "Account_userId_idx" ON "Account"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- AddForeignKey
ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Playlist" ADD CONSTRAINT "Playlist_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Video" ADD CONSTRAINT "Video_playlistId_fkey" FOREIGN KEY ("playlistId") REFERENCES "Playlist"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "emailVerified" TIMESTAMP(3);
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "role" TEXT NOT NULL DEFAULT 'user';
@@ -0,0 +1,16 @@
/*
Warnings:
- You are about to drop the column `seconds` on the `VideoProgress` table. All the data in the column will be lost.
*/
-- DropIndex
DROP INDEX "VideoProgress_videoId_idx";
-- AlterTable
ALTER TABLE "VideoProgress" DROP COLUMN "seconds",
ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "lastPos" INTEGER,
ADD COLUMN "percent" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "watchedSec" INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "VideoProgress" ADD COLUMN "durationSec" INTEGER;
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "VideoLike" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "VideoLike_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "VideoLike_userId_idx" ON "VideoLike"("userId");
-- CreateIndex
CREATE INDEX "VideoLike_videoId_idx" ON "VideoLike"("videoId");
-- CreateIndex
CREATE UNIQUE INDEX "VideoLike_userId_videoId_key" ON "VideoLike"("userId", "videoId");
-- AddForeignKey
ALTER TABLE "VideoLike" ADD CONSTRAINT "VideoLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoLike" ADD CONSTRAINT "VideoLike_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Video" ADD COLUMN "description" TEXT;
@@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "Comment" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CommentReply" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"commentId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CommentReply_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Comment_videoId_idx" ON "Comment"("videoId");
-- CreateIndex
CREATE INDEX "Comment_userId_idx" ON "Comment"("userId");
-- CreateIndex
CREATE INDEX "CommentReply_commentId_idx" ON "CommentReply"("commentId");
-- CreateIndex
CREATE INDEX "CommentReply_userId_idx" ON "CommentReply"("userId");
-- AddForeignKey
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CommentReply" ADD CONSTRAINT "CommentReply_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CommentReply" ADD CONSTRAINT "CommentReply_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "Comment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,11 @@
-- DropForeignKey
ALTER TABLE "VideoLike" DROP CONSTRAINT "VideoLike_videoId_fkey";
-- DropForeignKey
ALTER TABLE "VideoProgress" DROP CONSTRAINT "VideoProgress_videoId_fkey";
-- AddForeignKey
ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoLike" ADD CONSTRAINT "VideoLike_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,33 @@
-- CreateTable
CREATE TABLE "VideoWatchSegment" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"startSec" INTEGER NOT NULL,
"endSec" INTEGER NOT NULL,
"watchedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "VideoWatchSegment_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "VideoWatchSegment_userId_videoId_idx" ON "VideoWatchSegment"("userId", "videoId");
-- CreateIndex
CREATE INDEX "VideoWatchSegment_watchedAt_idx" ON "VideoWatchSegment"("watchedAt");
-- CreateIndex
CREATE INDEX "VideoWatchSegment_userId_idx" ON "VideoWatchSegment"("userId");
-- CreateIndex
CREATE INDEX "VideoWatchSegment_videoId_idx" ON "VideoWatchSegment"("videoId");
-- AddForeignKey
ALTER TABLE "VideoWatchSegment" ADD CONSTRAINT "VideoWatchSegment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoWatchSegment" ADD CONSTRAINT "VideoWatchSegment_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoWatchSegment" ADD CONSTRAINT "VideoWatchSegment_userId_videoId_fkey" FOREIGN KEY ("userId", "videoId") REFERENCES "VideoProgress"("userId", "videoId") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,31 @@
-- AlterTable
ALTER TABLE "Video" ADD COLUMN "instantAccess" BOOLEAN NOT NULL DEFAULT false;
-- CreateTable
CREATE TABLE "VideoUnlock" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"unlockedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "VideoUnlock_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "VideoUnlock_userId_idx" ON "VideoUnlock"("userId");
-- CreateIndex
CREATE INDEX "VideoUnlock_videoId_idx" ON "VideoUnlock"("videoId");
-- CreateIndex
CREATE INDEX "VideoUnlock_userId_videoId_idx" ON "VideoUnlock"("userId", "videoId");
-- CreateIndex
CREATE UNIQUE INDEX "VideoUnlock_userId_videoId_key" ON "VideoUnlock"("userId", "videoId");
-- AddForeignKey
ALTER TABLE "VideoUnlock" ADD CONSTRAINT "VideoUnlock_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoUnlock" ADD CONSTRAINT "VideoUnlock_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "Video" ADD COLUMN "userId" TEXT;
-- CreateIndex
CREATE INDEX "Video_userId_idx" ON "Video"("userId");
-- AddForeignKey
ALTER TABLE "Video" ADD CONSTRAINT "Video_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,11 @@
-- AlterTable
ALTER TABLE "Course" ADD COLUMN "userId" TEXT;
-- AlterTable
ALTER TABLE "Playlist" ADD COLUMN "userId" TEXT;
-- AddForeignKey
ALTER TABLE "Course" ADD CONSTRAINT "Course_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Playlist" ADD CONSTRAINT "Playlist_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,14 @@
-- CreateTable AllowedStudent
CREATE TABLE "AllowedStudent" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"levels" TEXT NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AllowedStudent_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AllowedStudent_email_key" ON "AllowedStudent"("email");
@@ -0,0 +1,21 @@
-- Add onDelete Cascade to foreign key constraints
-- Drop existing foreign key constraints and recreate them with CASCADE
ALTER TABLE "Enrollment" DROP CONSTRAINT IF EXISTS "Enrollment_userId_fkey";
ALTER TABLE "Enrollment" ADD CONSTRAINT "Enrollment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "VideoProgress" DROP CONSTRAINT IF EXISTS "VideoProgress_userId_fkey";
ALTER TABLE "VideoProgress" ADD CONSTRAINT "VideoProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "VideoLike" DROP CONSTRAINT IF EXISTS "VideoLike_userId_fkey";
ALTER TABLE "VideoLike" ADD CONSTRAINT "VideoLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Account" DROP CONSTRAINT IF EXISTS "Account_userId_fkey";
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Session" DROP CONSTRAINT IF EXISTS "Session_userId_fkey";
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- VideoWatchSegment already has cascade for user
-- Comment already has cascade for user
-- CommentReply already has cascade for user
@@ -0,0 +1,22 @@
-- CreateTable CoursePlaylist
CREATE TABLE "CoursePlaylist" (
"id" TEXT NOT NULL,
"courseId" TEXT NOT NULL,
"playlistId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CoursePlaylist_pkey" PRIMARY KEY ("id")
);
-- CreateIndex for CoursePlaylist
CREATE UNIQUE INDEX "CoursePlaylist_courseId_playlistId_key" ON "CoursePlaylist"("courseId", "playlistId");
CREATE INDEX "CoursePlaylist_courseId_idx" ON "CoursePlaylist"("courseId");
CREATE INDEX "CoursePlaylist_playlistId_idx" ON "CoursePlaylist"("playlistId");
-- Add foreign key constraints
ALTER TABLE "CoursePlaylist" ADD CONSTRAINT "CoursePlaylist_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CoursePlaylist" ADD CONSTRAINT "CoursePlaylist_playlistId_fkey" FOREIGN KEY ("playlistId") REFERENCES "Playlist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Update Playlist table to add ON DELETE CASCADE to courseId
ALTER TABLE "Playlist" DROP CONSTRAINT IF EXISTS "Playlist_courseId_fkey";
ALTER TABLE "Playlist" ADD CONSTRAINT "Playlist_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,28 @@
-- AlterTable
ALTER TABLE "AllowedStudent" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- CreateTable
CREATE TABLE "VideoCourse" (
"id" TEXT NOT NULL,
"videoId" TEXT NOT NULL,
"courseId" TEXT NOT NULL,
"exclusive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "VideoCourse_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "VideoCourse_courseId_idx" ON "VideoCourse"("courseId");
-- CreateIndex
CREATE INDEX "VideoCourse_videoId_idx" ON "VideoCourse"("videoId");
-- CreateIndex
CREATE UNIQUE INDEX "VideoCourse_videoId_courseId_key" ON "VideoCourse"("videoId", "courseId");
-- AddForeignKey
ALTER TABLE "VideoCourse" ADD CONSTRAINT "VideoCourse_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Video"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VideoCourse" ADD CONSTRAINT "VideoCourse_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AddColumn transcoding_status to Video model
ALTER TABLE "Video" ADD COLUMN "transcodingStatus" TEXT NOT NULL DEFAULT 'uploaded';
-- Create index on transcodingStatus for efficient querying
CREATE INDEX "Video_transcodingStatus_idx" ON "Video"("transcodingStatus");
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+53
View File
@@ -0,0 +1,53 @@
import { prisma } from '@/lib/prisma';
async function resetDatabase() {
try {
console.log('Starting database reset...');
// Clear all watch segments
await prisma.videoWatchSegment.deleteMany({});
console.log('✓ Cleared VideoWatchSegment records');
// Clear all progress records
await prisma.videoProgress.deleteMany({});
console.log('✓ Cleared VideoProgress records');
// Get all playlists with their videos
const playlists = await prisma.playlist.findMany({
include: { videos: { orderBy: { index: 'asc' } } },
});
// For each playlist, unlock first video, lock the rest
for (const playlist of playlists) {
if (playlist.videos.length > 0) {
// Unlock first video
await prisma.video.updateMany({
where: { playlistId: playlist.id, index: 0 },
data: { locked: false },
});
// Lock all other videos
await prisma.video.updateMany({
where: { playlistId: playlist.id, index: { gt: 0 } },
data: { locked: true },
});
console.log(`✓ Set unlock status for playlist: ${playlist.title}`);
}
}
console.log('✓ Database reset complete!');
console.log('\nSummary:');
console.log('- All watch segments cleared');
console.log('- All progress records cleared');
console.log('- First video in each playlist unlocked');
console.log('- Remaining videos locked (require 80% completion of previous video)');
process.exit(0);
} catch (error) {
console.error('Error during reset:', error);
process.exit(1);
}
}
resetDatabase();
+266
View File
@@ -0,0 +1,266 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
createdAt DateTime @default(now())
emailVerified DateTime?
role String @default("user")
accounts Account[]
comments Comment[]
replies CommentReply[]
enrollments Enrollment[]
sessions Session[]
likes VideoLike[]
progress VideoProgress[]
unlocks VideoUnlock[]
watchSegments VideoWatchSegment[]
uploadedVideos Video[] @relation("VideoUploader")
createdCourses Course[] @relation("CourseCreator")
createdPlaylists Playlist[] @relation("PlaylistCreator")
}
model Course {
id String @id @default(cuid())
code String @unique
title String
description String?
userId String?
createdAt DateTime @default(now())
creator User? @relation("CourseCreator", fields: [userId], references: [id], onDelete: SetNull)
enrollments Enrollment[]
playlists Playlist[]
playlistMappings CoursePlaylist[]
courseVideos VideoCourse[]
}
model Enrollment {
id String @id @default(cuid())
userId String
courseId String
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, courseId])
@@index([courseId])
}
model Playlist {
id String @id @default(cuid())
title String
description String?
courseId String // Primary course (backwards compatibility)
userId String?
sortOrder Int @default(0)
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
creator User? @relation("PlaylistCreator", fields: [userId], references: [id], onDelete: SetNull)
videos Video[]
courses CoursePlaylist[] // Additional courses this playlist is assigned to
@@index([courseId])
}
model CoursePlaylist {
id String @id @default(cuid())
courseId String
playlistId String
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
@@unique([courseId, playlistId])
@@index([courseId])
@@index([playlistId])
}
model Video {
id String @id @default(cuid())
title String
playlistId String
userId String?
durationSec Int?
url String
thumbnail String?
index Int @default(0)
locked Boolean @default(false)
createdAt DateTime @default(now())
description String?
instantAccess Boolean @default(false)
transcodingStatus String @default("uploaded")
comments Comment[]
playlist Playlist @relation(fields: [playlistId], references: [id])
uploader User? @relation("VideoUploader", fields: [userId], references: [id], onDelete: SetNull)
likes VideoLike[]
progress VideoProgress[]
unlocks VideoUnlock[]
watchSegments VideoWatchSegment[]
videoCourses VideoCourse[]
@@unique([playlistId, index])
@@index([playlistId])
@@index([userId])
@@index([transcodingStatus])
}
model VideoCourse {
id String @id @default(cuid())
videoId String
courseId String
exclusive Boolean @default(true)
createdAt DateTime @default(now())
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@unique([videoId, courseId])
@@index([courseId])
@@index([videoId])
}
model VideoProgress {
id String @id @default(cuid())
userId String
videoId String
updatedAt DateTime @updatedAt
completed Boolean @default(false)
createdAt DateTime @default(now())
lastPos Int?
percent Int @default(0)
watchedSec Int @default(0)
durationSec Int?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
watchSegments VideoWatchSegment[]
@@unique([userId, videoId])
}
model VideoWatchSegment {
id String @id @default(cuid())
userId String
videoId String
startSec Int
endSec Int
watchedAt DateTime @default(now())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
progress VideoProgress @relation(fields: [userId, videoId], references: [userId, videoId], onDelete: Cascade)
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
@@index([userId, videoId])
@@index([watchedAt])
@@index([userId])
@@index([videoId])
}
model VideoUnlock {
id String @id @default(cuid())
userId String
videoId String
unlockedAt DateTime @default(now())
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
@@unique([userId, videoId])
@@index([userId])
@@index([videoId])
@@index([userId, videoId])
}
model VideoLike {
id String @id @default(cuid())
userId String
videoId String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
@@unique([userId, videoId])
@@index([userId])
@@index([videoId])
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@index([userId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
model Comment {
id String @id @default(cuid())
userId String
videoId String
content String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
video Video @relation(fields: [videoId], references: [id], onDelete: Cascade)
replies CommentReply[]
@@index([videoId])
@@index([userId])
}
model CommentReply {
id String @id @default(cuid())
userId String
commentId String
content String
createdAt DateTime @default(now())
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([commentId])
@@index([userId])
}
model AllowedStudent {
id String @id @default(cuid())
email String @unique
levels String // comma-separated course codes: "3D100,3D200,3D300"
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+75
View File
@@ -0,0 +1,75 @@
// prisma/seed.js
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
async function main() {
console.log("🌱 Seeding database…");
const course = await prisma.course.upsert({
where: { code: "ANIM101" },
update: {},
create: {
code: "ANIM101",
title: "3D Animation | Character Design",
description: "Introductory course for animation students.",
},
});
await prisma.playlist.create({
data: {
title: "Introduction to Character Design",
description: "Basic foundations and workflow.",
sortOrder: 0,
courseId: course.id,
videos: {
create: [
{ title: "Welcome + Overview", index: 0, durationSec: 300, url: "/videos/test.mp4", thumbnail: "/01.jpg" },
{ title: "Understanding Silhouette", index: 1, durationSec: 420, url: "/videos/test.mp4", thumbnail: "/01.jpg" },
{ title: "Gesture & Appeal", index: 2, durationSec: 380, url: "/videos/test.mp4", thumbnail: "/01.jpg" },
],
},
},
});
await prisma.playlist.create({
data: {
title: "Advanced Character Techniques",
description: "Secondary forms, expression & posing.",
sortOrder: 1,
courseId: course.id,
videos: {
create: [
{ title: "Secondary Motion", index: 0, durationSec: 400, url: "/videos/test.mp4", thumbnail: "/01.jpg" },
{ title: "Expressions & Acting Choices", index: 1, durationSec: 600, url: "/videos/test.mp4", thumbnail: "/01.jpg", locked: true },
{ title: "Polish & Finishing", index: 2, durationSec: 500, url: "/videos/test.mp4", thumbnail: "/01.jpg" },
],
},
},
});
const user = await prisma.user.upsert({
where: { email: "student@example.com" },
update: {},
create: {
email: "student@example.com",
name: "Test Student",
},
});
await prisma.enrollment.upsert({
where: { userId_courseId: { userId: user.id, courseId: course.id } },
update: {},
create: {
userId: user.id,
courseId: course.id,
},
});
console.log("🌱 Seed complete!");
}
main()
.catch((e) => {
console.error(e);
})
.finally(() => prisma.$disconnect());
+129
View File
@@ -0,0 +1,129 @@
// prisma/seed.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
console.log("🌱 Seeding database…");
// --- Create a test course ---
const course = await prisma.course.upsert({
where: { code: "ANIM101" },
update: {},
create: {
code: "ANIM101",
title: "3D Animation | Character Design",
description: "Introductory course for animation students.",
},
});
// --- Create playlists for this course ---
const playlist1 = await prisma.playlist.create({
data: {
title: "Introduction to Character Design",
description: "Basic foundations and workflow.",
sortOrder: 0,
courseId: course.id,
videos: {
create: [
{
title: "Welcome + Overview",
index: 0,
durationSec: 300,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
},
{
title: "Understanding Silhouette",
index: 1,
durationSec: 420,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
},
{
title: "Gesture & Appeal",
index: 2,
durationSec: 380,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
},
],
},
},
include: { videos: true },
});
const playlist2 = await prisma.playlist.create({
data: {
title: "Advanced Character Techniques",
description: "Secondary forms, expression & posing.",
sortOrder: 1,
courseId: course.id,
videos: {
create: [
{
title: "Secondary Motion",
index: 0,
durationSec: 400,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
},
{
title: "Expressions & Acting Choices",
index: 1,
durationSec: 600,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
locked: true,
},
{
title: "Polish & Finishing",
index: 2,
durationSec: 500,
url: "/videos/test.mp4",
thumbnail: "/01.jpg",
},
],
},
},
include: { videos: true },
});
// --- Create a test user ---
const user = await prisma.user.upsert({
where: { email: "student@example.com" },
update: {},
create: {
email: "student@example.com",
name: "Test Student",
image: null,
},
});
// --- Enroll test user into the course ---
await prisma.enrollment.upsert({
where: {
userId_courseId: {
userId: user.id,
courseId: course.id,
},
},
update: {},
create: {
userId: user.id,
courseId: course.id,
},
});
console.log("🌱 Seed complete!");
console.log("Test user:", user.email);
console.log("Course created:", course.title);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});