- 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>
193 lines
5.0 KiB
Go
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
|
|
}
|