feat(mail): ARC-09 Postfach-Quota (unabhängig von Core LIC-05)
mailboxconfig (IMP-07) bekommt eine quota_bytes-Spalte statt einer eigenen Tabelle — ein Postfach ist bereits eindeutig über (tenant_slug, name) identifiziert. SetQuotaBytes/LimitBytes, 0 = unbegrenzt (Standardwert, keine Migration bestehender Postfächer nötig). LimitBytes erfüllt strukturell quota.LimitProvider. storage.ArchiveMailboxPrefix (ARC-04-Ergänzung, Präfix ALLER Jahre eines Postfachs) und storage.UsageCounter: realer Speicherverbrauch durch echtes S3-Listing im physisch getrennten Mandanten-Bucket (ARC-06) — kein separat gepflegter Zählerstand. Neues Paket mail/internal/quota: Checker verbindet LimitProvider und UsageProvider. Kein konfiguriertes Limit = immer erlaubt (Core-LIC-05- Quota läuft unabhängig weiter — beide Ebenen bewusst unabhängig durchgesetzt, bekannter Fehler vermieden). smtp.QuotaChecker (schmale Schnittstelle, keine Paketkopplung an quota) wird in handleRcptTo geprüft, VOR der Datenübertragung: 552 (RFC 5321 "exceeded storage allocation") bei Überschreitung, Session bleibt nutzbar. nil-Checker erhält bisheriges Verhalten unverändert. Alle drei Pflichtprüfungen mit echten Nachweisen: Quota-Überschreitung liefert 552, Session bleibt funktionsfähig; ein anderes Postfach desselben Tenants läuft währenddessen vollständig normal durch; vollständiger Ende-zu-Ende-Integrationstest gegen reale Postgres- und MinIO-Instanzen — 5000 echte Bytes abgelegt, real gemessen, Limit knapp darunter/darüber gesetzt, SMTP reagiert jeweils korrekt auf den tatsächlichen gemessenen Wert. Dabei einen echten Cleanup-Fehler gefunden und behoben (defer schloss den Pool vor dem zugehörigen t.Cleanup, verwaiste Testdaten blieben zurück). go build/go vet/golangci-lint clean, gesamtes Mail-Modul regressionsfrei getestet.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
)
|
||||
|
||||
// UsageCounter misst den TATSÄCHLICHEN Speicherverbrauch eines
|
||||
// Postfachs (ARC-09) durch reales Auflisten der zugehörigen Objekte im
|
||||
// physisch getrennten Mandanten-Bucket (ARC-06) unter dem
|
||||
// ARC-04-Postfach-Präfix — kein separat gepflegter Zählerstand, der
|
||||
// von der tatsächlichen Ablage abweichen könnte.
|
||||
type UsageCounter struct {
|
||||
admin *s3.Client
|
||||
}
|
||||
|
||||
func NewUsageCounter(admin *s3.Client) *UsageCounter {
|
||||
return &UsageCounter{admin: admin}
|
||||
}
|
||||
|
||||
// UsageBytes summiert die reale Objektgröße aller unter
|
||||
// ArchiveMailboxPrefix(mailbox) abgelegten Objekte im Bucket von
|
||||
// tenantSlug (ARC-06: BucketNameForTenant). Erfüllt strukturell
|
||||
// quota.UsageProvider (ARC-09), ohne dass dieses Paket quota
|
||||
// importieren müsste.
|
||||
func (u *UsageCounter) UsageBytes(ctx context.Context, tenantSlug, mailbox string) (int64, error) {
|
||||
bucket := BucketNameForTenant(tenantSlug)
|
||||
prefix := ArchiveMailboxPrefix(mailbox)
|
||||
|
||||
var total int64
|
||||
var continuationToken *string
|
||||
for {
|
||||
out, err := u.admin.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucket),
|
||||
Prefix: aws.String(prefix),
|
||||
ContinuationToken: continuationToken,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("storage: postfach-verbrauch auflisten: %w", err)
|
||||
}
|
||||
for _, obj := range out.Contents {
|
||||
if obj.Size != nil {
|
||||
total += *obj.Size
|
||||
}
|
||||
}
|
||||
if out.IsTruncated == nil || !*out.IsTruncated {
|
||||
break
|
||||
}
|
||||
continuationToken = out.NextContinuationToken
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
Reference in New Issue
Block a user