package db import ( "database/sql" "fmt" "time" ) // SyncMetadata represents sync tracking information type SyncMetadata struct { ID int64 EntityType string LastSyncAt time.Time RecordsUpdated int RecordsFailed int Status string ErrorMessage string CreatedAt time.Time UpdatedAt time.Time } // SyncStore handles sync metadata operations type SyncStore struct { db *DB } // NewSyncStore creates a new sync store func NewSyncStore(db *DB) *SyncStore { return &SyncStore{db: db} } // GetLastSync retrieves the last sync metadata for an entity type func (s *SyncStore) GetLastSync(entityType string) (*SyncMetadata, error) { var meta SyncMetadata var lastSyncAt, createdAt, updatedAt string var errorMessage sql.NullString err := s.db.conn.QueryRow(` SELECT id, entity_type, last_sync_at, records_updated, records_failed, status, COALESCE(error_message, ''), created_at, updated_at FROM sync_metadata WHERE entity_type = ? `, entityType).Scan( &meta.ID, &meta.EntityType, &lastSyncAt, &meta.RecordsUpdated, &meta.RecordsFailed, &meta.Status, &errorMessage, &createdAt, &updatedAt, ) if err == sql.ErrNoRows { return nil, nil // No sync record found } if err != nil { return nil, fmt.Errorf("failed to get sync metadata: %w", err) } meta.LastSyncAt, _ = time.Parse(time.RFC3339, lastSyncAt) meta.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) meta.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) meta.ErrorMessage = errorMessage.String return &meta, nil } // CanSync checks if enough time has passed since last sync func (s *SyncStore) CanSync(entityType string, minInterval time.Duration) (bool, time.Time, error) { meta, err := s.GetLastSync(entityType) if err != nil { return false, time.Time{}, err } // No previous sync if meta == nil { return true, time.Time{}, nil } // Check if minimum interval has passed nextAllowed := meta.LastSyncAt.Add(minInterval) if time.Now().Before(nextAllowed) { return false, nextAllowed, nil } return true, time.Time{}, nil } // RecordSyncStart records the start of a sync operation func (s *SyncStore) RecordSyncStart(entityType string) error { now := time.Now() _, err := s.db.conn.Exec(` INSERT INTO sync_metadata (entity_type, last_sync_at, status, records_updated, records_failed, created_at, updated_at) VALUES (?, ?, 'running', 0, 0, ?, ?) ON CONFLICT(entity_type) DO UPDATE SET last_sync_at = excluded.last_sync_at, status = 'running', records_updated = 0, records_failed = 0, error_message = NULL, updated_at = excluded.updated_at `, entityType, now.Format(time.RFC3339), now.Format(time.RFC3339), now.Format(time.RFC3339)) if err != nil { return fmt.Errorf("failed to record sync start: %w", err) } return nil } // RecordSyncComplete records the completion of a sync operation func (s *SyncStore) RecordSyncComplete(entityType string, updated, failed int, errMsg string) error { now := time.Now() status := "completed" if failed > 0 { status = "completed_with_errors" } var errorMessage *string if errMsg != "" { errorMessage = &errMsg } _, err := s.db.conn.Exec(` UPDATE sync_metadata SET status = ?, records_updated = ?, records_failed = ?, error_message = ?, updated_at = ? WHERE entity_type = ? `, status, updated, failed, errorMessage, now.Format(time.RFC3339), entityType) if err != nil { return fmt.Errorf("failed to record sync completion: %w", err) } return nil } // RecordSyncError records a sync operation failure func (s *SyncStore) RecordSyncError(entityType string, errMsg string) error { now := time.Now() _, err := s.db.conn.Exec(` UPDATE sync_metadata SET status = 'failed', error_message = ?, updated_at = ? WHERE entity_type = ? `, errMsg, now.Format(time.RFC3339), entityType) if err != nil { return fmt.Errorf("failed to record sync error: %w", err) } return nil } // GetAllSyncStatus retrieves sync status for all entity types func (s *SyncStore) GetAllSyncStatus() ([]SyncMetadata, error) { rows, err := s.db.conn.Query(` SELECT id, entity_type, last_sync_at, records_updated, records_failed, status, COALESCE(error_message, ''), created_at, updated_at FROM sync_metadata ORDER BY entity_type `) if err != nil { return nil, fmt.Errorf("failed to get sync status: %w", err) } defer rows.Close() var results []SyncMetadata for rows.Next() { var meta SyncMetadata var lastSyncAt, createdAt, updatedAt string var errorMessage sql.NullString err := rows.Scan( &meta.ID, &meta.EntityType, &lastSyncAt, &meta.RecordsUpdated, &meta.RecordsFailed, &meta.Status, &errorMessage, &createdAt, &updatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan sync metadata: %w", err) } meta.LastSyncAt, _ = time.Parse(time.RFC3339, lastSyncAt) meta.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) meta.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) meta.ErrorMessage = errorMessage.String results = append(results, meta) } return results, nil }