feat: add tenant + auth backend endpoints (Phase 1A)

Tenant management:
- POST /api/tenants — create tenant (creator becomes owner)
- GET /api/tenants — list tenants for authenticated user
- GET /api/tenants/:id — tenant details with access check
- POST /api/tenants/:id/invite — invite user by email (owner/admin)
- DELETE /api/tenants/:id/members/:uid — remove member
- GET /api/tenants/:id/members — list members

New packages:
- internal/services/tenant_service.go — CRUD on tenants + user_tenants
- internal/handlers/tenant_handler.go — HTTP handlers with auth checks
- internal/auth/tenant_resolver.go — X-Tenant-ID header middleware,
  defaults to user's first tenant for scoped routes

Authorization: owners/admins can invite and remove members. Cannot
remove the last owner. Users can remove themselves. TenantResolver
applies to resource routes (cases, deadlines, etc.) but not tenant
management routes.
This commit is contained in:
m
2026-03-25 13:27:39 +01:00
parent 8049ea3c63
commit 0b6bab8512
7 changed files with 808 additions and 6 deletions

View File

@@ -4,23 +4,48 @@ import (
"encoding/json"
"net/http"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
"github.com/jmoiron/sqlx"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/handlers"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
)
func New(db *sqlx.DB, authMW *auth.Middleware) http.Handler {
mux := http.NewServeMux()
// Services
tenantSvc := services.NewTenantService(db)
// Middleware
tenantResolver := auth.NewTenantResolver(tenantSvc)
// Handlers
tenantH := handlers.NewTenantHandler(tenantSvc)
// Public routes
mux.HandleFunc("GET /health", handleHealth(db))
// Authenticated API routes
api := http.NewServeMux()
api.HandleFunc("GET /api/cases", placeholder("cases"))
api.HandleFunc("GET /api/deadlines", placeholder("deadlines"))
api.HandleFunc("GET /api/appointments", placeholder("appointments"))
api.HandleFunc("GET /api/documents", placeholder("documents"))
// Tenant management (no tenant resolver — these operate across tenants)
api.HandleFunc("POST /api/tenants", tenantH.CreateTenant)
api.HandleFunc("GET /api/tenants", tenantH.ListTenants)
api.HandleFunc("GET /api/tenants/{id}", tenantH.GetTenant)
api.HandleFunc("POST /api/tenants/{id}/invite", tenantH.InviteUser)
api.HandleFunc("DELETE /api/tenants/{id}/members/{uid}", tenantH.RemoveMember)
api.HandleFunc("GET /api/tenants/{id}/members", tenantH.ListMembers)
// Tenant-scoped routes (require tenant context)
scoped := http.NewServeMux()
scoped.HandleFunc("GET /api/cases", placeholder("cases"))
scoped.HandleFunc("GET /api/deadlines", placeholder("deadlines"))
scoped.HandleFunc("GET /api/appointments", placeholder("appointments"))
scoped.HandleFunc("GET /api/documents", placeholder("documents"))
// Wire: auth -> tenant routes go directly, scoped routes get tenant resolver
api.Handle("/api/", tenantResolver.Resolve(scoped))
mux.Handle("/api/", authMW.RequireAuth(api))