Archived
1
0

chore: Описание технических требований в README

This commit is contained in:
2025-10-31 10:58:10 +03:00
parent 3bd6547226
commit cc05532985
5 changed files with 354 additions and 8 deletions

View File

@ -1,9 +1,72 @@
<div align="center">
<h1>
p1ctos4ve
</h1>
<p>
selfhosted image preservation service
</p>
</div>
# p1ctos4ve
Проект в рамках дисциплины «Конструирование программного обеспечения»: сервис сохранения и организации медиа со внешних ресурсов
## Участники
- Железняков Марк Викторович - `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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
assets/С4-2containers.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
assets/С4-3components.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB