Appearance
Модель данных
Последняя сверка с кодом: 2026-06-12
Полная схема:backend/prisma/schema.prisma
Аудитория: аналитики, новые разработчики
1. ER-диаграмма
mermaid
erDiagram
User ||--o{ Event : "owner"
User ||--o{ EventRole : "has role in"
User ||--o{ Media : "uploaded"
User ||--o{ MediaLike : "liked"
User ||--o{ Session : "session"
Event ||--|| EventSettings : "settings 1:1"
Event ||--o{ EventRole : "roles"
Event ||--o{ Media : "media"
Media ||--o{ MediaLike : "likes"2. Сущности
User
Создаётся при регистрации (Better Auth). Один пользователь может быть OWNER нескольких событий.
| Поле | Тип | Смысл |
|---|---|---|
id | String | UUID (Better Auth) |
email | String (unique) | Логин |
name | String | Отображаемое имя |
deletedAt | DateTime? | Soft-delete аккаунта (24.7) |
deletedEmail | String? | Оригинальный email до анонимизации; grace-period для повторной регистрации |
globalRole | USER / SUPER_ADMIN | Платформенная роль |
plan | FREE / BASE / PRO | Тариф |
avatarKey | String? | Ключ в storage (backlog) |
Event
Событие = альбом. Создаётся только залогиненным.
| Поле | Тип | Смысл |
|---|---|---|
id | String | Human-readable slug: demo-event, svadba-x7k2 |
title | String | Название |
date | DateTime? | Дата события (опц.) |
ownerId | String FK→User | Создатель |
deactivatedAt | DateTime? | Скрыто после soft-delete владельца (24.7) |
Связи: media[], roles[], settings (1:1).
EventSettings
Привязан к Event 1:1 (id = eventId). Создаётся автоматически при создании Event.
| Группа | Поля | Смысл |
|---|---|---|
| Доступ | qrAccess, albumVisibility, zipAccess | Кто видит что (AccessScope) |
| Upload | uploadEnabled, allowedMedia, maxFilesPerGuest, maxFileSizeMb, maxVideoDurationSec | Ограничения загрузки |
| Модерация | moderationEnabled | PENDING до approve |
| Брендинг | coverImageKey, backgroundImageKey, brandColor | Storage keys |
| MEMBER | memberInviteToken, allowMemberFolders | Инвайт-ссылка |
| ZIP | zipDailyLimit | (резерв) |
AccessScope: ALL · MEMBERS_ONLY · OWNER_ONLY · INVITE_ONLY
EventRole
Роль конкретного User в конкретном Event. Уникально по (userId, eventId).
| Поле | Смысл |
|---|---|
role | OWNER / MODERATOR / MEMBER |
OWNER всегда создаётся при POST /events. MODERATOR — OWNER назначает. MEMBER — через invite link.
Media
Одно медиа (фото или видео).
| Поле | Смысл |
|---|---|
id | nanoid(16), внутренний |
kind | photo или video |
originalKey | Storage key оригинала |
thumbKey | Storage key превью |
displayKey | Storage key display (перекодированный) |
posterKey | Постер видео (ffmpeg) |
mime, sizeBytes, width, height, durationMs | Метаданные |
blurhash | Placeholder при загрузке |
uploaderName | Имя гостя из localStorage |
uploaderUserId | FK→User (null если гость) |
contentSha256 | Дедупликация: уникально по (eventId, sha256) |
moderationStatus | PENDING / APPROVED / REJECTED |
processed | Превью уже создано |
MediaLike
Лайк — уникальная пара (userId, mediaId). Только залогиненный.
Session / Account / Verification
Таблицы Better Auth. Не используются прямо в domain-коде — только через auth.getSession(req).
3. Что где хранится
| Данные | Хранилище |
|---|---|
| Метаданные (user, event, media, roles) | Postgres |
| Файлы (оригинал, thumb, branding) | Local disk (dev) / Cloudflare R2 (prod) |
| Сессии SSE | In-memory (один процесс) |
| Имя гостя | LocalStorage браузера |
4. Каскадное удаление
| Если удалить | Удалится также |
|---|---|
| User | Sessions, EventRoles, события владельца (+ их Media/Settings) |
| Event | EventSettings, EventRoles, Media |
| Media | MediaLikes |
Media.uploaderUserId при удалении User → SET NULL (медиа гостей в чужих событиях сохраняется).
Удаление аккаунта (24.7)
- Тип: soft-delete —
deletedAt+ анонимизация email наuser. - При удалении: все сессии завершаются, login блокируется, события владельца →
deactivatedAt, медиа →uploaderUserId SET NULL. - Повторная регистрация с тем же email — после grace-period (
ACCOUNT_DELETE_GRACE_DAYS, default 30).
5. Индексы
Event: поcreatedAt,ownerIdMedia: по(eventId, createdAt),(eventId, processed),(eventId, moderationStatus),uploaderUserIdMediaLike: поmediaIdEventRole: по(eventId, role)
6. Event ID — почему human-readable
svadba-anna-ivan-x7k2 — slug из title (транслит) + 4 символа nanoid.
Зачем: URL читается в чате, при печати QR-карточки, в адресной строке.
Безопасность: энтропия суффикса (~16M вариантов) + длина slug делает перебор нецелесообразным.
Media ID — обычный nanoid(16), в URL не светится.