package handlers import ( "encoding/json" "net/http" "strconv" "mgit.msbls.de/m/KanzlAI-mGMT/internal/auth" "mgit.msbls.de/m/KanzlAI-mGMT/internal/services" ) type TemplateHandler struct { templates *services.TemplateService cases *services.CaseService parties *services.PartyService deadlines *services.DeadlineService tenants *services.TenantService } func NewTemplateHandler( templates *services.TemplateService, cases *services.CaseService, parties *services.PartyService, deadlines *services.DeadlineService, tenants *services.TenantService, ) *TemplateHandler { return &TemplateHandler{ templates: templates, cases: cases, parties: parties, deadlines: deadlines, tenants: tenants, } } // List handles GET /api/templates func (h *TemplateHandler) List(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } q := r.URL.Query() limit, _ := strconv.Atoi(q.Get("limit")) offset, _ := strconv.Atoi(q.Get("offset")) limit, offset = clampPagination(limit, offset) filter := services.TemplateFilter{ Category: q.Get("category"), Search: q.Get("search"), Limit: limit, Offset: offset, } if filter.Search != "" { if msg := validateStringLength("search", filter.Search, maxSearchLen); msg != "" { writeError(w, http.StatusBadRequest, msg) return } } templates, total, err := h.templates.List(r.Context(), tenantID, filter) if err != nil { internalError(w, "failed to list templates", err) return } writeJSON(w, http.StatusOK, map[string]any{ "data": templates, "total": total, }) } // Get handles GET /api/templates/{id} func (h *TemplateHandler) Get(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } templateID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid template ID") return } t, err := h.templates.GetByID(r.Context(), tenantID, templateID) if err != nil { internalError(w, "failed to get template", err) return } if t == nil { writeError(w, http.StatusNotFound, "template not found") return } writeJSON(w, http.StatusOK, t) } // Create handles POST /api/templates func (h *TemplateHandler) Create(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } var raw struct { Name string `json:"name"` Description *string `json:"description,omitempty"` Category string `json:"category"` Content string `json:"content"` Variables any `json:"variables,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&raw); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if raw.Name == "" { writeError(w, http.StatusBadRequest, "name is required") return } if msg := validateStringLength("name", raw.Name, maxTitleLen); msg != "" { writeError(w, http.StatusBadRequest, msg) return } if raw.Category == "" { writeError(w, http.StatusBadRequest, "category is required") return } var variables []byte if raw.Variables != nil { var err error variables, err = json.Marshal(raw.Variables) if err != nil { writeError(w, http.StatusBadRequest, "invalid variables") return } } input := services.CreateTemplateInput{ Name: raw.Name, Description: raw.Description, Category: raw.Category, Content: raw.Content, Variables: variables, } t, err := h.templates.Create(r.Context(), tenantID, input) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusCreated, t) } // Update handles PUT /api/templates/{id} func (h *TemplateHandler) Update(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } templateID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid template ID") return } var raw struct { Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` Category *string `json:"category,omitempty"` Content *string `json:"content,omitempty"` Variables any `json:"variables,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&raw); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if raw.Name != nil { if msg := validateStringLength("name", *raw.Name, maxTitleLen); msg != "" { writeError(w, http.StatusBadRequest, msg) return } } var variables []byte if raw.Variables != nil { variables, err = json.Marshal(raw.Variables) if err != nil { writeError(w, http.StatusBadRequest, "invalid variables") return } } input := services.UpdateTemplateInput{ Name: raw.Name, Description: raw.Description, Category: raw.Category, Content: raw.Content, Variables: variables, } t, err := h.templates.Update(r.Context(), tenantID, templateID, input) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if t == nil { writeError(w, http.StatusNotFound, "template not found") return } writeJSON(w, http.StatusOK, t) } // Delete handles DELETE /api/templates/{id} func (h *TemplateHandler) Delete(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } templateID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid template ID") return } if err := h.templates.Delete(r.Context(), tenantID, templateID); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } // Render handles POST /api/templates/{id}/render?case_id=X func (h *TemplateHandler) Render(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } userID, _ := auth.UserFromContext(r.Context()) templateID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid template ID") return } // Get template tmpl, err := h.templates.GetByID(r.Context(), tenantID, templateID) if err != nil { internalError(w, "failed to get template", err) return } if tmpl == nil { writeError(w, http.StatusNotFound, "template not found") return } // Build render data data := services.RenderData{} // Case data (optional) caseIDStr := r.URL.Query().Get("case_id") if caseIDStr != "" { caseID, err := parseUUID(caseIDStr) if err != nil { writeError(w, http.StatusBadRequest, "invalid case_id") return } caseDetail, err := h.cases.GetByID(r.Context(), tenantID, caseID) if err != nil { internalError(w, "failed to get case", err) return } if caseDetail == nil { writeError(w, http.StatusNotFound, "case not found") return } data.Case = &caseDetail.Case data.Parties = caseDetail.Parties // Get next upcoming deadline for this case deadlines, err := h.deadlines.ListForCase(tenantID, caseID) if err == nil && len(deadlines) > 0 { // Find next non-completed deadline for i := range deadlines { if deadlines[i].Status != "completed" { data.Deadline = &deadlines[i] break } } } } // Tenant data tenant, err := h.tenants.GetByID(r.Context(), tenantID) if err == nil && tenant != nil { data.Tenant = tenant } // User data (userID from context — detailed name/email would need a user table lookup) data.UserName = userID.String() data.UserEmail = "" rendered := h.templates.Render(tmpl, data) writeJSON(w, http.StatusOK, map[string]any{ "content": rendered, "template_id": tmpl.ID, "name": tmpl.Name, }) }