feat: image variants cache panel
This commit is contained in:
1 parent
951fef18ba
commit
d768668014
53 files changed
+5809
-46
No files matched your search
@@ -107,6 +107,21 @@ func (i ImageRepository) ExistsByStoredName(storedName string) (bool, error) {
|
||||
return matches > 0, err
|
||||
}
|
||||
|
||||
func (i ImageRepository) ListByStoredNames(storedNames []string) ([]domain.Image, error) {
|
||||
if len(storedNames) == 0 {
|
||||
return []domain.Image{}, nil
|
||||
}
|
||||
paths := make([]string, 0, len(storedNames))
|
||||
for _, storedName := range storedNames {
|
||||
paths = append(paths, domain.PublicPath(storedName))
|
||||
}
|
||||
var models []entity.Image
|
||||
if err := i.db.Where("path IN ?", paths).Order("id asc").Find(&models).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imagesToDomain(models), nil
|
||||
}
|
||||
|
||||
func (i ImageRepository) duplicatedHashes() *gorm.DB {
|
||||
return i.db.Model(&entity.Image{}).
|
||||
Select("sha256").
|
||||
|
||||
@@ -15,6 +15,7 @@ type VariantFileRepository struct {
|
||||
}
|
||||
|
||||
var _ domain.VariantFileRepository = &VariantFileRepository{}
|
||||
var _ domain.VariantCacheRepository = &VariantFileRepository{}
|
||||
|
||||
func NewVariantFileRepository(db *gorm.DB) *VariantFileRepository {
|
||||
return &VariantFileRepository{db}
|
||||
@@ -90,6 +91,76 @@ func (v *VariantFileRepository) TotalsBySource() (map[string]domain.VariantFileT
|
||||
return totals, nil
|
||||
}
|
||||
|
||||
const (
|
||||
unusedCondition = "variant_name IN ? AND COALESCE(last_requested_at, created_at) < ?"
|
||||
topSourcesOrder = "bytes DESC, source_name ASC"
|
||||
)
|
||||
|
||||
func (v *VariantFileRepository) UnusedTotals(filter domain.UnusedFilter) (domain.VariantFileTotals, error) {
|
||||
if len(filter.VariantNames) == 0 {
|
||||
return domain.VariantFileTotals{}, nil
|
||||
}
|
||||
var row struct {
|
||||
Files int
|
||||
Bytes int64
|
||||
}
|
||||
err := v.db.Model(&entity.ImageVariantFile{}).
|
||||
Select("COUNT(*) AS files, COALESCE(SUM(size), 0) AS bytes").
|
||||
Where(unusedCondition, filter.VariantNames, filter.Cutoff.UTC()).
|
||||
Scan(&row).Error
|
||||
if err != nil {
|
||||
return domain.VariantFileTotals{}, err
|
||||
}
|
||||
return domain.VariantFileTotals{Files: row.Files, Bytes: row.Bytes}, nil
|
||||
}
|
||||
|
||||
func (v *VariantFileRepository) TopSources(limit int) ([]domain.SourceTotals, error) {
|
||||
var rows []struct {
|
||||
SourceName string
|
||||
Files int
|
||||
Bytes int64
|
||||
}
|
||||
err := v.db.Model(&entity.ImageVariantFile{}).
|
||||
Select("source_name, COUNT(*) AS files, COALESCE(SUM(size), 0) AS bytes").
|
||||
Group("source_name").
|
||||
Order(topSourcesOrder).
|
||||
Limit(limit).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sources := make([]domain.SourceTotals, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
sources = append(sources, domain.SourceTotals{SourceName: row.SourceName, Files: row.Files, Bytes: row.Bytes})
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func (v *VariantFileRepository) ListPage(filter domain.VariantFileFilter, afterID uint32, limit int) ([]domain.VariantFile, error) {
|
||||
statement := v.db.Where("id > ?", afterID)
|
||||
if filter.VariantName != "" {
|
||||
statement = statement.Where("variant_name = ?", filter.VariantName)
|
||||
}
|
||||
if filter.SourceName != "" {
|
||||
statement = statement.Where("source_name = ?", filter.SourceName)
|
||||
}
|
||||
if filter.Unused != nil {
|
||||
if len(filter.Unused.VariantNames) == 0 {
|
||||
return []domain.VariantFile{}, nil
|
||||
}
|
||||
statement = statement.Where(unusedCondition, filter.Unused.VariantNames, filter.Unused.Cutoff.UTC())
|
||||
}
|
||||
var found []entity.ImageVariantFile
|
||||
if err := statement.Order("id asc").Limit(limit).Find(&found).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]domain.VariantFile, 0, len(found))
|
||||
for _, model := range found {
|
||||
files = append(files, mappers.VariantFileToDomain(model))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (v *VariantFileRepository) Totals() (map[string]domain.VariantFileTotals, error) {
|
||||
var rows []struct {
|
||||
VariantName string
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/image"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type VariantUsageRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
var _ domain.VariantUsageRepository = &VariantUsageRepository{}
|
||||
|
||||
func NewVariantUsageRepository(db *gorm.DB) *VariantUsageRepository {
|
||||
return &VariantUsageRepository{db}
|
||||
}
|
||||
|
||||
func (v *VariantUsageRepository) Record(batch domain.VariantUsageBatch) error {
|
||||
return v.db.Transaction(func(tx *gorm.DB) error {
|
||||
for _, hit := range batch.Hits {
|
||||
if err := recordHit(tx, hit); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, rejection := range batch.Rejections {
|
||||
if err := recordRejections(tx, rejection); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if batch.PruneBefore.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return tx.Where("day < ?", batch.PruneBefore.UTC().Format(domain.RejectionDayLayout)).Delete(&entity.ImageVariantRejectionDay{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func recordHit(tx *gorm.DB, hit domain.VariantHit) error {
|
||||
at := hit.At.UTC()
|
||||
return tx.Model(&entity.ImageVariantFile{}).
|
||||
Where("variant_name = ? AND source_name = ?", hit.VariantName, hit.SourceName).
|
||||
Where("last_requested_at IS NULL OR last_requested_at < ?", at).
|
||||
Update("last_requested_at", at).Error
|
||||
}
|
||||
|
||||
func recordRejections(tx *gorm.DB, rejection domain.RejectionCount) error {
|
||||
row := entity.ImageVariantRejectionDay{Day: rejection.Day.UTC().Format(domain.RejectionDayLayout), Count: rejection.Count}
|
||||
return tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "day"}},
|
||||
DoUpdates: clause.Assignments(map[string]any{"count": gorm.Expr("? + ?", clause.Column{Table: clause.CurrentTable, Name: "count"}, rejection.Count)}),
|
||||
}).Create(&row).Error
|
||||
}
|
||||
|
||||
func (v *VariantUsageRepository) RejectedSince(day time.Time) (int, error) {
|
||||
var total int
|
||||
err := v.db.Model(&entity.ImageVariantRejectionDay{}).
|
||||
Select("COALESCE(SUM(count), 0)").
|
||||
Where("day >= ?", day.UTC().Format(domain.RejectionDayLayout)).
|
||||
Scan(&total).Error
|
||||
return total, err
|
||||
}
|
||||
Reference in new issue
Block a user