Implement ecosystem contract v2: capabilities, shared ledgers, attachments

- GET /v1/capabilities and the extended sync entity allowlist (§0, §1).
- Shared ledgers (§7): ledgers, members and hashed one-time invite codes;
  X-Balance-Ledger selects the space on /v1/sync and /v1/attachments with
  its own records, change stream and cursor. Personal space keeps the v1
  tables and behaviour; the migration only adds tables.
- Attachments (§8): SHA-256, size and media type checks, blob files in
  BALANCE_ATTACHMENTS_DIR (default: attachments next to the database),
  garbage collection of unreferenced blobs with a grace period for fresh
  uploads, and ledger deletion removes its blobs.
- Tests for every new endpoint and error code; API, sync, data model,
  security, deployment and README docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wtandClaude Opus 5.5 committed 2026-10-31 23:30:12 +07:00
1 parent 10272f3851
commit dfb4fe4e1a
17 files changed
+2496 -56

No files matched your search

+30 -4
View File
@@ -11,14 +11,16 @@
## Общие сущности
Сервер должен различать как минимум следующие логические типы синхронизируемых данных, совместимые с клиентами Balance:
Сервер принимает сущности контракта v2 (`docs/CONTRACT_V2.md` §1):
```text
transaction
category
budget
transaction category budget account transfer recurring
goal debt rate setting attachment
```
Payload хранится как непрозрачный JSON; сервер читает из него только `sha256`
записей `attachment` для сборки мусора файлов. Ниже — сущности v1.
### Transaction
Логически содержит:
@@ -97,6 +99,30 @@ payload
7. Timestamps должны храниться в UTC.
8. Enum/string values должны быть совместимы с iOS и Android.
## Таблицы контракта v2
Таблицы v1 (`users`, `refresh_tokens`, `records`, `changes`) не меняются. Миграция
только добавляет таблицы через `CREATE TABLE IF NOT EXISTS`, поэтому существующая
база (в том числе Go v1) открывается без переписывания строк и `sequence`.
| Таблица | Ключ | Назначение |
| --- | --- | --- |
| `ledgers` | `id` (UUID) | имя, `owner_id`, `created_at` |
| `ledger_members` | `(ledger_id, user_id)`, индекс `user_id` | `role` = `owner`/`member`, `joined_at` |
| `ledger_invites` | `code_hash` (SHA-256 кода), индекс `ledger_id` | `expires_at`, `used_at`, `used_by`; код в открытом виде не хранится |
| `ledger_records` | `(ledger_id, entity, record_id)` | текущее состояние записей пространства |
| `ledger_changes` | `sequence` AUTOINCREMENT, индекс `(ledger_id, sequence)` | поток изменений пространства |
| `attachment_blobs` | `(space, sha256)` | метаданные файлов: `content_type`, `size`, `uploaded_at`, `referenced` |
`space` в `attachment_blobs` — `user:<userId>` или `ledger:<ledgerId>`. Все таблицы
ledger ссылаются на `ledgers(id) ON DELETE CASCADE`; строки `attachment_blobs`
пространства удаляются в той же транзакции, что и ledger.
Файлы лежат в `BALANCE_ATTACHMENTS_DIR` (по умолчанию `attachments` рядом с
файлом БД): `users/<userId>/<sha[0:2]>/<sha>` и
`ledgers/<ledgerId>/<sha[0:2]>/<sha>`. Каталоги создаются с правами `0750`.
Один и тот же файл в двух пространствах хранится дважды.
## Миграции
Schema changes должны быть backward-compatible с активными версиями клиентов либо сопровождаться versioned API/миграцией.