package manifests import ( "bytes" "context" "encoding/json" "errors" "fmt" "github.com/samber/lo" "github.com/sirupsen/logrus" "gorm.io/gorm" "io" "net/http" "nf-repo/internal/interfaces" "nf-repo/internal/model" "nf-repo/internal/util/rerr" "nf-repo/internal/util/tools" ) type PackageManifest struct { Id uint64 `json:"id" gorm:"primaryKey;column:id"` CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` Repo string `json:"repo" gorm:"uniqueIndex:repo_tag_idx;column:repo"` Target string `json:"target" gorm:"uniqueIndex:repo_tag_idx;column:target"` Digest string `json:"digest" gorm:"unique;column:digest"` ContentType string `json:"content_type" gorm:"column:content_type"` Content []byte `json:"content" gorm:"column:content;type:bytes"` } type dbManifests struct { db interfaces.Database } func (m *dbManifests) Get(ctx context.Context, repo string, target string) (io.ReadCloser, string, *rerr.RepositoryError) { var ( err error pm = new(PackageManifest) h model.Hash tx = m.db.TX(tools.Timeout(5)).Model(pm) ) if h, err = model.NewHash(target); err == nil { tx = tx.Where("digest", h.String()) } else { tx = tx.Where("repo", repo). Where("target", target) } if err = tx. Take(pm). Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, "", &rerr.RepositoryError{ Status: http.StatusNotFound, Code: "NAME_UNKNOWN", Message: fmt.Sprintf("Unknown name: %s@%s", repo, target), } } return nil, "", rerr.ErrInternal(err) } return io.NopCloser(bytes.NewReader(pm.Content)), pm.ContentType, nil } func (m *dbManifests) Put(ctx context.Context, repo string, target string, digest string, mf *model.Manifest) *rerr.RepositoryError { var ( err error pm = &PackageManifest{ Repo: repo, Target: target, Digest: digest, ContentType: mf.ContentType, Content: mf.Blob, } ) // todo on conflict if err = m.db.TX(tools.Timeout(5)).Create(pm).Error; err == nil { return nil } logrus. WithField("path", "dbManifests.Put.Create"). WithField("err", err.Error()). Trace() if err = m.db.TX(tools.Timeout(5)).Model(&PackageManifest{}). Where("(repo = ? AND target = ?) OR (digest = ?)", repo, target, digest). Updates(map[string]any{ "repo": repo, "target": target, "digest": digest, "content_type": mf.ContentType, "content": mf.Blob, }). Error; err != nil { logrus. WithField("path", "dbManifests.Put.Updates"). WithField("err", err.Error()). Debug() return rerr.ErrInternal(err) } return nil } func (m *dbManifests) Delete(ctx context.Context, repo string, target string) *rerr.RepositoryError { var ( err error ) if err = m.db.TX(tools.Timeout(5)). Where("repo", repo). Where("target", target). Delete(&PackageManifest{}). Error; err != nil { return rerr.ErrInternal(err) } return nil } func (m *dbManifests) Catelog(ctx context.Context, limit int, last int) (*model.Catalog, *rerr.RepositoryError) { var ( err error list = make([]*PackageManifest, 0) ) if err = m.db.TX(tools.Timeout(5)).Model(&PackageManifest{}). Order("updated_at"). Offset(last). Limit(limit). Find(&list). Error; err != nil { return nil, rerr.ErrInternal(err) } return &model.Catalog{ Repos: lo.Map(list, func(item *PackageManifest, index int) string { return item.Repo }), }, nil } func (m *dbManifests) Tags(ctx context.Context, repo string, limit, last int) (*model.Tag, *rerr.RepositoryError) { var ( err error list = make([]*PackageManifest, 0) ) if err = m.db.TX(tools.Timeout(5)).Model(&PackageManifest{}). Where("repo", repo). Order("updated_at"). Offset(last). Limit(limit). Find(&list). Error; err != nil { return nil, rerr.ErrInternal(err) } return &model.Tag{ Name: repo, Tags: lo.Map(list, func(item *PackageManifest, index int) string { return item.Target }), }, nil } func (m *dbManifests) Referrers(ctx context.Context, repo string, target string) (*model.IndexManifest, *rerr.RepositoryError) { var ( err error pm = new(PackageManifest) manifest = &model.IndexManifest{} tx = m.db.TX(tools.Timeout(5)).Model(pm) ) h, err := model.NewHash(target) if err != nil { tx = tx.Where("repo", repo).Where("digest", h.String()) } else { tx = tx.Where("repo", repo).Where("target", target) } if err = m.db.TX(tools.Timeout(5)).Model(pm). Take(pm). Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, &rerr.RepositoryError{ Status: http.StatusNotFound, Code: "NAME_UNKNOWN", Message: fmt.Sprintf("Unknown name: %s@%s", repo, target), } } logrus. WithField("path", "dbManifests.Referrers.Take"). WithField("repo", repo). WithField("target", target). WithField("err", err.Error()). Debug() return nil, rerr.ErrInternal(err) } if err = json.Unmarshal(pm.Content, manifest); err != nil { logrus. WithField("path", "dbManifests.Referrers.Unmarshal"). WithField("repo", repo). WithField("target", target). WithField("err", err.Error()). Debug() return nil, rerr.ErrInternal(err) } return manifest, nil } func NewManifestDBHandler(tx interfaces.Database) interfaces.ManifestHandler { var ( err error ) if err = tx.TX(tools.Timeout(5)).AutoMigrate(&PackageManifest{}); err != nil { logrus. WithField("path", "NewManifestDBHandler"). WithField("method", "AutoMigrate"). Panic(err) } return &dbManifests{db: tx} }