Files
nexarch/mail/internal/storage/usagecounter.go
T
sysops 1825387603 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.
2026-09-02 23:47:32 +02:00

56 lines
1.6 KiB
Go

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
}