Add background sync worker and complete local-first architecture

Implements automatic background synchronization every 5 minutes:
- Worker pushes pending changes to server (PushPendingChanges)
- Worker pulls new pricelists (SyncPricelistsIfNeeded)
- Graceful shutdown with context cancellation
- Automatic online/offline detection via DB ping

New files:
- internal/services/sync/worker.go - Background sync worker
- internal/services/local_configuration.go - Local-first CRUD
- internal/localdb/converters.go - MariaDB ↔ SQLite converters

Extended sync infrastructure:
- Pending changes queue (pending_changes table)
- Push/pull sync endpoints (/api/sync/push, /pending)
- ConfigurationGetter interface for handler compatibility
- LocalConfigurationService replaces ConfigurationService

All configuration operations now run through SQLite with automatic
background sync to MariaDB when online. Phase 2.5 nearly complete.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 22:17:00 +03:00
parent 143d217397
commit be77256d4e
11 changed files with 1131 additions and 27 deletions

View File

@@ -9,26 +9,33 @@
### Phase 2: Local SQLite Database ✅ DONE
### Phase 2.5: Full Offline Mode 🔶 IN PROGRESS
Приложение должно полностью работать без MariaDB, синхронизация при восстановлении связи.
**Local-first architecture:** приложение ВСЕГДА работает с SQLite, MariaDB только для синхронизации.
**Architecture:**
- Dual-source pattern: все операции идут через unified service layer
- Online: read/write MariaDB, async cache to SQLite
- Offline: read/write SQLite, queue changes for sync
**Принцип работы:**
- ВСЕ операции (CRUD) выполняются в SQLite
- При создании конфигурации:
1. Если online → проверить новые прайслисты на сервере → скачать если есть
2. Далее работаем с local_pricelists (и online, и offline одинаково)
- Background sync: push pending_changes → pull updates
**DONE:**
- ✅ Sync queue table (pending_changes) - `internal/localdb/models.go`
- ✅ Model converters: MariaDB ↔ SQLite - `internal/localdb/converters.go`
- ✅ LocalConfigurationService: все CRUD через SQLite - `internal/services/local_configuration.go`
- ✅ Pre-create pricelist check: `SyncPricelistsIfNeeded()` - `internal/services/sync/service.go`
- ✅ Push pending changes: `PushPendingChanges()` - sync service + handlers
- ✅ Sync API endpoints: `/api/sync/push`, `/pending/count`, `/pending`
- ✅ Integrate LocalConfigurationService in main.go (replace ConfigurationService)
- ✅ Add routes for new sync endpoints (`/api/sync/push`, `/pending/count`, `/pending`)
- ✅ ConfigurationGetter interface for handler compatibility
- ✅ Background sync worker: auto-sync every 5min (push + pull) - `internal/services/sync/worker.go`
**TODO:**
-Unified repository interface (online/offline transparent switching)
-Sync queue table (pending_changes: entity_type, entity_uuid, operation, payload, created_at)
-Background sync worker (push local changes when online)
-Conflict resolution (last-write-wins by updated_at, or manual)
-Initial data bootstrap (first sync downloads all needed data)
- ❌ Handlers use context.IsOffline to choose data source
- ❌ UI: pending changes counter, manual sync button, conflict alerts
**Sync flow:**
1. Online → Offline: continue work, changes saved locally with sync_status='pending'
2. Offline → Online: background worker pushes pending_changes, pulls updates
3. Conflict: if server version newer, mark as 'conflict' for manual resolution
-Conflict resolution (last-write-wins or manual)
-UI: pending counter in header
-UI: manual sync button
-UI: offline indicator (middleware already exists)
-RefreshPrices for local mode (via local_components)
### Phase 3: Projects and Specifications
- qt_projects, qt_specifications tables (MariaDB)