REST-сервис для учёта онлайн-подписок пользователей: CRUDL по записям, агрегат суммарной стоимости за календарный период. Цены — целые рубли, даты подписки — месяц и год.
- Go 1.26
- PostgreSQL
- Swagger
- Docker Compose
- Поднимите PostgreSQL и создайте БД (по умолчанию совпадает с
config.yaml: пользователь/пароль/имяsubscribe). - Примените миграции из каталога
migrations/(илиtask migrate:local, если установлен CLImigrate). - При необходимости скопируйте
.env.exampleв.envи задайте переменные (см. раздел «Конфигурация»). - Запуск:
task runСервер слушает адрес из config.yaml (по умолчанию :8080).
docker compose up --buildили:
task docker:upAPI будет на http://localhost:8080. В образе используется config.docker.yaml.
PostgreSQL с хоста подключается к 5433 порту (внутри compose сервисы по-прежнему используют порт 5432). Так не пересекается с локальным Postgres на :5432.
Путь к YAML задаётся переменной окружения SUBSCRIBE_CONFIG (в compose: /app/config.yaml). Если не задана, читается config.yaml из текущей директории.
| Переменная | Назначение |
|---|---|
SUBSCRIBE_SERVER_HOST, SUBSCRIBE_SERVER_PORT |
HTTP |
SUBSCRIBE_DB_HOST, SUBSCRIBE_DB_PORT, SUBSCRIBE_DB_USER, SUBSCRIBE_DB_PASSWORD, SUBSCRIBE_DB_NAME, SUBSCRIBE_DB_SSLMODE |
PostgreSQL |
SUBSCRIBE_LOG_LEVEL |
debug, info, warn, error |
Во всех полях и query-параметрах, где указан месяц и год, используется строка MM-YYYY, например 07-2025.
http://localhost:8080/api/v1
После запуска сервера:
- Swagger UI:
http://localhost:8080/swagger/index.html - Статические файлы:
docs/swagger.json,docs/swagger.yaml
Перегенерация осуществляется командой
task swagger| Метод | Путь | Описание |
|---|---|---|
| GET | /health |
Тело ok, статус 200 |
| Поле | Тип | Описание |
|---|---|---|
id |
string (UUID) | Идентификатор записи |
service_name |
string | Название сервиса |
price |
integer | Стоимость месяца в рублях (≥ 0) |
user_id |
string (UUID) | Пользователь (внешний id) |
start_date |
string | Начало подписки, MM-YYYY |
end_date |
string или отсутствует | Конец подписки, MM-YYYY; если нет — подписка без даты окончания |
created_at, updated_at |
string | Временные метки |
Тело запроса
| Поле | Обязательное | Описание |
|---|---|---|
service_name |
да | Непустая строка |
price |
да | Целое ≥ 0 |
user_id |
да | UUID |
start_date |
да | MM-YYYY |
end_date |
нет | null, пустая строка или отсутствие поля — без даты окончания; иначе валидный MM-YYYY, не раньше start_date |
Пример запроса
POST /api/v1/subscriptions HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"service_name": "Yandex Plus",
"price": 400,
"user_id": "60601fee-2bf1-4721-ae6f-7636e79a0cba",
"start_date": "07-2025"
}Пример ответа
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"service_name": "Yandex Plus",
"price": 400,
"user_id": "60601fee-2bf1-4721-ae6f-7636e79a0cba",
"start_date": "07-2025",
"created_at": "2026-04-03T12:00:00Z",
"updated_at": "2026-04-03T12:00:00Z"
}{id} — UUID подписки.
Частичное обновление: передавайте только поля, которые нужно изменить. Поле с null или отсутствующее в JSON обычно означает «не менять».
Пример
PUT /api/v1/subscriptions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{
"price": 500,
"end_date": "12-2026"
}Успех: 204 No Content, без тела.
Query-параметры (все необязательны):
| Параметр | Описание |
|---|---|
user_id |
UUID пользователя |
service_name |
Подстрока для поиска; в БД используется ILIKE (без учёта регистра) |
limit |
Размер страницы (если не задан — 20, максимум 100) |
offset |
Смещение (≥ 0) |
Пример
GET /api/v1/subscriptions?user_id=60601fee-2bf1-4721-ae6f-7636e79a0cba&service_name=Plus&limit=10&offset=0 HTTP/1.1
Host: localhost:8080{
"items": [ { "...": "Subscription" } ],
"total": 42
}Суммируется стоимость по всем календарным месяцам периода [from, to], пересекающимся с подпиской (с учётом опциональной даты окончания).
Query-параметры
| Параметр | Обязательный | Описание |
|---|---|---|
from |
да | Начало периода, MM-YYYY |
to |
да | Конец периода, MM-YYYY (from не позже to) |
user_id |
нет | UUID — только подписки пользователя |
service_name |
нет | фильтр по названию сервиса |
Пример
GET /api/v1/subscriptions/total?from=01-2025&to=03-2025&user_id=60601fee-2bf1-4721-ae6f-7636e79a0cba HTTP/1.1
Host: localhost:8080{
"total_rub": 1200,
"from": "01-2025",
"to": "03-2025"
}# Health
curl -s http://localhost:8080/health
# Создание
curl -s -X POST http://localhost:8080/api/v1/subscriptions \
-H "Content-Type: application/json" \
-d '{"service_name":"Yandex Plus","price":400,"user_id":"60601fee-2bf1-4721-ae6f-7636e79a0cba","start_date":"07-2025"}'
# Список
curl -s "http://localhost:8080/api/v1/subscriptions?limit=5"
# Total
curl -s "http://localhost:8080/api/v1/subscriptions/total?from=01-2025&to=12-2025"
# Удаление (подставьте UUID)
curl -s -X DELETE http://localhost:8080/api/v1/subscriptions/00000000-0000-0000-0000-000000000000