package sync import ( "errors" "fmt" "time" "git.mchus.pro/mchus/priceforge/internal/localdb" "git.mchus.pro/mchus/priceforge/internal/repository" "gorm.io/gorm" ) // ImportConfigurationsToLocal imports configurations from MariaDB into local SQLite. // Existing local configs with pending local changes are skipped to avoid data loss. func (s *Service) ImportConfigurationsToLocal() (*ConfigImportResult, error) { mariaDB, err := s.getDB() if err != nil { return nil, ErrOffline } configRepo := repository.NewConfigurationRepository(mariaDB) result := &ConfigImportResult{} offset := 0 const limit = 200 for { serverConfigs, _, err := configRepo.ListAll(offset, limit) if err != nil { return nil, fmt.Errorf("listing server configurations: %w", err) } if len(serverConfigs) == 0 { break } for i := range serverConfigs { cfg := serverConfigs[i] existing, err := s.localDB.GetConfigurationByUUID(cfg.UUID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("getting local configuration %s: %w", cfg.UUID, err) } if existing != nil && err == nil && existing.SyncStatus == "pending" { result.Skipped++ continue } if existing != nil && err == nil && !existing.IsActive { // Keep local deactivation sticky: do not resurrect hidden entries from server pull. result.Skipped++ continue } localCfg := localdb.ConfigurationToLocal(&cfg) now := time.Now() localCfg.SyncedAt = &now localCfg.SyncStatus = "synced" localCfg.UpdatedAt = now if existing != nil && err == nil { localCfg.ID = existing.ID result.Updated++ } else { result.Imported++ } if err := s.localDB.SaveConfiguration(localCfg); err != nil { return nil, fmt.Errorf("saving local configuration %s: %w", cfg.UUID, err) } } offset += len(serverConfigs) } return result, nil } // ImportProjectsToLocal imports projects from MariaDB into local SQLite. // Existing local projects with pending local changes are skipped to avoid data loss. func (s *Service) ImportProjectsToLocal() (*ProjectImportResult, error) { mariaDB, err := s.getDB() if err != nil { return nil, ErrOffline } projectRepo := repository.NewProjectRepository(mariaDB) result := &ProjectImportResult{} offset := 0 const limit = 200 for { serverProjects, _, err := projectRepo.List(offset, limit, true) if err != nil { return nil, fmt.Errorf("listing server projects: %w", err) } if len(serverProjects) == 0 { break } now := time.Now() for i := range serverProjects { project := serverProjects[i] existing, getErr := s.localDB.GetProjectByUUID(project.UUID) if getErr != nil && !errors.Is(getErr, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("getting local project %s: %w", project.UUID, getErr) } if existing != nil && getErr == nil { // Keep unsynced local changes intact. if existing.SyncStatus == "pending" { result.Skipped++ continue } existing.OwnerUsername = project.OwnerUsername existing.Name = project.Name existing.TrackerURL = project.TrackerURL existing.IsActive = project.IsActive existing.IsSystem = project.IsSystem existing.CreatedAt = project.CreatedAt existing.UpdatedAt = project.UpdatedAt serverID := project.ID existing.ServerID = &serverID existing.SyncStatus = "synced" existing.SyncedAt = &now if err := s.localDB.SaveProject(existing); err != nil { return nil, fmt.Errorf("saving local project %s: %w", project.UUID, err) } result.Updated++ continue } localProject := localdb.ProjectToLocal(&project) localProject.SyncStatus = "synced" localProject.SyncedAt = &now if err := s.localDB.SaveProject(localProject); err != nil { return nil, fmt.Errorf("saving local project %s: %w", project.UUID, err) } result.Imported++ } offset += len(serverProjects) } return result, nil }