Система управления балансами пользователей с аудитом через историю транзакций
- Docker & Docker Compose
- Node.js 18+
- npm
npm installdocker-compose up -d postgres redis# Применить миграции
DATABASE_URL="postgresql://user:password@localhost:5432/dbname?schema=public" \
npx prisma migrate deploy
# Заполнить тестовыми данными
DATABASE_URL="postgresql://user:password@localhost:5432/dbname?schema=public" \
npm run db:seedDATABASE_URL="postgresql://user:password@localhost:5432/dbname?schema=public" \
npm run start:devПриложение запустится на http://localhost:3050
Откройте index.html в браузере для визуального тестирования:
open index.html # macOS
# или просто откройте файл в браузереhttp://localhost:3050 перед использованием веб-интерфейса.
Веб-интерфейс предоставляет:
- 🎨 Компактный современный UI с двухколоночным layout
- 👥 Выбор пользователя из dropdown (загружается через API)
- 🛍️ Выбор продукта из dropdown (загружается через API)
- 📊 Таблицу истории транзакций с автоматической загрузкой и прокруткой
- 🔄 Автоматическое обновление истории и баланса при смене пользователя
- 🔒 Автоматическое скрытие кнопок Credit/Debit для SERVICE account
- 💼 Отображение бейджа "SERVICE ACCOUNT" для id=1
- ⚙️ Настройку параметров (baseUrl)
- ✅ Цветовую индикацию успеха/ошибок
- 🔄 CORS поддержка (origin: true)
Откройте api-test.http в VS Code (требуется расширение REST Client) и выполняйте запросы.
GET /usersВозвращает список всех пользователей системы (исключая SERVICE account).
Примечание: SERVICE account (id=1) не возвращается через API, но доступен в веб-интерфейсе через хардкод.
POST /accounts/user/:userId/credit
Content-Type: application/json
{
"amount": 500.00
}POST /accounts/user/:userId/debit
Content-Type: application/json
{
"amount": 50.00
}POST /accounts/user/:userId/debit
Content-Type: application/json
{
"amount": 100.00,
"productId": 1
}GET /accounts/user/:userId/auditВозвращает:
{
"accountId": 2,
"currentBalance": "10000",
"calculatedBalance": "10000",
"difference": "0",
"isValid": true
}GET /accounts/user/:userId/historyGET /accounts/user/:userId- NestJS 11 - Backend framework
- PostgreSQL 16 - База данных
- Redis 7 - Кеширование
- Prisma 6 - ORM
- TypeScript - Язык программирования
// account.balance - это кеш
// SUM(transactions) - это source of truth
const calculatedBalance = SUM(incoming) - SUM(outgoing);
if (|account.balance - calculatedBalance| > 0.01) {
throw new Error('Balance audit failed');
}Асинхронный аудит:
- Audit запускается автоматически после каждой транзакции (async)
- Не блокирует пользовательские операции
- Ошибки логируются для мониторинга
- Для ручной проверки:
GET /accounts/user/:userId/audit
- Все изменения балансов в
retryableTransaction - Автоматический retry при deadlock/serialization errors
- До 3 попыток с интервалом 500ms
- Users: TTL 5 минут
- Products: TTL 15 минут
- Graceful fallback к PostgreSQL
- id (1 = SERVICE, 2+ = пользователи)
- role (SERVICE | USER)
- userId (1:1 с User)
- balance (кеш, начальный баланс: SERVICE=$1M, USER=$0)
- incoming (source of truth)
- outgoing (source of truth)
- currency (всегда USD)
Важно:
- Service account (id=1): $1,000,000 - не аудируется через историю
- User accounts: $0 начальный баланс - используйте CREDIT для пополнения
- type (CREDIT | DEBIT)
- state (PENDING | HOLD | COMPLETED | FAILED)
- accountAId → accountBId
- amountOut, amountIn
- createdAt, completedAt
- title, price
- active
- productId, buyerUserId, sellerUserId
- totalPrice
- status (PAID)
Service Account (id=1) → User Account
Используется для внешних поступлений средств.
User Account → Service Account (id=1)
Используется для покупок и списаний. Проверяет достаточность средств.
Виртуальный счет (userId=1) для всех внешних операций. В реальной системе это может быть:
- Внешние платежи (карты, PayPal)
- Выплаты продавцам
- Комиссии системы
helltv/
├── prisma/
│ ├── schema.prisma # Модели БД
│ ├── migrations/ # Миграции
│ └── seed.ts # Тестовые данные
├── src/
│ ├── modules/
│ │ ├── redis/ # Redis кеш
│ │ ├── prisma/ # БД + retryableTransaction
│ │ ├── users/ # Пользователи (с кешем)
│ │ ├── products/ # Продукты (с кешем)
│ │ ├── accounts/ # Счета + API endpoints
│ │ ├── transactions/ # Транзакции (CORE)
│ │ ├── orders/ # Заказы
│ │ └── events/ # Event listeners
│ ├── common/
│ │ ├── enums/ # TransactionType, State
│ │ └── dto/ # DTOs для валидации
│ └── config/
│ └── config.ts # Конфигурация
├── memory-bank/ # Документация проекта
├── api-test.http # HTTP тесты
├── index.html # Vue.js веб-интерфейс для тестирования
└── docker-compose.yaml # PostgreSQL + Redis
npx prisma migrate dev --name migration_namenpx prisma studionpx prisma migrate resetnpm run buildПолная документация в папке memory-bank/:
- Описание проекта и требования
- Бизнес-процессы и архитектурные паттерны
- Технические детали и текущее состояние
- Откройте index.html в браузере
- Проверьте баланс: Нажмите "Выполнить" в карточке "Get Balance"
- Пополните баланс: Введите сумму (например, 500) и нажмите "Выполнить" в "Credit Balance"
- Купите продукт: Нажмите "Выполнить" в "Purchase Product"
- Проверьте аудит: Нажмите "Выполнить" в "Audit Balance"
- Посмотрите историю: Нажмите "Выполнить" в "Transaction History"
# 1. Пополнить баланс на $500
POST /accounts/user/2/credit { amount: 500 }
# 2. Купить продукт за $100
POST /orders/create { userId: 2, productId: 1 }
# 3. Проверить баланс (должен быть $400)
GET /accounts/user/2
# 4. Проверить аудит
GET /accounts/user/2/audit# Попытка списать больше чем есть
POST /accounts/user/2/debit { amount: 999999 }
# Ответ: 400 Bad Request
{
"statusCode": 400,
"message": "Insufficient balance. Available: 0, Required: 999999"
}# Несколько операций
POST /accounts/user/2/credit { amount: 1000 }
POST /accounts/user/2/debit { amount: 200 }
POST /accounts/user/2/debit { amount: 300 }
# Получить историю
GET /accounts/user/2/history
# Проверить консистентность
GET /accounts/user/2/audit# 1. Убедитесь что сервер запущен
npm run start:dev
# 2. Проверьте что CORS настроен (main.ts должен содержать app.enableCors())
# 3. Перезапустите сервер после изменений в main.ts
# 4. Проверьте URL в веб-интерфейсе (по умолчанию: http://localhost:3050)
# 5. Откройте консоль браузера (F12) для просмотра ошибок# Проверить что PostgreSQL запущен
docker-compose ps postgres
# Проверить логи
docker-compose logs postgres
# Перезапустить
docker-compose restart postgres# Проверить что Redis запущен
docker-compose ps redis
# Приложение работает и без Redis (fallback к БД)# Сбросить и применить заново
npx prisma migrate reset --forceMIT
Pull requests приветствуются!
Разработано с ❤️ для демонстрации правильной работы с финансовыми транзакциями