feat: add document upload/download backend (Phase 2K)

- StorageClient for Supabase Storage REST API (upload, download, delete)
- DocumentService with CRUD operations + storage integration
- DocumentHandler with multipart form upload support (50MB limit)
- Routes: GET/POST /api/cases/{id}/documents, GET/DELETE /api/documents/{docId}
- file_path format: {tenant_id}/{case_id}/{uuid}_{filename}
- Case events logged on upload/delete
- Added SUPABASE_SERVICE_KEY to config for server-side storage access
- Fixed pre-existing duplicate writeJSON/writeError in appointments.go
This commit is contained in:
m
2026-03-25 13:40:19 +01:00
parent 2c16f26448
commit 9bd8cc9e07
8 changed files with 479 additions and 30 deletions

View File

@@ -7,11 +7,12 @@ import (
"github.com/jmoiron/sqlx"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
"mgit.msbls.de/m/KanzlAI-mGMT/internal/config"
"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 {
func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config) http.Handler {
mux := http.NewServeMux()
// Services
@@ -23,6 +24,8 @@ func New(db *sqlx.DB, authMW *auth.Middleware) http.Handler {
deadlineSvc := services.NewDeadlineService(db)
deadlineRuleSvc := services.NewDeadlineRuleService(db)
calculator := services.NewDeadlineCalculator(holidaySvc)
storageCli := services.NewStorageClient(cfg.SupabaseURL, cfg.SupabaseServiceKey)
documentSvc := services.NewDocumentService(db, storageCli)
// Middleware
tenantResolver := auth.NewTenantResolver(tenantSvc)
@@ -35,6 +38,7 @@ func New(db *sqlx.DB, authMW *auth.Middleware) http.Handler {
deadlineH := handlers.NewDeadlineHandlers(deadlineSvc, db)
ruleH := handlers.NewDeadlineRuleHandlers(deadlineRuleSvc)
calcH := handlers.NewCalculateHandlers(calculator, deadlineRuleSvc)
docH := handlers.NewDocumentHandler(documentSvc)
// Public routes
mux.HandleFunc("GET /health", handleHealth(db))
@@ -86,8 +90,12 @@ func New(db *sqlx.DB, authMW *auth.Middleware) http.Handler {
scoped.HandleFunc("PUT /api/appointments/{id}", apptH.Update)
scoped.HandleFunc("DELETE /api/appointments/{id}", apptH.Delete)
// Placeholder routes for future phases
scoped.HandleFunc("GET /api/documents", placeholder("documents"))
// Documents
scoped.HandleFunc("GET /api/cases/{id}/documents", docH.ListByCase)
scoped.HandleFunc("POST /api/cases/{id}/documents", docH.Upload)
scoped.HandleFunc("GET /api/documents/{docId}", docH.Download)
scoped.HandleFunc("GET /api/documents/{docId}/meta", docH.GetMeta)
scoped.HandleFunc("DELETE /api/documents/{docId}", docH.Delete)
// Wire: auth -> tenant routes go directly, scoped routes get tenant resolver
api.Handle("/api/", tenantResolver.Resolve(scoped))
@@ -109,12 +117,3 @@ func handleHealth(db *sqlx.DB) http.HandlerFunc {
}
}
func placeholder(resource string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "not_implemented",
"resource": resource,
})
}
}