package models import ( "context" "encoding/json" "fmt" cairnapi "github.com/mattnite/cairn/internal/api" "gorm.io/gorm" ) type CreateArtifactParams struct { RepositoryID uint CommitID uint BuildID *uint CampaignID *uint Type string BlobKey string BlobSize int64 CrashMessage *string StackTrace *string Tags json.RawMessage Metadata json.RawMessage } func CreateArtifact(ctx context.Context, db *gorm.DB, p CreateArtifactParams) (*cairnapi.Artifact, error) { if p.Tags == nil { p.Tags = json.RawMessage("{}") } if p.Metadata == nil { p.Metadata = json.RawMessage("{}") } a := &Artifact{ RepositoryID: p.RepositoryID, CommitID: p.CommitID, BuildID: p.BuildID, CampaignID: p.CampaignID, Type: p.Type, BlobKey: p.BlobKey, BlobSize: p.BlobSize, CrashMessage: p.CrashMessage, StackTrace: p.StackTrace, Tags: p.Tags, Metadata: p.Metadata, } if err := db.WithContext(ctx).Create(a).Error; err != nil { return nil, fmt.Errorf("creating artifact: %w", err) } return enrichArtifact(ctx, db, *a) } func GetArtifact(ctx context.Context, db *gorm.DB, id uint) (*cairnapi.Artifact, error) { a := &Artifact{} if err := db.WithContext(ctx).First(a, id).Error; err != nil { return nil, fmt.Errorf("getting artifact: %w", err) } return enrichArtifact(ctx, db, *a) } type ListArtifactsParams struct { RepositoryID *uint CommitSHA string Type string SignatureID *uint CampaignID *uint Limit int Offset int } func ListArtifacts(ctx context.Context, db *gorm.DB, p ListArtifactsParams) ([]cairnapi.Artifact, int64, error) { if p.Limit <= 0 { p.Limit = 50 } query := db.WithContext(ctx).Model(&Artifact{}) if p.RepositoryID != nil { query = query.Where("repository_id = ?", *p.RepositoryID) } if p.Type != "" { query = query.Where("type = ?", p.Type) } if p.SignatureID != nil { query = query.Where("crash_signature_id = ?", *p.SignatureID) } if p.CampaignID != nil { query = query.Where("campaign_id = ?", *p.CampaignID) } if p.CommitSHA != "" { query = query.Joins("JOIN commits ON commits.id = artifacts.commit_id").Where("commits.sha = ?", p.CommitSHA) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("counting artifacts: %w", err) } var dbArtifacts []Artifact if err := query.Order("created_at DESC").Limit(p.Limit).Offset(p.Offset).Find(&dbArtifacts).Error; err != nil { return nil, 0, fmt.Errorf("listing artifacts: %w", err) } artifacts := make([]cairnapi.Artifact, 0, len(dbArtifacts)) for _, m := range dbArtifacts { a, err := enrichArtifact(ctx, db, m) if err != nil { return nil, 0, err } artifacts = append(artifacts, *a) } return artifacts, total, nil } func SearchArtifacts(ctx context.Context, db *gorm.DB, query string, limit, offset int) ([]cairnapi.Artifact, int64, error) { if limit <= 0 { limit = 50 } q := db.WithContext(ctx).Model(&Artifact{}).Where( "type ILIKE ? OR crash_message ILIKE ? OR stack_trace ILIKE ?", "%"+query+"%", "%"+query+"%", "%"+query+"%", ) var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("counting search results: %w", err) } var dbArtifacts []Artifact if err := q.Order("created_at DESC").Limit(limit).Offset(offset).Find(&dbArtifacts).Error; err != nil { return nil, 0, fmt.Errorf("searching artifacts: %w", err) } artifacts := make([]cairnapi.Artifact, 0, len(dbArtifacts)) for _, m := range dbArtifacts { a, err := enrichArtifact(ctx, db, m) if err != nil { return nil, 0, err } artifacts = append(artifacts, *a) } return artifacts, total, nil } func enrichArtifact(ctx context.Context, db *gorm.DB, model Artifact) (*cairnapi.Artifact, error) { repo := &Repository{} if err := db.WithContext(ctx).First(repo, model.RepositoryID).Error; err != nil { return nil, fmt.Errorf("loading artifact repository: %w", err) } commit := &Commit{} if err := db.WithContext(ctx).First(commit, model.CommitID).Error; err != nil { return nil, fmt.Errorf("loading artifact commit: %w", err) } artifact := artifactFromModel(model) artifact.RepoName = repo.Name artifact.CommitSHA = commit.SHA return &artifact, nil } func artifactFromModel(m Artifact) cairnapi.Artifact { return cairnapi.Artifact{ ID: m.ID, RepositoryID: m.RepositoryID, CommitID: m.CommitID, BuildID: m.BuildID, CampaignID: m.CampaignID, CrashSignatureID: m.CrashSignatureID, Type: m.Type, BlobKey: m.BlobKey, BlobSize: m.BlobSize, CrashMessage: m.CrashMessage, StackTrace: m.StackTrace, Tags: m.Tags, Metadata: m.Metadata, CreatedAt: m.CreatedAt, } }