Monorepo skeleton, Prisma schema, core domain and services with tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
45d7512d2b
64 files changed
+9483
No files matched your search
@@ -0,0 +1,749 @@
|
||||
// Barev Club — модель данных (PRD, раздел 7).
|
||||
// Схема заложена сразу целиком; часть сущностей используется начиная с Этапов 2–3.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
moduleFormat = "esm"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
// ───────────── Справочные перечисления ─────────────
|
||||
|
||||
enum Locale {
|
||||
ru
|
||||
en
|
||||
zh
|
||||
hy
|
||||
es
|
||||
}
|
||||
|
||||
/// Направление (раздел 2)
|
||||
enum CategoryCode {
|
||||
HOME
|
||||
DOCS
|
||||
LEARNING
|
||||
PERKS
|
||||
}
|
||||
|
||||
/// Сценарий заказа — определяет state machine и шаги исполнителя
|
||||
enum ServiceFlow {
|
||||
CLEANING
|
||||
REPAIR
|
||||
TRANSFER
|
||||
LANGUAGE
|
||||
CAREER
|
||||
DOCUMENTS
|
||||
}
|
||||
|
||||
enum ProviderRole {
|
||||
CLEANER
|
||||
HANDYMAN
|
||||
DRIVER
|
||||
TEACHER
|
||||
CAREER_CONSULTANT
|
||||
DOCS_AGENT
|
||||
}
|
||||
|
||||
/// Все статусы всех сценариев. Допустимые переходы — в packages/core (flows).
|
||||
enum OrderStatus {
|
||||
NEW
|
||||
ASSIGNED
|
||||
EN_ROUTE
|
||||
CLEANING
|
||||
ON_SITE
|
||||
ESTIMATE_APPROVED
|
||||
REPAIRING
|
||||
DRIVER_WAITING
|
||||
RIDE_IN_PROGRESS
|
||||
ENROLLED
|
||||
LESSON_SCHEDULED
|
||||
LESSON_IN_PROGRESS
|
||||
LESSON_DONE
|
||||
CONSULTANT_ASSIGNED
|
||||
RESUME_RECEIVED
|
||||
MEETING
|
||||
PLAN_SENT
|
||||
DOCS_REVIEW
|
||||
NEEDS_MORE_DOCS
|
||||
SUBMITTED
|
||||
DONE
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum ActorType {
|
||||
CLIENT
|
||||
PROVIDER
|
||||
DISPATCHER
|
||||
ADMIN
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
enum OfferStatus {
|
||||
OFFERED
|
||||
ACCEPTED
|
||||
DECLINED
|
||||
EXPIRED
|
||||
REVOKED
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
UNPAID
|
||||
PAID
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
enum RateType {
|
||||
FIXED
|
||||
PERCENT
|
||||
}
|
||||
|
||||
enum AdminRole {
|
||||
OWNER
|
||||
OPERATOR
|
||||
}
|
||||
|
||||
enum SubscriptionPlan {
|
||||
BAREV_PLUS
|
||||
}
|
||||
|
||||
enum SubscriptionSource {
|
||||
AGENCY_GIFT
|
||||
MANUAL
|
||||
PAID
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ACTIVE
|
||||
EXPIRED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum PromoKind {
|
||||
AGENCY_GIFT
|
||||
DISCOUNT
|
||||
}
|
||||
|
||||
enum FileKind {
|
||||
PHOTO_REPORT
|
||||
CLIENT_PHOTO
|
||||
DOCUMENT_SCAN
|
||||
RESUME
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum EstimateStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
enum TaskStatus {
|
||||
OPEN
|
||||
DONE
|
||||
}
|
||||
|
||||
enum PayoutStatus {
|
||||
DRAFT
|
||||
APPROVED
|
||||
PAID
|
||||
}
|
||||
|
||||
// ───────────── Пользователи ─────────────
|
||||
|
||||
/// Пользователь Telegram. Один и тот же человек может быть клиентом и исполнителем.
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
telegramId BigInt @unique
|
||||
username String?
|
||||
firstName String?
|
||||
lastName String?
|
||||
phone String?
|
||||
locale Locale @default(ru)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
client Client?
|
||||
provider Provider?
|
||||
dispatcher CompanyDispatcher[]
|
||||
messages Message[]
|
||||
}
|
||||
|
||||
model Client {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
consentAt DateTime?
|
||||
consentVersion String?
|
||||
agencyId String?
|
||||
agency Agency? @relation(fields: [agencyId], references: [id])
|
||||
agencyAttachedAt DateTime?
|
||||
/// источник: deeplink | manual_code | organic
|
||||
source String @default("organic")
|
||||
utm Json?
|
||||
/// подарочный месяц уже использован (один раз на клиента)
|
||||
giftUsedAt DateTime?
|
||||
/// короткий код клубной карты (Этап 3)
|
||||
cardCode String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
orders Order[]
|
||||
subscriptions Subscription[]
|
||||
ratings Rating[]
|
||||
cardUsages CardUsage[]
|
||||
promoUses PromoCodeUse[]
|
||||
|
||||
@@index([agencyId])
|
||||
}
|
||||
|
||||
// ───────────── Исполнители ─────────────
|
||||
|
||||
model ProviderCompany {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
/// какие роли исполнителей есть в компании (какие услуги она берёт)
|
||||
roles ProviderRole[]
|
||||
/// договорная ставка компании
|
||||
rateType RateType @default(PERCENT)
|
||||
rateValue Int @default(70)
|
||||
contactPhone String?
|
||||
inviteCode String @unique
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
employees Provider[]
|
||||
dispatchers CompanyDispatcher[]
|
||||
orders Order[]
|
||||
offers OrderOffer[]
|
||||
payouts Payout[]
|
||||
}
|
||||
|
||||
model CompanyDispatcher {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company ProviderCompany @relation(fields: [companyId], references: [id])
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([companyId, userId])
|
||||
}
|
||||
|
||||
model Provider {
|
||||
id String @id @default(cuid())
|
||||
/// заполняется, когда исполнитель открыл бота по инвайт-коду
|
||||
userId String? @unique
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
name String
|
||||
phone String?
|
||||
role ProviderRole
|
||||
companyId String?
|
||||
company ProviderCompany? @relation(fields: [companyId], references: [id])
|
||||
languages Locale[]
|
||||
/// ставка фрилансера; для сотрудников компании не используется
|
||||
rateType RateType @default(PERCENT)
|
||||
rateValue Int @default(70)
|
||||
ratingSum Int @default(0)
|
||||
ratingCount Int @default(0)
|
||||
onShift Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
inviteCode String @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
districts ProviderDistrict[]
|
||||
schedule ProviderSchedule[]
|
||||
orders Order[]
|
||||
offers OrderOffer[]
|
||||
ratings Rating[]
|
||||
payouts Payout[]
|
||||
|
||||
@@index([role, active])
|
||||
}
|
||||
|
||||
model District {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
sortOrder Int @default(0)
|
||||
providers ProviderDistrict[]
|
||||
orders Order[]
|
||||
}
|
||||
|
||||
model ProviderDistrict {
|
||||
providerId String
|
||||
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
||||
districtId String
|
||||
district District @relation(fields: [districtId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([providerId, districtId])
|
||||
}
|
||||
|
||||
/// Недельный график: 0 = понедельник … 6 = воскресенье, минуты от полуночи по Asia/Yerevan
|
||||
model ProviderSchedule {
|
||||
id String @id @default(cuid())
|
||||
providerId String
|
||||
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
||||
weekday Int
|
||||
startMinute Int
|
||||
endMinute Int
|
||||
|
||||
@@index([providerId, weekday])
|
||||
}
|
||||
|
||||
// ───────────── Каталог ─────────────
|
||||
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
code CategoryCode @unique
|
||||
sortOrder Int @default(0)
|
||||
/// скидка Barev+ на направление, %
|
||||
barevPlusDiscountPct Int @default(0)
|
||||
active Boolean @default(true)
|
||||
services Service[]
|
||||
}
|
||||
|
||||
model Service {
|
||||
id String @id @default(cuid())
|
||||
/// код услуги; тексты берутся из i18n по ключу services.<code>.*
|
||||
code String @unique
|
||||
categoryId String
|
||||
category Category @relation(fields: [categoryId], references: [id])
|
||||
flow ServiceFlow
|
||||
providerRole ProviderRole
|
||||
/// правила цены (см. packages/core/src/pricing)
|
||||
pricing Json
|
||||
/// описание параметров формы заказа (см. packages/core/src/params)
|
||||
paramsSchema Json
|
||||
/// окно записи и шаг слотов (см. packages/core/src/slots)
|
||||
slotConfig Json
|
||||
/// удержание при поздней отмене, %
|
||||
lateCancelFeePct Int @default(30)
|
||||
isOnline Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
checklist ChecklistItem[]
|
||||
orders Order[]
|
||||
}
|
||||
|
||||
/// Шаблон пункта чек-листа услуги
|
||||
model ChecklistItem {
|
||||
id String @id @default(cuid())
|
||||
serviceId String
|
||||
service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
|
||||
/// код пункта; подпись — i18n checklist.<code>
|
||||
code String
|
||||
requiresPhoto Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
@@unique([serviceId, code])
|
||||
}
|
||||
|
||||
model PaymentMethod {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
/// { ru, en, zh, hy, es }
|
||||
name Json
|
||||
active Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
orders Order[]
|
||||
}
|
||||
|
||||
// ───────────── Заказы ─────────────
|
||||
|
||||
model Order {
|
||||
id String @id @default(cuid())
|
||||
number Int @unique @default(autoincrement())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
serviceId String
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
params Json
|
||||
scheduledAt DateTime
|
||||
durationMinutes Int
|
||||
isOnline Boolean @default(false)
|
||||
addressText String?
|
||||
districtId String?
|
||||
district District? @relation(fields: [districtId], references: [id])
|
||||
lat Float?
|
||||
lng Float?
|
||||
note String?
|
||||
/// язык клиента на момент заказа
|
||||
locale Locale
|
||||
basePrice Int
|
||||
discountPct Int @default(0)
|
||||
discountAmount Int @default(0)
|
||||
totalPrice Int
|
||||
status OrderStatus @default(NEW)
|
||||
providerId String?
|
||||
provider Provider? @relation(fields: [providerId], references: [id])
|
||||
/// заказ передан компании; пока providerId пуст — «Назначен на компанию»
|
||||
companyId String?
|
||||
company ProviderCompany? @relation(fields: [companyId], references: [id])
|
||||
paymentMethodId String?
|
||||
paymentMethod PaymentMethod? @relation(fields: [paymentMethodId], references: [id])
|
||||
paymentStatus PaymentStatus @default(UNPAID)
|
||||
paidAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
cancelFee Int @default(0)
|
||||
cancelReason String?
|
||||
completedAt DateTime?
|
||||
repeatOfId String?
|
||||
reminded24hAt DateTime?
|
||||
reminded1hAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
history OrderStatusHistory[]
|
||||
checklist OrderChecklistItem[]
|
||||
offers OrderOffer[]
|
||||
files OrderFile[]
|
||||
messages Message[]
|
||||
estimates Estimate[]
|
||||
rating Rating?
|
||||
lessons LessonPackage?
|
||||
payments Payment[]
|
||||
commission AgencyCommission?
|
||||
tasks OperatorTask[]
|
||||
|
||||
@@index([status])
|
||||
@@index([scheduledAt])
|
||||
@@index([clientId])
|
||||
@@index([providerId, scheduledAt])
|
||||
}
|
||||
|
||||
model OrderStatusHistory {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
/// событие без смены статуса (например «Я на месте») или код действия
|
||||
event String?
|
||||
actorType ActorType
|
||||
actorId String?
|
||||
comment String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId, createdAt])
|
||||
}
|
||||
|
||||
/// Предложение заказа исполнителю или компании (принять / отклонить с таймаутом)
|
||||
model OrderOffer {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
providerId String?
|
||||
provider Provider? @relation(fields: [providerId], references: [id])
|
||||
companyId String?
|
||||
company ProviderCompany? @relation(fields: [companyId], references: [id])
|
||||
status OfferStatus @default(OFFERED)
|
||||
expiresAt DateTime?
|
||||
respondedAt DateTime?
|
||||
createdById String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId, status])
|
||||
@@index([providerId, status])
|
||||
@@index([companyId, status])
|
||||
}
|
||||
|
||||
model OrderChecklistItem {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
code String
|
||||
requiresPhoto Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
checkedAt DateTime?
|
||||
fileId String? @unique
|
||||
file OrderFile? @relation(fields: [fileId], references: [id])
|
||||
|
||||
@@unique([orderId, code])
|
||||
}
|
||||
|
||||
model OrderFile {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
kind FileKind
|
||||
storageKey String @unique
|
||||
mime String
|
||||
size Int
|
||||
uploadedBy ActorType
|
||||
uploadedById String?
|
||||
/// дата автоудаления (документы — через N дней после завершения)
|
||||
deleteAfter DateTime?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
checklistItem OrderChecklistItem?
|
||||
accessLog FileAccessLog[]
|
||||
}
|
||||
|
||||
model FileAccessLog {
|
||||
id String @id @default(cuid())
|
||||
fileId String
|
||||
file OrderFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
||||
actorType ActorType
|
||||
actorId String?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
/// Смета ремонта (Этап 2)
|
||||
model Estimate {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
items Json
|
||||
total Int
|
||||
status EstimateStatus @default(PENDING)
|
||||
respondedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
/// Чат клиент ↔ исполнитель через бота (Этап 2)
|
||||
model Message {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
fromType ActorType
|
||||
fromUserId String?
|
||||
fromUser User? @relation(fields: [fromUserId], references: [id])
|
||||
text String?
|
||||
fileId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId, createdAt])
|
||||
}
|
||||
|
||||
model Rating {
|
||||
id String @id @default(cuid())
|
||||
orderId String @unique
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
providerId String?
|
||||
provider Provider? @relation(fields: [providerId], references: [id])
|
||||
score Int
|
||||
comment String?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
/// Пакет уроков (Этап 2)
|
||||
model LessonPackage {
|
||||
id String @id @default(cuid())
|
||||
orderId String @unique
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
total Int
|
||||
completed Int @default(0)
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
/// manual | <будущий провайдер>
|
||||
provider String
|
||||
amount Int
|
||||
status PaymentStatus
|
||||
externalId String?
|
||||
markedById String?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model OperatorTask {
|
||||
id String @id @default(cuid())
|
||||
type String
|
||||
orderId String?
|
||||
order Order? @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
status TaskStatus @default(OPEN)
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
doneAt DateTime?
|
||||
}
|
||||
|
||||
// ───────────── Агентства, промокоды, членство ─────────────
|
||||
|
||||
model Agency {
|
||||
id String @id @default(cuid())
|
||||
/// код для deep-link и ручного ввода (латиница/цифры)
|
||||
code String @unique
|
||||
name String
|
||||
/// вознаграждение, % от оплаченных заказов клиентов агентства
|
||||
commissionPct Int @default(10)
|
||||
contact String?
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
clients Client[]
|
||||
commissions AgencyCommission[]
|
||||
promoCodes PromoCode[]
|
||||
}
|
||||
|
||||
model AgencyCommission {
|
||||
id String @id @default(cuid())
|
||||
agencyId String
|
||||
agency Agency @relation(fields: [agencyId], references: [id])
|
||||
orderId String @unique
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
pct Int
|
||||
amount Int
|
||||
/// YYYY-MM
|
||||
period String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([agencyId, period])
|
||||
}
|
||||
|
||||
model PromoCode {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
kind PromoKind
|
||||
agencyId String?
|
||||
agency Agency? @relation(fields: [agencyId], references: [id])
|
||||
discountPct Int?
|
||||
maxUses Int?
|
||||
usedCount Int @default(0)
|
||||
validFrom DateTime?
|
||||
validUntil DateTime?
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
uses PromoCodeUse[]
|
||||
}
|
||||
|
||||
model PromoCodeUse {
|
||||
id String @id @default(cuid())
|
||||
promoId String
|
||||
promo PromoCode @relation(fields: [promoId], references: [id])
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
orderId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([promoId, clientId])
|
||||
}
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
plan SubscriptionPlan @default(BAREV_PLUS)
|
||||
source SubscriptionSource
|
||||
status SubscriptionStatus @default(ACTIVE)
|
||||
startsAt DateTime
|
||||
endsAt DateTime
|
||||
remindedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([clientId, endsAt])
|
||||
}
|
||||
|
||||
// ───────────── Партнёры и клубная карта (Этап 3) ─────────────
|
||||
|
||||
model Partner {
|
||||
id String @id @default(cuid())
|
||||
/// cafe | restaurant | other
|
||||
category String
|
||||
name String
|
||||
/// { ru, en, ... }
|
||||
offer Json
|
||||
terms Json?
|
||||
address String?
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
usages CardUsage[]
|
||||
}
|
||||
|
||||
model CardUsage {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
partnerId String?
|
||||
partner Partner? @relation(fields: [partnerId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// ───────────── Финансы ─────────────
|
||||
|
||||
model Payout {
|
||||
id String @id @default(cuid())
|
||||
providerId String?
|
||||
provider Provider? @relation(fields: [providerId], references: [id])
|
||||
companyId String?
|
||||
company ProviderCompany? @relation(fields: [companyId], references: [id])
|
||||
periodStart DateTime
|
||||
periodEnd DateTime
|
||||
amount Int
|
||||
status PayoutStatus @default(DRAFT)
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// ───────────── Админка ─────────────
|
||||
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String
|
||||
passwordHash String
|
||||
role AdminRole @default(OPERATOR)
|
||||
totpSecret String?
|
||||
totpEnabled Boolean @default(false)
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
sessions AdminSession[]
|
||||
audit AuditLog[]
|
||||
}
|
||||
|
||||
model AdminSession {
|
||||
id String @id @default(cuid())
|
||||
adminUserId String
|
||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
|
||||
/// sha256 токена сессии
|
||||
tokenHash String @unique
|
||||
/// сессия прошла 2FA
|
||||
verified Boolean @default(false)
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
adminUserId String?
|
||||
adminUser AdminUser? @relation(fields: [adminUserId], references: [id])
|
||||
action String
|
||||
entity String
|
||||
entityId String?
|
||||
data Json?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([entity, entityId])
|
||||
}
|
||||
|
||||
/// Настройки (ключ → значение). Список ключей и дефолты — packages/core/src/settings.ts
|
||||
model Setting {
|
||||
key String @id
|
||||
value Json
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
/// Короткий токен для deep-link с лендинга, когда payload не влезает в 64 символа (UTM)
|
||||
model StartToken {
|
||||
id String @id
|
||||
locale Locale?
|
||||
agency String?
|
||||
utm Json?
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
Reference in new issue
Block a user