feat: implement order workflow, metrics, and handler unit tests
- implement UpdateOrderStatus and GetOrdersReport service methods - implement all order, user handlers (UpdateMe, GetMyTrips, CancelOrder, GetOrder, etc.) - extract OrderServicer interface for testability - add Prometheus metrics middleware (requests total, duration) - fix GetAll storage flavor for PostgreSQL ($1 placeholders) - add 17 unit tests for order handlers via httptest - wire Grafana datasource and update Prometheus scrape config - update README with full API reference, pricing, roles, monitoring
This commit is contained in:
173
README.md
173
README.md
@@ -2,44 +2,27 @@
|
|||||||
|
|
||||||
Бэкенд для логистической платформы. Управление заказами на перевозку, водителями, транспортом и складами. При создании заказа строится реальный маршрут через OSRM, считается стоимость перевозки, трекинг водителя в реальном времени через WebSocket.
|
Бэкенд для логистической платформы. Управление заказами на перевозку, водителями, транспортом и складами. При создании заказа строится реальный маршрут через OSRM, считается стоимость перевозки, трекинг водителя в реальном времени через WebSocket.
|
||||||
|
|
||||||
|
Репозиторий: [github.com/anxi0uz/logiflow](https://github.com/anxi0uz/logiflow.git)
|
||||||
|
|
||||||
## Стек
|
## Стек
|
||||||
|
|
||||||
- **Go 1.25** — Chi v5, oapi-codegen, pgx/v5, go-redis
|
- **Go 1.25** — Chi v5, oapi-codegen, pgx/v5, go-redis, errgroup
|
||||||
- **PostgreSQL** — миграции через Goose
|
- **PostgreSQL** — миграции через Goose
|
||||||
- **Redis** — хранение JWT access/refresh токенов
|
- **Redis** — хранение JWT access/refresh токенов
|
||||||
- **Nominatim** — геокодинг адресов (OpenStreetMap, без ключа)
|
- **Nominatim** — геокодинг адресов (OpenStreetMap, без ключа)
|
||||||
- **OSRM** — построение маршрутов и расчёт дистанции
|
- **OSRM** — построение маршрутов и расчёт дистанции
|
||||||
- **Prometheus + Grafana** — мониторинг
|
- **Prometheus + Grafana** — мониторинг HTTP метрик
|
||||||
- **Podman** — контейнеризация
|
- **Podman** — контейнеризация
|
||||||
|
|
||||||
## Запуск
|
## Быстрый старт
|
||||||
|
|
||||||
```bash
|
Создать сеть:
|
||||||
# поднять все сервисы
|
|
||||||
make up
|
|
||||||
|
|
||||||
# пересобрать и поднять
|
|
||||||
make build-up
|
|
||||||
|
|
||||||
# остановить
|
|
||||||
make down
|
|
||||||
|
|
||||||
# для деплоя (pull + build + up)
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Перед запуском создать сеть:
|
|
||||||
```bash
|
```bash
|
||||||
podman network create LogiflowNetwork
|
podman network create LogiflowNetwork
|
||||||
```
|
```
|
||||||
|
|
||||||
Скопировать `.env` и заполнить секреты:
|
Создать `.env` и заполнить:
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Обязательные переменные:
|
|
||||||
```
|
|
||||||
LOGIFLOW_DATABASE_USER=
|
LOGIFLOW_DATABASE_USER=
|
||||||
LOGIFLOW_DATABASE_PASSWORD=
|
LOGIFLOW_DATABASE_PASSWORD=
|
||||||
LOGIFLOW_DATABASE_NAME=
|
LOGIFLOW_DATABASE_NAME=
|
||||||
@@ -47,16 +30,44 @@ LOGIFLOW_REDIS_PASSWORD=
|
|||||||
LOGIFLOW_JWT_KEY=
|
LOGIFLOW_JWT_KEY=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Поднять все сервисы:
|
||||||
|
```bash
|
||||||
|
make up # запуск
|
||||||
|
make build-up # пересборка + запуск
|
||||||
|
make down # остановка
|
||||||
|
make deploy # pull + пересборка + запуск (для деплоя)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сервисы
|
||||||
|
|
||||||
|
| Сервис | Адрес |
|
||||||
|
|---|---|
|
||||||
|
| API | `http://localhost:3001` |
|
||||||
|
| Grafana | `http://localhost:3000` |
|
||||||
|
| Prometheus | `http://localhost:9090` |
|
||||||
|
| Метрики | `http://localhost:3001/metrics` |
|
||||||
|
|
||||||
## Конфигурация
|
## Конфигурация
|
||||||
|
|
||||||
Конфиг читается из `configs/config.toml`, переменные окружения с префиксом `LOGIFLOW_` перекрывают файл.
|
Конфиг читается из `configs/config.toml`, переменные окружения с префиксом `LOGIFLOW_` перекрывают файл.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
[server]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 3001
|
||||||
|
readTimeout = "10s"
|
||||||
|
writeTimeout = "30s"
|
||||||
|
idleTimeout = "60s"
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
refreshTokenTTL = "168h"
|
||||||
|
accessTokenTTL = "24h"
|
||||||
|
|
||||||
[pricing]
|
[pricing]
|
||||||
baseFee = 5000.0 # базовая ставка, руб
|
baseFee = 500.0 # базовая ставка, руб
|
||||||
perKm = 100.0 # руб/км
|
perKm = 25.0 # руб/км
|
||||||
perKg = 15.0 # руб/кг
|
perKg = 3.0 # руб/кг
|
||||||
perM3 = 600.0 # руб/м³
|
perM3 = 150.0 # руб/м³
|
||||||
```
|
```
|
||||||
|
|
||||||
Цена заказа считается по формуле:
|
Цена заказа считается по формуле:
|
||||||
@@ -68,12 +79,12 @@ total = baseFee + distance_km * perKm + weight_kg * perKg + volume_m3 * perM3
|
|||||||
|
|
||||||
| Роль | Возможности |
|
| Роль | Возможности |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `client` | Создаёт заказы, следит за своими заявками |
|
| `client` | Создаёт и отменяет свои заказы, следит за статусом |
|
||||||
| `driver` | Меняет свой статус, видит назначенные маршруты |
|
| `driver` | Меняет свой статус, видит назначенные заказы, двигает статус in_transit/delivered |
|
||||||
| `manager` | Назначает водителей на заказы |
|
| `manager` | Назначает водителей на заказы, управляет статусами, смотрит отчёты |
|
||||||
| `admin` | Создаёт профили водителей и менеджеров, видит всё |
|
| `admin` | Создаёт профили водителей, видит всё |
|
||||||
|
|
||||||
Клиенты регистрируются сами через `POST /auth/register`. Водителей и менеджеров создаёт только администратор.
|
Клиенты регистрируются через `POST /auth/register`. Роль назначается вручную в БД. Водителей создаёт `admin`, менеджеров — авторизованный пользователь.
|
||||||
|
|
||||||
## Авторизация
|
## Авторизация
|
||||||
|
|
||||||
@@ -83,6 +94,83 @@ JWT (HS256) + refresh токены. Access токен живёт 24 часа, re
|
|||||||
Authorization: <access_token>
|
Authorization: <access_token>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| POST | `/auth/register` | Регистрация клиента |
|
||||||
|
| POST | `/auth/login` | Вход, получение токенов |
|
||||||
|
| POST | `/auth/logout` | Выход, инвалидация токенов |
|
||||||
|
| POST | `/auth/refresh` | Обновление access токена |
|
||||||
|
|
||||||
|
### Пользователь
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/me` | Профиль текущего пользователя |
|
||||||
|
| PATCH | `/me` | Обновить имя, аватар, пароль |
|
||||||
|
| DELETE | `/me` | Удалить аккаунт |
|
||||||
|
| GET | `/me/trips` | История поездок (driver) |
|
||||||
|
|
||||||
|
### Заказы
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/orders` | Список заказов (по роли) |
|
||||||
|
| POST | `/orders` | Создать заказ |
|
||||||
|
| GET | `/orders/{id}` | Получить заказ |
|
||||||
|
| DELETE | `/orders/{id}` | Отменить заказ |
|
||||||
|
| PATCH | `/orders/{id}/status` | Обновить статус |
|
||||||
|
| GET | `/orders/{id}/route` | Маршрут заказа |
|
||||||
|
| GET | `/orders/{id}/route/ws` | WebSocket трекинг |
|
||||||
|
|
||||||
|
### Водители
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/drivers` | Список водителей |
|
||||||
|
| POST | `/drivers` | Создать водителя (admin) |
|
||||||
|
| GET | `/drivers/{slug}` | Получить водителя |
|
||||||
|
| PUT | `/drivers/{slug}` | Обновить водителя |
|
||||||
|
| DELETE | `/drivers/{slug}` | Удалить водителя |
|
||||||
|
| PATCH | `/drivers/me/status` | Обновить свой статус (driver) |
|
||||||
|
|
||||||
|
### Менеджеры
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/managers` | Список менеджеров |
|
||||||
|
| POST | `/managers` | Создать менеджера |
|
||||||
|
| GET | `/managers/{slug}` | Получить менеджера |
|
||||||
|
| DELETE | `/managers/{slug}` | Удалить менеджера |
|
||||||
|
|
||||||
|
### Склады
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/warehouses` | Список складов |
|
||||||
|
| POST | `/warehouses` | Создать склад |
|
||||||
|
| GET | `/warehouses/{slug}` | Получить склад |
|
||||||
|
| PUT | `/warehouses/{slug}` | Обновить склад |
|
||||||
|
| DELETE | `/warehouses/{slug}` | Удалить склад |
|
||||||
|
|
||||||
|
### Транспорт
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/vehicles` | Список ТС |
|
||||||
|
| POST | `/vehicles` | Добавить ТС |
|
||||||
|
| GET | `/vehicles/{slug}` | Получить ТС |
|
||||||
|
| PUT | `/vehicles/{slug}` | Обновить ТС |
|
||||||
|
| DELETE | `/vehicles/{slug}` | Удалить ТС |
|
||||||
|
|
||||||
|
### Отчёты
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/reports/orders` | Отчёт по заказам (manager) |
|
||||||
|
| GET | `/reports/dashboard` | Дашборд (manager) |
|
||||||
|
|
||||||
|
### Уведомления
|
||||||
|
| Метод | Путь | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| GET | `/notifications` | Список уведомлений |
|
||||||
|
| PATCH | `/notifications/{id}/read` | Отметить прочитанным |
|
||||||
|
|
||||||
## Флоу заказа
|
## Флоу заказа
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -110,4 +198,21 @@ orders (created_by_id → users, driver_id → drivers, manager_id → managers)
|
|||||||
notifications (user_id → users)
|
notifications (user_id → users)
|
||||||
```
|
```
|
||||||
|
|
||||||
Сервер поднимается на `localhost:3001`, Grafana на `localhost:3000`, Prometheus на `localhost:9090`.
|
## Мониторинг
|
||||||
|
|
||||||
|
Prometheus собирает метрики с `/metrics`. Grafana доступна на `localhost:3000` (admin/admin).
|
||||||
|
|
||||||
|
Доступные метрики:
|
||||||
|
- `logiflow_http_requests_total` — кол-во запросов по методу, пути, статусу
|
||||||
|
- `logiflow_http_duration_seconds` — latency запросов
|
||||||
|
|
||||||
|
Конфиг Prometheus: `configs/prometheus.yml`
|
||||||
|
Datasource Grafana: `configs/datasources/`
|
||||||
|
|
||||||
|
## Тесты
|
||||||
|
|
||||||
|
Юнит тесты хендлеров через `httptest` без реальной БД:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./tests/...
|
||||||
|
```
|
||||||
|
|||||||
7
configs/datasources/prometheus.yaml
Normal file
7
configs/datasources/prometheus.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
url: http://logiflow-prometheus:9090
|
||||||
|
isDefault: true
|
||||||
|
access: proxy
|
||||||
@@ -6,6 +6,6 @@ scrape_configs:
|
|||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["localhost:9090"]
|
- targets: ["localhost:9090"]
|
||||||
|
|
||||||
- job_name: "handbooks"
|
- job_name: "logiflow"
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["app:3001"]
|
- targets: ["app:3001"]
|
||||||
|
|||||||
29
go.mod
29
go.mod
@@ -27,9 +27,14 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||||
github.com/huandu/go-clone v1.7.3 // indirect
|
github.com/huandu/go-clone v1.7.3 // indirect
|
||||||
@@ -37,16 +42,40 @@ require (
|
|||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 // indirect
|
||||||
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||||
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.19.2 // indirect
|
||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||||
|
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
|
||||||
|
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
|
||||||
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
|
||||||
|
|||||||
149
go.sum
149
go.sum
@@ -1,6 +1,8 @@
|
|||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
@@ -8,33 +10,63 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
||||||
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
||||||
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||||
|
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang-cz/devslog v0.0.15 h1:ejoBLTCwJHWGbAmDf2fyTJJQO3AkzcPjw8SC9LaOQMI=
|
github.com/golang-cz/devslog v0.0.15 h1:ejoBLTCwJHWGbAmDf2fyTJJQO3AkzcPjw8SC9LaOQMI=
|
||||||
github.com/golang-cz/devslog v0.0.15/go.mod h1:bSe5bm0A7Nyfqtijf1OMNgVJHlWEuVSXnkuASiE1vV8=
|
github.com/golang-cz/devslog v0.0.15/go.mod h1:bSe5bm0A7Nyfqtijf1OMNgVJHlWEuVSXnkuASiE1vV8=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||||
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||||
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||||
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
||||||
@@ -44,6 +76,7 @@ github.com/huandu/go-sqlbuilder v1.39.1 h1:uUaj41yLNTQBe7ojNF6Im1RPbHCN4zCjMRyST
|
|||||||
github.com/huandu/go-sqlbuilder v1.39.1/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E=
|
github.com/huandu/go-sqlbuilder v1.39.1/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -54,6 +87,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
@@ -67,6 +102,11 @@ github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP
|
|||||||
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||||
github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4=
|
github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4=
|
||||||
github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
@@ -75,16 +115,48 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
|
|||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/oapi-codegen/oapi-codegen/v2 v2.6.0 h1:4i+F2cvwBFZeplxCssNdLy3MhNzUD87mI3HnayHZkAU=
|
||||||
|
github.com/oapi-codegen/oapi-codegen/v2 v2.6.0/go.mod h1:eWHeJSohQJIINJZzzQriVynfGsnlQVh0UkN2UYYcw4Q=
|
||||||
github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4=
|
github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4=
|
||||||
github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0=
|
github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0=
|
||||||
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||||
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||||
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||||
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
|
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
|
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
|
||||||
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
|
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
|
||||||
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||||
|
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
@@ -93,15 +165,26 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/samber/slog-chi v1.19.0 h1:fl4qH5Hhk7feHtyp4CxJUt7U1TqjPrZ1uueDW9D+Cps=
|
github.com/samber/slog-chi v1.19.0 h1:fl4qH5Hhk7feHtyp4CxJUt7U1TqjPrZ1uueDW9D+Cps=
|
||||||
github.com/samber/slog-chi v1.19.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
|
github.com/samber/slog-chi v1.19.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
|
||||||
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||||
|
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
|
||||||
|
github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
|
||||||
|
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
|
||||||
|
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
|
||||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
|
||||||
|
github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||||
|
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
@@ -112,18 +195,84 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func (s *Server) CreateManager(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := storage.Create(ctx, "users", userModel, tx); err != nil {
|
if err := storage.Create(ctx, "users", userModel, tx); err != nil {
|
||||||
slog.ErrorContext(ctx, "Error while creating user", slog.String("error", err.Error()))
|
slog.ErrorContext(ctx, "Error while creating user", slog.String("error", err.Error()))
|
||||||
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
tx.Rollback(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ func (s *Server) CreateManager(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := storage.Create(ctx, "managers", managerModel, tx); err != nil {
|
if err := storage.Create(ctx, "managers", managerModel, tx); err != nil {
|
||||||
slog.ErrorContext(ctx, "Error while creating manager", slog.String("error", err.Error()))
|
slog.ErrorContext(ctx, "Error while creating manager", slog.String("error", err.Error()))
|
||||||
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
tx.Rollback(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
internal/handler/metrics.go
Normal file
51
internal/handler/metrics.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpRequests = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Name: "logiflow_http_requests_total",
|
||||||
|
Help: "Количество HTTP запросов",
|
||||||
|
}, []string{"method", "path", "status"})
|
||||||
|
|
||||||
|
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Name: "logiflow_http_duration_seconds",
|
||||||
|
Help: "Время обработки HTTP запросов",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
}, []string{"method", "path"})
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) MiddlewareMetrics(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
wrapped := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
||||||
|
next.ServeHTTP(wrapped, r)
|
||||||
|
|
||||||
|
httpRequests.WithLabelValues(
|
||||||
|
r.Method,
|
||||||
|
r.URL.Path,
|
||||||
|
strconv.Itoa(wrapped.status),
|
||||||
|
).Inc()
|
||||||
|
|
||||||
|
httpDuration.WithLabelValues(r.Method, r.URL.Path).
|
||||||
|
Observe(time.Since(start).Seconds())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusRecorder struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *statusRecorder) WriteHeader(status int) {
|
||||||
|
r.status = status
|
||||||
|
r.ResponseWriter.WriteHeader(status)
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/anxi0uz/logiflow/internal/api"
|
"github.com/anxi0uz/logiflow/internal/api"
|
||||||
|
"github.com/anxi0uz/logiflow/internal/services"
|
||||||
|
storage "github.com/anxi0uz/logiflow/pkg"
|
||||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,10 +20,13 @@ func (s *Server) ListOrders(w http.ResponseWriter, r *http.Request, params api.L
|
|||||||
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch claims.Role {
|
orders, err := s.OrderSerice.ListOrders(ctx, claims.ID, claims.Role, params)
|
||||||
case "client":
|
if err != nil {
|
||||||
break
|
slog.ErrorContext(ctx, "Error while getting list of orders", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, orders, RespSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -51,13 +57,107 @@ func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {
|
|||||||
}, "order")
|
}, "order")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
func (s *Server) GetOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) CancelOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
order, err := s.OrderSerice.GetOrder(ctx, id, claims.ID, claims.Role)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, services.ErrForbidden) {
|
||||||
|
s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, storage.ErrNotFound) {
|
||||||
|
s.JSON(w, r, http.StatusNotFound, MsgNotFound, RespNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.ErrorContext(ctx, "Error while getting order", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, order, RespSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateOrderStatus(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
func (s *Server) CancelOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := s.OrderSerice.CancelOrder(ctx, id, claims.ID, claims.Role); err != nil {
|
||||||
|
if errors.Is(err, services.ErrCannotCancel) {
|
||||||
|
slog.ErrorContext(ctx, "Cant cancel order with that id", slog.String("id", id.String()))
|
||||||
|
s.JSON(w, r, http.StatusConflict, "order cant be cancelled in current status", RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, services.ErrForbidden) {
|
||||||
|
s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.ErrorContext(ctx, "error while cancelling order with that id", slog.Any("id", id), slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, "Cancelled", RespSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateOrderStatus(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req api.OrderStatusUpdate
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
order, err := s.OrderSerice.UpdateOrderStatus(ctx, id, claims.ID, claims.Role, req)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, services.ErrForbidden):
|
||||||
|
s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError)
|
||||||
|
case errors.Is(err, services.ErrCannotCancel):
|
||||||
|
s.JSON(w, r, http.StatusConflict, "cannot cancel order in current status", RespError)
|
||||||
|
default:
|
||||||
|
slog.ErrorContext(ctx, "...", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, order, RespSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) GetOrdersReport(w http.ResponseWriter, r *http.Request, params api.GetOrdersReportParams) {
|
func (s *Server) GetOrdersReport(w http.ResponseWriter, r *http.Request, params api.GetOrdersReportParams) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orders, err := s.OrderSerice.GetOrdersReport(ctx, claims.Role, params)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, services.ErrForbidden) {
|
||||||
|
s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.ErrorContext(ctx, "Error while getting orders report", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, orders, RespSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetDashboard(w http.ResponseWriter, r *http.Request) {}
|
func (s *Server) GetDashboard(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
|||||||
@@ -1,11 +1,26 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/anxi0uz/logiflow/internal/models"
|
||||||
|
storage "github.com/anxi0uz/logiflow/pkg"
|
||||||
|
"github.com/huandu/go-sqlbuilder"
|
||||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) GetRoute(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
func (s *Server) GetRoute(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {
|
||||||
|
ctx := r.Context()
|
||||||
|
route, err := storage.GetOne[models.Route](ctx, s.DB, "routes", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("order_id", id))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "Error while getting route", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, route, RespSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) RouteWebSocket(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
func (s *Server) RouteWebSocket(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
slogchi "github.com/samber/slog-chi"
|
slogchi "github.com/samber/slog-chi"
|
||||||
@@ -53,7 +54,7 @@ type Server struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
Redis *redis.Client
|
Redis *redis.Client
|
||||||
JwtKey []byte
|
JwtKey []byte
|
||||||
OrderSerice services.OrderService
|
OrderSerice services.OrderServicer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(db *pgxpool.Pool, redis *redis.Client, cfg *config.Config) *Server {
|
func NewServer(db *pgxpool.Pool, redis *redis.Client, cfg *config.Config) *Server {
|
||||||
@@ -63,7 +64,7 @@ func NewServer(db *pgxpool.Pool, redis *redis.Client, cfg *config.Config) *Serve
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
JwtKey: []byte(cfg.JwtOpt.Key),
|
JwtKey: []byte(cfg.JwtOpt.Key),
|
||||||
OrderSerice: *services.NewOrderService(db, *cfg),
|
OrderSerice: services.NewOrderService(db, *cfg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ func (s *Server) Run() error {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
r.Use(s.MiddlewareRequestID)
|
r.Use(s.MiddlewareRequestID)
|
||||||
|
r.Use(s.MiddlewareMetrics)
|
||||||
r.Use(slogchi.NewWithConfig(slog.Default(), slogchi.Config{
|
r.Use(slogchi.NewWithConfig(slog.Default(), slogchi.Config{
|
||||||
DefaultLevel: slog.LevelInfo,
|
DefaultLevel: slog.LevelInfo,
|
||||||
ClientErrorLevel: slog.LevelWarn, // 400–499 → Warn
|
ClientErrorLevel: slog.LevelWarn, // 400–499 → Warn
|
||||||
@@ -93,6 +94,7 @@ func (s *Server) Run() error {
|
|||||||
r.Use(s.AuthMiddleware)
|
r.Use(s.AuthMiddleware)
|
||||||
|
|
||||||
h := api.HandlerFromMux(s, r)
|
h := api.HandlerFromMux(s, r)
|
||||||
|
r.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Handler: h,
|
Handler: h,
|
||||||
@@ -121,7 +123,7 @@ func (s *Server) Run() error {
|
|||||||
|
|
||||||
func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/auth/register" || r.URL.Path == "/auth/login" {
|
if r.URL.Path == "/auth/register" || r.URL.Path == "/auth/login" || r.URL.Path == "/metrics" {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,9 +263,99 @@ func (s *Server) GetMe(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
s.JSON(w, r, http.StatusOK, user, "ok")
|
s.JSON(w, r, http.StatusOK, user, "ok")
|
||||||
}
|
}
|
||||||
func (s *Server) UpdateMe(w http.ResponseWriter, r *http.Request) {}
|
func (s *Server) UpdateMe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) GetMyTrips(w http.ResponseWriter, r *http.Request) {}
|
var req api.UserUpdate
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := storage.GetOne[models.User](ctx, s.DB, "users", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("id", claims.ID))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "user not found", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.FullName != nil {
|
||||||
|
user.FullName = *req.FullName
|
||||||
|
}
|
||||||
|
if req.AvatarUrl != nil {
|
||||||
|
user.AvatarURL = *req.AvatarUrl
|
||||||
|
}
|
||||||
|
if req.Password != nil {
|
||||||
|
if req.CurrentPassword == nil {
|
||||||
|
s.JSON(w, r, http.StatusBadRequest, "currentPassword is required", RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(*req.CurrentPassword)); err != nil {
|
||||||
|
s.JSON(w, r, http.StatusBadRequest, "wrong current password", RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "bcrypt failed", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.PasswordHash = string(hash)
|
||||||
|
}
|
||||||
|
user.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := storage.Update(ctx, "users", *user, s.DB, func(sb *sqlbuilder.UpdateBuilder) {
|
||||||
|
sb.Where(sb.Equal("id", claims.ID))
|
||||||
|
}); err != nil {
|
||||||
|
slog.ErrorContext(ctx, "update user failed", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.JSON(w, r, http.StatusOK, user, RespSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetMyTrips(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, ok := ctx.Value("user").(*Claims)
|
||||||
|
if !ok {
|
||||||
|
slog.ErrorContext(ctx, "Error while casting claims")
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if claims.Role != "driver" {
|
||||||
|
s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := storage.GetOne[models.Driver](ctx, s.DB, "drivers", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("user_id", claims.ID))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "error while getting driver", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orders, err := storage.GetAll[models.Order](ctx, "orders", s.DB, func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("driver_id", driver.ID))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "Error while getting orders", slog.String("error", err.Error()))
|
||||||
|
s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.JSON(w, r, http.StatusOK, orders, RespSuccess)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *models.User) {
|
func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *models.User) {
|
||||||
access, err := s.generateAccessToken(user, s.Config.RedisAccessTokenDur())
|
access, err := s.generateAccessToken(user, s.Config.RedisAccessTokenDur())
|
||||||
|
|||||||
@@ -24,8 +24,17 @@ type OrderService struct {
|
|||||||
db *pgxpool.Pool
|
db *pgxpool.Pool
|
||||||
config config.Config
|
config config.Config
|
||||||
}
|
}
|
||||||
|
type OrderServicer interface {
|
||||||
|
CreateOrder(ctx context.Context, req api.OrderCreate, userID uuid.UUID) (*CreateOrderResult, error)
|
||||||
|
ListOrders(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error)
|
||||||
|
GetOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) (*models.Order, error)
|
||||||
|
CancelOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error
|
||||||
|
UpdateOrderStatus(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error)
|
||||||
|
GetOrdersReport(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error)
|
||||||
|
}
|
||||||
|
|
||||||
var ErrForbidden = errors.New("forbidden")
|
var ErrForbidden = errors.New("forbidden")
|
||||||
|
var ErrCannotCancel = errors.New("cant cancel")
|
||||||
|
|
||||||
type CreateOrderResult struct {
|
type CreateOrderResult struct {
|
||||||
Order models.Order
|
Order models.Order
|
||||||
@@ -169,7 +178,7 @@ func (s *OrderService) CreateOrder(ctx context.Context, req api.OrderCreate, use
|
|||||||
func (s *OrderService) ListOrders(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error) {
|
func (s *OrderService) ListOrders(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error) {
|
||||||
var driverID *uuid.UUID
|
var driverID *uuid.UUID
|
||||||
if role == "driver" {
|
if role == "driver" {
|
||||||
driver, err := storage.GetOne[models.Driver](ctx, s.db, "driver", func(sb *sqlbuilder.SelectBuilder) {
|
driver, err := storage.GetOne[models.Driver](ctx, s.db, "drivers", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
sb.Where(sb.EQ("user_id", userID))
|
sb.Where(sb.EQ("user_id", userID))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -177,6 +186,7 @@ func (s *OrderService) ListOrders(ctx context.Context, userID uuid.UUID, role st
|
|||||||
}
|
}
|
||||||
driverID = &driver.ID
|
driverID = &driver.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
orders, err := storage.GetAll[models.Order](ctx, "orders", s.db, func(sb *sqlbuilder.SelectBuilder) {
|
orders, err := storage.GetAll[models.Order](ctx, "orders", s.db, func(sb *sqlbuilder.SelectBuilder) {
|
||||||
switch role {
|
switch role {
|
||||||
case "client":
|
case "client":
|
||||||
@@ -192,6 +202,7 @@ func (s *OrderService) ListOrders(ctx context.Context, userID uuid.UUID, role st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list orders: %w", err)
|
return nil, fmt.Errorf("list orders: %w", err)
|
||||||
}
|
}
|
||||||
@@ -201,14 +212,111 @@ func (s *OrderService) GetOrder(ctx context.Context, id uuid.UUID, userID uuid.U
|
|||||||
order, err := storage.GetOne[models.Order](ctx, s.db, "orders", func(sb *sqlbuilder.SelectBuilder) {
|
order, err := storage.GetOne[models.Order](ctx, s.db, "orders", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
sb.Where(sb.EQ("id", id))
|
sb.Where(sb.EQ("id", id))
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if role == "client" && (order.CreatedByID == nil || *order.CreatedByID != userID) {
|
if role == "client" && (order.CreatedByID == nil || *order.CreatedByID != userID) {
|
||||||
return nil, ErrForbidden
|
return nil, ErrForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
return order, nil
|
return order, nil
|
||||||
}
|
}
|
||||||
func (s *OrderService) CancelOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error
|
func (s *OrderService) CancelOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error {
|
||||||
func (s *OrderService) UpdateOrderStatus(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error)
|
order, err := storage.GetOne[models.Order](ctx, s.db, "orders", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
func (s *OrderService) GetOrdersReport(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error)
|
sb.Where(sb.EQ("id", id))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if role == "client" && (order.CreatedByID == nil || *order.CreatedByID != userID) {
|
||||||
|
return ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Status != "pending" {
|
||||||
|
return ErrCannotCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Status = "cancelled"
|
||||||
|
if err := storage.Update(ctx, "orders", order, s.db, func(sb *sqlbuilder.UpdateBuilder) {
|
||||||
|
sb.Where(sb.EQ("id", id))
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *OrderService) UpdateOrderStatus(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error) {
|
||||||
|
if role == "client" {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := storage.GetOne[models.Order](ctx, s.db, "orders", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("id", id))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == "driver" {
|
||||||
|
driver, err := storage.GetOne[models.Driver](ctx, s.db, "drivers", func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
sb.Where(sb.EQ("user_id", userID))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get driver: %w", err)
|
||||||
|
}
|
||||||
|
if order.DriverID == nil || *order.DriverID != driver.ID {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
if req.Status != api.OrderStatusUpdateStatusInTransit && req.Status != api.OrderStatusUpdateStatusDelivered {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !req.Status.Valid() {
|
||||||
|
return nil, fmt.Errorf("invalid status: %s", req.Status)
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
order.Status = string(req.Status)
|
||||||
|
if req.Status == api.OrderStatusUpdateStatusAssigned {
|
||||||
|
if req.DriverId == nil {
|
||||||
|
return nil, fmt.Errorf("driverId required when assigned")
|
||||||
|
}
|
||||||
|
order.DriverID = req.DriverId
|
||||||
|
order.AssignedAt = &now
|
||||||
|
}
|
||||||
|
if req.Status == api.OrderStatusUpdateStatusDelivered {
|
||||||
|
order.DeliveredAt = &now
|
||||||
|
}
|
||||||
|
if err := storage.Update(ctx, "orders", *order, s.db, func(sb *sqlbuilder.UpdateBuilder) {
|
||||||
|
sb.Where(sb.EQ("id", id))
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("update order: %w", err)
|
||||||
|
}
|
||||||
|
return order, nil
|
||||||
|
}
|
||||||
|
func (s *OrderService) GetOrdersReport(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error) {
|
||||||
|
if role != "manager" {
|
||||||
|
return nil, ErrForbidden
|
||||||
|
}
|
||||||
|
orders, err := storage.GetAll[models.Order](ctx, "orders", s.db, func(sb *sqlbuilder.SelectBuilder) {
|
||||||
|
if params.Status != nil {
|
||||||
|
sb.Where(sb.EQ("status", string(*params.Status)))
|
||||||
|
}
|
||||||
|
if params.DriverId != nil {
|
||||||
|
sb.Where(sb.EQ("driver_id", *params.DriverId))
|
||||||
|
}
|
||||||
|
if params.WarehouseId != nil {
|
||||||
|
sb.Where(sb.EQ("origin_warehouse_id", *params.WarehouseId))
|
||||||
|
}
|
||||||
|
if params.From != nil {
|
||||||
|
sb.Where(sb.GE("created_at", params.From.Time))
|
||||||
|
}
|
||||||
|
if params.To != nil {
|
||||||
|
sb.Where(sb.LE("created_at", params.To.Time))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get orders report: %w", err)
|
||||||
|
}
|
||||||
|
return orders, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type Querier interface {
|
|||||||
var ErrNotFound = errors.New("not found")
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
func GetAll[T any](ctx context.Context, table string, db Querier, opts ...func(*sqlbuilder.SelectBuilder)) ([]T, error) {
|
func GetAll[T any](ctx context.Context, table string, db Querier, opts ...func(*sqlbuilder.SelectBuilder)) ([]T, error) {
|
||||||
sb := sqlbuilder.NewStruct(new(T)).SelectFrom(table)
|
sb := sqlbuilder.NewStruct(new(T)).For(sqlbuilder.PostgreSQL).SelectFrom(table)
|
||||||
|
|
||||||
sb.From(table)
|
sb.From(table)
|
||||||
|
|
||||||
|
|||||||
431
tests/order_handler_test.go
Normal file
431
tests/order_handler_test.go
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anxi0uz/logiflow/internal/api"
|
||||||
|
"github.com/anxi0uz/logiflow/internal/handler"
|
||||||
|
"github.com/anxi0uz/logiflow/internal/models"
|
||||||
|
"github.com/anxi0uz/logiflow/internal/services"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Mock ---
|
||||||
|
|
||||||
|
type mockOrderService struct {
|
||||||
|
createOrder func(ctx context.Context, req api.OrderCreate, userID uuid.UUID) (*services.CreateOrderResult, error)
|
||||||
|
listOrders func(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error)
|
||||||
|
getOrder func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) (*models.Order, error)
|
||||||
|
cancelOrder func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error
|
||||||
|
updateOrderStatus func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error)
|
||||||
|
getOrdersReport func(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockOrderService) CreateOrder(ctx context.Context, req api.OrderCreate, userID uuid.UUID) (*services.CreateOrderResult, error) {
|
||||||
|
return m.createOrder(ctx, req, userID)
|
||||||
|
}
|
||||||
|
func (m *mockOrderService) ListOrders(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error) {
|
||||||
|
return m.listOrders(ctx, userID, role, params)
|
||||||
|
}
|
||||||
|
func (m *mockOrderService) GetOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) (*models.Order, error) {
|
||||||
|
return m.getOrder(ctx, id, userID, role)
|
||||||
|
}
|
||||||
|
func (m *mockOrderService) CancelOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error {
|
||||||
|
return m.cancelOrder(ctx, id, userID, role)
|
||||||
|
}
|
||||||
|
func (m *mockOrderService) UpdateOrderStatus(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error) {
|
||||||
|
return m.updateOrderStatus(ctx, id, userID, role, req)
|
||||||
|
}
|
||||||
|
func (m *mockOrderService) GetOrdersReport(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error) {
|
||||||
|
return m.getOrdersReport(ctx, role, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
|
||||||
|
func newTestServer(svc services.OrderServicer) *handler.Server {
|
||||||
|
return &handler.Server{
|
||||||
|
OrderSerice: svc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withClaims(r *http.Request, id uuid.UUID, role string) *http.Request {
|
||||||
|
claims := &handler.Claims{ID: id, Role: role}
|
||||||
|
ctx := context.WithValue(r.Context(), "user", claims)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonBody(t *testing.T, v any) *bytes.Buffer {
|
||||||
|
t.Helper()
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal body: %v", err)
|
||||||
|
}
|
||||||
|
return bytes.NewBuffer(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CreateOrder ---
|
||||||
|
|
||||||
|
func TestCreateOrder_Success(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
createOrder: func(_ context.Context, _ api.OrderCreate, _ uuid.UUID) (*services.CreateOrderResult, error) {
|
||||||
|
return &services.CreateOrderResult{
|
||||||
|
Order: models.Order{ID: orderID, Status: "pending"},
|
||||||
|
Route: models.Route{ID: uuid.New(), OrderID: orderID},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/orders", jsonBody(t, api.OrderCreate{DestinationAddress: "Moscow"}))
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CreateOrder(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusCreated {
|
||||||
|
t.Errorf("expected 201, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateOrder_InvalidBody(t *testing.T) {
|
||||||
|
s := newTestServer(&mockOrderService{})
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/orders", bytes.NewBufferString("not json"))
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CreateOrder(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Errorf("expected 400, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateOrder_ServiceError(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
createOrder: func(_ context.Context, _ api.OrderCreate, _ uuid.UUID) (*services.CreateOrderResult, error) {
|
||||||
|
return nil, errors.New("geocode failed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/orders", jsonBody(t, api.OrderCreate{DestinationAddress: "Moscow"}))
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CreateOrder(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ListOrders ---
|
||||||
|
|
||||||
|
func TestListOrders_Success(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
listOrders: func(_ context.Context, _ uuid.UUID, _ string, _ api.ListOrdersParams) ([]models.Order, error) {
|
||||||
|
return []models.Order{{ID: uuid.New(), Status: "pending"}}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/orders", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.ListOrders(w, r, api.ListOrdersParams{})
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListOrders_ServiceError(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
listOrders: func(_ context.Context, _ uuid.UUID, _ string, _ api.ListOrdersParams) ([]models.Order, error) {
|
||||||
|
return nil, errors.New("db error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/orders", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.ListOrders(w, r, api.ListOrdersParams{})
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GetOrder ---
|
||||||
|
|
||||||
|
func TestGetOrder_Success(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||||
|
return &models.Order{ID: orderID, Status: "pending"}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOrder_Forbidden(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||||
|
return nil, services.ErrForbidden
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusForbidden {
|
||||||
|
t.Errorf("expected 403, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOrder_ServiceError(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||||
|
return nil, errors.New("db error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CancelOrder ---
|
||||||
|
|
||||||
|
func TestCancelOrder_Success(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CancelOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelOrder_CannotCancel(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||||
|
return services.ErrCannotCancel
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CancelOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusConflict {
|
||||||
|
t.Errorf("expected 409, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelOrder_Forbidden(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||||
|
return services.ErrForbidden
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CancelOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusForbidden {
|
||||||
|
t.Errorf("expected 403, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelOrder_ServiceError(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||||
|
return errors.New("db error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.CancelOrder(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- UpdateOrderStatus ---
|
||||||
|
|
||||||
|
func TestUpdateOrderStatus_Success(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||||
|
return &models.Order{ID: orderID, Status: "assigned"}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusAssigned})
|
||||||
|
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.UpdateOrderStatus(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateOrderStatus_Forbidden(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||||
|
return nil, services.ErrForbidden
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusAssigned})
|
||||||
|
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.UpdateOrderStatus(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusForbidden {
|
||||||
|
t.Errorf("expected 403, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateOrderStatus_InvalidBody(t *testing.T) {
|
||||||
|
s := newTestServer(&mockOrderService{})
|
||||||
|
r := httptest.NewRequest(http.MethodPatch, "/orders/"+uuid.New().String()+"/status", bytes.NewBufferString("bad json"))
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.UpdateOrderStatus(w, r, uuid.New())
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Errorf("expected 400, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateOrderStatus_ServiceError(t *testing.T) {
|
||||||
|
orderID := uuid.New()
|
||||||
|
svc := &mockOrderService{
|
||||||
|
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||||
|
return nil, errors.New("db error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusInTransit})
|
||||||
|
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||||
|
r = withClaims(r, uuid.New(), "driver")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.UpdateOrderStatus(w, r, orderID)
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GetOrdersReport ---
|
||||||
|
|
||||||
|
func TestGetOrdersReport_Success(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||||
|
return []models.Order{{ID: uuid.New(), Status: "delivered"}}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOrdersReport_Forbidden(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||||
|
return nil, services.ErrForbidden
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "client")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||||
|
|
||||||
|
if w.Code != http.StatusForbidden {
|
||||||
|
t.Errorf("expected 403, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOrdersReport_ServiceError(t *testing.T) {
|
||||||
|
svc := &mockOrderService{
|
||||||
|
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||||
|
return nil, errors.New("db error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s := newTestServer(svc)
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||||
|
r = withClaims(r, uuid.New(), "manager")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||||
|
|
||||||
|
if w.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("expected 500, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user