This commit is contained in:
xCyanGrizzly
2026-02-18 14:26:36 +01:00
commit 3a5726e82b
167 changed files with 104081 additions and 0 deletions

View File

@@ -0,0 +1,385 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'USER');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"image" TEXT,
"hashedPassword" TEXT,
"role" "Role" NOT NULL DEFAULT 'USER',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_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
);
-- CreateTable
CREATE TABLE "Vendor" (
"id" TEXT NOT NULL,
"name" VARCHAR(64) NOT NULL,
"website" VARCHAR(256),
"notes" TEXT,
"archived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "Vendor_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Location" (
"id" TEXT NOT NULL,
"name" VARCHAR(64) NOT NULL,
"description" VARCHAR(256),
"archived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "Location_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Filament" (
"id" TEXT NOT NULL,
"name" VARCHAR(128) NOT NULL,
"brand" VARCHAR(64) NOT NULL,
"material" VARCHAR(32) NOT NULL,
"color" VARCHAR(64) NOT NULL,
"colorHex" VARCHAR(7) NOT NULL,
"diameter" DOUBLE PRECISION NOT NULL DEFAULT 1.75,
"spoolWeight" DOUBLE PRECISION NOT NULL,
"usedWeight" DOUBLE PRECISION NOT NULL DEFAULT 0,
"emptySpoolWeight" DOUBLE PRECISION NOT NULL DEFAULT 0,
"purchaseDate" TIMESTAMP(3),
"cost" DOUBLE PRECISION,
"notes" TEXT,
"archived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
"vendorId" TEXT,
"locationId" TEXT,
CONSTRAINT "Filament_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Resin" (
"id" TEXT NOT NULL,
"name" VARCHAR(128) NOT NULL,
"brand" VARCHAR(64) NOT NULL,
"resinType" VARCHAR(32) NOT NULL,
"color" VARCHAR(64) NOT NULL,
"colorHex" VARCHAR(7) NOT NULL,
"bottleSize" DOUBLE PRECISION NOT NULL,
"usedML" DOUBLE PRECISION NOT NULL DEFAULT 0,
"purchaseDate" TIMESTAMP(3),
"cost" DOUBLE PRECISION,
"notes" TEXT,
"archived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
"vendorId" TEXT,
"locationId" TEXT,
CONSTRAINT "Resin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Paint" (
"id" TEXT NOT NULL,
"name" VARCHAR(128) NOT NULL,
"brand" VARCHAR(64) NOT NULL,
"line" VARCHAR(64),
"color" VARCHAR(64) NOT NULL,
"colorHex" VARCHAR(7) NOT NULL,
"finish" VARCHAR(32) NOT NULL,
"volumeML" DOUBLE PRECISION NOT NULL,
"usedML" DOUBLE PRECISION NOT NULL DEFAULT 0,
"purchaseDate" TIMESTAMP(3),
"cost" DOUBLE PRECISION,
"notes" TEXT,
"archived" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
"vendorId" TEXT,
"locationId" TEXT,
CONSTRAINT "Paint_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"name" VARCHAR(64) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT NOT NULL,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TagOnFilament" (
"filamentId" TEXT NOT NULL,
"tagId" TEXT NOT NULL,
CONSTRAINT "TagOnFilament_pkey" PRIMARY KEY ("filamentId","tagId")
);
-- CreateTable
CREATE TABLE "TagOnResin" (
"resinId" TEXT NOT NULL,
"tagId" TEXT NOT NULL,
CONSTRAINT "TagOnResin_pkey" PRIMARY KEY ("resinId","tagId")
);
-- CreateTable
CREATE TABLE "TagOnPaint" (
"paintId" TEXT NOT NULL,
"tagId" TEXT NOT NULL,
CONSTRAINT "TagOnPaint_pkey" PRIMARY KEY ("paintId","tagId")
);
-- CreateTable
CREATE TABLE "UsageLog" (
"id" TEXT NOT NULL,
"itemType" VARCHAR(16) NOT NULL,
"itemId" TEXT NOT NULL,
"amount" DOUBLE PRECISION NOT NULL,
"unit" VARCHAR(4) NOT NULL,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT NOT NULL,
"filamentId" TEXT,
"resinId" TEXT,
"paintId" TEXT,
CONSTRAINT "UsageLog_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserSettings" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"lowStockThreshold" DOUBLE PRECISION NOT NULL DEFAULT 10,
"currency" VARCHAR(3) NOT NULL DEFAULT 'USD',
"theme" VARCHAR(8) NOT NULL DEFAULT 'dark',
"units" VARCHAR(8) NOT NULL DEFAULT 'metric',
CONSTRAINT "UserSettings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- CreateIndex
CREATE INDEX "Vendor_userId_idx" ON "Vendor"("userId");
-- CreateIndex
CREATE INDEX "Vendor_archived_idx" ON "Vendor"("archived");
-- CreateIndex
CREATE INDEX "Location_userId_idx" ON "Location"("userId");
-- CreateIndex
CREATE INDEX "Location_archived_idx" ON "Location"("archived");
-- CreateIndex
CREATE INDEX "Filament_userId_idx" ON "Filament"("userId");
-- CreateIndex
CREATE INDEX "Filament_vendorId_idx" ON "Filament"("vendorId");
-- CreateIndex
CREATE INDEX "Filament_locationId_idx" ON "Filament"("locationId");
-- CreateIndex
CREATE INDEX "Filament_material_idx" ON "Filament"("material");
-- CreateIndex
CREATE INDEX "Filament_archived_idx" ON "Filament"("archived");
-- CreateIndex
CREATE INDEX "Filament_brand_idx" ON "Filament"("brand");
-- CreateIndex
CREATE INDEX "Resin_userId_idx" ON "Resin"("userId");
-- CreateIndex
CREATE INDEX "Resin_vendorId_idx" ON "Resin"("vendorId");
-- CreateIndex
CREATE INDEX "Resin_locationId_idx" ON "Resin"("locationId");
-- CreateIndex
CREATE INDEX "Resin_resinType_idx" ON "Resin"("resinType");
-- CreateIndex
CREATE INDEX "Resin_archived_idx" ON "Resin"("archived");
-- CreateIndex
CREATE INDEX "Resin_brand_idx" ON "Resin"("brand");
-- CreateIndex
CREATE INDEX "Paint_userId_idx" ON "Paint"("userId");
-- CreateIndex
CREATE INDEX "Paint_vendorId_idx" ON "Paint"("vendorId");
-- CreateIndex
CREATE INDEX "Paint_locationId_idx" ON "Paint"("locationId");
-- CreateIndex
CREATE INDEX "Paint_finish_idx" ON "Paint"("finish");
-- CreateIndex
CREATE INDEX "Paint_archived_idx" ON "Paint"("archived");
-- CreateIndex
CREATE INDEX "Paint_brand_idx" ON "Paint"("brand");
-- CreateIndex
CREATE INDEX "Tag_userId_idx" ON "Tag"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Tag_name_userId_key" ON "Tag"("name", "userId");
-- CreateIndex
CREATE INDEX "UsageLog_userId_idx" ON "UsageLog"("userId");
-- CreateIndex
CREATE INDEX "UsageLog_itemType_itemId_idx" ON "UsageLog"("itemType", "itemId");
-- CreateIndex
CREATE INDEX "UsageLog_createdAt_idx" ON "UsageLog"("createdAt");
-- CreateIndex
CREATE UNIQUE INDEX "UserSettings_userId_key" ON "UserSettings"("userId");
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Vendor" ADD CONSTRAINT "Vendor_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Location" ADD CONSTRAINT "Location_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Filament" ADD CONSTRAINT "Filament_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Filament" ADD CONSTRAINT "Filament_vendorId_fkey" FOREIGN KEY ("vendorId") REFERENCES "Vendor"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Filament" ADD CONSTRAINT "Filament_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Location"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Resin" ADD CONSTRAINT "Resin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Resin" ADD CONSTRAINT "Resin_vendorId_fkey" FOREIGN KEY ("vendorId") REFERENCES "Vendor"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Resin" ADD CONSTRAINT "Resin_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Location"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Paint" ADD CONSTRAINT "Paint_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Paint" ADD CONSTRAINT "Paint_vendorId_fkey" FOREIGN KEY ("vendorId") REFERENCES "Vendor"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Paint" ADD CONSTRAINT "Paint_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "Location"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Tag" ADD CONSTRAINT "Tag_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnFilament" ADD CONSTRAINT "TagOnFilament_filamentId_fkey" FOREIGN KEY ("filamentId") REFERENCES "Filament"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnFilament" ADD CONSTRAINT "TagOnFilament_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnResin" ADD CONSTRAINT "TagOnResin_resinId_fkey" FOREIGN KEY ("resinId") REFERENCES "Resin"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnResin" ADD CONSTRAINT "TagOnResin_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnPaint" ADD CONSTRAINT "TagOnPaint_paintId_fkey" FOREIGN KEY ("paintId") REFERENCES "Paint"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagOnPaint" ADD CONSTRAINT "TagOnPaint_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLog" ADD CONSTRAINT "UsageLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLog" ADD CONSTRAINT "UsageLog_filamentId_fkey" FOREIGN KEY ("filamentId") REFERENCES "Filament"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLog" ADD CONSTRAINT "UsageLog_resinId_fkey" FOREIGN KEY ("resinId") REFERENCES "Resin"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UsageLog" ADD CONSTRAINT "UsageLog_paintId_fkey" FOREIGN KEY ("paintId") REFERENCES "Paint"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserSettings" ADD CONSTRAINT "UserSettings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

301
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,301 @@
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
// ───────────────────────────────────────
// Auth.js required models
// ───────────────────────────────────────
enum Role {
ADMIN
USER
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
hashedPassword String?
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
filaments Filament[]
resins Resin[]
paints Paint[]
vendors Vendor[]
locations Location[]
usageLogs UsageLog[]
tags Tag[]
settings UserSettings?
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// ───────────────────────────────────────
// Domain models
// ───────────────────────────────────────
model Vendor {
id String @id @default(cuid())
name String @db.VarChar(64)
website String? @db.VarChar(256)
notes String? @db.Text
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
filaments Filament[]
resins Resin[]
paints Paint[]
@@index([userId])
@@index([archived])
}
model Location {
id String @id @default(cuid())
name String @db.VarChar(64)
description String? @db.VarChar(256)
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
filaments Filament[]
resins Resin[]
paints Paint[]
@@index([userId])
@@index([archived])
}
model Filament {
id String @id @default(cuid())
name String @db.VarChar(128)
brand String @db.VarChar(64)
material String @db.VarChar(32)
color String @db.VarChar(64)
colorHex String @db.VarChar(7)
diameter Float @default(1.75)
spoolWeight Float
usedWeight Float @default(0)
emptySpoolWeight Float @default(0)
purchaseDate DateTime?
cost Float?
notes String? @db.Text
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
vendorId String?
locationId String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
tags TagOnFilament[]
usageLogs UsageLog[]
@@index([userId])
@@index([vendorId])
@@index([locationId])
@@index([material])
@@index([archived])
@@index([brand])
}
model Resin {
id String @id @default(cuid())
name String @db.VarChar(128)
brand String @db.VarChar(64)
resinType String @db.VarChar(32)
color String @db.VarChar(64)
colorHex String @db.VarChar(7)
bottleSize Float
usedML Float @default(0)
purchaseDate DateTime?
cost Float?
notes String? @db.Text
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
vendorId String?
locationId String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
tags TagOnResin[]
usageLogs UsageLog[]
@@index([userId])
@@index([vendorId])
@@index([locationId])
@@index([resinType])
@@index([archived])
@@index([brand])
}
model Paint {
id String @id @default(cuid())
name String @db.VarChar(128)
brand String @db.VarChar(64)
line String? @db.VarChar(64)
color String @db.VarChar(64)
colorHex String @db.VarChar(7)
finish String @db.VarChar(32)
volumeML Float
usedML Float @default(0)
purchaseDate DateTime?
cost Float?
notes String? @db.Text
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
vendorId String?
locationId String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
tags TagOnPaint[]
usageLogs UsageLog[]
@@index([userId])
@@index([vendorId])
@@index([locationId])
@@index([finish])
@@index([archived])
@@index([brand])
}
model Tag {
id String @id @default(cuid())
name String @db.VarChar(64)
createdAt DateTime @default(now())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
filaments TagOnFilament[]
resins TagOnResin[]
paints TagOnPaint[]
@@unique([name, userId])
@@index([userId])
}
model TagOnFilament {
filamentId String
tagId String
filament Filament @relation(fields: [filamentId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([filamentId, tagId])
}
model TagOnResin {
resinId String
tagId String
resin Resin @relation(fields: [resinId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([resinId, tagId])
}
model TagOnPaint {
paintId String
tagId String
paint Paint @relation(fields: [paintId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([paintId, tagId])
}
model UsageLog {
id String @id @default(cuid())
itemType String @db.VarChar(16)
itemId String
amount Float
unit String @db.VarChar(4)
notes String? @db.Text
createdAt DateTime @default(now())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
filament Filament? @relation(fields: [filamentId], references: [id], onDelete: Cascade)
filamentId String?
resin Resin? @relation(fields: [resinId], references: [id], onDelete: Cascade)
resinId String?
paint Paint? @relation(fields: [paintId], references: [id], onDelete: Cascade)
paintId String?
@@index([userId])
@@index([itemType, itemId])
@@index([createdAt])
}
model UserSettings {
id String @id @default(cuid())
userId String @unique
lowStockThreshold Float @default(10)
currency String @default("USD") @db.VarChar(3)
theme String @default("dark") @db.VarChar(8)
units String @default("metric") @db.VarChar(8)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

633
prisma/seed.ts Normal file
View File

@@ -0,0 +1,633 @@
import { PrismaClient } from "../src/generated/prisma";
import { PrismaPg } from "@prisma/adapter-pg";
import pg from "pg";
import { hash } from "bcryptjs";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });
async function main() {
console.log("Seeding database...");
// Create admin user
const adminPassword = await hash("password123", 10);
const admin = await prisma.user.upsert({
where: { email: "admin@dragonsstash.local" },
update: {},
create: {
name: "Admin",
email: "admin@dragonsstash.local",
hashedPassword: adminPassword,
role: "ADMIN",
settings: {
create: {
lowStockThreshold: 10,
currency: "USD",
theme: "dark",
units: "metric",
},
},
},
});
// Create regular user
const userPassword = await hash("password123", 10);
const user = await prisma.user.upsert({
where: { email: "user@dragonsstash.local" },
update: {},
create: {
name: "Demo User",
email: "user@dragonsstash.local",
hashedPassword: userPassword,
role: "USER",
settings: {
create: {
lowStockThreshold: 15,
currency: "EUR",
theme: "dark",
units: "metric",
},
},
},
});
// Create vendors
const vendors = await Promise.all([
prisma.vendor.create({
data: {
name: "Prusament",
website: "https://www.prusa3d.com/category/prusament/",
notes: "Premium filament by Prusa Research",
userId: admin.id,
},
}),
prisma.vendor.create({
data: {
name: "Hatchbox",
website: "https://www.hatchbox3d.com",
notes: "Popular budget-friendly filament brand",
userId: admin.id,
},
}),
prisma.vendor.create({
data: {
name: "Elegoo",
website: "https://www.elegoo.com",
notes: "Resin and printer manufacturer",
userId: admin.id,
},
}),
prisma.vendor.create({
data: {
name: "Citadel",
website: "https://www.games-workshop.com",
notes: "Games Workshop miniature paints",
userId: admin.id,
},
}),
prisma.vendor.create({
data: {
name: "Vallejo",
website: "https://acrilicosvallejo.com",
notes: "Professional model and miniature paints",
userId: admin.id,
},
}),
]);
// Create locations
const locations = await Promise.all([
prisma.location.create({
data: { name: "Shelf A", description: "Main filament storage shelf", userId: admin.id },
}),
prisma.location.create({
data: { name: "Shelf B", description: "Secondary storage", userId: admin.id },
}),
prisma.location.create({
data: { name: "Drawer 1", description: "Paint storage drawer", userId: admin.id },
}),
prisma.location.create({
data: { name: "Drawer 2", description: "Resin and accessories", userId: admin.id },
}),
]);
// Create tags
const tags = await Promise.all([
prisma.tag.create({ data: { name: "favorites", userId: admin.id } }),
prisma.tag.create({ data: { name: "project-x", userId: admin.id } }),
prisma.tag.create({ data: { name: "weathering", userId: admin.id } }),
prisma.tag.create({ data: { name: "terrain", userId: admin.id } }),
prisma.tag.create({ data: { name: "miniatures", userId: admin.id } }),
]);
// Create filaments
const filaments = await Promise.all([
prisma.filament.create({
data: {
name: "Prusament PLA Galaxy Black",
brand: "Prusament",
material: "PLA",
color: "Galaxy Black",
colorHex: "#1a1a2e",
spoolWeight: 1000,
usedWeight: 350,
cost: 29.99,
purchaseDate: new Date("2025-11-01"),
userId: admin.id,
vendorId: vendors[0].id,
locationId: locations[0].id,
tags: { create: [{ tagId: tags[0].id }] },
},
}),
prisma.filament.create({
data: {
name: "Hatchbox PLA True White",
brand: "Hatchbox",
material: "PLA",
color: "True White",
colorHex: "#ffffff",
spoolWeight: 1000,
usedWeight: 800,
cost: 24.99,
purchaseDate: new Date("2025-09-15"),
userId: admin.id,
vendorId: vendors[1].id,
locationId: locations[0].id,
},
}),
prisma.filament.create({
data: {
name: "Prusament PETG Orange",
brand: "Prusament",
material: "PETG",
color: "Orange",
colorHex: "#f97316",
spoolWeight: 1000,
usedWeight: 150,
cost: 32.99,
purchaseDate: new Date("2025-12-01"),
userId: admin.id,
vendorId: vendors[0].id,
locationId: locations[0].id,
tags: { create: [{ tagId: tags[0].id }, { tagId: tags[1].id }] },
},
}),
prisma.filament.create({
data: {
name: "Hatchbox ABS Red",
brand: "Hatchbox",
material: "ABS",
color: "Red",
colorHex: "#dc2626",
spoolWeight: 1000,
usedWeight: 50,
cost: 22.99,
purchaseDate: new Date("2026-01-10"),
userId: admin.id,
vendorId: vendors[1].id,
locationId: locations[1].id,
},
}),
prisma.filament.create({
data: {
name: "Prusament PLA Azure Blue",
brand: "Prusament",
material: "PLA",
color: "Azure Blue",
colorHex: "#3b82f6",
spoolWeight: 1000,
usedWeight: 500,
cost: 29.99,
purchaseDate: new Date("2025-10-20"),
userId: admin.id,
vendorId: vendors[0].id,
locationId: locations[0].id,
},
}),
prisma.filament.create({
data: {
name: "Hatchbox TPU Black",
brand: "Hatchbox",
material: "TPU",
color: "Black",
colorHex: "#0a0a0a",
spoolWeight: 800,
usedWeight: 200,
cost: 27.99,
purchaseDate: new Date("2025-11-15"),
userId: admin.id,
vendorId: vendors[1].id,
locationId: locations[1].id,
},
}),
prisma.filament.create({
data: {
name: "Prusament PLA Lipstick Red",
brand: "Prusament",
material: "PLA",
color: "Lipstick Red",
colorHex: "#e11d48",
spoolWeight: 1000,
usedWeight: 950,
cost: 29.99,
purchaseDate: new Date("2025-08-01"),
notes: "Almost empty, need to reorder",
userId: admin.id,
vendorId: vendors[0].id,
locationId: locations[0].id,
},
}),
prisma.filament.create({
data: {
name: "Hatchbox PETG Transparent",
brand: "Hatchbox",
material: "PETG",
color: "Transparent",
colorHex: "#e2e8f0",
spoolWeight: 1000,
usedWeight: 100,
cost: 25.99,
purchaseDate: new Date("2026-01-20"),
userId: admin.id,
vendorId: vendors[1].id,
locationId: locations[1].id,
},
}),
prisma.filament.create({
data: {
name: "Prusament ASA Signal Orange",
brand: "Prusament",
material: "ASA",
color: "Signal Orange",
colorHex: "#ea580c",
spoolWeight: 850,
usedWeight: 400,
cost: 35.99,
purchaseDate: new Date("2025-10-05"),
userId: admin.id,
vendorId: vendors[0].id,
locationId: locations[0].id,
},
}),
prisma.filament.create({
data: {
name: "Hatchbox PLA Silk Gold",
brand: "Hatchbox",
material: "PLA",
color: "Silk Gold",
colorHex: "#d4a017",
spoolWeight: 1000,
usedWeight: 250,
cost: 26.99,
purchaseDate: new Date("2025-12-15"),
userId: admin.id,
vendorId: vendors[1].id,
locationId: locations[0].id,
tags: { create: [{ tagId: tags[4].id }] },
},
}),
]);
// Create resins
const resins = await Promise.all([
prisma.resin.create({
data: {
name: "Elegoo Standard Grey",
brand: "Elegoo",
resinType: "Standard",
color: "Grey",
colorHex: "#6b7280",
bottleSize: 1000,
usedML: 450,
cost: 29.99,
purchaseDate: new Date("2025-11-10"),
userId: admin.id,
vendorId: vendors[2].id,
locationId: locations[3].id,
},
}),
prisma.resin.create({
data: {
name: "Elegoo ABS-Like Clear Blue",
brand: "Elegoo",
resinType: "ABS-Like",
color: "Clear Blue",
colorHex: "#60a5fa",
bottleSize: 500,
usedML: 350,
cost: 34.99,
purchaseDate: new Date("2025-10-20"),
userId: admin.id,
vendorId: vendors[2].id,
locationId: locations[3].id,
},
}),
prisma.resin.create({
data: {
name: "Elegoo Water-Washable Ceramic Grey",
brand: "Elegoo",
resinType: "Water-Washable",
color: "Ceramic Grey",
colorHex: "#9ca3af",
bottleSize: 1000,
usedML: 100,
cost: 36.99,
purchaseDate: new Date("2026-01-05"),
userId: admin.id,
vendorId: vendors[2].id,
locationId: locations[3].id,
tags: { create: [{ tagId: tags[4].id }] },
},
}),
prisma.resin.create({
data: {
name: "Elegoo Flexible Black",
brand: "Elegoo",
resinType: "Flexible",
color: "Black",
colorHex: "#171717",
bottleSize: 500,
usedML: 480,
cost: 39.99,
purchaseDate: new Date("2025-09-01"),
notes: "Nearly empty",
userId: admin.id,
vendorId: vendors[2].id,
locationId: locations[3].id,
},
}),
prisma.resin.create({
data: {
name: "Elegoo Tough White",
brand: "Elegoo",
resinType: "Tough",
color: "White",
colorHex: "#f5f5f5",
bottleSize: 1000,
usedML: 200,
cost: 42.99,
purchaseDate: new Date("2025-12-20"),
userId: admin.id,
vendorId: vendors[2].id,
locationId: locations[3].id,
},
}),
]);
// Create paints
const paints = await Promise.all([
prisma.paint.create({
data: {
name: "Abaddon Black",
brand: "Citadel",
line: "Base",
color: "Black",
colorHex: "#231f20",
finish: "Matte",
volumeML: 12,
usedML: 6,
cost: 5.49,
purchaseDate: new Date("2025-10-01"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
tags: { create: [{ tagId: tags[4].id }] },
},
}),
prisma.paint.create({
data: {
name: "Mephiston Red",
brand: "Citadel",
line: "Base",
color: "Red",
colorHex: "#9a1115",
finish: "Matte",
volumeML: 12,
usedML: 3,
cost: 5.49,
purchaseDate: new Date("2025-10-01"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Retributor Armour",
brand: "Citadel",
line: "Base",
color: "Gold",
colorHex: "#c39e5a",
finish: "Metallic",
volumeML: 12,
usedML: 8,
cost: 5.49,
purchaseDate: new Date("2025-09-15"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Nuln Oil",
brand: "Citadel",
line: "Shade",
color: "Black",
colorHex: "#14120e",
finish: "Wash",
volumeML: 24,
usedML: 10,
cost: 7.99,
purchaseDate: new Date("2025-10-01"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
tags: { create: [{ tagId: tags[2].id }] },
},
}),
prisma.paint.create({
data: {
name: "Agrax Earthshade",
brand: "Citadel",
line: "Shade",
color: "Brown",
colorHex: "#4b3620",
finish: "Wash",
volumeML: 24,
usedML: 15,
cost: 7.99,
purchaseDate: new Date("2025-08-20"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
tags: { create: [{ tagId: tags[2].id }, { tagId: tags[3].id }] },
},
}),
prisma.paint.create({
data: {
name: "Model Color White",
brand: "Vallejo",
line: "Model Color",
color: "White",
colorHex: "#f8f8f8",
finish: "Matte",
volumeML: 17,
usedML: 5,
cost: 3.99,
purchaseDate: new Date("2025-11-01"),
userId: admin.id,
vendorId: vendors[4].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Model Color German Grey",
brand: "Vallejo",
line: "Model Color",
color: "German Grey",
colorHex: "#4a4a4a",
finish: "Matte",
volumeML: 17,
usedML: 2,
cost: 3.99,
purchaseDate: new Date("2025-11-01"),
userId: admin.id,
vendorId: vendors[4].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Contrast Blood Angels Red",
brand: "Citadel",
line: "Contrast",
color: "Red",
colorHex: "#c01411",
finish: "Contrast",
volumeML: 18,
usedML: 12,
cost: 7.99,
purchaseDate: new Date("2025-09-10"),
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Leadbelcher",
brand: "Citadel",
line: "Base",
color: "Silver",
colorHex: "#a8a8a8",
finish: "Metallic",
volumeML: 12,
usedML: 11,
cost: 5.49,
purchaseDate: new Date("2025-07-01"),
notes: "Almost empty, reorder soon",
userId: admin.id,
vendorId: vendors[3].id,
locationId: locations[2].id,
},
}),
prisma.paint.create({
data: {
name: "Surface Primer Black",
brand: "Vallejo",
line: "Surface Primer",
color: "Black",
colorHex: "#1a1a1a",
finish: "Primer",
volumeML: 60,
usedML: 20,
cost: 9.99,
purchaseDate: new Date("2025-12-01"),
userId: admin.id,
vendorId: vendors[4].id,
locationId: locations[2].id,
},
}),
]);
// Create some usage logs
await Promise.all([
prisma.usageLog.create({
data: {
itemType: "FILAMENT",
itemId: filaments[0].id,
filamentId: filaments[0].id,
amount: 50,
unit: "g",
notes: "Printed phone stand",
userId: admin.id,
},
}),
prisma.usageLog.create({
data: {
itemType: "FILAMENT",
itemId: filaments[2].id,
filamentId: filaments[2].id,
amount: 100,
unit: "g",
notes: "Printed enclosure parts",
userId: admin.id,
},
}),
prisma.usageLog.create({
data: {
itemType: "RESIN",
itemId: resins[0].id,
resinId: resins[0].id,
amount: 150,
unit: "ml",
notes: "Printed miniature batch",
userId: admin.id,
},
}),
prisma.usageLog.create({
data: {
itemType: "PAINT",
itemId: paints[0].id,
paintId: paints[0].id,
amount: 2,
unit: "ml",
notes: "Base coated 5 miniatures",
userId: admin.id,
},
}),
prisma.usageLog.create({
data: {
itemType: "PAINT",
itemId: paints[3].id,
paintId: paints[3].id,
amount: 3,
unit: "ml",
notes: "Washed batch of 10 infantry",
userId: admin.id,
},
}),
]);
console.log("Database seeded successfully!");
console.log(` Admin: admin@dragonsstash.local / password123`);
console.log(` User: user@dragonsstash.local / password123`);
console.log(` Vendors: ${vendors.length}`);
console.log(` Locations: ${locations.length}`);
console.log(` Filaments: ${filaments.length}`);
console.log(` Resins: ${resins.length}`);
console.log(` Paints: ${paints.length}`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
await pool.end();
});