Files

213 lines
4.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Серверная синхронизация
Android-клиент использует тот же self-hosted BalanceServer, что и остальные клиенты Balance.
## API
POST endpoints:
```text
/v1/auth/register
/v1/auth/login
/v1/auth/logout
/v1/auth/refresh
/v1/sync
```
Авторизованный запрос:
```http
Authorization: Bearer <access-token>
Content-Type: application/json; charset=utf-8
```
## Session
`ServerSession` содержит:
- access token;
- refresh token;
- expiresAt;
- userId;
- email.
`SecureSessionStore` сериализует session JSON и шифрует его AES/GCM.
AES key создаётся и хранится в Android Keystore.
При ошибке расшифровки store очищается.
## Server URL
`ServerPreferences.normalizeServer` принимает полный URL без:
- user info;
- query;
- fragment.
Разрешены HTTPS и локальный HTTP.
HTTP допускается для:
- `localhost`
- `127.0.0.1`
- `10.0.2.2`
`10.0.2.2` — специальный адрес Android Emulator для host machine.
## Authentication
Перед авторизованным запросом клиент проверяет expiry.
Если осталось менее 60 секунд, выполняется refresh.
При HTTP 401 клиент один раз refresh-ит session и повторяет исходный запрос.
Если refresh не удался, session очищается.
## Sync request
```json
{
"cursor": 0,
"deviceId": "UUID",
"changes": [],
"pull": true
}
```
Push использует тот же endpoint с `pull=false`.
## Change envelope
```json
{
"entity": "transaction",
"id": "UUID",
"deleted": false,
"updatedAt": "2026-09-08T10:00:00Z",
"payload": {}
}
```
Entities:
```text
transaction
category
budget
```
## Transaction payload
```json
{
"amount": 1000,
"date": "2026-09-08T10:00:00Z",
"note": "Кофе",
"categoryName": "Кофе",
"categoryIcon": "cup.and.saucer.fill",
"categoryEmoji": "☕️",
"categoryColorName": "brown",
"isBalanceAdjustment": false,
"kindRawValue": "expense"
}
```
## Category payload
```json
{
"name": "Кофе",
"icon": "cup.and.saucer.fill",
"emoji": "☕️",
"colorName": "brown",
"kindRawValue": "expense",
"createdAt": "2026-09-01T10:00:00Z"
}
```
## Budget payload
```json
{
"categoryName": "Кофе",
"categoryIcon": "cup.and.saucer.fill",
"categoryEmoji": "☕️",
"limit": 5000,
"monthStart": "2026-09-01T00:00:00Z"
}
```
## Push
Изменения выбираются по `updatedAt` между `lastPush` и cutoff.
Batch size: 100.
Если JSON batch больше 1.5 MB, он рекурсивно делится.
## Pull
Клиент отправляет текущий cursor.
Сервер возвращает:
```json
{
"cursor": 42,
"changes": [],
"hasMore": false
}
```
Если `hasMore=true`, клиент повторяет pull с новым cursor.
Есть защитные проверки:
- cursor не может уменьшиться;
- `hasMore=true` должен сопровождаться продвижением cursor.
## Applying remote changes
Изменения сортируются по `sequence`.
Для обычного изменения:
```text
apply remote iff remote.updatedAt > local.updatedAt
```
Для удаления:
```text
delete iff local.updatedAt <= remote.updatedAt
```
После применения соответствующий deletion journal очищается.
## Scope
Cursor и lastPush привязаны к:
```text
serverUrl + userId
```
Это позволяет иметь независимое состояние sync для разных серверов/аккаунтов.
## Reset
`resetAndSync()` очищает cursor и lastPush текущего scope, затем запускает обычную sync.
Локальные финансовые данные при этом не удаляются.
## Background
`SyncWorker`:
- запускается WorkManager;
- требует network connection;
- пропускает работу без session/server URL;
- возвращает `retry`, если sync завершилась ошибкой.
## Совместимость
README проекта указывает совместимость Android-клиента с BalanceServer protocol 3.1. При изменении контракта сервера необходимо синхронно обновлять `ServerApi`, `SyncRepository` и документацию.