package manifests import ( "bytes" "context" "encoding/json" "errors" "fmt" "github.com/samber/lo" "github.com/sirupsen/logrus" "gorm.io/gorm" "gorm.io/gorm/clause" "io" "net/http" "nf-repo/internal/interfaces" "nf-repo/internal/model" "nf-repo/internal/util/rerr" "nf-repo/internal/util/tools" ) type pm struct { model.PackageManifest Digest string `json:"digest" gorm:"column:digest"` Size int `json:"size" gorm:"column:size"` } 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 hash model.Hash pd = new(model.PackageDigest) ) if hash, err = model.NewHash(target); err == nil { if err = m.db.TX(tools.Timeout(5)). Model(&model.PackageDigest{}). Where("digest", hash.String()). Take(pd). 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(pd.Content)), pd.ContentType, nil } var pm = new(model.PackageManifest) if err = m.db.TX(tools.Timeout(5)). Model(&model.PackageManifest{}). Where("repo", repo). Where("tag", target). 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) } if err = m.db.TX(tools.Timeout(5)). Model(&model.PackageDigest{}). Where("id", pm.DigestId). Take(pd). Error; err != nil { logrus. WithField("path", "dbManifests.Get"). WithField("digest", pm.DigestId). WithField("err", err.Error()). Error() return nil, "", rerr.ErrInternal(err) } return io.NopCloser(bytes.NewReader(pd.Content)), pd.ContentType, nil } func (m *dbManifests) Put(ctx context.Context, repo string, tag string, digest string, mf *model.RepoSimpleManifest) *rerr.RepositoryError { var ( err error pm = &model.PackageManifest{ Repo: repo, Tag: tag, } pd = &model.PackageDigest{ Digest: digest, ContentType: mf.ContentType, Content: mf.Blob, } blob = new(model.RepoSimpleManifestBlob) ) if err = json.Unmarshal(mf.Blob, blob); err != nil { return rerr.ErrInternal(err) } pd.Size = blob.CountSize() if err = m.db.TX(tools.Timeout(5)). Clauses(clause.Returning{}). Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "digest"}}, DoUpdates: clause.Set{ { Column: clause.Column{Name: "content_type"}, Value: mf.ContentType, }, { Column: clause.Column{Name: "content"}, Value: mf.Blob, }, { Column: clause.Column{Name: "size"}, Value: pd.Size, }, }}). Create(pd). Error; err != nil { logrus. WithField("path", "dbManifests.Put.Create"). WithField("err", err.Error()). Error() return rerr.ErrInternal(err) } pm.DigestId = pd.Id if err = m.db.TX(tools.Timeout(5)).Create(pm).Error; err == nil { return nil } if err = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}). Where("(repo = ? AND tag = ?)", repo, tag). Updates(map[string]any{ "repo": repo, "tag": tag, "digest_id": pm.DigestId, }). 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(&model.PackageManifest{}). Error; err != nil { return rerr.ErrInternal(err) } return nil } func (m *dbManifests) Catalog(ctx context.Context, limit int, last int, keyword string) (*model.Catalog, *rerr.RepositoryError) { var ( err error list = make([]*pm, 0) tx = m.db.TX(tools.Timeout(5)) ) tx = tx.Model(&model.PackageManifest{}). Select("\"package_manifests\".*", "\"pd\".\"digest\" as digest", "\"pd\".\"size\" as size") if keyword != "" { k := fmt.Sprintf("%%%s%%", keyword) tx = tx.Where("package_manifests.repo like ?", k) } if err = tx.Group("\"package_manifests\".\"repo\""). Joins("LEFT JOIN package_digests pd on \"pd\".\"id\" = \"package_manifests\".\"digest_id\""). Order("updated_at DESC, tag = 'latest' DESC"). Offset(last). Limit(limit). Find(&list). Error; err != nil { return nil, rerr.ErrInternal(err) } return &model.Catalog{ Repositories: lo.Map(list, func(item *pm, index int) string { return item.Repo }), Repos: lo.Map(list, func(item *pm, index int) *model.PackageManifest { item.PackageManifest.Digest = item.Digest item.PackageManifest.Size = item.Size return &item.PackageManifest }), }, nil } func (m *dbManifests) Tags(ctx context.Context, repo string, limit, last int, keyword string) (*model.Tag, *rerr.RepositoryError) { var ( err error list = make([]*pm, 0) tx = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}).Select("\"package_manifests\".*", "\"pd\".\"digest\" as digest") txc = m.db.TX(tools.Timeout(5)).Model(&model.PackageManifest{}).Select("COUNT(id)") total int ) if keyword != "" { k := fmt.Sprintf("%%%s%%", keyword) tx = tx.Where("\"package_manifests\".\"tag\" like ?", k) txc = txc.Where("tag like ?", k) } if err = txc.Find(&total).Error; err != nil { return nil, rerr.ErrInternal(err) } if err = tx. Where("package_manifests.repo", repo). Joins("LEFT JOIN package_digests pd on \"pd\".\"id\" = \"package_manifests\".\"digest_id\""). 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 *pm, index int) string { return item.Tag }), RepoTags: lo.Map(list, func(item *pm, index int) *model.PackageManifest { item.PackageManifest.Digest = item.Digest return &item.PackageManifest }), Total: total, }, nil } func (m *dbManifests) Referrers(ctx context.Context, repo string, target string) (*model.IndexManifest, *rerr.RepositoryError) { var ( err error pm = new(model.PackageManifest) pd = new(model.PackageDigest) manifest = &model.IndexManifest{} tx = m.db.TX(tools.Timeout(5)) hash model.Hash ) if hash, err = model.NewHash(target); err == nil { if err = tx.Model(&model.PackageDigest{}). Where("repo", repo). Where("digest", hash.String()). Take(pd). 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) } } else { if err = tx.Model(&model.PackageManifest{}). Where("repo", repo). Where("tag", target). Take(pd). 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). Error() return nil, rerr.ErrInternal(err) } if err = tx.Model(&model.PackageDigest{}). Where("digest_id", pm.DigestId). Take(pd). Error; err != nil { logrus. WithField("path", "dbManifests.Referrers.Take"). WithField("repo", repo). WithField("target", target). Error() return nil, rerr.ErrInternal(err) } } if err = json.Unmarshal(pd.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( &model.PackageManifest{}, &model.PackageDigest{}, ); err != nil { logrus. WithField("path", "NewManifestDBHandler"). WithField("method", "AutoMigrate"). Panic(err) } return &dbManifests{db: tx} }