added AGENTS.md and docs for BalanceServer
This commit is contained in:
1 parent
684ac58b73
commit
2f891c9503
10 files changed
+1022
No files matched your search
@@ -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 ./...` проходит.
|
||||||
|
- [ ] Документация обновлена.
|
||||||
@@ -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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
@@ -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 текущей реализации имеют приоритет над этой общей рекомендацией.
|
||||||
@@ -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 совпадал с клиентской логикой.
|
||||||
@@ -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 без периода совместимости.
|
||||||
@@ -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 готов.
|
||||||
@@ -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 на другом устройстве.
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
Любое изменение протокола требует проверки обеих реализаций.
|
||||||
@@ -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.
|
||||||
@@ -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` — идентификатор устройства, а не механизм авторизации.
|
||||||
Reference in new issue
Block a user