507 lines
21 KiB
Markdown
507 lines
21 KiB
Markdown
# Техническое задание (ТЗ)
|
||
## Проект: облачное хранилище фото и видео (S3 + Python backend + SQLite → PostgreSQL)
|
||
**Формат фронтенда:** статический сайт (SPA) в папке `static/`, хостинг в S3
|
||
**UI:** Material UI (MUI)
|
||
**Backend:** Python (FastAPI)
|
||
**Хранилище файлов:** S3 / S3-compatible (MinIO и т.п.)
|
||
**База данных:** SQLite на старте, в будущем — PostgreSQL (миграции через Alembic)
|
||
|
||
---
|
||
|
||
## 1. Цели и принципы
|
||
|
||
### 1.1 Цель продукта
|
||
Создать удобное, быстрое и безопасное облачное хранилище для фото и видео с фокусом на:
|
||
- максимально простой и быстрый загрузчик (drag&drop, пакетные загрузки, большие файлы);
|
||
- удобную библиотеку (лента/сеткой), быстрый просмотр, поиск/фильтры;
|
||
- безопасный доступ и шаринг ссылками;
|
||
- готовность к росту: миграция SQLite → PostgreSQL без переписывания бизнес-логики.
|
||
|
||
### 1.2 Принципы реализации
|
||
- **SOLID / DRY / Clean Architecture**: разделение слоёв (API → Service → Repository → Infrastructure).
|
||
- **Докстринги обязательны** для публичных методов/классов и основных сервисов.
|
||
- **Минимум комментариев в коде**: предпочтение самодокументирующемуся коду и докстрингам.
|
||
- **Стабильный API-контракт**: OpenAPI/Swagger (FastAPI).
|
||
- **Сразу закладываем расширяемость** (теги, альбомы, шаринг, фоновые задачи).
|
||
|
||
---
|
||
|
||
## 2. Область охвата (Scope)
|
||
|
||
### 2.1 MVP (минимально жизнеспособная версия)
|
||
1) Регистрация/логин (минимум: email+пароль)
|
||
2) Загрузка фото/видео в S3 (через pre-signed URLs)
|
||
3) Библиотека медиа:
|
||
- список/сетка,
|
||
- просмотр (лайтбокс) фото,
|
||
- просмотр/проигрывание видео,
|
||
- базовые сортировки (по дате добавления, по дате съёмки).
|
||
4) Удаление (корзина/soft-delete) и восстановление.
|
||
5) Простой шаринг публичной ссылкой (срок действия).
|
||
6) Генерация превью для изображений (thumbnails).
|
||
7) Хранение метаданных (SQLite), подготовлено для PostgreSQL (ORM + миграции).
|
||
|
||
### 2.2 Версия v1 (после MVP)
|
||
- Альбомы/папки (логическая группировка).
|
||
- Теги и поиск по тегам/дате/типу/размеру.
|
||
- Извлечение EXIF (камера, дата съёмки, гео — опционально).
|
||
- Превью/постер для видео.
|
||
- Множественный выбор, пакетные операции (переместить/удалить/добавить теги).
|
||
- Пользовательские квоты и статистика хранения.
|
||
- Логи действий (аудит: загрузка/удаление/шаринг).
|
||
|
||
### 2.3 Вне рамок (Non-goals) на старте
|
||
- Полноценное видеотранскодирование в HLS/DASH.
|
||
- Распознавание лиц/умные альбомы.
|
||
- Коллаборация “семейные библиотеки” (несколько владельцев одного альбома).
|
||
- End-to-end шифрование на клиенте.
|
||
|
||
---
|
||
|
||
## 3. Роли и сценарии
|
||
|
||
### 3.1 Роли
|
||
- **User**: загружает и управляет своей медиатекой.
|
||
- **Admin** (опционально в v1): администрирование пользователей/квот и доступ ко всем облакам пользователей.
|
||
|
||
### 3.2 Ключевые user stories (MVP)
|
||
1) Как пользователь, я хочу быстро перетащить папку/файлы и загрузить их в облако.
|
||
2) Как пользователь, я хочу просматривать фото в полноэкранном режиме и листать стрелками.
|
||
3) Как пользователь, я хочу смотреть видео прямо в браузере.
|
||
4) Как пользователь, я хочу удалить файлы и при необходимости восстановить из корзины.
|
||
5) Как пользователь, я хочу поделиться ссылкой на один файл/альбом на ограниченное время.
|
||
|
||
---
|
||
|
||
## 4. UX/UI требования (фронтенд)
|
||
|
||
### 4.1 Технологическое решение фронтенда
|
||
**Рекомендуемый компромисс “JS чистый + MUI”:**
|
||
- Реализовать SPA на **React + MUI**,
|
||
- Сборка (Vite) выдаёт статические артефакты в `static/`,
|
||
- На проде в S3 лежит **только** `static/` (HTML/CSS/JS).
|
||
|
||
> Важно: Material UI (MUI) — React-библиотека. “Чистый JS без сборки” возможен через CDN + UMD, но это хуже по производительности и DX. В ТЗ закладываем правильную схему: разработка с Vite, деплой артефактов в `static/`.
|
||
|
||
### 4.2 Страницы/экраны (MVP)
|
||
1) **Login / Register**
|
||
2) **Library** (главная)
|
||
- переключатель “Grid / List”,
|
||
- фильтр по типу (photo/video),
|
||
- сортировка,
|
||
- строка поиска (в MVP можно скрыть за feature-flag).
|
||
3) **Viewer**
|
||
- фото: зум, листание, скачать,
|
||
- видео: плеер (HTML5), скачать.
|
||
4) **Upload**
|
||
- drag&drop зона,
|
||
- прогресс по файлу и общий прогресс,
|
||
- повтор при ошибке.
|
||
5) **Trash**
|
||
- список удалённых, restore/purge.
|
||
6) **Shared view** (страница по публичной ссылке)
|
||
|
||
### 4.3 Компоненты UI
|
||
- AppBar + боковое меню (Drawer)
|
||
- MediaGrid (виртуализация списка, infinite scroll)
|
||
- MediaCard (thumbnail, иконка видео, размер/дата)
|
||
- UploadDialog (dropzone + очередь + прогресс)
|
||
- ViewerModal (keyboard nav: ← → Esc)
|
||
- FiltersBar (chips/select)
|
||
|
||
### 4.4 Нефункциональные требования к UI
|
||
- **Responsive**: desktop-first, корректно на мобилках.
|
||
- **Оптимизация**:
|
||
- thumbnails грузятся лениво,
|
||
- viewer тянет оригинал только по запросу,
|
||
- infinite scroll.
|
||
- **Удобство**:
|
||
- drag&drop в любом месте Library,
|
||
- горячие клавиши в Viewer,
|
||
- понятные ошибки и “повторить загрузку”.
|
||
|
||
---
|
||
|
||
## 5. Архитектура (общая)
|
||
|
||
### 5.1 Компоненты
|
||
1) **Static SPA** (S3 hosting)
|
||
2) **Backend API** (Python)
|
||
3) **Database** (SQLite → PostgreSQL)
|
||
4) **Object storage** (S3)
|
||
5) **Background worker** (опционально сразу, обязательно для v1) — генерация превью/постеров
|
||
|
||
### 5.2 Потоки
|
||
**Upload (рекомендуемый):**
|
||
1) SPA запрашивает у backend `create_upload` (получает pre-signed URL / multipart init).
|
||
2) SPA грузит файл напрямую в S3.
|
||
3) SPA вызывает `finalize_upload` (backend фиксирует метаданные, ставит задачу на превью).
|
||
4) Backend возвращает Asset ID.
|
||
|
||
**Download/View:**
|
||
- SPA запрашивает `get_asset_download_url` → получает краткоживущую signed-ссылку на S3 (оригинал/thumbnail).
|
||
|
||
---
|
||
|
||
## 6. Backend требования
|
||
|
||
### 6.1 Стек (рекомендуется)
|
||
- **FastAPI** (ASGI)
|
||
- **Pydantic** (схемы)
|
||
- **SQLAlchemy 2.x (async)** + **Alembic** (миграции)
|
||
- **SQLite** (MVP), конфигурация диалекта для лёгкого перехода на PostgreSQL
|
||
- S3 SDK: boto3 (sync) или aioboto3 (async) / или минимальный клиент через presigned
|
||
- Auth: JWT access + refresh (HTTP-only cookies) или Bearer tokens
|
||
- Logging: structlog/loguru + стандартный logging
|
||
- Tests: pytest + httpx
|
||
|
||
### 6.2 Слоистая архитектура (Clean)
|
||
- `api/` — роуты, схемы, зависимости, auth middleware
|
||
- `services/` — бизнес-логика (upload, library, share)
|
||
- `repositories/` — доступ к данным (CRUD)
|
||
- `infra/` — S3 client, db session factory, config, background tasks
|
||
- `domain/` — модели домена/интерфейсы (по необходимости)
|
||
|
||
### 6.3 Обязательные нефункциональные требования
|
||
- Валидация входных данных (Pydantic).
|
||
- Единый формат ошибок (problem+json либо собственный стандарт).
|
||
- Rate limiting (минимально на login/share) — можно отложить до v1.
|
||
- Безопасность:
|
||
- пароли хранить только в виде хеша (argon2/bcrypt),
|
||
- все ссылки на S3 — только signed,
|
||
- CORS настроен строго,
|
||
- CSRF защита при cookie-based auth.
|
||
- Наблюдаемость:
|
||
- структурные логи,
|
||
- correlation id (request id),
|
||
- healthcheck endpoint.
|
||
|
||
---
|
||
|
||
## 7. Модель данных (SQLite → PostgreSQL)
|
||
|
||
### 7.1 Основные сущности
|
||
**users**
|
||
- `id` (UUID)
|
||
- `email` (unique)
|
||
- `password_hash`
|
||
- `created_at`, `updated_at`
|
||
- `is_active`
|
||
|
||
**assets**
|
||
- `id` (UUID)
|
||
- `user_id` (FK users)
|
||
- `type` enum: `photo|video`
|
||
- `status` enum: `uploading|ready|failed|deleted`
|
||
- `original_filename`
|
||
- `content_type`
|
||
- `size_bytes`
|
||
- `sha256` (optional, for dedup)
|
||
- `captured_at` (datetime, optional)
|
||
- `created_at`
|
||
- `deleted_at` (nullable)
|
||
- `storage_key_original` (S3 object key)
|
||
- `storage_key_thumb` (nullable)
|
||
- `width`, `height` (nullable)
|
||
- `duration_sec` (nullable)
|
||
|
||
**albums** (v1)
|
||
- `id` UUID
|
||
- `user_id`
|
||
- `title`
|
||
- `created_at`
|
||
|
||
**album_items** (v1)
|
||
- `album_id`
|
||
- `asset_id`
|
||
- `position` (int)
|
||
|
||
**tags** (v1)
|
||
- `id`, `user_id`, `name`
|
||
|
||
**asset_tags** (v1)
|
||
- `asset_id`, `tag_id`
|
||
|
||
**shares**
|
||
- `id` UUID
|
||
- `owner_user_id`
|
||
- `asset_id` (nullable) — если шаринг одного файла
|
||
- `album_id` (nullable) — если шаринг альбома
|
||
- `token` (unique, random)
|
||
- `expires_at` (nullable)
|
||
- `password_hash` (nullable)
|
||
- `created_at`
|
||
- `revoked_at` (nullable)
|
||
|
||
**auth_sessions** (опционально)
|
||
- `id`
|
||
- `user_id`
|
||
- `refresh_token_hash`
|
||
- `created_at`, `expires_at`, `revoked_at`
|
||
|
||
### 7.2 Требования к миграциям
|
||
- Все изменения БД — только через Alembic.
|
||
- Никаких raw SQL “внутри приложения”, кроме миграций.
|
||
- Схема должна работать в SQLite и PostgreSQL:
|
||
- UUID хранить как TEXT (SQLite) и UUID (PostgreSQL) через тип-переопределения,
|
||
- JSON поля избегать в MVP (или хранить TEXT).
|
||
|
||
---
|
||
|
||
## 8. Object Storage (S3)
|
||
|
||
### 8.1 Bucket и ключи
|
||
- Bucket: `MEDIA_BUCKET`
|
||
- Ключ оригинала: `u/{user_id}/o/{yyyy}/{mm}/{asset_id}{ext}`
|
||
- Ключ превью: `u/{user_id}/t/{yyyy}/{mm}/{asset_id}.jpg`
|
||
- (v1) ключ постера для видео: `u/{user_id}/p/{yyyy}/{mm}/{asset_id}.jpg`
|
||
|
||
### 8.2 Политики доступа
|
||
- Bucket **private**.
|
||
- Доступ только через pre-signed URLs с коротким TTL:
|
||
- thumbnails: 5–30 минут,
|
||
- originals: 1–10 минут (настраиваемо).
|
||
|
||
---
|
||
|
||
## 9. API (контракт)
|
||
|
||
### 9.1 Общие правила
|
||
- Версионирование: `/api/v1`
|
||
- Авторизация: `Authorization: Bearer <token>` (или cookie-based)
|
||
- Ответы: JSON
|
||
- Ошибки: единый формат `{ "error": { "code": "...", "message": "...", "details": ... } }`
|
||
|
||
### 9.2 Эндпоинты (MVP)
|
||
|
||
#### Auth
|
||
- `POST /api/v1/auth/register`
|
||
- `POST /api/v1/auth/login`
|
||
- `POST /api/v1/auth/logout`
|
||
- `GET /api/v1/auth/me`
|
||
- (опц) `POST /api/v1/auth/refresh`
|
||
|
||
#### Assets (library)
|
||
- `GET /api/v1/assets?cursor=&limit=&type=&sort=`
|
||
- `GET /api/v1/assets/{asset_id}`
|
||
- `DELETE /api/v1/assets/{asset_id}` (soft-delete)
|
||
- `POST /api/v1/assets/{asset_id}/restore`
|
||
- `DELETE /api/v1/assets/{asset_id}/purge` (hard-delete, только из Trash)
|
||
|
||
#### Upload (direct-to-S3)
|
||
- `POST /api/v1/uploads/create`
|
||
- вход: `original_filename, content_type, size_bytes`
|
||
- выход: `asset_id, upload_method, presigned_url|presigned_post|multipart`
|
||
- `POST /api/v1/uploads/{asset_id}/finalize`
|
||
- вход: `etag` (или parts list), `sha256` (опц)
|
||
- выход: `asset`
|
||
|
||
#### URLs (signed access)
|
||
- `GET /api/v1/assets/{asset_id}/download-url?kind=original|thumb`
|
||
- `GET /api/v1/assets/{asset_id}/stream-url` (для видео, kind=original на старте)
|
||
|
||
#### Shares
|
||
- `POST /api/v1/shares` (создать ссылку)
|
||
- `GET /api/v1/shares/{token}` (получить метаданные)
|
||
- `GET /api/v1/shares/{token}/download-url?asset_id=&kind=`
|
||
- `POST /api/v1/shares/{token}/revoke`
|
||
|
||
### 9.3 Пагинация
|
||
- Cursor-based (рекомендуется): `cursor` = base64(last_created_at, last_id)
|
||
- Ответ: `{ items: [...], next_cursor: "...", has_more: true }`
|
||
|
||
---
|
||
|
||
## 10. Превью (thumbnails) и обработка
|
||
|
||
### 10.1 MVP: только изображения
|
||
- После `finalize_upload` backend ставит задачу “generate_thumbnail(asset_id)”.
|
||
|
||
**Режим исполнения (на выбор):**
|
||
1) **Inline** (допустимо в MVP): генерация превью синхронно при finalize, если файл небольшой.
|
||
2) **Background worker** (лучше): Celery/RQ + Redis, либо встроенный простейший worker.
|
||
|
||
Рекомендуемый минимум:
|
||
- Redis + RQ.
|
||
|
||
### 10.2 Технические требования к thumbnails
|
||
- Формат: JPEG
|
||
- Максимальная сторона: 512px или 1024px (настраиваемо)
|
||
- Прогрессивный JPEG (желательно)
|
||
- Сохранение в S3 по `storage_key_thumb`
|
||
- При ошибке: `assets.status = failed` или отдельное поле `thumb_status`
|
||
|
||
### 10.3 v1: видео постеры
|
||
- Генерация постера (кадр на 1–3 секунде) через ffmpeg.
|
||
- Хранение отдельным ключом.
|
||
|
||
---
|
||
|
||
## 11. Безопасность
|
||
|
||
### 11.1 Аутентификация и пароли
|
||
- Хэширование argon2id (предпочтительно) или bcrypt.
|
||
- Ограничение попыток логина (v1).
|
||
- Сессии/refresh токены можно отзывать.
|
||
|
||
### 11.2 Доступ к файлам
|
||
- Все доступы к S3 только через signed URLs.
|
||
- Проверка владения asset’ом во всех endpoints.
|
||
- Подпись ссылок короткая и на конкретный объект.
|
||
|
||
### 11.3 Публичные ссылки
|
||
- token должен быть криптостойким (>= 128 бит энтропии).
|
||
- Возможность ограничить срок действия.
|
||
- (v1) опциональный пароль на ссылку.
|
||
|
||
---
|
||
|
||
## 12. Производительность и ограничения
|
||
|
||
### 12.1 Ограничения MVP
|
||
- Максимальный размер видео: конфиг `MAX_UPLOAD_SIZE_BYTES` (например 5–20 ГБ).
|
||
- Для больших файлов — multipart upload (S3 multipart).
|
||
- Лимит выдачи списка: 50–200 элементов, infinite scroll.
|
||
|
||
### 12.2 Кэширование
|
||
- Thumbnails: CDN-friendly, но доступ через signed URL (TTL).
|
||
- Статические файлы фронтенда: cache-control immutable для hashed assets.
|
||
|
||
---
|
||
|
||
## 13. Конфигурация (env)
|
||
|
||
### 13.1 Backend env variables (пример)
|
||
- `APP_ENV=dev|prod`
|
||
- `DATABASE_URL=sqlite+aiosqlite:///./app.db` (позже: `postgresql+asyncpg://...`)
|
||
- `S3_ENDPOINT_URL=` (если MinIO)
|
||
- `S3_REGION=`
|
||
- `S3_ACCESS_KEY_ID=`
|
||
- `S3_SECRET_ACCESS_KEY=`
|
||
- `MEDIA_BUCKET=`
|
||
- `SIGNED_URL_TTL_SECONDS=600`
|
||
- `CORS_ORIGINS=https://your-domain`
|
||
- `JWT_SECRET=`
|
||
- `JWT_ACCESS_TTL_SECONDS=900`
|
||
- `JWT_REFRESH_TTL_SECONDS=1209600`
|
||
- `MAX_UPLOAD_SIZE_BYTES=`
|
||
|
||
---
|
||
|
||
## 14. Деплой и окружения
|
||
|
||
### 14.1 Static (S3)
|
||
- Папка `static/` заливается в S3 bucket для сайта.
|
||
|
||
### 14.2 Backend
|
||
- Docker-образ, запускается как контейнер (uvicorn/gunicorn).
|
||
- Рекомендуемый `docker-compose` для dev: backend + minio + sqlite (локально) + redis.
|
||
|
||
### 14.3 База данных
|
||
- MVP: SQLite файл в volume.
|
||
- v1: PostgreSQL отдельным сервисом, переключение через `DATABASE_URL` + миграции alembic.
|
||
|
||
---
|
||
|
||
## 15. Структура репозитория (рекомендация)
|
||
|
||
```
|
||
repo/
|
||
backend/
|
||
src/
|
||
app/
|
||
api/
|
||
v1/
|
||
services/
|
||
repositories/
|
||
infra/
|
||
domain/
|
||
main.py
|
||
alembic/
|
||
tests/
|
||
pyproject.toml
|
||
Dockerfile
|
||
frontend/
|
||
src/
|
||
public/
|
||
vite.config.js
|
||
package.json
|
||
static/ # build output (deploy to S3)
|
||
docker-compose.yml
|
||
README.md
|
||
```
|
||
|
||
---
|
||
|
||
## 16. Критерии приёмки (Acceptance Criteria)
|
||
|
||
### 16.1 MVP
|
||
- Пользователь может зарегистрироваться/войти.
|
||
- Пользователь может загрузить:
|
||
- минимум 1 фото и 1 видео,
|
||
- пакет из 100+ фото,
|
||
- большой файл (в пределах лимита), без падения сервера.
|
||
- В библиотеке отображаются thumbnails для фото.
|
||
- Просмотр фото/видео работает в браузере.
|
||
- Удаление перемещает в корзину, restore возвращает.
|
||
- Public share link открывается без логина (если активен) и даёт доступ только к расшаренному объекту.
|
||
- База — SQLite, миграции работают, есть путь миграции на PostgreSQL (без изменения кода сервисов).
|
||
|
||
### 16.2 Качество кода
|
||
- Линтер/форматтер настроен (ruff + black или ruff format).
|
||
- Тесты покрывают ключевые сценарии (auth, create/finalize upload, list assets, share).
|
||
- Докстринги присутствуют в слоях services/repositories/infra.
|
||
|
||
---
|
||
|
||
## 17. План работ (укрупнённо)
|
||
|
||
### Этап 1 — фундамент (1)
|
||
- Скелет backend (FastAPI), конфиг, подключение БД, миграции.
|
||
- User + Auth (register/login/me).
|
||
- S3 интеграция: pre-signed upload + signed download.
|
||
|
||
### Этап 2 — assets (2)
|
||
- CRUD метаданных assets.
|
||
- Library list с пагинацией.
|
||
- Trash (soft-delete/restore/purge).
|
||
|
||
### Этап 3 — frontend MVP (3)
|
||
- Login/Register.
|
||
- Library grid + infinite scroll.
|
||
- Upload dialog + прогресс.
|
||
- Viewer modal.
|
||
- Trash page.
|
||
|
||
### Этап 4 — thumbnails (4)
|
||
- Генерация превью (inline или background).
|
||
- Отображение превью в Library.
|
||
|
||
### Этап 5 — shares (5)
|
||
- Создание/открытие share links.
|
||
- Shared view UI.
|
||
|
||
---
|
||
|
||
## 18. Риски и решения
|
||
|
||
- **Большие видео**: сразу закладываем multipart upload, иначе упремся в лимиты/таймауты.
|
||
- **MUI и “чистый JS”**: реальный путь — React+MUI и сборка в `static/`.
|
||
- **SQLite ограничения**: используем репозитории/ORM и Alembic, чтобы миграция на PostgreSQL была сменой `DATABASE_URL`.
|
||
- **Стоимость S3**: thumbnails уменьшают трафик; оригиналы только по запросу.
|
||
|
||
---
|
||
|
||
## 19. Доп. ПО (если понадобятся очереди/воркеры)
|
||
- **Redis** (рекомендуется)
|
||
- **RQ** (проще)
|
||
- Для видео в v1: **ffmpeg** (в контейнере воркера)
|
||
|
||
---
|
||
|
||
## 20. Примечания по лицензиям и приватности
|
||
- Пользовательские файлы и метаданные считаются приватными, но админ может всё просматривать.
|
||
- Логи не должны содержать содержимое файлов и чувствительные данные (пароли/токены).
|
||
- При шаринге — показывать минимум метаданных.
|
||
|