← Все проекты уровня 7
Уровень 7 · Сервисы с базой данныхВариант C

Личные финансы

Учёт операций между счетами в одной валюте с точной арифметикой в минимальных денежных единицах.

Техническое задание

Модель данных и назначение

Создайте REST API личных финансов на Go с PostgreSQL, миграциями и ручным SQL без ORM. Account содержит id, name, currency, balance_minor, active и timestamps. Category содержит id, name, kind (income или expense) и timestamps. Operation хранит id, type (income, expense, transfer), сумму в целых копейках RUB, исходный и целевой счёт, категорию для обычной операции, description и occurred_at. Сервис поддерживает только RUB, все счета имеют currency=RUB. amountMinor и balanceMinor выражены в копейках; максимальная сумма операции и максимальный баланс каждого счёта равны 1,000,000,000,000 копеек. Float запрещён.

API, проверки и эксплуатация

POST /api/accounts принимает name; создаёт активный счёт с нулевым балансом (201). POST /api/categories принимает name и kind, отвечает 201; name уникален без учёта регистра после trim. GET обоих каталогов использует cursor и limit от 1 до 100, сортировку по id и документирует поля пагинации. POST /api/operations принимает type, amountMinor, accountId, categoryId для income/expense, sourceAccountId и targetAccountId для transfer, description и occurredAt, при успехе отвечает 201. Для обычной операции требуется ровно один accountId и подходящая категория. Для перевода требуются два разных активных счёта RUB; categoryId запрещён. Неизвестные и взаимоисключающие поля дают 400. Сумма должна быть целым числом от 1 до 1,000,000,000,000 копеек; баланс каждого счёта после операции должен быть от 0 до 1,000,000,000,000 копеек.

Каждая операция и связанные изменения балансов выполняются одной транзакцией. Перевод списывает с одного счёта и зачисляет на другой атомарно, никогда не создавая промежуточное состояние с частичным переводом. Расход при недостатке денег возвращает 409 insufficient_funds без операции; доход при превышении лимита также не записывается. Операция неизменяема после создания. GET /api/accounts/{id} возвращает актуальный баланс либо 404. GET /api/operations поддерживает from, to, accountId, categoryId, type, cursor и limit. from и to — RFC 3339 UTC, from включительно, to исключительно; требуется from<to. Порядок occurred_at DESC,id DESC стабилен, ответ содержит страницу и cursor следующей страницы. GET /api/operations/{id} возвращает операцию либо 404. 400 означает ошибочный ввод, 404 — отсутствующую сущность, 409 — конфликт бизнес-инварианта. Контекстные SQL-запросы имеют тайм-аут и отменяются вместе с запросом. Индексы покрывают фильтры истории, N+1 исключён. Храните суммы в bigint и ограничивайте арифметику. Логи не содержат описания операций или финансовых сумм; ошибки клиенту не раскрывают SQL.

Структура программы

Код разделён на небольшие internal-пакеты: internal/httpapi содержит обработчики и DTO, internal/finance — операции RUB, лимиты и переводы, internal/postgres — транзакционное хранение. main.go связывает пакеты и управляет сервером. Домен не импортирует транспортные типы; расчёты тестируются без HTTP и БД.

Критерии готовности

Ожидаемый результат

Поставка

  • Запускаемый Go REST API и PostgreSQL-миграции хранят счета, категории и операции с суммами в целых копейках RUB.
  • README описывает лимит 1e12 копеек, валюту, типы операций, временные фильтры и ошибки; тесты используют локальную базу.

Приёмка

  • Доход на 1250 копеек увеличивает balanceMinor ровно на 1250; ответ содержит целые числа и currency=RUB.
  • Перевод атомарно уменьшает один счёт и увеличивает другой на одну сумму.
  • Расход сверх остатка и баланс выше 1e12 получают 409, не создавая операцию.
  • Десятичная сумма, ноль и перевод на тот же счёт дают 400 без изменения балансов.
  • from включает граничную операцию, to исключает её; невалидный диапазон времени возвращает 400.
  • README показывает связи пакетов; денежные правила тестируются без HTTP/БД, транзакции — отдельными SQL-сценариями.