- Create kanzlai.notes table (polymorphic FK with CHECK constraint,
partial indexes, RLS)
- Add Note model, NoteService (ListByParent, Create, Update, Delete),
and NoteHandler with endpoints: GET/POST /api/notes, PUT/DELETE /api/notes/{id}
- Add GET /api/deadlines/{deadlineID} detail endpoint
- Add GET /api/appointments/{id} detail endpoint
- Add GET /api/case-events/{id} detail endpoint (new CaseEventHandler)
- Fix dashboard query: add case_id to upcoming_deadlines SELECT,
add id and case_id to recent_activity SELECT
- Register all new routes in router.go
160 lines
3.9 KiB
Go
160 lines
3.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
type NoteHandler struct {
|
|
svc *services.NoteService
|
|
}
|
|
|
|
func NewNoteHandler(svc *services.NoteService) *NoteHandler {
|
|
return &NoteHandler{svc: svc}
|
|
}
|
|
|
|
// List handles GET /api/notes?{parent_type}_id={id}
|
|
func (h *NoteHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "missing tenant")
|
|
return
|
|
}
|
|
|
|
parentType, parentID, err := parseNoteParent(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
notes, err := h.svc.ListByParent(r.Context(), tenantID, parentType, parentID)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to list notes")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, notes)
|
|
}
|
|
|
|
// Create handles POST /api/notes
|
|
func (h *NoteHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "missing tenant")
|
|
return
|
|
}
|
|
userID, _ := auth.UserFromContext(r.Context())
|
|
|
|
var input services.CreateNoteInput
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if input.Content == "" {
|
|
writeError(w, http.StatusBadRequest, "content is required")
|
|
return
|
|
}
|
|
|
|
var createdBy *uuid.UUID
|
|
if userID != uuid.Nil {
|
|
createdBy = &userID
|
|
}
|
|
|
|
note, err := h.svc.Create(r.Context(), tenantID, createdBy, input)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to create note")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, note)
|
|
}
|
|
|
|
// Update handles PUT /api/notes/{id}
|
|
func (h *NoteHandler) Update(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "missing tenant")
|
|
return
|
|
}
|
|
|
|
noteID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid note ID")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Content string `json:"content"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if req.Content == "" {
|
|
writeError(w, http.StatusBadRequest, "content is required")
|
|
return
|
|
}
|
|
|
|
note, err := h.svc.Update(r.Context(), tenantID, noteID, req.Content)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to update note")
|
|
return
|
|
}
|
|
if note == nil {
|
|
writeError(w, http.StatusNotFound, "note not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, note)
|
|
}
|
|
|
|
// Delete handles DELETE /api/notes/{id}
|
|
func (h *NoteHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "missing tenant")
|
|
return
|
|
}
|
|
|
|
noteID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid note ID")
|
|
return
|
|
}
|
|
|
|
if err := h.svc.Delete(r.Context(), tenantID, noteID); err != nil {
|
|
writeError(w, http.StatusNotFound, "note not found")
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// parseNoteParent extracts the parent type and ID from query parameters.
|
|
func parseNoteParent(r *http.Request) (string, uuid.UUID, error) {
|
|
params := map[string]string{
|
|
"case_id": "case",
|
|
"deadline_id": "deadline",
|
|
"appointment_id": "appointment",
|
|
"case_event_id": "case_event",
|
|
}
|
|
|
|
for param, parentType := range params {
|
|
if v := r.URL.Query().Get(param); v != "" {
|
|
id, err := uuid.Parse(v)
|
|
if err != nil {
|
|
return "", uuid.Nil, fmt.Errorf("invalid %s", param)
|
|
}
|
|
return parentType, id, nil
|
|
}
|
|
}
|
|
|
|
return "", uuid.Nil, fmt.Errorf("one of case_id, deadline_id, appointment_id, or case_event_id is required")
|
|
}
|