package store import ( "ci/internal/model" "os" "path/filepath" "github.com/glebarez/sqlite" "gorm.io/gorm" ) // Store wraps the database connection and provides data access methods. type Store struct { DB *gorm.DB } // New creates a new Store, initializes the database, and runs migrations. func New(dataDir string) (*Store, error) { if err := os.MkdirAll(dataDir, 0755); err != nil { return nil, err } dbPath := filepath.Join(dataDir, "ci.db") db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { return nil, err } if err := db.AutoMigrate(&model.Project{}, &model.BuildRecord{}, &model.DeployConfig{}, &model.DeployRecord{}); err != nil { return nil, err } return &Store{DB: db}, nil } // --- Project CRUD --- func (s *Store) CreateProject(p *model.Project) error { return s.DB.Create(p).Error } func (s *Store) ListProjects() ([]model.Project, error) { var projects []model.Project err := s.DB.Order("created_at desc").Find(&projects).Error return projects, err } func (s *Store) GetProject(id uint) (*model.Project, error) { var p model.Project err := s.DB.First(&p, id).Error if err != nil { return nil, err } return &p, nil } func (s *Store) UpdateProject(p *model.Project) error { return s.DB.Save(p).Error } func (s *Store) DeleteProject(id uint) error { return s.DB.Delete(&model.Project{}, id).Error } // --- BuildRecord CRUD --- func (s *Store) CreateBuildRecord(r *model.BuildRecord) error { return s.DB.Create(r).Error } func (s *Store) UpdateBuildRecord(r *model.BuildRecord) error { return s.DB.Save(r).Error } func (s *Store) GetBuildRecord(id uint) (*model.BuildRecord, error) { var r model.BuildRecord err := s.DB.Preload("Project").First(&r, id).Error if err != nil { return nil, err } return &r, nil } func (s *Store) ListBuildRecords(projectID uint, limit int) ([]model.BuildRecord, error) { var records []model.BuildRecord if limit <= 0 { limit = 20 } err := s.DB.Where("project_id = ?", projectID). Order("started_at desc"). Limit(limit). Find(&records).Error return records, err } // --- DeployConfig CRUD --- // GetDeployConfig returns the deploy config for a project, or nil if not configured. func (s *Store) GetDeployConfig(projectID uint) (*model.DeployConfig, error) { var cfg model.DeployConfig err := s.DB.Where("project_id = ?", projectID).First(&cfg).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // not configured is not an error } return nil, err } return &cfg, nil } // UpsertDeployConfig creates or updates the deploy config for a project. func (s *Store) UpsertDeployConfig(cfg *model.DeployConfig) error { // Use FirstOrCreate + Assign to handle upsert var existing model.DeployConfig err := s.DB.Where("project_id = ?", cfg.ProjectID).First(&existing).Error if err == nil { // Update existing cfg.ID = existing.ID cfg.CreatedAt = existing.CreatedAt return s.DB.Save(cfg).Error } // Create new return s.DB.Create(cfg).Error } // DeleteDeployConfig removes the deploy config for a project. func (s *Store) DeleteDeployConfig(projectID uint) error { return s.DB.Where("project_id = ?", projectID).Delete(&model.DeployConfig{}).Error } // --- DeployRecord CRUD --- // CreateDeployRecord creates a new deploy record. func (s *Store) CreateDeployRecord(r *model.DeployRecord) error { return s.DB.Create(r).Error } // UpdateDeployRecord updates an existing deploy record. func (s *Store) UpdateDeployRecord(r *model.DeployRecord) error { return s.DB.Save(r).Error } // GetDeployRecord returns a single deploy record by ID. func (s *Store) GetDeployRecord(id uint) (*model.DeployRecord, error) { var r model.DeployRecord err := s.DB.Preload("Project").First(&r, id).Error if err != nil { return nil, err } return &r, nil } // ListDeployRecords returns deploy history for a project. func (s *Store) ListDeployRecords(projectID uint, limit int) ([]model.DeployRecord, error) { var records []model.DeployRecord if limit <= 0 { limit = 20 } err := s.DB.Where("project_id = ?", projectID). Order("started_at desc"). Limit(limit). Find(&records).Error return records, err }