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.
217 lines
5.6 KiB
Go
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)
|
|
}
|