feat: Prisma schema, migrations en seed voor auth en audit-log
- Models: User, Session, FlowRun, FlowStep met FlowStatus enum - prisma.config.ts met DATABASE_URL via @prisma/adapter-pg (Prisma 7 API) - Initiële migratie applied op ops_dashboard database - Seed-script voor 1 user via SEED_USER_EMAIL/SEED_USER_PASSWORD env-vars - lib/prisma.ts als gedeelde singleton client voor Next.js Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
60393e40b1
commit
cce0f25419
10 changed files with 1433 additions and 6 deletions
3
.env.example
Normal file
3
.env.example
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/ops_dashboard"
|
||||||
|
SEED_USER_EMAIL="admin@example.com"
|
||||||
|
SEED_USER_PASSWORD="changeme"
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -31,7 +31,9 @@ yarn-error.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
|
||||||
17
lib/prisma.ts
Normal file
17
lib/prisma.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg'
|
||||||
|
|
||||||
|
function createPrismaClient() {
|
||||||
|
const connectionString = process.env.DATABASE_URL
|
||||||
|
if (!connectionString) {
|
||||||
|
throw new Error('DATABASE_URL environment variable is required')
|
||||||
|
}
|
||||||
|
const adapter = new PrismaPg({ connectionString })
|
||||||
|
return new PrismaClient({ adapter })
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
|
||||||
|
|
||||||
|
export const prisma = globalForPrisma.prisma ?? createPrismaClient()
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||||
1227
package-lock.json
generated
1227
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
|
@ -5,14 +5,25 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start"
|
"start": "next start",
|
||||||
|
"db:seed": "ts-node --esm prisma/seed.ts"
|
||||||
|
},
|
||||||
|
"prisma": {
|
||||||
|
"seed": "ts-node --esm prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.4.1",
|
"@base-ui/react": "^1.4.1",
|
||||||
|
"@prisma/adapter-pg": "^7.8.0",
|
||||||
|
"@prisma/client": "^7.8.0",
|
||||||
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/pg": "^8.20.0",
|
||||||
|
"bcryptjs": "^3.0.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^1.14.0",
|
"lucide-react": "^1.14.0",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
|
"pg": "^8.20.0",
|
||||||
|
"prisma": "^7.8.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"shadcn": "^4.7.0",
|
"shadcn": "^4.7.0",
|
||||||
|
|
@ -25,6 +36,7 @@
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
prisma.config.ts
Normal file
10
prisma.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig, env } from 'prisma/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
migrations: {
|
||||||
|
seed: 'ts-node --esm prisma/seed.ts',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
})
|
||||||
67
prisma/migrations/20260513150226_init/migration.sql
Normal file
67
prisma/migrations/20260513150226_init/migration.sql
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "FlowStatus" AS ENUM ('pending', 'running', 'success', 'failed', 'cancelled');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"pwd_hash" TEXT NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Session" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"user_id" TEXT NOT NULL,
|
||||||
|
"token_hash" TEXT NOT NULL,
|
||||||
|
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "FlowRun" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"user_id" TEXT NOT NULL,
|
||||||
|
"flow_key" TEXT NOT NULL,
|
||||||
|
"status" "FlowStatus" NOT NULL DEFAULT 'pending',
|
||||||
|
"dry_run" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"ended_at" TIMESTAMP(3),
|
||||||
|
"exit_code" INTEGER,
|
||||||
|
|
||||||
|
CONSTRAINT "FlowRun_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "FlowStep" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"flow_run_id" TEXT NOT NULL,
|
||||||
|
"step_index" INTEGER NOT NULL,
|
||||||
|
"command_key" TEXT NOT NULL,
|
||||||
|
"args_json" TEXT,
|
||||||
|
"stdout" TEXT,
|
||||||
|
"stderr" TEXT,
|
||||||
|
"exit_code" INTEGER,
|
||||||
|
"started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"ended_at" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "FlowStep_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_token_hash_key" ON "Session"("token_hash");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "FlowRun" ADD CONSTRAINT "FlowRun_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "FlowStep" ADD CONSTRAINT "FlowStep_flow_run_id_fkey" FOREIGN KEY ("flow_run_id") REFERENCES "FlowRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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"
|
||||||
59
prisma/schema.prisma
Normal file
59
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlowStatus {
|
||||||
|
pending
|
||||||
|
running
|
||||||
|
success
|
||||||
|
failed
|
||||||
|
cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
email String @unique
|
||||||
|
pwd_hash String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
sessions Session[]
|
||||||
|
flow_runs FlowRun[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
user_id String
|
||||||
|
token_hash String @unique
|
||||||
|
expires_at DateTime
|
||||||
|
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model FlowRun {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
user_id String
|
||||||
|
flow_key String
|
||||||
|
status FlowStatus @default(pending)
|
||||||
|
dry_run Boolean @default(false)
|
||||||
|
started_at DateTime @default(now())
|
||||||
|
ended_at DateTime?
|
||||||
|
exit_code Int?
|
||||||
|
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
|
||||||
|
steps FlowStep[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model FlowStep {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
flow_run_id String
|
||||||
|
step_index Int
|
||||||
|
command_key String
|
||||||
|
args_json String?
|
||||||
|
stdout String?
|
||||||
|
stderr String?
|
||||||
|
exit_code Int?
|
||||||
|
started_at DateTime @default(now())
|
||||||
|
ended_at DateTime?
|
||||||
|
flow_run FlowRun @relation(fields: [flow_run_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
35
prisma/seed.ts
Normal file
35
prisma/seed.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
import { PrismaPg } from '@prisma/adapter-pg'
|
||||||
|
import bcrypt from 'bcryptjs'
|
||||||
|
|
||||||
|
const connectionString = process.env.DATABASE_URL
|
||||||
|
if (!connectionString) throw new Error('DATABASE_URL is required')
|
||||||
|
|
||||||
|
const adapter = new PrismaPg({ connectionString })
|
||||||
|
const prisma = new PrismaClient({ adapter })
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const email = process.env.SEED_USER_EMAIL
|
||||||
|
const password = process.env.SEED_USER_PASSWORD
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
throw new Error('SEED_USER_EMAIL and SEED_USER_PASSWORD env vars are required')
|
||||||
|
}
|
||||||
|
|
||||||
|
const pwd_hash = await bcrypt.hash(password, 12)
|
||||||
|
|
||||||
|
const user = await prisma.user.upsert({
|
||||||
|
where: { email },
|
||||||
|
update: { pwd_hash },
|
||||||
|
create: { email, pwd_hash },
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Seeded user: ${user.email} (id: ${user.id})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
.finally(() => prisma.$disconnect())
|
||||||
Loading…
Add table
Add a link
Reference in a new issue