added AGENTS.md and docs for BalanceServer

This commit is contained in:
wt
2026-09-08 13:11:53 +07:00
parent 684ac58b73
commit 2f891c9503
10 changed files with 1022 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
# AGENTS.md
## Назначение
**BalanceServer** — серверная часть приложения Balance, используемая iOS/macOS/watchOS и Android-клиентами.
Этот файл является operational guide для AI-агентов и разработчиков. При изменениях сначала изучай существующий код и сохраняй текущий API-контракт клиентов.
## Быстрая карта проекта
Проект написан на Go 1.25.0.
Основные Go-файлы:
```text
- internal/database/database.go
- internal/config/config.go
- internal/auth/tokens.go
- internal/auth/password.go
- internal/httpapi/auth_handlers.go
- internal/httpapi/server.go
- internal/httpapi/sync_handler.go
- internal/httpapi/web.go
- internal/httpapi/server_test.go
```
Количество Go-файлов: **9**.
Обнаруженные технологии/интеграции: SQLite, Docker.
## Правила изменений
### 1. Не ломать клиентский контракт
Сервер обслуживает несколько клиентов:
- Apple Balance;
- Android Balance.
Изменение JSON-полей, HTTP methods, endpoint paths, auth semantics или sync semantics считается breaking change, даже если сервер компилируется.
Перед изменением API ищи использование endpoint/field в server code и документации клиентов.
### 2. Sync — центральный контракт
Синхронизация должна оставаться:
- детерминированной;
- идемпотентной при повторной доставке;
- устойчивой к повторным запросам;
- корректной при нескольких устройствах одного пользователя.
Не меняй правила cursor/sequence/update timestamps без обновления `docs/SYNC.md`.
### 3. Аутентификация
Никогда не:
- логируй пароль;
- логируй access/refresh token;
- возвращай секреты в ошибках;
- храни plaintext passwords.
При изменении auth обязательно проверь:
- регистрацию;
- login;
- refresh;
- logout;
- истечение токенов;
- авторизацию sync endpoint.
### 4. База данных
Любое изменение schema должно иметь:
- migration strategy;
- обратную совместимость там, где её требуют старые клиенты;
- проверку индексов для user/entity/id/sequence/update timestamp.
Не удаляй существующие поля только потому, что текущий клиент ими не пользуется.
### 5. Конкурентность
Go server должен быть безопасен при одновременных запросах от нескольких устройств.
Особое внимание:
- генерации sequence;
- выдаче cursor;
- записи изменений;
- refresh-token rotation;
- race между push и pull.
### 6. Ошибки
HTTP API должен возвращать стабильную структуру ошибок. Не отдавай stack traces, SQL errors или внутренние пути клиенту.
Внутренние детали логируй только в безопасном виде.
### 7. Тесты
Перед PR:
```bash
go test ./...
go vet ./...
go build ./...
```
Если проект использует дополнительные проверки/линтеры, запускай их по Makefile/CI.
### 8. Документация
При изменении:
- endpoint → обновить `docs/API.md`;
- sync → `docs/SYNC.md`;
- DB model → `docs/DATA_MODEL.md`;
- deploy/config → `docs/DEPLOYMENT.md`;
- архитектуры → `docs/ARCHITECTURE.md`.
## Checklist
- [ ] Не изменён API случайно.
- [ ] Auth secrets не попадают в logs/errors.
- [ ] Sync остаётся идемпотентным.
- [ ] DB migration учтена.
- [ ] Concurrent requests безопасны.
- [ ] `go test ./...` проходит.
- [ ] `go vet ./...` проходит.
- [ ] Документация обновлена.
+14
View File
@@ -110,3 +110,17 @@ The sync endpoint accepts transactions, custom categories and monthly budgets. T
- Back up the SQLite database and its `-wal`/`-shm` files together, or stop the server before copying the database.
- Changing `BALANCE_JWT_SECRET` invalidates access tokens. Existing refresh sessions remain valid and will receive new access tokens after refresh.
- The in-process authentication limiter allows 30 authentication requests per IP per minute. Use a reverse proxy for stronger public rate limiting and request logging.
## Документация проекта
- [`AGENTS.md`](AGENTS.md) — правила для AI-агентов и разработчиков.
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — архитектура backend.
- [`docs/DATA_MODEL.md`](docs/DATA_MODEL.md) — модель данных.
- [`docs/API.md`](docs/API.md) — HTTP API.
- [`docs/SYNC.md`](docs/SYNC.md) — протокол синхронизации.
- [`docs/SECURITY.md`](docs/SECURITY.md) — требования безопасности.
- [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) — локальная разработка и проверки.
- [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) — deployment/production.
- [`docs/PRODUCT.md`](docs/PRODUCT.md) — функциональная роль сервера.
BalanceServer является общим backend для Apple- и Android-клиентов Balance.
+151
View File
@@ -0,0 +1,151 @@
# HTTP API
## Общие правила
API предназначен для iOS/macOS/watchOS и Android Balance.
Базовый URL задаётся клиентом и зависит от deployment environment.
JSON:
```http
Content-Type: application/json
```
Защищённые endpoints используют:
```http
Authorization: Bearer <access-token>
```
## Authentication
Основные операции:
```text
POST /v1/auth/register
POST /v1/auth/login
POST /v1/auth/logout
POST /v1/auth/refresh
```
### Register
Создаёт пользовательский аккаунт.
Input должен валидироваться сервером. Password никогда не хранится plaintext.
### Login
Проверяет credentials и создаёт authenticated session.
### Refresh
Принимает refresh credential и выдаёт новый access credential согласно текущей server-side session policy.
### Logout
Инвалидирует текущую session/refresh state согласно текущей реализации.
## Synchronization
Основной endpoint:
```text
POST /v1/sync
```
Логический request:
```json
{
"cursor": 123,
"deviceId": "UUID",
"changes": [],
"pull": true
}
```
Точная JSON schema должна оставаться совместимой с `ServerSync.swift` и Android `SyncRepository`.
## Change
Логическая форма:
```json
{
"entity": "transaction",
"id": "UUID",
"deleted": false,
"updatedAt": "2026-09-08T10:00:00Z",
"payload": {}
}
```
Поддерживаемые entity:
```text
transaction
category
budget
```
## Push
Client отправляет локальные изменения.
Server должен:
1. аутентифицировать пользователя;
2. проверить ownership/ID;
3. валидировать payload;
4. применить изменения;
5. создать server change entries;
6. вернуть результат и актуальную sync metadata.
Операция должна быть безопасна при retry.
## Pull
Client передаёт cursor.
Server возвращает изменения после этого cursor, включая deletions.
Если существует pagination, ответ должен содержать понятный признак продолжения и новый cursor/sequence.
## Ошибки
Рекомендуемый контракт:
```json
{
"error": {
"code": "invalid_request",
"message": "Human-readable safe message"
}
}
```
Не возвращать:
- SQL error;
- stack trace;
- password/token;
- filesystem paths;
- внутренние identifiers инфраструктуры.
## HTTP status semantics
Рекомендуемая семантика:
```text
200 OK — успешный request
201 Created — создание ресурса
400 Bad Request — malformed/invalid input
401 Unauthorized — missing/expired auth
403 Forbidden — resource belongs to another user
404 Not Found — resource/endpoint unavailable
409 Conflict — semantic conflict, если endpoint использует такую семантику
429 Too Many Requests — rate limit
500 Internal Server Error — internal failure
```
Конкретные status codes текущей реализации имеют приоритет над этой общей рекомендацией.
+119
View File
@@ -0,0 +1,119 @@
# Архитектура BalanceServer
## Роль сервера
BalanceServer — backend для синхронизации личных финансов между устройствами.
Поток данных:
```text
iOS/macOS/watchOS ─┐
├── HTTPS/JSON API ──> BalanceServer ──> Database
Android ───────────┘
```
Сервер отвечает за:
- регистрацию и авторизацию;
- пользовательские сессии;
- хранение финансовых изменений;
- push/pull synchronization;
- глобальную последовательность изменений;
- удаление через tombstones;
- выдачу изменений начиная с cursor.
## Исходный код
Текущая кодовая база содержит 9 Go-файлов.
Пути:
```text
- internal/auth
- internal/config
- internal/database
- internal/httpapi
```
Конкретные границы package/handler/service/repository нужно сохранять при рефакторинге: перенос кода между слоями не должен менять внешний API.
## HTTP layer
HTTP layer принимает JSON requests от мобильных клиентов.
Каждый handler должен:
1. проверить method/content type;
2. аутентифицировать пользователя, если endpoint защищён;
3. валидировать input;
4. вызвать domain/storage logic;
5. вернуть стабильный JSON response.
## Domain / sync layer
Sync layer является наиболее критичной частью backend.
Он должен разделять:
- изменения, созданные текущим клиентом;
- изменения, которые нужно вернуть клиенту;
- удалённые записи;
- cursor/sequence.
Ключевая модель:
```text
client cursor
│
▼
server change log
│
├── sequence 101
├── sequence 102
├── sequence 103
└── ...
```
Клиент может запросить изменения после своего cursor и затем сохранить новый cursor.
## Persistence
Хранилище должно сохранять как минимум:
- user/account identity;
- credentials/session metadata;
- financial records;
- change metadata;
- deletion/tombstone metadata;
- monotonically increasing sequence, если оно является частью текущего sync protocol.
Конкретные entities и schema документированы в `docs/DATA_MODEL.md`.
## Security boundaries
```text
Internet
│
▼
HTTP server
│
├── authentication
├── authorization
├── input validation
│
▼
domain/sync
│
▼
database
```
Нельзя позволять клиенту выбирать произвольный `user_id` для чужих records.
## Совместимость клиентов
Apple и Android реализации должны видеть одну и ту же семантику данных.
Особенно важно, чтобы:
- enum values были стабильными;
- даты имели единый формат;
- UUID/string IDs не менялись;
- `deleted` semantics были одинаковыми;
- cursor/sequence были монотонными;
- conflict resolution совпадал с клиентской логикой.
+119
View File
@@ -0,0 +1,119 @@
# Модель данных BalanceServer
Ниже описана серверная модель на основании исходного кода проекта. При изменении schema обновляйте этот документ.
## Найденные определения моделей/schema
- internal/database/database.go
- internal/database/database.go
- internal/database/database.go
- internal/config/config.go
- internal/auth/tokens.go
- internal/auth/tokens.go
- internal/httpapi/auth_handlers.go
- internal/httpapi/auth_handlers.go
- internal/httpapi/auth_handlers.go
- internal/httpapi/server.go
- internal/httpapi/server.go
- internal/httpapi/server.go
- internal/httpapi/sync_handler.go
- internal/httpapi/sync_handler.go
- internal/httpapi/sync_handler.go
- internal/httpapi/web.go
- internal/httpapi/server_test.go
- internal/httpapi/server_test.go
- internal/httpapi/server_test.go
## Общие сущности
Сервер должен различать как минимум следующие логические типы синхронизируемых данных, совместимые с клиентами Balance:
```text
transaction
category
budget
```
### Transaction
Логически содержит:
- стабильный ID;
- amount;
- date;
- note;
- category presentation data;
- transaction kind;
- balance-adjustment flag;
- update timestamp;
- принадлежность пользователю.
### Category
Логически содержит:
- стабильный ID;
- name;
- icon/emoji;
- color;
- operation kind;
- creation/update metadata;
- принадлежность пользователю.
### Budget
Логически содержит:
- стабильный ID;
- category information;
- monthly limit;
- month start;
- update timestamp;
- принадлежность пользователю.
## Change metadata
Для sync необходима метаинформация:
```text
user
entity
record ID
deleted
updatedAt
sequence
payload
```
`sequence` используется как серверный порядок изменений, а `updatedAt` — как timestamp версии самой записи.
## Tombstones
Удаление нельзя реализовывать только физическим `DELETE`, если клиентам нужно узнать о нём при следующем pull.
Используется логическое изменение:
```json
{
"entity": "transaction",
"id": "…",
"deleted": true,
"updatedAt": "…"
}
```
После того как tombstone перестанет быть нужен для поддерживаемого sync history window, сервер может применять retention policy. Такая политика должна быть согласована с cursor semantics.
## Invariants
1. Record ID стабилен между sync.
2. Record принадлежит одному пользователю.
3. Клиент не может читать/изменять чужую запись.
4. `sequence` не должен уменьшаться.
5. Повторная доставка одного change не должна повреждать данные.
6. Удаление должно быть видимо клиенту через sync.
7. Timestamps должны храниться в UTC.
8. Enum/string values должны быть совместимы с iOS и Android.
## Миграции
Schema changes должны быть backward-compatible с активными версиями клиентов либо сопровождаться versioned API/миграцией.
Не переименовывайте JSON field без периода совместимости.
+108
View File
@@ -0,0 +1,108 @@
# Deployment
## Компоненты
```text
Internet
│
HTTPS
│
▼
Reverse proxy / Load Balancer
│
▼
BalanceServer
│
▼
Database
```
## Configuration
Все environment variables и flags должны задаваться через deployment environment, а не hard-code в Go.
Перед production deployment составьте список переменных, которые реально читает текущий код:
```text
- docker-compose.yml
- docker-compose.yml
- docker-compose.yml
- go.mod
- README.md
- README.md
- README.md
- Dockerfile
- Dockerfile
- Dockerfile
- internal/database/database.go
- internal/database/database.go
- internal/database/database.go
- internal/config/config.go
- internal/config/config.go
- internal/config/config.go
- internal/auth/tokens.go
- internal/auth/tokens.go
- internal/auth/tokens.go
- internal/auth/password.go
```
## Docker
Если репозиторий содержит Dockerfile/compose, используйте их как canonical deployment recipe.
Не включайте secrets в Docker image.
## Health/readiness
Если проект предоставляет health endpoint, load balancer должен использовать его для readiness.
Database connectivity желательно проверять отдельно от liveness.
## Database migrations
Migration должна выполняться контролируемо:
```text
backup
↓
migration
↓
health check
↓
application rollout
```
Не запускайте destructive migration автоматически без backup/rollback plan.
## Backups
Финансовые данные — пользовательские данные высокой ценности.
Минимум:
- регулярные DB backups;
- retention policy;
- периодическая проверка restore;
- encrypted backup storage.
## Observability
Рекомендуется мониторить:
- HTTP 5xx;
- auth failures;
- sync failures;
- latency;
- DB latency/errors;
- active users/devices;
- size/lag of sync change log;
- disk/storage usage.
## Release checklist
- [ ] Tests green.
- [ ] Database migrations проверены.
- [ ] Backup создан.
- [ ] Secrets injected через secure mechanism.
- [ ] HTTPS включён.
- [ ] Health checks работают.
- [ ] Sync tested with both mobile clients.
- [ ] Rollback plan готов.
+107
View File
@@ -0,0 +1,107 @@
# Разработка и запуск
## Требования
Проект использует Go 1.25.0.
Проверить локальную версию:
```bash
go version
```
## Установка зависимостей
```bash
go mod download
```
## Сборка
```bash
go build ./...
```
## Тесты
```bash
go test ./...
```
С race detector:
```bash
go test -race ./...
```
## Static checks
```bash
go vet ./...
```
Если в проекте есть Makefile/CI, его команды имеют приоритет как canonical build pipeline.
## Локальный запуск
Перед запуском изучите конфигурацию в коде и `.env`/deployment files.
Не коммитьте реальные credentials.
Пример общего workflow:
```bash
go mod download
go test ./...
go build ./...
./<server-binary>
```
Имя binary и обязательные env vars определяются текущей entrypoint/config реализацией.
## Database
Перед первым запуском:
1. поднять требуемую БД;
2. применить migrations, если проект их использует;
3. задать connection settings;
4. проверить health/startup logs.
## Production
Рекомендуемый pipeline:
```text
source
↓
go test ./...
↓
go vet ./...
↓
go build ./...
↓
container/package
↓
deploy
↓
health check
```
Для production нужны:
- TLS termination;
- persistent database;
- backups;
- monitoring;
- log rotation;
- secrets management;
- migration procedure.
## Совместимость
При релизе server проверяйте минимум:
- старый iOS client + новый server;
- новый iOS client + server;
- Android client + server;
- concurrent sync с двух устройств;
- login/refresh после истечения access token;
- delete → pull на другом устройстве.
+62
View File
@@ -0,0 +1,62 @@
# Функциональная документация BalanceServer
## Назначение
BalanceServer является backend для приложения Balance — персонального финансового учёта.
Он не реализует пользовательский UI. Его задача — безопасно хранить и синхронизировать данные между устройствами.
## Пользовательские данные
Синхронизируются:
- финансовые операции;
- пользовательские категории;
- месячные бюджеты;
- удаления этих сущностей.
## Multi-device
Один пользователь может работать с несколькими устройствами.
Пример:
```text
iPhone добавил расход
↓
Server
↓
Android получил расход
↓
Mac получил расход
```
## Offline-first
Мобильные приложения сохраняют изменения локально и затем отправляют их серверу.
Поэтому сервер должен корректно принимать изменения спустя некоторое время после их создания.
## Delete
Удаление является syncable operation, а не только локальным DB delete.
Другие устройства должны получить tombstone и удалить локальную запись.
## Privacy
Сервер хранит персональные финансовые данные. Поэтому production deployment должен обеспечивать:
- TLS;
- authentication;
- authorization;
- encrypted backups;
- controlled database access;
- минимальное логирование финансовых payloads.
## Клиентская совместимость
Backend должен рассматриваться как общий контракт для:
- Apple Balance;
- Android Balance.
Любое изменение протокола требует проверки обеих реализаций.
+79
View File
@@ -0,0 +1,79 @@
# Безопасность
## Credentials
Пароли пользователей должны храниться только в виде slow password hash с современным password hashing алгоритмом.
Не хранить plaintext password.
## Tokens
Access/refresh tokens не должны попадать в:
- application logs;
- panic messages;
- error responses;
- analytics;
- database debug dumps.
## Authorization
Каждый sync mutation должен проверять ownership по authenticated user.
Нельзя доверять `userId` из client payload, если authenticated identity уже известна из token.
Правильная модель:
```text
Authorization header
↓
authenticated user
↓
server-side ownership
↓
requested entity
```
## Transport
Production API должен работать через HTTPS/TLS.
HTTP следует оставлять только для controlled local development.
## Input validation
Проверяйте:
- UUID/ID;
- enum values;
- amount;
- timestamps;
- string lengths;
- batch size;
- JSON size;
- cursor/sequence bounds.
## Logging
Безопасно логировать:
- request ID;
- endpoint;
- status code;
- latency;
- batch size;
- non-sensitive user/record identifiers в допустимой для проекта форме.
Нельзя логировать:
- passwords;
- Authorization headers;
- access tokens;
- refresh tokens;
- полные request bodies финансовых данных без явной необходимости.
## Abuse protection
Production deployment должен иметь:
- rate limiting для auth endpoints;
- reasonable request body limits;
- timeouts;
- graceful shutdown;
- database connection limits;
- monitoring.
+138
View File
@@ -0,0 +1,138 @@
# Синхронизация
## Цель
Один аккаунт Balance может использоваться на нескольких устройствах:
```text
iPhone
│
├── transaction A
├── category B
└── budget C
│
▼
BalanceServer
│
┌────┴────┐
▼ ▼
Android Mac
```
## Cursor
Каждый клиент хранит cursor последнего успешно применённого server change.
Принцип:
```text
cursor = 100
server:
101 A
102 B
103 C
pull(cursor=100)
→ A, B, C
→ new cursor = 103
```
Клиент должен сохранять cursor только после успешного применения соответствующего batch.
## Sequence
Server sequence задаёт глобальный порядок изменений в sync stream.
Требования:
- монотонность;
- отсутствие уменьшения;
- однозначность порядка;
- корректность при concurrent requests.
## Push
Push передаёт изменения клиента:
```text
local mutations
↓
POST /v1/sync
↓
validation
↓
ownership check
↓
persist
↓
append to change stream
```
Retry не должен создавать две разные логические записи из одного client change.
## Pull
Pull получает изменения после cursor.
Сервер не должен возвращать изменения другого пользователя.
При pagination клиент должен иметь возможность продолжить с нового cursor.
## Conflict resolution
Клиенты Balance используют timestamp-based versioning.
Типовая проверка:
```text
local.updatedAt < remote.updatedAt
```
Следовательно, server должен сохранять `updatedAt` достаточно точно и не подменять его произвольным временем каждого чтения.
При конкурентных writes сервер должен иметь однозначное правило победителя.
## Deletions
Удаление создаёт tombstone/change:
```json
{
"entity": "transaction",
"id": "...",
"deleted": true,
"updatedAt": "..."
}
```
Tombstone должен попасть в change stream так же, как обычное изменение.
## Idempotency
Push может повториться из-за:
- timeout;
- network failure;
- app restart;
- retry after 401/token refresh.
Поэтому сервер должен обрабатывать повторную отправку безопасно.
## Batch
Apple-клиент отправляет batches до 100 изменений и может делить крупные payloads.
Android должен использовать совместимый protocol.
Server не должен предполагать, что все изменения приходят одним запросом.
## Reset/full sync
Если client потерял cursor или требует reset, сервер должен поддерживать предусмотренный текущим API механизм полного pull либо новый cursor baseline.
Нельзя молча трактовать неизвестный/просроченный cursor как актуальный.
## Security
Sync endpoint всегда ограничен текущим authenticated user.
`deviceId` — идентификатор устройства, а не механизм авторизации.