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:
@@ -56,6 +56,7 @@ func New(dbPath string) (*LocalDB, error) {
|
||||
&LocalPricelistItem{},
|
||||
&LocalComponent{},
|
||||
&AppSetting{},
|
||||
&PendingChange{},
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("migrating sqlite database: %w", err)
|
||||
}
|
||||
@@ -337,3 +338,73 @@ func (l *LocalDB) DeleteLocalPricelist(id uint) error {
|
||||
// Delete pricelist
|
||||
return l.db.Delete(&LocalPricelist{}, id).Error
|
||||
}
|
||||
|
||||
// PendingChange methods
|
||||
|
||||
// AddPendingChange adds a change to the sync queue
|
||||
func (l *LocalDB) AddPendingChange(entityType, entityUUID, operation, payload string) error {
|
||||
change := PendingChange{
|
||||
EntityType: entityType,
|
||||
EntityUUID: entityUUID,
|
||||
Operation: operation,
|
||||
Payload: payload,
|
||||
CreatedAt: time.Now(),
|
||||
Attempts: 0,
|
||||
}
|
||||
return l.db.Create(&change).Error
|
||||
}
|
||||
|
||||
// GetPendingChanges returns all pending changes ordered by creation time
|
||||
func (l *LocalDB) GetPendingChanges() ([]PendingChange, error) {
|
||||
var changes []PendingChange
|
||||
err := l.db.Order("created_at ASC").Find(&changes).Error
|
||||
return changes, err
|
||||
}
|
||||
|
||||
// GetPendingChangesByEntity returns pending changes for a specific entity
|
||||
func (l *LocalDB) GetPendingChangesByEntity(entityType, entityUUID string) ([]PendingChange, error) {
|
||||
var changes []PendingChange
|
||||
err := l.db.Where("entity_type = ? AND entity_uuid = ?", entityType, entityUUID).
|
||||
Order("created_at ASC").Find(&changes).Error
|
||||
return changes, err
|
||||
}
|
||||
|
||||
// DeletePendingChange removes a change from the sync queue after successful sync
|
||||
func (l *LocalDB) DeletePendingChange(id int64) error {
|
||||
return l.db.Delete(&PendingChange{}, id).Error
|
||||
}
|
||||
|
||||
// IncrementPendingChangeAttempts updates the attempt counter and last error
|
||||
func (l *LocalDB) IncrementPendingChangeAttempts(id int64, errorMsg string) error {
|
||||
return l.db.Model(&PendingChange{}).Where("id = ?", id).Updates(map[string]interface{}{
|
||||
"attempts": gorm.Expr("attempts + 1"),
|
||||
"last_error": errorMsg,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// CountPendingChanges returns the total number of pending changes
|
||||
func (l *LocalDB) CountPendingChanges() int64 {
|
||||
var count int64
|
||||
l.db.Model(&PendingChange{}).Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
// CountPendingChangesByType returns the number of pending changes by entity type
|
||||
func (l *LocalDB) CountPendingChangesByType(entityType string) int64 {
|
||||
var count int64
|
||||
l.db.Model(&PendingChange{}).Where("entity_type = ?", entityType).Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
// MarkChangesSynced marks multiple pending changes as synced by deleting them
|
||||
func (l *LocalDB) MarkChangesSynced(ids []int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.db.Where("id IN ?", ids).Delete(&PendingChange{}).Error
|
||||
}
|
||||
|
||||
// GetPendingCount returns the total number of pending changes (alias for CountPendingChanges)
|
||||
func (l *LocalDB) GetPendingCount() int64 {
|
||||
return l.CountPendingChanges()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user