- Consolidate UI TODO items into single sync status partial task - Move conflict resolution to Phase 4 - Add LOCAL_FIRST_INTEGRATION.md with architecture guide - Add unified repository interface for future use Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
179 lines
6.1 KiB
Markdown
179 lines
6.1 KiB
Markdown
# Local-First Architecture Integration Guide
|
||
|
||
## Overview
|
||
|
||
QuoteForge теперь поддерживает local-first архитектуру: приложение ВСЕГДА работает с SQLite (localdb), MariaDB используется только для синхронизации.
|
||
|
||
## Реализованные компоненты
|
||
|
||
### 1. Конвертеры моделей (`internal/localdb/converters.go`)
|
||
|
||
Конвертеры между MariaDB и SQLite моделями:
|
||
- `ConfigurationToLocal()` / `LocalToConfiguration()`
|
||
- `PricelistToLocal()` / `LocalToPricelist()`
|
||
- `ComponentToLocal()` / `LocalToComponent()`
|
||
|
||
### 2. LocalDB методы (`internal/localdb/localdb.go`)
|
||
|
||
Добавлены методы для работы с pending changes:
|
||
- `MarkChangesSynced(ids []int64)` - помечает изменения как синхронизированные
|
||
- `GetPendingCount()` - возвращает количество несинхронизированных изменений
|
||
|
||
### 3. Sync Service расширения (`internal/services/sync/service.go`)
|
||
|
||
Новые методы:
|
||
- `SyncPricelistsIfNeeded()` - проверяет и скачивает новые прайслисты при необходимости
|
||
- `PushPendingChanges()` - отправляет все pending changes на сервер
|
||
- `pushSingleChange()` - обрабатывает один pending change
|
||
- `pushConfigurationCreate/Update/Delete()` - специфичные методы для конфигураций
|
||
|
||
**ВАЖНО**: Конструктор изменен - теперь требует `ConfigurationRepository`:
|
||
```go
|
||
syncService := sync.NewService(pricelistRepo, configRepo, local)
|
||
```
|
||
|
||
### 4. LocalConfigurationService (`internal/services/local_configuration.go`)
|
||
|
||
Новый сервис для работы с конфигурациями в local-first режиме:
|
||
- Все операции CRUD работают через SQLite
|
||
- Автоматически добавляет изменения в pending_changes
|
||
- При создании конфигурации (если online) проверяет новые прайслисты
|
||
|
||
```go
|
||
localConfigService := services.NewLocalConfigurationService(
|
||
localDB,
|
||
syncService,
|
||
quoteService,
|
||
isOnlineFunc,
|
||
)
|
||
```
|
||
|
||
### 5. Sync Handler расширения (`internal/handlers/sync.go`)
|
||
|
||
Новые endpoints:
|
||
- `POST /api/sync/push` - отправить pending changes на сервер
|
||
- `GET /api/sync/pending/count` - получить количество pending changes
|
||
- `GET /api/sync/pending` - получить список pending changes
|
||
|
||
## Интеграция
|
||
|
||
### Шаг 1: Обновить main.go
|
||
|
||
```go
|
||
// В cmd/server/main.go
|
||
syncService := sync.NewService(pricelistRepo, configRepo, local)
|
||
|
||
// Создать isOnline функцию
|
||
isOnlineFunc := func() bool {
|
||
sqlDB, err := db.DB()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
return sqlDB.Ping() == nil
|
||
}
|
||
|
||
// Создать LocalConfigurationService
|
||
localConfigService := services.NewLocalConfigurationService(
|
||
local,
|
||
syncService,
|
||
quoteService,
|
||
isOnlineFunc,
|
||
)
|
||
```
|
||
|
||
### Шаг 2: Обновить ConfigurationHandler
|
||
|
||
Заменить `ConfigurationService` на `LocalConfigurationService` в handlers:
|
||
|
||
```go
|
||
// Было:
|
||
configHandler := handlers.NewConfigurationHandler(configService, exportService)
|
||
|
||
// Стало:
|
||
configHandler := handlers.NewConfigurationHandler(localConfigService, exportService)
|
||
```
|
||
|
||
### Шаг 3: Добавить endpoints для sync
|
||
|
||
В роутере добавить:
|
||
```go
|
||
syncGroup := router.Group("/api/sync")
|
||
{
|
||
syncGroup.POST("/push", syncHandler.PushPendingChanges)
|
||
syncGroup.GET("/pending/count", syncHandler.GetPendingCount)
|
||
syncGroup.GET("/pending", syncHandler.GetPendingChanges)
|
||
}
|
||
```
|
||
|
||
## Как это работает
|
||
|
||
### Создание конфигурации
|
||
|
||
1. Пользователь создает конфигурацию
|
||
2. `LocalConfigurationService.Create()`:
|
||
- Если online → `SyncPricelistsIfNeeded()` проверяет новые прайслисты
|
||
- Сохраняет конфигурацию в SQLite
|
||
- Добавляет в `pending_changes` с operation="create"
|
||
3. Конфигурация доступна локально сразу
|
||
|
||
### Синхронизация с сервером
|
||
|
||
**Manual sync:**
|
||
```bash
|
||
POST /api/sync/push
|
||
```
|
||
|
||
**Background sync (TODO):**
|
||
- Периодический worker вызывает `syncService.PushPendingChanges()`
|
||
- Проверяет online статус
|
||
- Отправляет все pending changes на сервер
|
||
- Удаляет успешно синхронизированные записи
|
||
|
||
### Offline режим
|
||
|
||
1. Все операции работают нормально через SQLite
|
||
2. Изменения копятся в `pending_changes`
|
||
3. При восстановлении соединения автоматически синхронизируются
|
||
|
||
## Pending Changes Queue
|
||
|
||
Таблица `pending_changes`:
|
||
```go
|
||
type PendingChange struct {
|
||
ID int64 // Auto-increment
|
||
EntityType string // "configuration", "project", "specification"
|
||
EntityUUID string // UUID сущности
|
||
Operation string // "create", "update", "delete"
|
||
Payload string // JSON snapshot сущности
|
||
CreatedAt time.Time
|
||
Attempts int // Счетчик попыток синхронизации
|
||
LastError string // Последняя ошибка синхронизации
|
||
}
|
||
```
|
||
|
||
## TODO для Phase 2.5
|
||
|
||
- [ ] Background sync worker (автоматическая синхронизация каждые N минут)
|
||
- [ ] Conflict resolution (при конфликтах обновления)
|
||
- [ ] UI: pending counter в header
|
||
- [ ] UI: manual sync button
|
||
- [ ] UI: conflict alerts
|
||
- [ ] Retry logic для failed pending changes
|
||
- [ ] RefreshPrices для local mode (через local_components)
|
||
|
||
## Testing
|
||
|
||
```bash
|
||
# Compile
|
||
go build ./cmd/server
|
||
|
||
# Run
|
||
./quoteforge
|
||
|
||
# Check pending changes
|
||
curl http://localhost:8080/api/sync/pending/count
|
||
|
||
# Manual sync
|
||
curl -X POST http://localhost:8080/api/sync/push
|
||
```
|