Goondex/internal/db/sync_store.go
Stu Leak 16fb407a3c v0.1.0-dev4: Add web frontend with UI component library
- Implement full web interface with Go html/template server
- Add GX component library (buttons, dialogs, tables, forms, etc.)
- Create scene/performer/studio/movie detail and listing pages
- Add Adult Empire scraper for additional metadata sources
- Implement movie support with database schema
- Add import and sync services for data management
- Include comprehensive API and frontend documentation
- Add custom color scheme and responsive layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 10:47:30 -05:00

193 lines
5.0 KiB
Go

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
}