Add docker
This commit is contained in:
311
docs/rest-api.md
Normal file
311
docs/rest-api.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 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
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user