initial: server + shared

This commit is contained in:
admin
2026-06-11 13:41:38 +05:00
commit 65da047e7c
148 changed files with 15900 additions and 0 deletions
+353
View File
@@ -0,0 +1,353 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
/// Категория изделий (игрушки, сувениры и т.д.)
model Category {
id String @id @default(cuid())
name String
slug String @unique
sort Int @default(0)
products Product[]
}
model Product {
id String @id @default(cuid())
title String
slug String @unique
shortDescription String?
description String?
/// Количество на складе
quantity Int @default(0)
/// Материалы (список, например: ["хлопок","дерево"])
materials String @default("[]")
/// Цена в копейках (целое число, без дробной части)
priceCents Int
imageUrl String?
published Boolean @default(false)
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
categoryId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
images ProductImage[]
reviews Review[]
orderItems OrderItem[]
cartItems CartItem[]
}
model ProductImage {
id String @id @default(cuid())
url String
sort Int @default(0)
createdAt DateTime @default(now())
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String
@@index([productId, sort])
}
/// Медиатека админки: зарегистрированные файлы /uploads/... (без обязательной привязки к товару).
model GalleryImage {
id String @id @default(cuid())
url String @unique
isResized Boolean @default(false)
createdAt DateTime @default(now())
catalogSliderSlides CatalogSliderSlide[]
}
/// Слайды главной витрины (каталог): картинка из галереи + подпись.
model CatalogSliderSlide {
id String @id @default(cuid())
sortOrder Int
caption String @default("")
textColor String @default("#ffffff")
galleryImageId String
galleryImage GalleryImage @relation(fields: [galleryImageId], references: [id], onDelete: Cascade)
@@index([sortOrder])
}
model User {
id String @id @default(cuid())
email String @unique
displayName String?
avatar String?
avatarStyle String?
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
codes AuthCode[]
addresses ShippingAddress[]
cartItems CartItem[]
orders Order[]
reviews Review[]
orderMessageReadStates UserOrderMessageReadState[]
oauthAccounts OAuthAccount[]
pendingEmails PendingEmail[]
notificationPreference NotificationPreference?
notificationLogs NotificationLog[]
}
/// Прочитанность чата по заказу (для сообщений от админа после lastReadAt)
model UserOrderMessageReadState {
id String @id @default(cuid())
lastReadAt DateTime @default("1970-01-01T00:00:00.000Z")
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
orderId String
@@unique([userId, orderId])
@@index([userId])
}
model CartItem {
id String @id @default(cuid())
qty Int
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String
@@unique([userId, productId])
@@index([userId])
}
model Order {
id String @id @default(cuid())
/// Статус заказа (валидация переходов на уровне API)
status String @default("DRAFT")
deliveryFeeLocked Boolean @default(false)
/// 'delivery' | 'pickup'
deliveryType String @default("delivery")
/// RUSSIAN_POST | OZON_PVZ | YANDEX_PVZ | FIVE_POST при deliveryType=delivery
deliveryCarrier String?
/// 'online' | 'on_pickup' — способ расчёта для заказа
paymentMethod String @default("online")
itemsSubtotalCents Int @default(0)
deliveryFeeCents Int @default(0)
totalCents Int @default(0)
currency String @default("RUB")
addressSnapshotJson String?
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
items OrderItem[]
messages OrderMessage[]
payments Payment[]
messageReadStates UserOrderMessageReadState[]
@@index([userId, createdAt])
@@index([status, updatedAt])
}
model Payment {
id String @id @default(cuid())
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
yookassaPaymentId String @unique
status String
amountCents Int
currency String @default("RUB")
confirmationUrl String?
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orderId])
}
model OrderItem {
id String @id @default(cuid())
qty Int
titleSnapshot String
priceCentsSnapshot Int
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
orderId String
product Product @relation(fields: [productId], references: [id], onDelete: Restrict)
productId String
@@index([orderId])
}
model OrderMessage {
id String @id @default(cuid())
/// 'user' | 'admin'
authorType String
text String
/// URL вида /uploads/… (чек к оплате и т.п.)
attachmentUrl String?
createdAt DateTime @default(now())
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
orderId String
@@index([orderId, createdAt])
}
model Review {
id String @id @default(cuid())
rating Int
text String?
imageUrl String?
/// 'pending' | 'approved' | 'rejected'
status String @default("pending")
createdAt DateTime @default(now())
moderatedAt DateTime?
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
productId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
@@index([productId, status, createdAt])
@@index([status, createdAt])
@@unique([productId, userId])
}
model ShippingAddress {
id String @id @default(cuid())
label String?
recipientName String
recipientPhone String
addressLine String
comment String?
lat Float
lng Float
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
@@index([userId, isDefault])
@@index([userId, updatedAt])
}
model OAuthAccount {
id String @id @default(cuid())
/// 'vk' | 'yandex'
provider String
providerUserId String
accessToken String?
refreshToken String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
@@unique([provider, providerUserId])
@@index([userId])
}
model PendingEmail {
id String @id @default(cuid())
userId String
email String
token String @unique
expiresAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([token])
@@index([userId])
}
model AuthCode {
id String @id @default(cuid())
email String
codeHash String
purpose String
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String?
@@index([email, purpose])
@@index([expiresAt])
}
/// Настройки оповещений пользователя
model NotificationPreference {
id String @id @default(cuid())
userId String @unique
globalEnabled Boolean @default(true)
orderCreated Boolean @default(true)
orderStatusChanged Boolean @default(true)
orderMessageReceived Boolean @default(true)
paymentStatusChanged Boolean @default(true)
deliveryFeeAdjusted Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
/// Настройки оповещений админа
model AdminNotificationSettings {
id String @id @default(cuid())
emailEnabled Boolean @default(true)
telegramEnabled Boolean @default(false)
telegramChatId String?
newOrder Boolean @default(true)
newOrderMessage Boolean @default(true)
newReview Boolean @default(true)
authCodeDuplicate Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
/// Лог отправки оповещений
model NotificationLog {
id String @id @default(cuid())
userId String?
eventType String
channel String
status String
error String?
payload String
attempts Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([status, createdAt])
@@index([userId, createdAt])
}
/// Результат ручной проверки тест-чеклиста
model ChecklistResult {
id String @id @default(cuid())
itemKey String @unique
passed Boolean
comment String?
checkedAt DateTime @default(now())
updatedAt DateTime @updatedAt
}