package db import ( "brunel/domain" "time" "github.com/alphadose/haxmap" "github.com/jinzhu/now" "gorm.io/gorm" "gorm.io/gorm/clause" ) const MaxBatchSize = 1999 type Repository struct { db *gorm.DB } func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } func (r *Repository) GetUser(username string) (domain.User, error) { var user domain.User tx := r.db.Where("username = ?", username).First(&user) return user, tx.Error } func (r *Repository) CreateUser(user domain.User) error { tx := r.db.Create(&user) return tx.Error } func (r *Repository) UpdateUser(user domain.User) error { tx := r.db.Save(&user) return tx.Error } func (r *Repository) CreateSession(session domain.Session) error { tx := r.db.Create(&session) return tx.Error } func (r *Repository) GetSession(token string) (domain.Session, error) { now := now.BeginningOfMinute() var session domain.Session tx := r.db.Where("token = ? AND expiry > ?", token, now).First(&session) return session, tx.Error } func (r *Repository) DeleteSession(token string) error { tx := r.db.Delete(&domain.Session{}, "token = ?", token) return tx.Error } func (r *Repository) DeleteExpiredSessions() error { now := now.BeginningOfMinute() tx := r.db.Where("expiry < ?", now).Delete(&domain.Session{}) return tx.Error } func (r *Repository) UpdatePackage(pkg domain.SourcePackage) error { dto := sourcePackageToDto(pkg) tx := r.db.Save(&dto) return tx.Error } func (r *Repository) GetPackages() ([]domain.SourcePackage, error) { var allPackages []domain.SourcePackage offset := 0 for { var batchPackages []domain.SourcePackageDTO tx := r.db.Preload(clause.Associations). Offset(offset). Limit(MaxBatchSize). Find(&batchPackages) if tx.Error != nil { return nil, tx.Error } // Convert DTOs to domain objects for _, v := range batchPackages { allPackages = append(allPackages, sourcePackageDtoToDomain(v)) } // If we've retrieved fewer records than MaxBatchSize, we're done if len(batchPackages) < MaxBatchSize { break } // Move to the next batch offset += MaxBatchSize } return allPackages, nil } func (r *Repository) GetPackage(name string) (domain.SourcePackage, error) { var pkg domain.SourcePackageDTO tx := r.db.Where("name = ?", name).First(&pkg) if tx.Error != nil { return domain.SourcePackage{}, tx.Error } return sourcePackageDtoToDomain(pkg), nil } func (r *Repository) SavePackages(pkgs *haxmap.Map[string, domain.SourcePackage]) error { var packs []domain.SourcePackageDTO pkgs.ForEach(func(k string, v domain.SourcePackage) bool { packs = append(packs, sourcePackageToDto(v)) return true }) return r.saveInBatches(packs) } func (r *Repository) saveInBatches(packs []domain.SourcePackageDTO) error { for i := 0; i < len(packs); i += MaxBatchSize { end := i + MaxBatchSize if end > len(packs) { end = len(packs) } batch := packs[i:end] if err := r.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&batch).Error; err != nil { return err } } return nil } func (r *Repository) DropPackages() error { tx := r.db.Where("1 = 1").Delete(&domain.SourcePackageDTO{}) if tx.Error != nil { return tx.Error } tx = r.db.Where("1 = 1").Delete(&domain.PackageInfo{}) return tx.Error } func (r *Repository) UpdateLastUpdateTime(time time.Time) error { val := &domain.TimeContainer{ Time: time, ID: "lastupdatetime", } tx := r.db.Save(val) return tx.Error } func (r *Repository) UpdateBuildState(state domain.BuildState) error { tx := r.db.Save(&state) return tx.Error } func (r *Repository) GetBuildState(name string) (domain.BuildState, error) { var state domain.BuildState tx := r.db.Where("name = ?", name).First(&state) return state, tx.Error } func (r *Repository) GetFailedBuilds() ([]domain.BuildState, error) { var states []domain.BuildState tx := r.db.Where("status = ? AND build_number > 1", string(domain.Error)).Find(&states) return states, tx.Error } func sourcePackageToDto(pkg domain.SourcePackage) domain.SourcePackageDTO { dto := domain.SourcePackageDTO{ Name: pkg.Name, Version: pkg.Version, NewVersion: pkg.NewVersion, Status: pkg.Status, LastBuildStatus: pkg.LastBuildStatus, BuildAttempts: pkg.BuildAttempts, Has32bit: pkg.Has32bit, Packages: make([]domain.PackageInfo, 0), } pkg.Packages.ForEach(func(k string, v domain.PackageInfo) bool { dto.Packages = append(dto.Packages, v) return true }) return dto } func sourcePackageDtoToDomain(dto domain.SourcePackageDTO) domain.SourcePackage { pkg := domain.SourcePackage{ Name: dto.Name, Version: dto.Version, NewVersion: dto.NewVersion, Status: dto.Status, LastBuildStatus: dto.LastBuildStatus, BuildAttempts: dto.BuildAttempts, Has32bit: dto.Has32bit, Packages: haxmap.New[string, domain.PackageInfo](), } for _, v := range dto.Packages { pkg.Packages.Set(v.PackageName, v) } return pkg }