first commit

This commit is contained in:
2026-04-14 19:44:21 -05:00
commit 068576cf4b
36 changed files with 13680 additions and 0 deletions

View File

@@ -0,0 +1 @@
# Keep Prisma migrations directory in VCS

View File

@@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "public"."User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."RefreshToken" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"revokedAt" TIMESTAMP(3),
"replacedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "public"."User"("email");
-- CreateIndex
CREATE INDEX "User_email_idx" ON "public"."User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "RefreshToken_tokenHash_key" ON "public"."RefreshToken"("tokenHash");
-- CreateIndex
CREATE INDEX "RefreshToken_userId_idx" ON "public"."RefreshToken"("userId");
-- CreateIndex
CREATE INDEX "RefreshToken_expiresAt_idx" ON "public"."RefreshToken"("expiresAt");
-- AddForeignKey
ALTER TABLE "public"."RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."RefreshToken" ADD CONSTRAINT "RefreshToken_replacedById_fkey" FOREIGN KEY ("replacedById") REFERENCES "public"."RefreshToken"("id") ON DELETE SET NULL ON UPDATE CASCADE;

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"

38
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,38 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
refreshTokens RefreshToken[]
@@index([email])
}
model RefreshToken {
id String @id @default(uuid())
userId String
tokenHash String @unique
expiresAt DateTime
revokedAt DateTime?
replacedById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
replacedBy RefreshToken? @relation("RefreshRotation", fields: [replacedById], references: [id])
replaces RefreshToken[] @relation("RefreshRotation")
@@index([userId])
@@index([expiresAt])
}

52
prisma/seed.mjs Normal file
View File

@@ -0,0 +1,52 @@
import { PrismaClient } from '@prisma/client'
import { randomBytes, scryptSync } from 'node:crypto'
const prisma = new PrismaClient()
function hashPassword(password) {
const salt = randomBytes(16).toString('base64url')
const derivedKey = scryptSync(password, salt, 64).toString('base64url')
return `scrypt$${salt}$${derivedKey}`
}
async function upsertUser({ email, password }) {
const passwordHash = hashPassword(password)
await prisma.user.upsert({
where: { email },
update: {
passwordHash
},
create: {
email,
passwordHash
}
})
}
async function main() {
await upsertUser({
email: 'student@example.com',
password: 'student123'
})
await upsertUser({
email: 'admin@example.com',
password: 'admin123'
})
await upsertUser({
email: 'limited@example.com',
password: 'limited123'
})
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (error) => {
console.error('Seed failed:', error)
await prisma.$disconnect()
process.exit(1)
})