Files
imen_pishghaman/apps/api/prisma/schema.prisma
T
S. EsmaeiliandCursor 49f30a96b1 feat: polish admin UI, phone login, and product details
Switch admin auth to mobile, restyle shell/login with Vuesax icons, and add product detail fields plus public product pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 17:18:17 +03:30

189 lines
5.7 KiB
Plaintext

generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
moduleFormat = "esm"
importFileExtension = "js"
}
datasource db {
provider = "postgresql"
}
enum Role {
ADMIN
EDITOR
}
enum MessageStatus {
NEW
READ
ARCHIVED
}
model AdminUser {
id Int @id @default(autoincrement())
phone String @unique
name String
passwordHash String
role Role @default(EDITOR)
isActive Boolean @default(true)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
refreshTokens RefreshToken[]
auditLogs AuditLog[]
}
model RefreshToken {
id Int @id @default(autoincrement())
userId Int
user AdminUser @relation(fields: [userId], references: [id], onDelete: Cascade)
/// sha256 of the opaque token stored in the httpOnly cookie
tokenHash String @unique
expiresAt DateTime
revokedAt DateTime?
userAgent String @default("")
createdAt DateTime @default(now())
@@index([userId])
}
model Media {
id Int @id @default(autoincrement())
/// object key in the S3 bucket; the public URL is MEDIA_PUBLIC_URL + "/" + key
key String @unique
mime String
size Int
width Int?
height Int?
alt String @default("")
originalName String @default("")
createdAt DateTime @default(now())
categoryCovers Category[] @relation("CategoryCover")
productImages Product[] @relation("ProductImage")
productGallery ProductImage[]
}
model Category {
id Int @id @default(autoincrement())
slug String @unique
code String
name String
nameEn String @default("")
shortDesc String @default("")
fullDesc String @default("")
/// Lucide icon name
icon String @default("")
coverImageId Int?
coverImage Media? @relation("CategoryCover", fields: [coverImageId], references: [id], onDelete: SetNull)
sortOrder Int @default(0)
isPublished Boolean @default(true)
seoTitle String @default("")
seoDescription String @default("")
groups ProductGroup[]
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([sortOrder])
}
model ProductGroup {
id Int @id @default(autoincrement())
categoryId Int
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
slug String
name String
desc String @default("")
sortOrder Int @default(0)
products Product[]
@@unique([categoryId, slug])
}
model Product {
id Int @id @default(autoincrement())
categoryId Int
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
groupId Int?
group ProductGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
code String @unique
slug String @unique
name String
nameEn String @default("")
desc String @default("")
specs String[]
variants String[]
/// long description for the product page; a blank line starts a new paragraph
details String @default("")
brand String @default("")
/// country of manufacture
origin String @default("")
sizes String[] @default([])
standards String[] @default([])
features String[] @default([])
applications String[] @default([])
/// technical specification table: [{ "name": "...", "value": "..." }]
attributes Json @default("[]")
packaging String @default("")
minOrder String @default("")
seoTitle String @default("")
seoDescription String @default("")
imageId Int?
image Media? @relation("ProductImage", fields: [imageId], references: [id], onDelete: SetNull)
gallery ProductImage[]
sortOrder Int @default(0)
isPublished Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([categoryId, sortOrder])
}
model ProductImage {
productId Int
mediaId Int
sortOrder Int @default(0)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
media Media @relation(fields: [mediaId], references: [id], onDelete: Cascade)
@@id([productId, mediaId])
}
/// One row per editable website section; `data` is validated by the zod schema for `key` (@imen/shared)
model SiteSection {
key String @id
data Json
updatedById Int?
updatedAt DateTime @updatedAt
}
model ContactMessage {
id Int @id @default(autoincrement())
name String
org String
phone String
message String @default("")
status MessageStatus @default(NEW)
ip String @default("")
userAgent String @default("")
createdAt DateTime @default(now())
@@index([status, createdAt])
}
model AuditLog {
id Int @id @default(autoincrement())
userId Int?
user AdminUser? @relation(fields: [userId], references: [id], onDelete: SetNull)
/// create | update | delete | login ...
action String
/// product | category | section | media | user | message
entity String
entityId String @default("")
summary String @default("")
createdAt DateTime @default(now())
@@index([createdAt])
}