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("") galleryImageId String galleryImage GalleryImage @relation(fields: [galleryImageId], references: [id], onDelete: Cascade) @@index([sortOrder]) } model User { id String @id @default(cuid()) email String @unique name String? phone String? passwordHash String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt codes AuthCode[] addresses ShippingAddress[] cartItems CartItem[] orders Order[] reviews Review[] orderMessageReadStates UserOrderMessageReadState[] oauthAccounts OAuthAccount[] 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[] messageReadStates UserOrderMessageReadState[] @@index([userId, createdAt]) @@index([status, updatedAt]) } 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 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 InfoPageBlock { id String @id @default(cuid()) key String @unique title String body String sort Int @default(0) published Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([published, sort]) } /// Настройки оповещений пользователя 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) 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]) }