package core import ( "context" "encoding/json" "fmt" "log/slog" "path/filepath" aptclone "github.com/syonad/clonepack/internal/clone/apt" rpmclone "github.com/syonad/clonepack/internal/clone/rpm" "github.com/syonad/clonepack/internal/store" ) var validTypes = map[string]bool{"apt": true, "rpm": true, "docker": true, "binary": true} type RepoService struct { store store.RepoStore dataDir string } func NewRepoService(s store.RepoStore, dataDir string) *RepoService { return &RepoService{store: s, dataDir: dataDir} } var validSyncModes = map[string]bool{"auto": true, "manual": true} type CreateRepoInput struct { Name string `json:"name"` Type string `json:"type"` SourceURL string `json:"source_url"` SyncMode string `json:"sync_mode"` AptSuite string `json:"apt_suite,omitempty"` AptComponents []string `json:"apt_components,omitempty"` AptArchitectures []string `json:"apt_architectures,omitempty"` } func (s *RepoService) Create(ctx context.Context, in CreateRepoInput) (*store.Repo, error) { if in.Name == "" { return nil, fmt.Errorf("name is required") } if !validTypes[in.Type] { return nil, fmt.Errorf("invalid type: %s (must be apt, rpm, docker, or binary)", in.Type) } if in.SourceURL == "" { return nil, fmt.Errorf("source_url is required") } if in.SyncMode == "" { in.SyncMode = "auto" } else if !validSyncModes[in.SyncMode] { return nil, fmt.Errorf("invalid sync_mode: %s (must be auto or manual)", in.SyncMode) } var configJSON string if in.Type == "apt" { if in.AptSuite == "" { return nil, fmt.Errorf("apt_suite is required for apt repositories") } if len(in.AptComponents) == 0 { in.AptComponents = []string{"main"} } if len(in.AptArchitectures) == 0 { in.AptArchitectures = []string{"amd64"} } cfg := aptclone.Config{ Suite: in.AptSuite, Components: in.AptComponents, Architectures: in.AptArchitectures, } b, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal apt config: %w", err) } configJSON = string(b) } r := &store.Repo{Name: in.Name, Type: in.Type, SourceURL: in.SourceURL, SyncMode: in.SyncMode, Config: configJSON} id, err := s.store.CreateRepo(ctx, r) if err != nil { return nil, err } repo, err := s.store.GetRepo(ctx, id) if err != nil { return nil, err } s.initStorage(repo) return repo, nil } func (s *RepoService) initStorage(repo *store.Repo) { localDir := filepath.Join(s.dataDir, "repos", fmt.Sprintf("%d", repo.ID), repo.Type) switch repo.Type { case "rpm": if err := rpmclone.InitEmptyRepo(localDir); err != nil { slog.Warn("init storage failed", "repo_id", repo.ID, "error", err) } case "apt": var cfg aptclone.Config if err := json.Unmarshal([]byte(repo.Config), &cfg); err != nil { slog.Warn("init storage: parse config failed", "repo_id", repo.ID, "error", err) return } if err := aptclone.InitEmptyRepo(localDir, cfg); err != nil { slog.Warn("init storage failed", "repo_id", repo.ID, "error", err) } } } type UpdateRepoInput struct { SourceURL *string SyncMode *string AptSuite *string AptComponents []string AptArchitectures []string } func (s *RepoService) Update(ctx context.Context, id int64, in UpdateRepoInput) (*store.Repo, error) { repo, err := s.store.GetRepo(ctx, id) if err != nil { return nil, err } if in.SourceURL != nil { if *in.SourceURL == "" { return nil, fmt.Errorf("source_url cannot be empty") } repo.SourceURL = *in.SourceURL } if in.SyncMode != nil { if !validSyncModes[*in.SyncMode] { return nil, fmt.Errorf("invalid sync_mode: %s (must be auto or manual)", *in.SyncMode) } repo.SyncMode = *in.SyncMode } if repo.Type == "apt" && (in.AptSuite != nil || in.AptComponents != nil || in.AptArchitectures != nil) { var cfg aptclone.Config if repo.Config != "" { if err := json.Unmarshal([]byte(repo.Config), &cfg); err != nil { return nil, fmt.Errorf("parse existing apt config: %w", err) } } if in.AptSuite != nil { if *in.AptSuite == "" { return nil, fmt.Errorf("apt_suite cannot be empty") } cfg.Suite = *in.AptSuite } if in.AptComponents != nil { cfg.Components = in.AptComponents } if in.AptArchitectures != nil { cfg.Architectures = in.AptArchitectures } b, err := json.Marshal(cfg) if err != nil { return nil, fmt.Errorf("marshal apt config: %w", err) } repo.Config = string(b) } else if repo.Type != "apt" && (in.AptSuite != nil || in.AptComponents != nil || in.AptArchitectures != nil) { return nil, fmt.Errorf("apt fields only apply to apt repositories") } if err := s.store.UpdateRepo(ctx, id, repo.SourceURL, repo.SyncMode, repo.Config); err != nil { return nil, err } return s.store.GetRepo(ctx, id) } func (s *RepoService) List(ctx context.Context) ([]store.Repo, error) { return s.store.ListRepos(ctx) } func (s *RepoService) Get(ctx context.Context, id int64) (*store.Repo, error) { return s.store.GetRepo(ctx, id) } func (s *RepoService) GetByName(ctx context.Context, name string) (*store.Repo, error) { return s.store.GetRepoByName(ctx, name) } func (s *RepoService) Delete(ctx context.Context, id int64) error { return s.store.DeleteRepo(ctx, id) }