# 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 ` из `POST /admin/login`. - **Ошибки**: при валидационных/авторизационных ошибках сервер возвращает код `4xx` с телом `{"error": "описание"}`. ## Базовые схемы | Схема | Описание | | --- | --- | | **Page** | Обёртка пагинации: `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": "" }`. ### 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`. Пример элемента: `{ "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`, где `items` отсортированы по `publishedAt` у опубликованных записей. ### GET /api/v1/news/{slug} Возвращает `NewsDTO` опубликованной новости (если slug неактивен — `404`). ### GET /api/v1/admin/news **Query-параметры**: `limit` (50), `page` (1, ≥1). **Ответ**: `Page` (включая черновики). ### 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": }`. ### 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": }`. ### 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`. Пример элемента: ```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` (включая черновики). ### 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": }`. ### 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 } ```