405 lines
14 KiB
YAML
405 lines
14 KiB
YAML
openapi: 3.0.3
|
||
|
||
info:
|
||
title: Contracts Service API
|
||
version: "1.0.0"
|
||
description: |
|
||
REST API сервиса **contracts** (`platform/contracts`) — управление
|
||
договорами (контрактами): создание (в т.ч. массовое), получение по id,
|
||
списочный вывод с фильтрами и пагинацией, обновление.
|
||
|
||
Сервис написан на Go (**Fiber v3**). Приложение собирается в
|
||
`internal/app/http/app.go` (`New`). Роутинг вложен под общий префикс
|
||
`/api/v0` (`app.server.Group("/api/v0")`), внутри — группа
|
||
`/contracts` (`internal/controller/http/v0`).
|
||
|
||
### Аутентификация
|
||
Все эндпоинты группы `/api/v0/contracts` защищены middleware
|
||
(`internal/adapter/auth/middleware.go`). Поддерживаются два режима:
|
||
|
||
1. **Zitadel** — если передан заголовок `Identity: Bearer <jwt>`,
|
||
пользователь берётся из полезной нагрузки этого токена
|
||
(`urn:zitadel:iam:user:metadata`). Подпись на уровне приложения
|
||
не проверяется (валидность обеспечивается сетевым слоем/Istio).
|
||
2. **sarex-backend** — если заголовка `Identity` нет, подпись основного
|
||
токена `Authorization: Bearer <jwt>` проверяется публичным RSA-ключом
|
||
(`PUBLIC_KEY`).
|
||
|
||
Корневой эндпоинт `GET /api/v0/` (health/ping) аутентификации не требует.
|
||
|
||
### Идентификаторы
|
||
Идентификатор договора — **ULID** (строка), парсится через
|
||
`ulid.Parse`. `resource_id` — **UUID**.
|
||
|
||
### Пагинация
|
||
Списочный вывод использует `limit`/`offset` (query-параметры, по умолчанию
|
||
`limit=100`, `offset=0`).
|
||
|
||
### Обработка ошибок
|
||
Ошибки возвращаются с соответствующим HTTP-статусом; тело — либо строка
|
||
с описанием, либо `{"error": "..."}` (при внутренней панике). Коды:
|
||
`400` — некорректный запрос/невалидный id, `401` — проблемы аутентификации,
|
||
`403` — пользователь не состоит в компании (`tenant_id`), `404` — договор
|
||
не найден, `500` — внутренняя ошибка, `501` — метод не реализован.
|
||
|
||
### Замечания (расхождения кода)
|
||
- `PATCH` и `DELETE` (как по коллекции, так и по id) возвращают
|
||
**`501 Not Implemented`** — обработчики-заглушки.
|
||
- `POST /api/v0/contracts` принимает **как одиночный объект, так и массив**:
|
||
тип создания выбирается по форме тела (объект → создание одного договора,
|
||
массив → пакетное создание).
|
||
- `GET /api/v0/contracts` (список) требует query-параметр `tenant_id`
|
||
(middleware `UserInCompanyMiddleware` проверяет, что пользователь состоит
|
||
в этой компании; иначе `400`/`403`).
|
||
|
||
servers:
|
||
- url: https://api.sarex.io/contracts
|
||
description: production
|
||
- url: https://stage-api.sarex.io/contracts
|
||
description: stage
|
||
- url: https://api.preprod.sarex.io/contracts
|
||
description: preprod
|
||
|
||
security:
|
||
- bearerAuth: []
|
||
|
||
tags:
|
||
- name: contracts
|
||
description: Договоры
|
||
- name: service
|
||
description: Служебные эндпоинты
|
||
|
||
paths:
|
||
/api/v0/:
|
||
get:
|
||
tags: [service]
|
||
summary: Health / ping
|
||
description: Возвращает `200 OK` без тела. Аутентификация не требуется.
|
||
security: []
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
|
||
/api/v0/contracts:
|
||
post:
|
||
tags: [contracts]
|
||
summary: Создать договор или несколько договоров
|
||
description: |
|
||
Принимает либо одиночный объект `CreateContractRequest`, либо массив
|
||
таких объектов. Форма тела определяет режим создания.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
oneOf:
|
||
- $ref: "#/components/schemas/CreateContractRequest"
|
||
- type: array
|
||
items:
|
||
$ref: "#/components/schemas/CreateContractRequest"
|
||
responses:
|
||
"201":
|
||
description: Договор(ы) создан(ы)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
oneOf:
|
||
- $ref: "#/components/schemas/ContractResponse"
|
||
- type: array
|
||
items:
|
||
$ref: "#/components/schemas/ContractResponse"
|
||
"400":
|
||
description: Некорректное тело запроса / ошибка валидации
|
||
"401":
|
||
description: Ошибка аутентификации
|
||
"500":
|
||
description: Внутренняя ошибка
|
||
get:
|
||
tags: [contracts]
|
||
summary: Список договоров
|
||
description: |
|
||
Возвращает договоры с фильтрацией и пагинацией. Требует `tenant_id`
|
||
(проверяется принадлежность пользователя к компании).
|
||
parameters:
|
||
- name: tenant_id
|
||
in: query
|
||
required: true
|
||
schema:
|
||
type: integer
|
||
format: int64
|
||
description: Идентификатор компании/арендатора
|
||
- name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
format: int64
|
||
default: 100
|
||
- name: offset
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
format: int64
|
||
default: 0
|
||
- name: resource_id
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
- name: contractor_id
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
format: int64
|
||
responses:
|
||
"200":
|
||
description: Список договоров
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ContractPaginatedResponse"
|
||
"400":
|
||
description: Некорректные параметры запроса / отсутствует tenant_id
|
||
"401":
|
||
description: Ошибка аутентификации
|
||
"403":
|
||
description: Пользователь не состоит в указанной компании
|
||
"500":
|
||
description: Внутренняя ошибка
|
||
put:
|
||
tags: [contracts]
|
||
summary: Пакетное обновление договоров
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/UpdateContractRequest"
|
||
responses:
|
||
"200":
|
||
description: Договоры обновлены
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/ContractResponse"
|
||
"400":
|
||
description: Некорректное тело запроса
|
||
"401":
|
||
description: Ошибка аутентификации
|
||
"500":
|
||
description: Внутренняя ошибка
|
||
patch:
|
||
tags: [contracts]
|
||
summary: Пакетное частичное обновление (не реализовано)
|
||
responses:
|
||
"501":
|
||
description: Not Implemented
|
||
delete:
|
||
tags: [contracts]
|
||
summary: Пакетное удаление (не реализовано)
|
||
responses:
|
||
"501":
|
||
description: Not Implemented
|
||
|
||
/api/v0/contracts/{id}:
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
description: Идентификатор договора (ULID)
|
||
get:
|
||
tags: [contracts]
|
||
summary: Получить договор по id
|
||
responses:
|
||
"200":
|
||
description: Договор
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ContractResponse"
|
||
"400":
|
||
description: Невалидный id
|
||
"401":
|
||
description: Ошибка аутентификации
|
||
"404":
|
||
description: Договор не найден
|
||
"500":
|
||
description: Внутренняя ошибка
|
||
put:
|
||
tags: [contracts]
|
||
summary: Обновить договор
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UpdateContractRequest"
|
||
responses:
|
||
"200":
|
||
description: Договор обновлён
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ContractResponse"
|
||
"400":
|
||
description: Невалидный id / некорректное тело
|
||
"401":
|
||
description: Ошибка аутентификации
|
||
"404":
|
||
description: Договор не найден
|
||
"500":
|
||
description: Внутренняя ошибка
|
||
patch:
|
||
tags: [contracts]
|
||
summary: Частичное обновление (не реализовано)
|
||
responses:
|
||
"501":
|
||
description: Not Implemented
|
||
delete:
|
||
tags: [contracts]
|
||
summary: Удалить договор (не реализовано)
|
||
responses:
|
||
"501":
|
||
description: Not Implemented
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
description: |
|
||
Основной токен `Authorization: Bearer <jwt>` (проверяется по RSA-ключу).
|
||
Опционально может передаваться заголовок `Identity: Bearer <jwt>`
|
||
(режим Zitadel), который имеет приоритет.
|
||
|
||
schemas:
|
||
Contractor:
|
||
type: object
|
||
description: Произвольный JSON-объект с данными контрагента (в БД — JSONB).
|
||
additionalProperties: true
|
||
|
||
CreateContractRequest:
|
||
type: object
|
||
required: [number, tenant_id, started_at, deadline_at]
|
||
properties:
|
||
number:
|
||
type: string
|
||
minLength: 1
|
||
description: Номер договора
|
||
tenant_id:
|
||
type: integer
|
||
format: int64
|
||
description: Идентификатор компании/арендатора
|
||
resource_id:
|
||
type: string
|
||
format: uuid
|
||
nullable: true
|
||
contractor:
|
||
$ref: "#/components/schemas/Contractor"
|
||
started_at:
|
||
type: string
|
||
format: date-time
|
||
deadline_at:
|
||
type: string
|
||
format: date-time
|
||
cost:
|
||
type: number
|
||
format: double
|
||
minimum: 0
|
||
description:
|
||
type: string
|
||
|
||
UpdateContractRequest:
|
||
type: object
|
||
required: [number, tenant_id, started_at, deadline_at]
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: ULID договора
|
||
number:
|
||
type: string
|
||
minLength: 1
|
||
tenant_id:
|
||
type: integer
|
||
format: int64
|
||
resource_id:
|
||
type: string
|
||
format: uuid
|
||
nullable: true
|
||
contractor:
|
||
$ref: "#/components/schemas/Contractor"
|
||
started_at:
|
||
type: string
|
||
format: date-time
|
||
deadline_at:
|
||
type: string
|
||
format: date-time
|
||
cost:
|
||
type: number
|
||
format: double
|
||
minimum: 0
|
||
description:
|
||
type: string
|
||
|
||
ContractResponse:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: ULID договора
|
||
number:
|
||
type: string
|
||
tenant_id:
|
||
type: integer
|
||
format: int64
|
||
resource_id:
|
||
type: string
|
||
format: uuid
|
||
nullable: true
|
||
contractor:
|
||
$ref: "#/components/schemas/Contractor"
|
||
created_at:
|
||
type: string
|
||
format: date-time
|
||
updated_at:
|
||
type: string
|
||
format: date-time
|
||
started_at:
|
||
type: string
|
||
format: date-time
|
||
deadline_at:
|
||
type: string
|
||
format: date-time
|
||
cost:
|
||
type: number
|
||
format: double
|
||
description:
|
||
type: string
|
||
|
||
ContractPaginatedResponse:
|
||
type: object
|
||
properties:
|
||
count:
|
||
type: integer
|
||
format: int64
|
||
limit:
|
||
type: integer
|
||
format: int64
|
||
offset:
|
||
type: integer
|
||
format: int64
|
||
results:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/ContractResponse"
|