Files
archivmail/internal/api/tenant_handlers.go
T
sysops 1d27dc2d8b refactor: große API-Handler-Dateien in fokussierte Module aufteilen
Reine Code-Verschiebung, keine Logikänderung. Betroffen:
- ldap_tenants.go (994 Zeilen) -> ldap_tenants.go (Routing) + ldap_handlers.go
  + tenant_handlers.go + tenant_domain_handlers.go + tenant_logo_handlers.go
  + tenant_helpers.go
- import_handlers.go (621 Zeilen) -> imap_handlers.go + pop3_handlers.go
  + import_helpers.go
- admin_handlers.go (702 Zeilen) -> admin_users_handlers.go +
  admin_status_handlers.go + admin_services_handlers.go +
  admin_security_handlers.go

Lokal kein Go-Build möglich — Verifikation manuell per Funktions- und
Import-Abgleich Alt/Neu (alle Symbole exakt einmal vorhanden). Build
muss vor Deploy auf 192.168.1.131/132 bestätigt werden.
2026-06-21 23:38:57 +02:00

217 lines
5.6 KiB
Go

package api
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"archivmail/internal/audit"
"archivmail/internal/tenantstore"
"archivmail/internal/userstore"
)
// ── Tenant handlers ──────────────────────────────────────────────────────────
func (s *Server) handleListTenants(w http.ResponseWriter, r *http.Request) {
if s.tenantStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant store not available")
return
}
tenants, err := s.tenantStore.List(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to list tenants")
return
}
writeJSON(w, http.StatusOK, tenants)
}
func (s *Server) handleCreateTenant(w http.ResponseWriter, r *http.Request) {
if s.tenantStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant store not available")
return
}
var req struct {
Name string `json:"name"`
Slug string `json:"slug"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
if req.Name == "" || req.Slug == "" {
writeError(w, http.StatusBadRequest, "name and slug are required")
return
}
tenant, err := s.tenantStore.Create(r.Context(), req.Name, req.Slug)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create tenant")
return
}
// Create default users for the new tenant.
type defaultUserCreds struct {
Username string `json:"username"`
Password string `json:"password"`
Role string `json:"role"`
}
type createTenantResponse struct {
*tenantstore.Tenant
DefaultUsers []defaultUserCreds `json:"default_users"`
}
resp := createTenantResponse{Tenant: tenant}
for _, spec := range []struct {
suffix string
role string
}{
{suffix: "admin", role: userstore.RoleDomainAdmin},
{suffix: "auditor", role: userstore.RoleAuditor},
} {
pw, pwErr := tenantRandomPassword()
if pwErr != nil {
writeError(w, http.StatusInternalServerError, "failed to generate password")
return
}
username := req.Slug + "-" + spec.suffix
email := fmt.Sprintf("%s@%s.local", username, req.Slug)
u, uErr := s.users.Create(userstore.CreateUserRequest{
Username: username,
Email: email,
Password: pw,
Role: spec.role,
TenantID: &tenant.ID,
})
if uErr != nil {
writeError(w, http.StatusInternalServerError, "failed to create default user")
return
}
resp.DefaultUsers = append(resp.DefaultUsers, defaultUserCreds{
Username: u.Username,
Password: pw,
Role: spec.role,
})
}
sess := sessionFromCtx(r.Context())
s.audlog.Log(audit.Entry{
EventType: "tenant_created",
Username: sess.Username,
TenantID: sess.TenantID,
IPAddress: s.remoteIP(r),
Success: true,
Detail: "Mandant erstellt: " + req.Name,
})
writeJSON(w, http.StatusCreated, resp)
}
func (s *Server) handleGetTenant(w http.ResponseWriter, r *http.Request) {
if s.tenantStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant store not available")
return
}
id, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
tenant, err := s.tenantStore.Get(r.Context(), id)
if err != nil {
writeError(w, http.StatusNotFound, "tenant not found")
return
}
writeJSON(w, http.StatusOK, tenant)
}
func (s *Server) handleUpdateTenant(w http.ResponseWriter, r *http.Request) {
if s.tenantStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant store not available")
return
}
id, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
var req struct {
Name string `json:"name"`
Active *bool `json:"active"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
// Load existing to keep unset fields
existing, err := s.tenantStore.Get(r.Context(), id)
if err != nil {
writeError(w, http.StatusNotFound, "tenant not found")
return
}
name := existing.Name
active := existing.Active
if req.Name != "" {
name = req.Name
}
if req.Active != nil {
active = *req.Active
}
tenant, err := s.tenantStore.Update(r.Context(), id, name, active)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to update tenant")
return
}
writeJSON(w, http.StatusOK, tenant)
}
func (s *Server) handleDeleteTenant(w http.ResponseWriter, r *http.Request) {
if s.tenantStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant store not available")
return
}
id, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
sess := sessionFromCtx(r.Context())
if err := s.tenantStore.Delete(r.Context(), id); err != nil {
writeError(w, http.StatusInternalServerError, "failed to delete tenant")
return
}
s.audlog.Log(audit.Entry{
EventType: "tenant_deleted",
Username: sess.Username,
TenantID: sess.TenantID,
IPAddress: s.remoteIP(r),
Success: true,
Detail: "Mandant gelöscht: " + strconv.FormatInt(id, 10),
})
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) handleListTenantUsers(w http.ResponseWriter, r *http.Request) {
tenantID, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
users, err := s.users.ListByTenant(r.Context(), tenantID)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to list tenant users")
return
}
if users == nil {
users = []*userstore.User{}
}
writeJSON(w, http.StatusOK, users)
}