package repository import ( "context" "fmt" "time" "github.com/geo-platform/tenant-api/internal/tenant/repository/generated" ) type ImageStorageUsage struct { UsedBytes int64 UpdatedAt time.Time } type QuotaReserveResult struct { UsedBytes int64 QuotaBytes int64 } type ImageUsageRepository interface { EnsureRow(ctx context.Context, tenantID int64) error Get(ctx context.Context, tenantID int64) (*ImageStorageUsage, error) Release(ctx context.Context, tenantID, releaseBytes int64) error AtomicReserve(ctx context.Context, tenantID, sizeBytes int64) (*QuotaReserveResult, error) } type imageUsageRepository struct { q generated.Querier db generated.DBTX } func NewImageUsageRepository(db generated.DBTX) ImageUsageRepository { return &imageUsageRepository{q: newQuerier(db), db: db} } func (r *imageUsageRepository) EnsureRow(ctx context.Context, tenantID int64) error { return r.q.EnsureImageStorageUsageRow(ctx, tenantID) } func (r *imageUsageRepository) Get(ctx context.Context, tenantID int64) (*ImageStorageUsage, error) { row, err := r.q.GetImageStorageUsage(ctx, tenantID) if err != nil { return nil, err } return &ImageStorageUsage{ UsedBytes: row.UsedBytes, UpdatedAt: timeFromTimestamp(row.UpdatedAt), }, nil } func (r *imageUsageRepository) Release(ctx context.Context, tenantID, releaseBytes int64) error { return r.q.ReleaseImageStorageQuota(ctx, generated.ReleaseImageStorageQuotaParams{ ReleaseBytes: releaseBytes, TenantID: tenantID, }) } func (r *imageUsageRepository) AtomicReserve(ctx context.Context, tenantID, sizeBytes int64) (*QuotaReserveResult, error) { row := r.db.QueryRow(ctx, ` WITH plan AS ( SELECT COALESCE((p.quota_policy_json ->> 'image_storage_bytes')::BIGINT, 104857600) AS quota_bytes FROM tenant_plan_subscriptions s JOIN plans p ON p.id = s.plan_id WHERE s.tenant_id = $1 AND s.status = 'active' AND s.deleted_at IS NULL AND s.end_at > NOW() LIMIT 1 ) UPDATE tenant_image_storage_usage u SET used_bytes = u.used_bytes + $2, updated_at = NOW() FROM plan WHERE u.tenant_id = $1 AND u.used_bytes + $2 <= plan.quota_bytes RETURNING u.used_bytes, plan.quota_bytes `, tenantID, sizeBytes) var result QuotaReserveResult if err := row.Scan(&result.UsedBytes, &result.QuotaBytes); err != nil { return nil, fmt.Errorf("reserve image storage quota: %w", err) } return &result, nil }