chore: Описание технических требований в README
This commit is contained in:
79
README.md
79
README.md
@ -1,9 +1,72 @@
|
|||||||
<div align="center">
|
# p1ctos4ve
|
||||||
<h1>
|
Проект в рамках дисциплины «Конструирование программного обеспечения»: сервис сохранения и организации медиа со внешних ресурсов
|
||||||
p1ctos4ve
|
|
||||||
</h1>
|
|
||||||
<p>
|
|
||||||
selfhosted image preservation service
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
## Участники
|
||||||
|
- Железняков Марк Викторович - `5130904/30105`
|
||||||
|
- Михеев Егор Романович - `5130904/30105`
|
||||||
|
- Михальченко Владислав Сергеевич - `5130904/30105`
|
||||||
|
- Ботыгин Иван Алексеевич - `5130904/30105`
|
||||||
|
|
||||||
|
## Определение проблемы
|
||||||
|
Пользователи, сохраняющие большое количество медиафайлов (фотографий, видео, GIF) из разных источников (социальные сети, мессенджеры и другие специализированные платформы), сталкиваются с трудностями их систематизации и последующего поиска. Файлы скапливаются в беспорядке, отсутствует единая система тегов и категорий, что делает быстрый поиск нужного материала практически невозможным и требует значительных ручных усилий по организации. При этом всегда присутствует риск удаления материалов из первоисточника.
|
||||||
|
|
||||||
|
## Выработка требований
|
||||||
|
|
||||||
|
### Пользовательские истории
|
||||||
|
1. Как пользователь, я хочу добавлять медиафайлы (фото, видео, GIF) в свою библиотеку из различных источников (прямая загрузка, по ссылке, через интеграцию с Tenor/Pinterest), чтобы централизованно хранить все материалы.
|
||||||
|
2. Как пользователь, я хочу иметь мощную систему поиска по библиотеке с фильтрацией по типу файла, тегам, категориям и дате добавления, чтобы быстро находить конкретные материалы.
|
||||||
|
3. Как пользователь, я хочу иметь возможность просматривать свою медиатеку в удобном интерфейсе (галерея, список) с предпросмотром, чтобы легко ориентироваться в содержимом.
|
||||||
|
|
||||||
|
## Разработка архитектуры и детальное проектирование
|
||||||
|
|
||||||
|
### Оценка масштаба
|
||||||
|
> Оценка масштаба производится при условии нагрузки на сервис в размере 10 000 активных пользователей в сутки. При этом сервис подазумевает опцию селфхостинга, что может снизить требования к ресурсам центрального экземпляра (инстанса) сервиса.
|
||||||
|
|
||||||
|
#### Характер нагрузки
|
||||||
|
|
||||||
|
##### Соотношение R/W нагрузки
|
||||||
|
**70% на чтение и 30% на запись.** Обосновать это можно тем, что запись метаданных и загрузка файлов будет занимать большую долю операций в сервисе с пиками во время массовых импортов из Tenor/Pinterest. При этом чтение данных будет производится сильно реже
|
||||||
|
|
||||||
|
##### Трафик
|
||||||
|
Для входящего трафика предположим, что один пользователь в среднем будем генерировать 10 МБ данных. Так, 10 000 пользователей * 10 МБ = 100 ГБ/день.
|
||||||
|
|
||||||
|
##### Объемы дисковой системы
|
||||||
|
При начальном размере файла ~5 МБ и 10 загрузках на пользователя в день: 10 000 * 10 * 5 МБ = 500 ГБ/день. Поэтому может потребоваться дешевое S3-хранилище.
|
||||||
|
|
||||||
|
### Диаграммы C4 Model
|
||||||
|
|
||||||
|
#### Контекст
|
||||||
|
![[assets/С4-1context.jpg]]
|
||||||
|
|
||||||
|
#### Контейнеры
|
||||||
|
![[assets/С4-2containers.jpg]]
|
||||||
|
|
||||||
|
#### Компоненты
|
||||||
|
![[С4-3components.jpg]]
|
||||||
|
|
||||||
|
#### Код
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
### Контракты API
|
||||||
|
Краткая информация о методах API доступна в [assets/API.md](API.md), а полная документация - в [Scalar](https://scalar.com/) на эндпоинте `/openapi`.
|
||||||
|
|
||||||
|
### Схема БД и оправдание с точки зрения нефункциональных требований
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
### Схема масштабирования сервиса при росте нагрузки в 10 раз
|
||||||
|
Для возможности масштабирования можно использовать следующие подходы:
|
||||||
|
- Размещение бекенда за балансировщиком нагрузки (например, Nginx, Traefik или Caddy): трафик будет распределяться равномерно между двумя или более экземплярами API
|
||||||
|
- Репликация БД - основная база принимает запись, а одна или несколько реплик получают изменения по журналу (WAL) и обслуживают преимущественно чтение.
|
||||||
|
- Кеширование частозапрашиваемых данных через Redis: результаты чтения (например, списки, карточки по ID, агрегации, результаты поиска с популярными фильтрами) складываются в KV хранилище с некоторым TTL, что позволяет сократить время ответа на повторяющиеся запросы
|
||||||
|
|
||||||
|
## Кодирование и отладка
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
## Unit-тестирование
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
## Интеграционное тестирование
|
||||||
|
> WIP
|
||||||
|
|
||||||
|
## Сборка и запуск
|
||||||
|
> WIP
|
||||||
|
|||||||
283
assets/API.md
Normal file
283
assets/API.md
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
# p1ctos4ve - контракты API
|
||||||
|
|
||||||
|
## Better Auth
|
||||||
|
API использует фреймворк [Better Auth](https://better-auth.com) для управления пользователями и сессиями.
|
||||||
|
|
||||||
|
Для работы с авторизацией необходимо использовать [клиентскую библиотеку](https://www.better-auth.com/docs/concepts/client) Better Auth, прямой вызов методов авторизации из клиентов не предусматривается.
|
||||||
|
|
||||||
|
### `POST /auth/api/sign-up/email`
|
||||||
|
|
||||||
|
Регистрация нового пользователя с почтой и паролем.
|
||||||
|
|
||||||
|
**Параметры запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"email": "string",
|
||||||
|
"password": "string",
|
||||||
|
"image": "string (опционально)",
|
||||||
|
"callbackURL": "string (опционально)",
|
||||||
|
"rememberMe": "boolean (опционально, по умолчанию true)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "string | null",
|
||||||
|
"user": {
|
||||||
|
"id": "string",
|
||||||
|
"email": "string",
|
||||||
|
"name": "string",
|
||||||
|
"image": "string | null",
|
||||||
|
"emailVerified": "boolean",
|
||||||
|
"createdAt": "string (date-time)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /auth/api/sign-in/email`
|
||||||
|
|
||||||
|
Вход с использованием почты и пароля.
|
||||||
|
|
||||||
|
**Параметры запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "string",
|
||||||
|
"password": "string",
|
||||||
|
"callbackURL": "string (опционально)",
|
||||||
|
"rememberMe": "string (опционально)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"redirect": false,
|
||||||
|
"token": "string",
|
||||||
|
"url": null,
|
||||||
|
"user": {
|
||||||
|
"id": "string",
|
||||||
|
"email": "string",
|
||||||
|
"name": "string | null",
|
||||||
|
"image": "string | null",
|
||||||
|
"emailVerified": "boolean",
|
||||||
|
"createdAt": "string (date-time)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /auth/api/sign-out`
|
||||||
|
|
||||||
|
Выход из системы и завершение текущей сессии.
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": "boolean"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сейвы
|
||||||
|
API для работы с сохраненными медиафайлами (видео, фото). Каждый сейв имеет настройку видимости, определяющую режим доступа.
|
||||||
|
|
||||||
|
### Режимы видимости сейва
|
||||||
|
- `public` - сейв доступен всем пользователям, отображается в публичных списках и профиле владельца
|
||||||
|
- `link` - сейв доступен только по прямой ссылке (shareUrl), не отображается в публичных списках. Владелец всегда видит его в своих списках
|
||||||
|
|
||||||
|
### `GET /saves/my`
|
||||||
|
|
||||||
|
Возвращает все сейвы текущего пользователя независимо от режима видимости.
|
||||||
|
|
||||||
|
**Аутентификация:** Опциональна
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"description": "string",
|
||||||
|
"tags": ["string"],
|
||||||
|
"visibility": "public" | "link",
|
||||||
|
"shareUrl": "string (только для visibility: link)",
|
||||||
|
"createdAt": "string (date-time)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /saves/u/{slug}`
|
||||||
|
Возвращает **только публичные сейвы** (visibility: public) конкретного пользователя по его никнейму (slug). Сейвы с видимостью "link" не включаются в этот список, даже если запрашивает владелец.
|
||||||
|
|
||||||
|
**Параметры пути:**
|
||||||
|
- `slug` (string, обязательный) - никнейм пользователя
|
||||||
|
|
||||||
|
**Аутентификация:** Опциональна
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"description": "string",
|
||||||
|
"tags": ["string"],
|
||||||
|
"visibility": "public",
|
||||||
|
"createdAt": "string (date-time)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GET /saves/{id}`
|
||||||
|
Возвращает информацию о конкретном сейве.
|
||||||
|
|
||||||
|
**Параметры пути:**
|
||||||
|
- `id` (number/string, обязательный) - ID сейва
|
||||||
|
|
||||||
|
**Аутентификация:** Опциональна
|
||||||
|
|
||||||
|
**Правила доступа:**
|
||||||
|
- **Владелец:** полный доступ ко всем сейвам (public и link)
|
||||||
|
- **Другие пользователи:**
|
||||||
|
- **public сейвы:** доступны всем
|
||||||
|
- **link сейвы:** доступны только при обращении по корректной shareUrl; запрос по голому ID вернет `404 Not Found`
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"description": "string",
|
||||||
|
"tags": ["string"],
|
||||||
|
"visibility": "public" | "link",
|
||||||
|
"shareUrl": "string (только для visibility: link)",
|
||||||
|
"userId": "string",
|
||||||
|
"createdAt": "string (date-time)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Коды ошибок:**
|
||||||
|
- `404 Not Found` - сейв не найден или недоступен (для link сейвов без shareUrl)
|
||||||
|
- `403 Forbidden` - нет прав доступа
|
||||||
|
|
||||||
|
### `DELETE /saves/{id}`
|
||||||
|
Удаляет сейв по его ID. Доступно только владельцу.
|
||||||
|
|
||||||
|
**Параметры пути:**
|
||||||
|
- `id` (number/string, обязательный) - ID сейва
|
||||||
|
|
||||||
|
**Аутентификация:** Обязательна
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Сейв успешно удален"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `PATCH /saves/{id}`
|
||||||
|
Обновляет метаданные сейва (название, описание, теги, видимость). Доступно только владельцу.
|
||||||
|
|
||||||
|
**Параметры пути:**
|
||||||
|
- `id` (number/string, обязательный) - ID сейва
|
||||||
|
|
||||||
|
**Параметры запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string (опционально)",
|
||||||
|
"description": "string (опционально)",
|
||||||
|
"tags": ["string"] (опционально),
|
||||||
|
"visibility": "public" | "link" (опционально)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Аутентификация:** Обязательна
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`, `application/x-www-form-urlencoded`, или `multipart/form-data`
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"description": "string",
|
||||||
|
"tags": ["string"],
|
||||||
|
"visibility": "public" | "link",
|
||||||
|
"shareUrl": "string (если visibility: link)",
|
||||||
|
"updatedAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Примечание:** При установке `visibility: "link"` сервер автоматически генерирует или возвращает существующую `shareUrl`. При смене на `visibility: "public"` поле `shareUrl` становится неактуальным.
|
||||||
|
|
||||||
|
### `POST /saves/external`
|
||||||
|
Скачивает и сохраняет медиафайл по URL из стороннего источника.
|
||||||
|
|
||||||
|
**Параметры запроса:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "string (URI, обязательный)",
|
||||||
|
"name": "string (опционально)",
|
||||||
|
"description": "string (опционально)",
|
||||||
|
"tags": ["string"] (опционально),
|
||||||
|
"visibility": "public" | "link" (опционально, по умолчанию: link)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Аутентификация:** Обязательна
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`, `application/x-www-form-urlencoded`, или `multipart/form-data`
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"url": "string",
|
||||||
|
"visibility": "public" | "link",
|
||||||
|
"shareUrl": "string (если visibility: link)",
|
||||||
|
"createdAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если `visibility` не указана, по умолчанию устанавливается `link`.
|
||||||
|
|
||||||
|
### `POST /saves/upload`
|
||||||
|
Загружает медиафайл с устройства.
|
||||||
|
|
||||||
|
**Параметры запроса (multipart/form-data):**
|
||||||
|
- `file` (`binary`, обязательный) - файл для загрузки
|
||||||
|
- `name` (`string`, опционально) - название сейва
|
||||||
|
- `description` (`string`, опционально) - описание сейва
|
||||||
|
- `tags` (`array[string]`, опционально) - теги
|
||||||
|
- `visibility` (`"public" | "link"`, опционально, по умолчанию: "link") - режим видимости
|
||||||
|
|
||||||
|
**Аутентификация:** Обязательна
|
||||||
|
|
||||||
|
**Content-Type:** `multipart/form-data`
|
||||||
|
|
||||||
|
**Ответ (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "number",
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"url": "string",
|
||||||
|
"visibility": "public" | "link",
|
||||||
|
"shareUrl": "string (если visibility: link)",
|
||||||
|
"createdAt": "string (date-time)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если `visibility` не указана, по умолчанию устанавливается `link`.
|
||||||
BIN
assets/С4-1context.jpg
Normal file
BIN
assets/С4-1context.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
assets/С4-2containers.jpg
Normal file
BIN
assets/С4-2containers.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/С4-3components.jpg
Normal file
BIN
assets/С4-3components.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Reference in New Issue
Block a user