Files
diploma-server/docs/rest-api.md
Evgenii Saenko ea390b1533 Add docker
2025-12-17 11:52:18 +03:00

311 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# REST API (Swagger-Style)
## Общая информация
- **Базовый URL**: `http://0.0.0.0:8080/api/v1`
- **Форматы**: все конечные точки принимают и возвращают `application/json`, если не указан иной `Content-Type`.
- **Часовой формат**: даты/время сериализуются в `ISO_LOCAL_DATE_TIME`, например `2024-05-12T13:45:00`.
- **JWT-аутентификация**: приватные маршруты располагаются под `/api/v1/admin/**` и требуют заголовок `Authorization: Bearer <token>` из `POST /admin/login`.
- **Ошибки**: при валидационных/авторизационных ошибках сервер возвращает код `4xx` с телом `{"error": "описание"}`.
## Базовые схемы
| Схема | Описание |
| --- | --- |
| **Page<T>** | Обёртка пагинации: `items` (список сущностей `T`), `total` (общее количество), `limit` (число элементов на странице), `offset` (смещение). |
| **ServiceCategoryDTO** | `{ id: number, name: string, slug: string }`. |
| **ServiceDTO** | `{ id, title, slug, description, priceFrom: number\|null, imageUrl: string\|null, status: "PUBLISHED"\|"DRAFT"\|"ARCHIVED", category: ServiceCategoryDTO\|null, createdAt, updatedAt }`. |
| **NewsDTO** | `{ id, title, slug, summary, content, status: "draft"\|"published"\|"archived", imageUrl: string\|null, publishedAt: string\|null }`. |
| **LeadDTO** | `{ id, fullName, email, phone: string\|null, createdAt }`. |
| **AdminDTO** | `{ id, username, createdAt }`. |
---
## Администраторы и аутентификация
| Метод | Путь | Требуется JWT | Описание |
| --- | --- | --- | --- |
| POST | `/admin/login` | Нет | Получение JWT токена. |
| GET | `/admin/password_hash` | Нет | Вспомогательный эндпойнт для генерации bcrypt-хэша. |
| GET | `/admin` | Да | Получить профиль текущего администратора. |
| POST | `/admin` | Да | Создать нового администратора. |
| PUT | `/admin/{id}/password` | Да | Сменить пароль администратора. |
| DELETE | `/admin/{id}` | Да | Удалить администратора. |
### POST /api/v1/admin/login
**Тело запроса**
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `username` | string | да | Мин. 3 символа (`[A-Za-z0-9_.-]`). |
| `password` | string | да | Мин. 8 символов. |
**Ответ 200**
| Поле | Тип | Примечание |
| --- | --- | --- |
| `id` | number | Идентификатор администратора. |
| `username` | string | Введённое имя. |
| `token` | string | JWT access token. |
| `tokenType` | string | Всегда `Bearer`. |
| `expiresInMinutes` | number | Время жизни токена (минуты). |
Пример:
```json
{
"id": 1,
"username": "admin",
"token": "eyJhbGciOi...",
"tokenType": "Bearer",
"expiresInMinutes": 60
}
```
### GET /api/v1/admin/password_hash
**Query-параметры**
| Параметр | Тип | Обязательно | Описание |
| --- | --- | --- | --- |
| `password` | string | нет | Исходный пароль. По умолчанию `admin123`. |
**Ответ 200**: `{ "pass": "<bcrypt-hash>" }`.
### GET /api/v1/admin
Возвращает `AdminDTO` текущего пользователя по subject токена.
### POST /api/v1/admin
**Тело запроса** `AdminRegisterRequest`:
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `username` | string | да | Уникальное имя (регулярное выражение как при логине). |
| `password` | string | да | Мин. 8 символов. |
**Ответ 201** `AdminRegisterResponse`:
| Поле | Тип |
| --- | --- |
| `id` | number |
| `username` | string |
### PUT /api/v1/admin/{id}/password
**Параметры пути**: `id` numeric ID.
**Тело запроса** `ChangePasswordRequest`:
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `currentPassword` | string | да | Текущий пароль (проверяется через bcrypt). |
| `newPassword` | string | да | Новый пароль, минимум 8 символов. |
**Ответ 200**: `{ "updated": true }`.
### DELETE /api/v1/admin/{id}
Удаляет администратора. Ответ `200 OK`: `{ "deleted": true }`.
---
## Лиды (заявки)
| Метод | Путь | JWT | Описание |
| --- | --- | --- | --- |
| POST | `/leads` | Нет | Оставить заявку. |
| GET | `/admin/leads` | Да | Список лидов с фильтром. |
| GET | `/admin/leads/{id}` | Да | Получить лид по ID. |
| DELETE | `/admin/leads/{id}` | Да | Удалить лид. |
### POST /api/v1/leads
**Тело запроса** `LeadCreateRequest`:
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `fullName` | string | да | Имя клиента. |
| `email` | string | да | Проверяется регуляркой `^[\\w.+-]+@[\\w.-]+\\.[A-Za-z]{2,}$`. |
| `phone` | string | нет | Любой формат, сохраняется как строка. |
**Ответ 201**: `{ "id": 42 }`.
### GET /api/v1/admin/leads
**Query-параметры**
| Имя | Тип | По умолчанию | Описание |
| --- | --- | --- | --- |
| `limit` | integer | 50 | Размер страницы. |
| `page` | integer | 1 | Должна быть ≥ 1, иначе `400`. |
| `q` | string | null | Поиск по `fullName`, `email`, `phone`. |
**Ответ 200**: `Page<LeadDTO>`.
Пример элемента: `{ "id": 7, "fullName": "Иван Иванов", "email": "ivan@example.com", "phone": "+79001234567", "createdAt": "2024-05-05T10:34:00" }`.
### GET /api/v1/admin/leads/{id}
Параметр `id` (long). Ответ `LeadDTO`.
### DELETE /api/v1/admin/leads/{id}
Ответ `{ "deleted": true }`.
---
## Новости
| Метод | Путь | JWT | Описание |
| --- | --- | --- | --- |
| GET | `/news` | Нет | Пагинированный список опубликованных новостей. |
| GET | `/news/{slug}` | Нет | Получение новости по `slug`. |
| GET | `/admin/news` | Да | Список всех новостей. |
| POST | `/admin/news` | Да | Создать новость. |
| PUT | `/admin/news/{slug}` | Да | Обновить новость. |
| POST | `/admin/news/{slug}/publish` | Да | Публикация новости. |
| DELETE | `/admin/news/{slug}` | Да | Удалить новость. |
### GET /api/v1/news
**Query-параметры**: `limit` (default 20), `page` (default 1, ≥1).
**Ответ**: `Page<NewsDTO>`, где `items` отсортированы по `publishedAt` у опубликованных записей.
### GET /api/v1/news/{slug}
Возвращает `NewsDTO` опубликованной новости (если slug неактивен — `404`).
### GET /api/v1/admin/news
**Query-параметры**: `limit` (50), `page` (1, ≥1).
**Ответ**: `Page<NewsDTO>` (включая черновики).
### POST /api/v1/admin/news
**Тело запроса** `NewsCreate`:
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `title` | string | да | Заголовок. |
| `slug` | string | да | Уникальный slug (валидация на уровне базы). |
| `summary` | string | да | Краткое описание. |
| `content` | string | да | Полный текст (HTML/Markdown). |
| `status` | string | нет | `draft` (по умолчанию) \| `published` \| `archived`. |
| `imageUrl` | string | нет | Ссылка на обложку. |
**Ответ**: `{ "id": <number> }`.
### PUT /api/v1/admin/news/{slug}
**Тело запроса** `NewsUpdateRequest` (все поля опциональны, как в `NewsCreate`). Ответ `{ "updated": true }`.
### POST /api/v1/admin/news/{slug}/publish
Без тела. Устанавливает `status = "published"`, `publishedAt = now`. Ответ `{ "published": true }`.
### DELETE /api/v1/admin/news/{slug}
Ответ `{ "deleted": true }`.
---
## Категории услуг
| Метод | Путь | JWT | Описание |
| --- | --- | --- | --- |
| GET | `/service-categories` | Нет | Публичный список категорий. |
| GET | `/service-categories/{slug}` | Нет | Получить категорию по slug. |
| GET | `/admin/service-categories` | Да | Пагинация категорий. |
| POST | `/admin/service-categories` | Да | Создать категорию. |
| PUT | `/admin/service-categories/{id}` | Да | Обновить категорию. |
| DELETE | `/admin/service-categories/{id}` | Да | Удалить категорию. |
### GET /api/v1/service-categories
Возвращает массив `ServiceCategoryDTO`.
### GET /api/v1/service-categories/{slug}
Ответ `ServiceCategoryDTO`.
### GET /api/v1/admin/service-categories
**Query-параметры**: `limit` (100), `offset` (0).
Ответ: список `ServiceCategoryDTO` (без обёртки).
### POST /api/v1/admin/service-categories
**Тело запроса** `CategoryCreateRequest` (`name`, `slug` обязательные). Ответ `{ "id": <number> }`.
### PUT /api/v1/admin/service-categories/{id}
Тело `CategoryUpdateRequest` (оба поля опциональны). Ответ `{ "updated": true }`.
### DELETE /api/v1/admin/service-categories/{id}
Ответ `{ "deleted": true }`.
---
## Услуги
| Метод | Путь | JWT | Описание |
| --- | --- | --- | --- |
| GET | `/services` | Нет | Список опубликованных услуг с фильтрами. |
| GET | `/services/{slug}` | Нет | Одна услуга по slug. |
| GET | `/admin/services` | Да | Список всех услуг. |
| POST | `/admin/services` | Да | Создать услугу. |
| PUT | `/admin/services/{id}` | Да | Обновить услугу. |
| PUT | `/admin/services/{id}/status` | Да | Изменить статус. |
| DELETE | `/admin/services/{id}` | Да | Удалить услугу. |
### GET /api/v1/services
**Query-параметры**
| Имя | Тип | По умолчанию | Описание |
| --- | --- | --- | --- |
| `limit` | integer | 20 | Размер страницы. |
| `page` | integer | 1 | ≥ 1. |
| `q` | string | null | Поиск по `title` и `description` (case-insensitive). |
| `category` | string | null | `slug` категории. |
| `minPrice` | number | null | Минимальная цена (decimal). |
| `maxPrice` | number | null | Максимальная цена (decimal). |
**Ответ**: `Page<ServiceDTO>`.
Пример элемента:
```json
{
"id": 10,
"title": "Разработка мобильного приложения",
"slug": "mobile-dev",
"description": "Полный цикл разработки",
"priceFrom": 150000.0,
"imageUrl": "https://cdn.example/app.jpg",
"status": "PUBLISHED",
"category": { "id": 2, "name": "Разработка", "slug": "development" },
"createdAt": "2024-04-01T09:15:00",
"updatedAt": "2024-05-05T12:00:00"
}
```
### GET /api/v1/services/{slug}
Ответ `ServiceDTO` (включая `category`). Ошибка `404`, если slug не найден или услуга скрыта.
### GET /api/v1/admin/services
**Query-параметры**
| Имя | Тип | По умолчанию | Описание |
| --- | --- | --- | --- |
| `limit` | integer | 50 | Размер страницы. |
| `page` | integer | 1 | ≥ 1. |
| `q` | string | null | Фильтр по названию/описанию. |
| `status` | string | null | `PUBLISHED` \| `DRAFT` \| `ARCHIVED`. |
Ответ: `Page<ServiceDTO>` (включая черновики).
### POST /api/v1/admin/services
**Тело запроса** `ServiceCreateRequest`:
| Поле | Тип | Обязательно | Примечание |
| --- | --- | --- | --- |
| `title` | string | да | Название услуги. |
| `slug` | string | да | Уникальный slug (`^[a-z0-9-]{3,}$`). |
| `description` | string | да | Детальное описание. |
| `priceFrom` | string | нет | Десятичное число, например `"1499.99"`. |
| `imageUrl` | string | нет | URL изображения. |
| `status` | string | нет | По умолчанию `PUBLISHED`. |
| `categoryId` | number | нет | ID существующей категории. |
**Ответ 201**: `{ "id": <number> }`.
### PUT /api/v1/admin/services/{id}
Тело `ServiceUpdateRequest` (все поля опциональны, формат как в `ServiceCreateRequest`). Ответ `{ "updated": true }`.
### PUT /api/v1/admin/services/{id}/status
**Тело запроса** `StatusRequest` с полем `status` (`PUBLISHED`\|`DRAFT`\|`ARCHIVED`). Ответ `{ "updated": true }`.
### DELETE /api/v1/admin/services/{id}
Ответ `{ "deleted": true }`.
---
## Пример структуры Page
```json
{
"items": [ /* сущности */ ],
"total": 125,
"limit": 20,
"offset": 0
}
```