added AGENTS.md and docs in root dir
Android CI / Build and Test (push) Failing after 13m25s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
Android CI / Build and Test (push) Failing after 13m25s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
# AGENTS.md — корень монорепозитория Balance
|
||||||
|
|
||||||
|
Этот файл — точка входа для AI-агентов и разработчиков, работающих в
|
||||||
|
репозитории Balance. Он описывает, как устроены три подпроекта, как они
|
||||||
|
связаны друг с другом, и куда идти за подробностями. **Каждый подпроект
|
||||||
|
имеет собственный `AGENTS.md` с детальными правилами — читай его перед
|
||||||
|
любым изменением внутри соответствующей папки.**
|
||||||
|
|
||||||
|
## Что это за проект
|
||||||
|
|
||||||
|
Balance — приложение для учёта личных финансов, доступное на пяти
|
||||||
|
платформах, которые используют одну и ту же доменную модель и один и
|
||||||
|
тот же протокол синхронизации:
|
||||||
|
|
||||||
|
| Платформа | Папка | Стек |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| iOS 17+ | `Balance/` | SwiftUI + SwiftData |
|
||||||
|
| macOS 14+ | `Balance/` (`BalanceMac/`) | SwiftUI + SwiftData |
|
||||||
|
| watchOS 10+ | `Balance/` (`BalanceWatch/`) | SwiftUI + SwiftData |
|
||||||
|
| Android 8.0+ | `BalanceAndroid/` | Kotlin + Jetpack Compose + Room |
|
||||||
|
| Web (встроен в сервер) | `BalanceServer/` | Go + embedded static client |
|
||||||
|
| Сервер синхронизации | `BalanceServer/` | Go + SQLite |
|
||||||
|
|
||||||
|
Все клиенты — iOS, macOS, watchOS, Android, web — синхронизируются через
|
||||||
|
один и тот же self-hosted сервер (`BalanceServer`) по единому HTTP(S)
|
||||||
|
API: регистрация, вход, refresh-токены, push/pull изменений с курсором,
|
||||||
|
tombstone-удаления, пользовательские категории и месячные бюджеты.
|
||||||
|
|
||||||
|
## Структура репозитория
|
||||||
|
|
||||||
|
```text
|
||||||
|
Balance/ # монорепозиторий
|
||||||
|
├── AGENTS.md # этот файл — общие правила для агентов
|
||||||
|
├── README.md # общий обзор и точки входа
|
||||||
|
├── docs/ # сводная документация по всему проекту
|
||||||
|
│ ├── ARCHITECTURE.md # как связаны клиенты и сервер
|
||||||
|
│ ├── SYNC_PROTOCOL.md # единый протокол синхронизации
|
||||||
|
│ ├── DATA_MODEL.md # сопоставление сущностей между платформами
|
||||||
|
│ ├── SECURITY.md # сквозные требования безопасности
|
||||||
|
│ └── DEVELOPMENT.md # как собрать и проверить все три проекта
|
||||||
|
│
|
||||||
|
├── Balance/ # Xcode-проект: iOS + macOS + watchOS
|
||||||
|
│ ├── AGENTS.md # правила для Apple-клиента
|
||||||
|
│ ├── README.md
|
||||||
|
│ └── docs/ # ARCHITECTURE, DATA_MODEL, SYNC, DEVELOPMENT, PRODUCT
|
||||||
|
│
|
||||||
|
├── BalanceAndroid/ # Android-проект (Gradle/Kotlin)
|
||||||
|
│ ├── AGENTS.md # правила для Android-клиента
|
||||||
|
│ ├── README.md
|
||||||
|
│ └── docs/ # ARCHITECTURE, DATA_MODEL, SYNC, DEVELOPMENT, PRODUCT
|
||||||
|
│
|
||||||
|
└── BalanceServer/ # Go-сервер синхронизации + web-клиент
|
||||||
|
├── AGENTS.md # правила для сервера
|
||||||
|
├── README.md
|
||||||
|
└── docs/ # ARCHITECTURE, API, DATA_MODEL, SYNC, SECURITY, DEVELOPMENT, DEPLOYMENT, PRODUCT
|
||||||
|
```
|
||||||
|
|
||||||
|
## Как работать в этом репозитории
|
||||||
|
|
||||||
|
### 1. Сначала пойми, в каком подпроекте изменение
|
||||||
|
|
||||||
|
Каждый подпроект — независимый билд с собственными зависимостями,
|
||||||
|
тестами и CI-шагами. Изменения в одной папке не должны требовать
|
||||||
|
правок в другой, **кроме** случаев, когда меняется общий контракт:
|
||||||
|
|
||||||
|
- HTTP API / формат sync-запросов и ответов (`BalanceServer`);
|
||||||
|
- набор синхронизируемых полей сущности (транзакция, категория, бюджет);
|
||||||
|
- правила финансовых расчётов (баланс, корректировки, аналитика).
|
||||||
|
|
||||||
|
Если меняется общий контракт — обнови все три подпроекта согласованно
|
||||||
|
и синхронизируй документацию (`docs/SYNC_PROTOCOL.md`, а также
|
||||||
|
`docs/SYNC.md`/`docs/API.md`/`docs/DATA_MODEL.md` внутри каждой папки).
|
||||||
|
|
||||||
|
### 2. Единая доменная модель
|
||||||
|
|
||||||
|
Три клиента независимо реализуют одну и ту же модель на своём стеке
|
||||||
|
(SwiftData / Room), но со семантически одинаковыми сущностями:
|
||||||
|
|
||||||
|
- **Транзакция** (доход/расход, сумма, дата, заметка, категория —
|
||||||
|
хранится snapshot'ом имени/иконки/цвета категории на момент операции);
|
||||||
|
- **Пользовательская категория** (имя, иконка/emoji, цвет);
|
||||||
|
- **Месячный бюджет** (категория, месяц, лимит);
|
||||||
|
- **Tombstone/deletion journal** для распространения удалений между
|
||||||
|
устройствами.
|
||||||
|
|
||||||
|
Общие правила расчётов (баланс = доходы − расходы; корректировки
|
||||||
|
баланса учитываются в общем балансе, но не в периодической аналитике;
|
||||||
|
расходы группируются по имени категории) продублированы в:
|
||||||
|
|
||||||
|
- `Balance/Balance/Utilities/FinanceCalculations.swift` (Apple);
|
||||||
|
- `BalanceAndroid/.../data/FinanceMath.kt` (Android).
|
||||||
|
|
||||||
|
При изменении этих правил на одной платформе — переноси изменение на
|
||||||
|
другую и обновляй оба набора тестов
|
||||||
|
(`BalanceTests/FinanceCalculationsTests.swift`, `FinanceMathTest`).
|
||||||
|
|
||||||
|
### 3. Синхронизация — общий контракт
|
||||||
|
|
||||||
|
Единый протокол описан в `docs/SYNC_PROTOCOL.md` (сводно) и подробно —
|
||||||
|
в `BalanceServer/docs/API.md` и `BalanceServer/docs/SYNC.md`. Клиентские
|
||||||
|
реализации:
|
||||||
|
|
||||||
|
- Apple: `Balance/Balance/Shared/ServerSync.swift`;
|
||||||
|
- Android: `BalanceAndroid/.../sync/SyncRepository.kt`.
|
||||||
|
|
||||||
|
Изменение cursor/sequence semantics, JSON-полей или auth-заголовков —
|
||||||
|
breaking change для всех клиентов сразу. Не меняй его в одном
|
||||||
|
подпроекте, не проверив остальные два.
|
||||||
|
|
||||||
|
### 4. Безопасность (сквозные требования)
|
||||||
|
|
||||||
|
- Секреты (пароли, access/refresh токены) никогда не хранятся в
|
||||||
|
открытом виде: Keychain (Apple), Android Keystore + AES/GCM
|
||||||
|
(Android), Argon2id-хеши + SHA-256 refresh-token хеши (сервер).
|
||||||
|
- HTTPS обязателен для удалённого сервера на всех клиентах; HTTP
|
||||||
|
разрешён только для `localhost`/`127.0.0.1`/`::1`.
|
||||||
|
- Не логируй пароли, токены или заголовок `Authorization` ни в одном
|
||||||
|
из подпроектов.
|
||||||
|
- Не коммить секреты, production-URL или credentials.
|
||||||
|
|
||||||
|
Подробности — `docs/SECURITY.md` и `BalanceServer/docs/SECURITY.md`.
|
||||||
|
|
||||||
|
### 5. Тестирование перед PR
|
||||||
|
|
||||||
|
Запусти проверки для всех подпроектов, которые ты менял:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apple (из Balance/)
|
||||||
|
xcodebuild -project Balance.xcodeproj -scheme Balance \
|
||||||
|
-destination 'platform=iOS Simulator,name=<available iPhone>' test
|
||||||
|
|
||||||
|
# Android (из BalanceAndroid/)
|
||||||
|
./gradlew clean test assembleDebug
|
||||||
|
|
||||||
|
# Server (из BalanceServer/)
|
||||||
|
go test ./...
|
||||||
|
go vet ./...
|
||||||
|
go build ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Если менялся общий контракт (API/sync/data model) — прогони проверки
|
||||||
|
во всех трёх, а не только в изменённой папке.
|
||||||
|
|
||||||
|
### 6. Документация
|
||||||
|
|
||||||
|
- Меняешь контракт синхронизации → обнови `docs/SYNC_PROTOCOL.md` в
|
||||||
|
корне **и** `BalanceServer/docs/SYNC.md`/`API.md`, а также
|
||||||
|
`docs/SYNC.md` в `Balance/` и `BalanceAndroid/`.
|
||||||
|
- Меняешь модель данных на любой платформе → обнови `docs/DATA_MODEL.md`
|
||||||
|
в корне и в затронутом подпроекте.
|
||||||
|
- Меняешь способ сборки/запуска → обнови `docs/DEVELOPMENT.md` в
|
||||||
|
корне и соответствующий `README.md`/`docs/DEVELOPMENT.md` подпроекта.
|
||||||
|
- Каждый подпроект содержит собственный подробный `AGENTS.md` — этот
|
||||||
|
файл не заменяет их, а даёт общую картину и правила координации между
|
||||||
|
платформами.
|
||||||
|
|
||||||
|
## PR checklist (для изменений, затрагивающих несколько платформ)
|
||||||
|
|
||||||
|
- [ ] Определены все подпроекты, которые затрагивает изменение.
|
||||||
|
- [ ] Общий контракт (API/sync/data model) обновлён согласованно во
|
||||||
|
всех платформах, а не только в одной.
|
||||||
|
- [ ] Финансовые расчёты остаются идентичными на Apple и Android.
|
||||||
|
- [ ] Обновлены `docs/SYNC_PROTOCOL.md`, `docs/DATA_MODEL.md` в корне,
|
||||||
|
если менялась схема/протокол.
|
||||||
|
- [ ] Секреты и токены не добавлены в репозиторий ни в одном подпроекте.
|
||||||
|
- [ ] Тесты пройдены в каждом изменённом подпроекте.
|
||||||
|
- [ ] Локальный `AGENTS.md` соответствующего подпроекта тоже соблюдён.
|
||||||
@@ -1,13 +1,85 @@
|
|||||||
# Balance — все платформы
|
# Balance — все платформы
|
||||||
|
|
||||||
В архиве находятся нативные приложения личных финансов и общий self-hosted сервер синхронизации.
|
Balance — приложение для учёта личных финансов с нативными клиентами
|
||||||
|
для iOS, macOS, watchOS, Android и встроенным web-интерфейсом, а также
|
||||||
|
self-hosted сервер синхронизации на Go. В архиве находится
|
||||||
|
монорепозиторий из трёх независимо собираемых проектов.
|
||||||
|
|
||||||
| Папка | Назначение |
|
| Папка | Назначение | Стек |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [`Balance/`](Balance/README.md) | iOS 17+, macOS 14+ и watchOS 10+ | SwiftUI + SwiftData |
|
||||||
|
| [`BalanceAndroid/`](BalanceAndroid/README.md) | Android 8.0+ | Kotlin + Jetpack Compose + Room |
|
||||||
|
| [`BalanceServer/`](BalanceServer/README.md) | Go-сервер авторизации, синхронизации и встроенный web-клиент | Go + SQLite |
|
||||||
|
|
||||||
|
Все пять клиентов — iOS, macOS, watchOS, Android и web — используют
|
||||||
|
одну модель данных и один протокол сервера: операции, пользовательские
|
||||||
|
категории, бюджеты, удаления, регистрация, вход, refresh-токены и
|
||||||
|
постраничная двусторонняя синхронизация. Web-интерфейс открывается по
|
||||||
|
адресу запущенного сервера и не требует отдельной установки.
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
1. **Поднять сервер** — см. [`BalanceServer/README.md`](BalanceServer/README.md#quick-start-with-docker)
|
||||||
|
(Docker или локальная сборка Go-бинарника).
|
||||||
|
2. **Открыть Apple-проект** — `open Balance/Balance.xcodeproj`, см.
|
||||||
|
[`Balance/README.md`](Balance/README.md).
|
||||||
|
3. **Собрать Android-приложение** — `cd BalanceAndroid && ./gradlew assembleDebug`,
|
||||||
|
см. [`BalanceAndroid/README.md`](BalanceAndroid/README.md).
|
||||||
|
4. В любом клиенте укажите адрес запущенного сервера в настройках
|
||||||
|
аккаунта, чтобы включить синхронизацию между устройствами.
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
Документация организована на двух уровнях:
|
||||||
|
|
||||||
|
- **Корневой уровень** (`docs/`) — то, что относится ко всем платформам
|
||||||
|
сразу: как устроена система в целом, единый протокол синхронизации,
|
||||||
|
сквозная модель данных и требования безопасности.
|
||||||
|
- **Уровень подпроекта** (`Balance/docs/`, `BalanceAndroid/docs/`,
|
||||||
|
`BalanceServer/docs/`) — детали конкретной платформы: код, схемы,
|
||||||
|
сборка, деплой.
|
||||||
|
|
||||||
|
| Документ | Описание |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Balance` | iOS 17+, macOS 14+ и watchOS 10+, SwiftUI + SwiftData |
|
| [`AGENTS.md`](AGENTS.md) | Правила для AI-агентов и разработчиков, работающих во всём репозитории |
|
||||||
| `BalanceAndroid` | Android 8.0+, Kotlin + Jetpack Compose + Room |
|
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Как связаны клиенты и сервер, общая схема системы |
|
||||||
| `BalanceServer` | Go-сервер авторизации и синхронизации со встроенным web-приложением |
|
| [`docs/SYNC_PROTOCOL.md`](docs/SYNC_PROTOCOL.md) | Единый протокол синхронизации между всеми клиентами и сервером |
|
||||||
|
| [`docs/DATA_MODEL.md`](docs/DATA_MODEL.md) | Сопоставление доменных сущностей между платформами |
|
||||||
|
| [`docs/SECURITY.md`](docs/SECURITY.md) | Сквозные требования безопасности (токены, HTTPS, хранение секретов) |
|
||||||
|
| [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) | Как собрать, запустить и протестировать все три проекта |
|
||||||
|
|
||||||
Все пять клиентов — iOS, macOS, watchOS, Android и web — используют одну модель данных и один протокол сервера: операции, пользовательские категории, бюджеты, удаления, регистрация, вход, refresh-токены и постраничная двусторонняя синхронизация. Web-интерфейс открывается по адресу запущенного сервера и не требует отдельной установки.
|
### Документация по подпроектам
|
||||||
|
|
||||||
Инструкции по запуску находятся в `Balance/README.md`, `BalanceAndroid/README.md` и `BalanceServer/README.md`.
|
- **Apple (`Balance/`):** [`AGENTS.md`](Balance/AGENTS.md) ·
|
||||||
|
[`docs/ARCHITECTURE.md`](Balance/docs/ARCHITECTURE.md) ·
|
||||||
|
[`docs/DATA_MODEL.md`](Balance/docs/DATA_MODEL.md) ·
|
||||||
|
[`docs/SYNC.md`](Balance/docs/SYNC.md) ·
|
||||||
|
[`docs/DEVELOPMENT.md`](Balance/docs/DEVELOPMENT.md) ·
|
||||||
|
[`docs/PRODUCT.md`](Balance/docs/PRODUCT.md)
|
||||||
|
- **Android (`BalanceAndroid/`):** [`AGENTS.md`](BalanceAndroid/AGENTS.md) ·
|
||||||
|
[`docs/ARCHITECTURE.md`](BalanceAndroid/docs/ARCHITECTURE.md) ·
|
||||||
|
[`docs/DATA_MODEL.md`](BalanceAndroid/docs/DATA_MODEL.md) ·
|
||||||
|
[`docs/SYNC.md`](BalanceAndroid/docs/SYNC.md) ·
|
||||||
|
[`docs/DEVELOPMENT.md`](BalanceAndroid/docs/DEVELOPMENT.md) ·
|
||||||
|
[`docs/PRODUCT.md`](BalanceAndroid/docs/PRODUCT.md)
|
||||||
|
- **Server (`BalanceServer/`):** [`AGENTS.md`](BalanceServer/AGENTS.md) ·
|
||||||
|
[`docs/ARCHITECTURE.md`](BalanceServer/docs/ARCHITECTURE.md) ·
|
||||||
|
[`docs/API.md`](BalanceServer/docs/API.md) ·
|
||||||
|
[`docs/DATA_MODEL.md`](BalanceServer/docs/DATA_MODEL.md) ·
|
||||||
|
[`docs/SYNC.md`](BalanceServer/docs/SYNC.md) ·
|
||||||
|
[`docs/SECURITY.md`](BalanceServer/docs/SECURITY.md) ·
|
||||||
|
[`docs/DEVELOPMENT.md`](BalanceServer/docs/DEVELOPMENT.md) ·
|
||||||
|
[`docs/DEPLOYMENT.md`](BalanceServer/docs/DEPLOYMENT.md) ·
|
||||||
|
[`docs/PRODUCT.md`](BalanceServer/docs/PRODUCT.md)
|
||||||
|
|
||||||
|
## Структура репозитория
|
||||||
|
|
||||||
|
```text
|
||||||
|
Balance/
|
||||||
|
├── AGENTS.md # общие правила для агентов по всему репо
|
||||||
|
├── README.md # этот файл
|
||||||
|
├── docs/ # сводная документация по всем платформам
|
||||||
|
├── Balance/ # Xcode-проект: iOS + macOS + watchOS
|
||||||
|
├── BalanceAndroid/ # Android (Gradle/Kotlin)
|
||||||
|
└── BalanceServer/ # Go-сервер + встроенный web-клиент
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Архитектура Balance (обзор всей системы)
|
||||||
|
|
||||||
|
Этот документ описывает, как связаны между собой три подпроекта.
|
||||||
|
Детальная архитектура каждой платформы — в `docs/ARCHITECTURE.md`
|
||||||
|
соответствующего подпроекта.
|
||||||
|
|
||||||
|
## Общая схема
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ iOS клиент │ │ macOS клиент│ │watchOS клиент│ Apple: SwiftUI + SwiftData
|
||||||
|
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ (Balance/)
|
||||||
|
│ │ │
|
||||||
|
└────────┬────────┴────────┬────────┘
|
||||||
|
│ │
|
||||||
|
HTTPS (или HTTP только для localhost)
|
||||||
|
│ │
|
||||||
|
┌────────┴────────┐ │
|
||||||
|
│ Android клиент │ │ Kotlin + Jetpack Compose + Room
|
||||||
|
│ (BalanceAndroid)│ │ (BalanceAndroid/)
|
||||||
|
└────────┬────────┘ │
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌───────────────────────────────┐
|
||||||
|
│ BalanceServer │ Go 1.25 + SQLite
|
||||||
|
│ ┌───────────┬───────────────┐ │ (BalanceServer/)
|
||||||
|
│ │ HTTP API │ Web-клиент │ │
|
||||||
|
│ │ /v1/* │ (embedded) │ │
|
||||||
|
│ └───────────┴───────────────┘ │
|
||||||
|
└───────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый нативный клиент (iOS/macOS/watchOS/Android) хранит полную копию
|
||||||
|
данных локально (SwiftData или Room) и работает офлайн. Сервер —
|
||||||
|
единственная точка правды при конфликте между устройствами одного
|
||||||
|
аккаунта: он хранит append-only лог изменений с курсором и применяет
|
||||||
|
last-write-wins по времени изменения записи (`updatedAt`/`syncUpdatedAt`).
|
||||||
|
|
||||||
|
Web-клиент не является отдельным подпроектом — это статические файлы,
|
||||||
|
встроенные (embed) в Go-бинарник сервера, использующие тот же HTTP API,
|
||||||
|
что и нативные клиенты, но с cookie-based сессией вместо Bearer-токена.
|
||||||
|
|
||||||
|
## Компоненты сервера
|
||||||
|
|
||||||
|
- **HTTP API** (`internal/httpapi/`) — auth-эндпоинты и `/v1/sync`.
|
||||||
|
- **auth** (`internal/auth/`) — пароли (Argon2id), JWT access/refresh
|
||||||
|
токены.
|
||||||
|
- **database** (`internal/database/`) — SQLite-хранилище, схема,
|
||||||
|
индексы.
|
||||||
|
- **config** (`internal/config/`) — переменные окружения.
|
||||||
|
- **web** (`internal/httpapi/web.go`) — раздача встроенного
|
||||||
|
web-приложения.
|
||||||
|
|
||||||
|
Подробнее — [`BalanceServer/docs/ARCHITECTURE.md`](../BalanceServer/docs/ARCHITECTURE.md).
|
||||||
|
|
||||||
|
## Компоненты Apple-клиента
|
||||||
|
|
||||||
|
- **Models** — SwiftData `@Model` сущности.
|
||||||
|
- **Utilities/FinanceCalculations** — единый источник правил расчёта
|
||||||
|
баланса и аналитики.
|
||||||
|
- **Shared/ServerSync** — HTTP-клиент, авторизация, sync orchestration,
|
||||||
|
Keychain.
|
||||||
|
- **Views** — общий SwiftUI UI; `BalanceMac/` и `BalanceWatch/` —
|
||||||
|
платформенно-специфичные точки входа и экраны.
|
||||||
|
|
||||||
|
Подробнее — [`Balance/docs/ARCHITECTURE.md`](../Balance/docs/ARCHITECTURE.md).
|
||||||
|
|
||||||
|
## Компоненты Android-клиента
|
||||||
|
|
||||||
|
- **data/** — Room entities, DAO, `FinanceMath` (та же роль, что и
|
||||||
|
`FinanceCalculations` в Apple-клиенте).
|
||||||
|
- **sync/** — `ServerApi` (HTTP/auth), `ServerPreferences` (URL/cursor),
|
||||||
|
`SecureSessionStore` (Android Keystore), `SyncRepository`
|
||||||
|
(orchestration), `SyncWorker` (периодический фон).
|
||||||
|
- **ui/** — Jetpack Compose экраны, состояние из `MainViewModel`.
|
||||||
|
|
||||||
|
Подробнее — [`BalanceAndroid/docs/ARCHITECTURE.md`](../BalanceAndroid/docs/ARCHITECTURE.md).
|
||||||
|
|
||||||
|
## Принцип согласованности между платформами
|
||||||
|
|
||||||
|
Три клиента реализуют одну и ту же доменную модель и один и тот же
|
||||||
|
sync-протокол независимо друг от друга, на разных стеках. Это
|
||||||
|
осознанный компромисс (нативный UX на каждой платформе) с обратной
|
||||||
|
стороной: любое изменение общего контракта нужно вносить согласованно
|
||||||
|
в трёх местах. См. `docs/SYNC_PROTOCOL.md` и `docs/DATA_MODEL.md`.
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Сквозная модель данных
|
||||||
|
|
||||||
|
Три платформы реализуют одну и ту же доменную модель независимо, на
|
||||||
|
своём стеке хранения. Этот документ сопоставляет поля между ними.
|
||||||
|
Полные описания — [`Balance/docs/DATA_MODEL.md`](../Balance/docs/DATA_MODEL.md),
|
||||||
|
[`BalanceAndroid/docs/DATA_MODEL.md`](../BalanceAndroid/docs/DATA_MODEL.md),
|
||||||
|
[`BalanceServer/docs/DATA_MODEL.md`](../BalanceServer/docs/DATA_MODEL.md).
|
||||||
|
|
||||||
|
## Транзакция
|
||||||
|
|
||||||
|
| Логическое поле | Apple (`FinanceTransaction`) | Android (`TransactionEntity`) | Сервер |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Идентификатор | `id: UUID` | `id: String` (UUID) | stable ID |
|
||||||
|
| Сумма | `amount: Double` | `amount: Double` | amount |
|
||||||
|
| Дата операции | `date: Date` | `date: Long` (epoch millis) | date |
|
||||||
|
| Заметка | `note: String` | `note: String` | note |
|
||||||
|
| Имя категории (snapshot) | `categoryName: String` | `categoryName: String` | category presentation data |
|
||||||
|
| Иконка категории | `categoryIcon: String` (SF Symbol) | `categoryIcon: String` | — |
|
||||||
|
| Emoji категории | `categoryEmoji: String` | `categoryEmoji: String` | — |
|
||||||
|
| Цвет категории | `categoryColorName: String` | `categoryColorName: String` | — |
|
||||||
|
| Корректировка баланса | `isBalanceAdjustment: Bool` | `isBalanceAdjustment: Boolean` | balance-adjustment flag |
|
||||||
|
| Тип операции | `kindRawValue: String` (`income`/`expense`) | `kindRawValue: String` | transaction kind |
|
||||||
|
| Версия/время изменения | `syncUpdatedAt: Date` | `updatedAt: Long` | `updatedAt` |
|
||||||
|
| Владелец | сессия аккаунта | сессия аккаунта | принадлежность пользователю |
|
||||||
|
|
||||||
|
**Категория хранится snapshot'ом**, а не ссылкой, на всех платформах:
|
||||||
|
это позволяет не терять отображение старой операции при удалении
|
||||||
|
категории и синхронизировать запись без relationship. Из-за этого
|
||||||
|
переименование пользовательской категории должно явно обновлять уже
|
||||||
|
существующие транзакции и бюджеты — и Apple-, и Android-клиент это
|
||||||
|
делают.
|
||||||
|
|
||||||
|
## Пользовательская категория
|
||||||
|
|
||||||
|
| Логическое поле | Apple (`CustomCategory`) | Android (`categories`) | Сервер |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Идентификатор | `id: UUID` | `id` | stable ID |
|
||||||
|
| Имя | `name: String` | `name` | name |
|
||||||
|
| Иконка | `icon: String` | `icon` | icon/emoji |
|
||||||
|
| Emoji | `emoji: String` | `emoji` | — |
|
||||||
|
| Цвет | `colorName: String` | `colorName` | color |
|
||||||
|
| Тип | `kindRawValue: String` | `kindRawValue` | operation kind |
|
||||||
|
| Создание | `createdAt: Date` | `createdAt` | creation metadata |
|
||||||
|
| Версия | `syncUpdatedAt: Date` | `updatedAt` | update metadata |
|
||||||
|
|
||||||
|
Имя пользовательской категории уникально без учёта регистра/диакритики
|
||||||
|
относительно системных и других пользовательских категорий (проверяется
|
||||||
|
на клиентах). Встроенные системные категории (Продукты, Транспорт,
|
||||||
|
Зарплата и т.д.) не персистятся как отдельные записи ни на одной
|
||||||
|
платформе — они закодированы в клиентском коде.
|
||||||
|
|
||||||
|
## Месячный бюджет
|
||||||
|
|
||||||
|
| Логическое поле | Apple (`MonthlyBudget`) | Android (`budgets`) | Сервер |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Идентификатор | `id: UUID` | `id` | stable ID |
|
||||||
|
| Имя категории | `categoryName: String` | `categoryName` | category information |
|
||||||
|
| Иконка/emoji категории | `categoryIcon`, `categoryEmoji` | `categoryIcon`, `categoryEmoji` | — |
|
||||||
|
| Лимит | `limit: Double` | `limitAmount: Double` | monthly limit |
|
||||||
|
| Начало месяца | `monthStart: Date` | `monthStart: Long` | month start |
|
||||||
|
| Версия | `syncUpdatedAt: Date` | `updatedAt: Long` | `updatedAt` |
|
||||||
|
|
||||||
|
Бюджет идентифицируется собственным UUID, но связывается с категорией
|
||||||
|
по сохранённому имени (`categoryName`), а не по ссылке.
|
||||||
|
|
||||||
|
## Tombstone / deletion journal
|
||||||
|
|
||||||
|
| Логическое поле | Apple (`SyncTombstone`) | Android (`deletion_journal`) | Сервер (change record) |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Идентификатор записи | `id: UUID` | `id = entity:recordId` | record ID |
|
||||||
|
| Тип сущности | `entity: String` | `entity` | entity |
|
||||||
|
| ID удалённой записи | `recordID: String` | `recordId` | id |
|
||||||
|
| Время | `updatedAt: Date` | `updatedAt: Long` | `updatedAt` |
|
||||||
|
| Флаг удаления | подразумевается (`deleted: true` в payload) | подразумевается | `deleted: true` |
|
||||||
|
|
||||||
|
Поддерживаемые значения `entity` одинаковы на всех платформах:
|
||||||
|
`transaction`, `category`, `budget`.
|
||||||
|
|
||||||
|
## Финансовые инварианты (одинаковы на Apple и Android)
|
||||||
|
|
||||||
|
1. `amount` не может быть отрицательным через обычный UI.
|
||||||
|
2. Общий баланс = `sum(income.amount) − sum(expense.amount)`; записи с
|
||||||
|
`isBalanceAdjustment == true` **учитываются** в этой сумме.
|
||||||
|
3. Периодическая аналитика **исключает** balance adjustments и записи
|
||||||
|
вне выбранного периода.
|
||||||
|
4. Расходы группируются по `categoryName` (не по ID категории).
|
||||||
|
5. `syncUpdatedAt`/`updatedAt` обязано меняться при каждом изменении
|
||||||
|
синхронизируемой сущности.
|
||||||
|
6. Удаление синхронизируемой сущности обязано создать tombstone —
|
||||||
|
прямой `DELETE`/`delete()` без tombstone запрещён.
|
||||||
|
|
||||||
|
Источники правды по расчётам:
|
||||||
|
`Balance/Balance/Utilities/FinanceCalculations.swift` (Apple) и
|
||||||
|
`BalanceAndroid/.../data/FinanceMath.kt` (Android). При изменении
|
||||||
|
правил на одной платформе перенеси изменение на другую.
|
||||||
|
|
||||||
|
## Инварианты на стороне сервера
|
||||||
|
|
||||||
|
1. Record ID стабилен между синхронизациями.
|
||||||
|
2. Запись принадлежит ровно одному пользователю; клиент не может
|
||||||
|
читать/изменять чужие записи.
|
||||||
|
3. `sequence` (серверный порядок изменений) не должен уменьшаться.
|
||||||
|
4. Повторная доставка одного и того же изменения не должна повреждать
|
||||||
|
данные (идемпотентность).
|
||||||
|
5. Удаление обязано быть видимым клиенту через следующий pull.
|
||||||
|
6. Все timestamps хранятся в UTC.
|
||||||
|
7. Enum/строковые значения (`kindRawValue` и т.п.) совместимы между
|
||||||
|
iOS и Android — не переименовывать без периода совместимости.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Разработка (все платформы)
|
||||||
|
|
||||||
|
Сводная шпаргалка по сборке, запуску и проверке всех трёх подпроектов.
|
||||||
|
Подробности — в `docs/DEVELOPMENT.md` каждого подпроекта.
|
||||||
|
|
||||||
|
## Требования по платформам
|
||||||
|
|
||||||
|
| Подпроект | Инструменты | Минимальные версии |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Balance/` | Xcode, Swift | Xcode 16+, iOS 17+, macOS 14+, watchOS 10+ |
|
||||||
|
| `BalanceAndroid/` | Android Studio / Gradle | AGP 8.13, Kotlin 2.3.21, compileSdk 36, minSdk 26, Java 17 |
|
||||||
|
| `BalanceServer/` | Go | Go 1.25.0+ |
|
||||||
|
|
||||||
|
## Локальный запуск всей системы
|
||||||
|
|
||||||
|
Порядок, удобный для end-to-end проверки sync между платформами:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Поднять сервер
|
||||||
|
cd BalanceServer
|
||||||
|
cp .env.example .env
|
||||||
|
# задать BALANCE_JWT_SECRET (см. README.md)
|
||||||
|
go mod tidy
|
||||||
|
go run ./cmd/balance-server
|
||||||
|
# сервер слушает http://localhost:8080
|
||||||
|
|
||||||
|
# 2. Открыть Apple-проект и указать http://localhost:8080 в Settings → Server account
|
||||||
|
cd ../Balance
|
||||||
|
open Balance.xcodeproj
|
||||||
|
|
||||||
|
# 3. Собрать и запустить Android-приложение
|
||||||
|
cd ../BalanceAndroid
|
||||||
|
./gradlew assembleDebug
|
||||||
|
# на эмуляторе сервер локального хоста доступен как http://10.0.2.2:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Для физического iPhone/Android-устройства `localhost` указывает на само
|
||||||
|
устройство, а не на машину разработчика — используйте сетевой адрес
|
||||||
|
или HTTPS reverse proxy (см. `docs/SECURITY.md`).
|
||||||
|
|
||||||
|
## Сборка и тесты по отдельности
|
||||||
|
|
||||||
|
### Apple (`Balance/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xcodebuild -project Balance.xcodeproj -list
|
||||||
|
|
||||||
|
xcodebuild -project Balance.xcodeproj -scheme Balance \
|
||||||
|
-destination 'generic/platform=iOS' build
|
||||||
|
|
||||||
|
xcodebuild -project Balance.xcodeproj -scheme Balance \
|
||||||
|
-destination 'platform=iOS Simulator,name=<available iPhone>' test
|
||||||
|
```
|
||||||
|
|
||||||
|
Схемы: `Balance` (iOS), `BalanceMac`, `BalanceWatch`. Без
|
||||||
|
`BalanceCloudKitEnabled=YES` приложение работает в local-mode
|
||||||
|
(SwiftData без CloudKit) — рекомендуемый режим для разработки.
|
||||||
|
|
||||||
|
### Android (`BalanceAndroid/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew clean test assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
Тесты — `app/src/test/FinanceMathTest`. `local.properties` с `sdk.dir`
|
||||||
|
не должен попадать в Git.
|
||||||
|
|
||||||
|
### Server (`BalanceServer/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
go test ./...
|
||||||
|
go test -race ./...
|
||||||
|
go vet ./...
|
||||||
|
go build ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker-путь — `docker compose up -d --build` (см.
|
||||||
|
[`BalanceServer/README.md`](../BalanceServer/README.md)).
|
||||||
|
|
||||||
|
## Что проверять при изменении общего контракта
|
||||||
|
|
||||||
|
Если PR меняет sync API, модель данных или финансовые расчёты — прогони
|
||||||
|
проверки во всех трёх подпроектах, а не только в изменённом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
old iOS client + new server
|
||||||
|
new iOS client + server
|
||||||
|
Android client + server
|
||||||
|
concurrent sync с двух устройств одного аккаунта
|
||||||
|
login/refresh после истечения access token
|
||||||
|
delete → pull на другом устройстве
|
||||||
|
```
|
||||||
|
|
||||||
|
## Типовые проблемы синхронизации (одинаковый чек-лист для обеих платформ)
|
||||||
|
|
||||||
|
1. Проверь server URL в настройках клиента.
|
||||||
|
2. Проверь правило HTTPS/localhost (см. `docs/SECURITY.md`).
|
||||||
|
3. Проверь авторизацию/сессию (login, не истёк ли refresh).
|
||||||
|
4. Проверь дату последней синхронизации и сохранённый cursor.
|
||||||
|
5. Проверь совместимость версии клиента и `BalanceServer`.
|
||||||
|
6. Если данные не удаляются на другом устройстве — убедись, что
|
||||||
|
удаление прошло через tombstone-путь
|
||||||
|
(`modelContext.deleteForSync` на Apple,
|
||||||
|
`FinanceDao.deleteTransaction/deleteCategory/deleteBudget` на
|
||||||
|
Android), а не через прямой `delete()`.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Безопасность (сквозные требования)
|
||||||
|
|
||||||
|
Этот документ собирает требования безопасности, которые применяются
|
||||||
|
ко **всем** подпроектам одновременно. Платформенно-специфичные детали —
|
||||||
|
в [`BalanceServer/docs/SECURITY.md`](../BalanceServer/docs/SECURITY.md),
|
||||||
|
`Balance/AGENTS.md` (раздел «Безопасность») и
|
||||||
|
`BalanceAndroid/AGENTS.md` (раздел «Security»).
|
||||||
|
|
||||||
|
## Хранение секретов на клиентах
|
||||||
|
|
||||||
|
| | Apple | Android |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Access/refresh токены | Keychain (service `BalanceServer`) | зашифрованное хранилище через Android Keystore (AES/GCM), `SecureSessionStore` |
|
||||||
|
| Несекретные настройки (URL сервера, cursor, sync metadata) | `UserDefaults` | `ServerPreferences` |
|
||||||
|
| Запрещено | пароль в `UserDefaults`, SwiftData или файлах | токены в обычных `SharedPreferences` |
|
||||||
|
|
||||||
|
Ни один клиент не должен хранить пароль пользователя после
|
||||||
|
аутентификации — только access/refresh токены.
|
||||||
|
|
||||||
|
## Хранение секретов на сервере
|
||||||
|
|
||||||
|
- Пароли — только Argon2id-хеши, plaintext никогда не сохраняется.
|
||||||
|
- Refresh-токены хранятся как SHA-256 хеши, не в открытом виде.
|
||||||
|
- `BALANCE_JWT_SECRET` должен быть случайной строкой не короче 32
|
||||||
|
символов; его смена инвалидирует access-токены (refresh-сессии
|
||||||
|
остаются валидны и получают новый access-токен при следующем
|
||||||
|
refresh).
|
||||||
|
|
||||||
|
## Транспорт
|
||||||
|
|
||||||
|
- Для удалённого (не-localhost) сервера все клиенты обязаны
|
||||||
|
использовать HTTPS.
|
||||||
|
- HTTP разрешён клиентам **только** для `localhost`, `127.0.0.1` и
|
||||||
|
`::1` — это относится и к Apple-, и к Android-клиенту.
|
||||||
|
- Production-деплой сервера должен работать через HTTPS/TLS
|
||||||
|
(например, через reverse proxy — Caddy/nginx), см.
|
||||||
|
[`BalanceServer/README.md#https`](../BalanceServer/README.md#https).
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
|
||||||
|
Запрещено логировать на любой из трёх платформ:
|
||||||
|
|
||||||
|
- пароли;
|
||||||
|
- access/refresh токены;
|
||||||
|
- заголовок `Authorization` целиком;
|
||||||
|
- полные request bodies с финансовыми данными без явной необходимости.
|
||||||
|
|
||||||
|
Безопасно логировать (на сервере): request ID, endpoint, status code,
|
||||||
|
latency, размер batch, неконфиденциальные идентификаторы записей.
|
||||||
|
|
||||||
|
## Авторизация на сервере
|
||||||
|
|
||||||
|
Каждая sync-мутация обязана проверять ownership по authenticated user
|
||||||
|
из токена/сессии. Никогда не доверяй `userId`, пришедшему в теле
|
||||||
|
запроса от клиента, если identity уже известна из
|
||||||
|
Authorization-заголовка или cookie-сессии:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Authorization header / session cookie
|
||||||
|
↓
|
||||||
|
authenticated user
|
||||||
|
↓
|
||||||
|
server-side ownership check
|
||||||
|
↓
|
||||||
|
requested entity
|
||||||
|
```
|
||||||
|
|
||||||
|
## Валидация входных данных (сервер)
|
||||||
|
|
||||||
|
Проверяются: UUID/ID, enum-значения, `amount`, timestamps, длины строк,
|
||||||
|
размер batch, размер JSON, границы cursor/sequence.
|
||||||
|
|
||||||
|
## Защита от злоупотреблений (production-деплой сервера)
|
||||||
|
|
||||||
|
- rate limiting для auth-эндпоинтов (встроенный лимитер — 30
|
||||||
|
auth-запросов на IP в минуту; для более сильного публичного
|
||||||
|
rate limiting используйте reverse proxy);
|
||||||
|
- разумные ограничения на размер тела запроса;
|
||||||
|
- таймауты и graceful shutdown;
|
||||||
|
- ограничение подключений к БД;
|
||||||
|
- мониторинг.
|
||||||
|
|
||||||
|
## Секреты в репозитории
|
||||||
|
|
||||||
|
Ни в одном из трёх подпроектов не должны появляться:
|
||||||
|
|
||||||
|
- production URL серверов;
|
||||||
|
- реальные `BALANCE_JWT_SECRET` или иные секреты;
|
||||||
|
- закоммиченные `.env`, keystore-файлы, сертификаты;
|
||||||
|
- захардкоженные тестовые credentials, которые могут быть спутаны с
|
||||||
|
боевыми.
|
||||||
|
|
||||||
|
`.env.example` в `BalanceServer/` и `local.properties.example` в
|
||||||
|
`BalanceAndroid/` — единственные допустимые «примерные» файлы с
|
||||||
|
конфигурацией; реальные значения в них не хранятся.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Единый протокол синхронизации
|
||||||
|
|
||||||
|
Все клиенты (iOS, macOS, watchOS, Android, web) синхронизируются с
|
||||||
|
`BalanceServer` через один и тот же HTTP(S) API и один и тот же набор
|
||||||
|
инвариантов. Это сводка контракта; детальные описания на стороне
|
||||||
|
сервера — [`BalanceServer/docs/API.md`](../BalanceServer/docs/API.md) и
|
||||||
|
[`BalanceServer/docs/SYNC.md`](../BalanceServer/docs/SYNC.md).
|
||||||
|
Клиентские реализации — [`Balance/docs/SYNC.md`](../Balance/docs/SYNC.md)
|
||||||
|
и [`BalanceAndroid/docs/SYNC.md`](../BalanceAndroid/docs/SYNC.md).
|
||||||
|
|
||||||
|
## Эндпоинты
|
||||||
|
|
||||||
|
| Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/health` | Health check |
|
||||||
|
| `POST` | `/v1/auth/register` | Создание аккаунта |
|
||||||
|
| `POST` | `/v1/auth/login` | Вход, выдача access/refresh токенов |
|
||||||
|
| `POST` | `/v1/auth/refresh` | Ротация токенов |
|
||||||
|
| `POST` | `/v1/auth/logout` | Отзыв refresh-токена |
|
||||||
|
| `GET` | `/v1/me` | Текущий аккаунт |
|
||||||
|
| `POST` | `/v1/sync` | Push и pull изменений |
|
||||||
|
|
||||||
|
Нативные клиенты используют `Authorization: Bearer <access-token>`.
|
||||||
|
Web-клиент использует `HttpOnly`/`SameSite=Strict` cookie-сессию —
|
||||||
|
это единственное отличие транспорта, сама sync-семантика идентична.
|
||||||
|
|
||||||
|
## Основные инварианты
|
||||||
|
|
||||||
|
1. **Cursor** — каждый клиент хранит курсор последнего успешно
|
||||||
|
применённого изменения. Курсор сохраняется только после успешного
|
||||||
|
применения соответствующего batch, не раньше.
|
||||||
|
2. **Sequence** — сервер поддерживает глобальный монотонный порядок
|
||||||
|
изменений в change stream; порядок не должен уменьшаться и должен
|
||||||
|
быть однозначным при конкурентных запросах.
|
||||||
|
3. **Push** — клиент отправляет свои изменения (`POST /v1/sync`);
|
||||||
|
сервер валидирует, проверяет владение записью (ownership) и
|
||||||
|
добавляет изменение в change stream. Повторный push (retry) не
|
||||||
|
должен создавать вторую логическую запись из одного и того же
|
||||||
|
client-change (идемпотентность).
|
||||||
|
4. **Pull** — клиент запрашивает изменения после своего курсора;
|
||||||
|
сервер никогда не возвращает чужие данные (изоляция по аккаунту) и
|
||||||
|
поддерживает постраничный pull с продолжением от нового курсора.
|
||||||
|
5. **Conflict resolution** — last-write-wins по `updatedAt`
|
||||||
|
(`syncUpdatedAt` на Apple, `updatedAt` на Android/сервере). Сервер —
|
||||||
|
единственный источник правды в этом сравнении, поэтому не должен
|
||||||
|
произвольно перезаписывать `updatedAt` при каждом чтении.
|
||||||
|
6. **Удаления (tombstones)** — удаление сущности не является простым
|
||||||
|
`DELETE`; оно создаёт запись изменения вида
|
||||||
|
`{ "entity": ..., "id": ..., "deleted": true, "updatedAt": ... }`,
|
||||||
|
которая проходит через тот же change stream, что и обычные
|
||||||
|
изменения. На Apple это `SyncTombstone`/`modelContext.deleteForSync`,
|
||||||
|
на Android — `deletion_journal` через DAO-методы `deleteTransaction` /
|
||||||
|
`deleteCategory` / `deleteBudget`.
|
||||||
|
7. **Batch** — Apple-клиент отправляет батчи до 100 изменений и может
|
||||||
|
делить крупные payload'ы; сервер не должен предполагать, что все
|
||||||
|
изменения аккаунта приходят одним запросом. Android должен
|
||||||
|
оставаться совместимым с этим же батчингом.
|
||||||
|
8. **Reset/full sync** — если клиент теряет курсор, сервер должен
|
||||||
|
поддерживать предусмотренный API механизм полного pull или новый
|
||||||
|
baseline-курсор. Неизвестный/просроченный курсор нельзя молча
|
||||||
|
трактовать как актуальный.
|
||||||
|
|
||||||
|
## Синхронизируемые сущности
|
||||||
|
|
||||||
|
- **Транзакция** (доход/расход, сумма, дата, заметка, snapshot
|
||||||
|
категории);
|
||||||
|
- **Пользовательская категория** (имя, иконка/emoji, цвет);
|
||||||
|
- **Месячный бюджет** (категория, месяц, лимит);
|
||||||
|
- **Tombstone / deletion journal** запись для удалений.
|
||||||
|
|
||||||
|
Подробное пополевое сопоставление — [`docs/DATA_MODEL.md`](DATA_MODEL.md).
|
||||||
|
|
||||||
|
## Что нельзя менять без координации
|
||||||
|
|
||||||
|
Любое из следующих изменений — breaking change для всех клиентов
|
||||||
|
одновременно, даже если каждый подпроект по отдельности компилируется
|
||||||
|
и проходит собственные тесты:
|
||||||
|
|
||||||
|
- имена/типы JSON-полей в payload'ах `/v1/sync` и auth-эндпоинтов;
|
||||||
|
- HTTP-методы или пути эндпоинтов;
|
||||||
|
- семантика курсора/sequence;
|
||||||
|
- правило разрешения конфликтов (last-write-wins по `updatedAt`);
|
||||||
|
- формат tombstone-записи.
|
||||||
|
|
||||||
|
При таком изменении обнови одновременно: `BalanceServer` (API +
|
||||||
|
хендлеры + `docs/API.md`/`docs/SYNC.md`), `Balance/Shared/ServerSync.swift`
|
||||||
|
(+ `Balance/docs/SYNC.md`), `BalanceAndroid/.../sync/*`
|
||||||
|
(+ `BalanceAndroid/docs/SYNC.md`) и этот файл.
|
||||||
Reference in New Issue
Block a user