feat(PROJ-29): Tenant-Quotas & Usage-Limits vollständig implementiert
- storage/quota.go: SQL-Bug gefixt (emails.size → size_bytes, email_refs JOIN)
- tenantstore/quota.go: GetUsage nutzt jetzt email_refs JOIN für korrekte Tenant-Isolation
- smtpd: ErrQuotaExceeded → SMTP 452 statt 554 (MTA-retry statt permanent reject)
- admin_handlers: handleCreateUser prüft max_users-Quota → HTTP 402 bei Überschreitung
- quota_handlers: handleGetTenantUsage gibt jetzt warnings-Feld mit soft-limit-Prozenten zurück
- server.go: spec-konforme Alias-Route GET /api/admin/tenants/{id}/usage registriert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
// handleGetTenantUsage returns current quota config and usage for a tenant.
|
||||
// GET /api/admin/tenant/{id}/quota — superadmin only (PROJ-29).
|
||||
// GET /api/admin/tenant/{id}/quota — superadmin only (PROJ-29).
|
||||
// GET /api/admin/tenants/{id}/usage — superadmin only (PROJ-29, spec-conformant alias).
|
||||
func (s *Server) handleGetTenantUsage(w http.ResponseWriter, r *http.Request) {
|
||||
tenantID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -32,9 +33,48 @@ func (s *Server) handleGetTenantUsage(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Compute soft-limit warnings (≥80% = soft, ≥100% = hard).
|
||||
type warnings struct {
|
||||
StoragePct *float64 `json:"storage_pct"`
|
||||
UsersPct *float64 `json:"users_pct"`
|
||||
EmailsPct *float64 `json:"emails_pct"`
|
||||
SoftLimitReached bool `json:"soft_limit_reached"`
|
||||
}
|
||||
w80 := warnings{}
|
||||
softReached := false
|
||||
if quota.MaxStorageBytes != nil && *quota.MaxStorageBytes > 0 {
|
||||
pct := float64(usage.StorageBytes) / float64(*quota.MaxStorageBytes) * 100
|
||||
w80.StoragePct = &pct
|
||||
if pct >= 80 {
|
||||
softReached = true
|
||||
}
|
||||
}
|
||||
if quota.MaxUsers != nil && *quota.MaxUsers > 0 {
|
||||
pct := float64(usage.UserCount) / float64(*quota.MaxUsers) * 100
|
||||
w80.UsersPct = &pct
|
||||
if pct >= 80 {
|
||||
softReached = true
|
||||
}
|
||||
}
|
||||
if quota.MaxEmails != nil && *quota.MaxEmails > 0 {
|
||||
pct := float64(usage.EmailCount) / float64(*quota.MaxEmails) * 100
|
||||
w80.EmailsPct = &pct
|
||||
if pct >= 80 {
|
||||
softReached = true
|
||||
}
|
||||
}
|
||||
w80.SoftLimitReached = softReached
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"quota": quota,
|
||||
"usage": usage,
|
||||
"storage_bytes": usage.StorageBytes,
|
||||
"user_count": usage.UserCount,
|
||||
"email_count": usage.EmailCount,
|
||||
"quotas": map[string]interface{}{
|
||||
"max_storage_bytes": quota.MaxStorageBytes,
|
||||
"max_users": quota.MaxUsers,
|
||||
"max_emails": quota.MaxEmails,
|
||||
},
|
||||
"warnings": w80,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user