// Package metadataapi implementiert MET-03s Backend-Voraussetzung: // Felddefinitionen listen und Metadatenwerte EINES Dokuments lesen/setzen // — Grundlage für ein Formular, das sich automatisch an die Felddefinition // anpasst. Ruft ausschließlich internal/metadatafield auf. package metadataapi import ( "encoding/json" "net/http" "github.com/jackc/pgx/v5/pgxpool" "gitea.perlbach24.de/scripte/nexarch/dms/internal/metadatafield" ) type Server struct { pool *pgxpool.Pool } func NewServer(pool *pgxpool.Pool) *Server { return &Server{pool: pool} } func (s *Server) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /metadata-fields", s.handleListFields) mux.HandleFunc("GET /documents/{id}/metadata", s.handleGetValues) mux.HandleFunc("PUT /documents/{id}/metadata", s.handleSetValues) } func writeJSON(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]string{"error": message}) } type fieldDTO struct { ID string `json:"id"` Key string `json:"key"` Label string `json:"label"` Type string `json:"type"` Required bool `json:"required"` DefaultValue *string `json:"default_value"` } // handleListFields liefert ALLE Felddefinitionen — Grundlage für ein // Formular, das sich automatisch an sie anpasst (Akzeptanzkriterium 1). func (s *Server) handleListFields(w http.ResponseWriter, r *http.Request) { fields, err := metadatafield.ListAll(r.Context(), s.pool) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } dtos := make([]fieldDTO, len(fields)) for i, f := range fields { dtos[i] = fieldDTO{ID: f.ID, Key: f.Key, Label: f.Label, Type: f.Type, Required: f.Required, DefaultValue: f.DefaultValue} } writeJSON(w, dtos) } // handleGetValues liefert die aktuellen Metadatenwerte EINES Dokuments als // field_id -> value. func (s *Server) handleGetValues(w http.ResponseWriter, r *http.Request) { documentID := r.PathValue("id") rows, err := s.pool.Query(r.Context(), `SELECT field_id, value FROM document_metadata_values WHERE document_id = $1`, documentID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } defer rows.Close() values := map[string]string{} for rows.Next() { var fieldID, value string if err := rows.Scan(&fieldID, &value); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } values[fieldID] = value } if err := rows.Err(); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, values) } // handleSetValues speichert MEHRERE Feldwerte EINES Dokuments in einem // Aufruf (ein Formular-Speichern-Klick, ein Request) — jeder Wert wird // gegen seine Felddefinition validiert (metadatafield.Validate), bevor // irgendetwas geschrieben wird, damit kein Teilzustand entsteht. func (s *Server) handleSetValues(w http.ResponseWriter, r *http.Request) { documentID := r.PathValue("id") var values map[string]string if err := json.NewDecoder(r.Body).Decode(&values); err != nil { writeError(w, http.StatusBadRequest, "ungueltiger request-body: "+err.Error()) return } for fieldID, value := range values { field, err := metadatafield.Get(r.Context(), s.pool, fieldID) if err == metadatafield.ErrNotFound { writeError(w, http.StatusBadRequest, "unbekanntes feld: "+fieldID) return } if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if err := metadatafield.Validate(field, value); err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()) return } } tx, err := s.pool.Begin(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } defer func() { _ = tx.Rollback(r.Context()) }() for fieldID, value := range values { if _, err := tx.Exec(r.Context(), ` INSERT INTO document_metadata_values (document_id, field_id, value) VALUES ($1, $2, $3) ON CONFLICT (document_id, field_id) DO UPDATE SET value = EXCLUDED.value `, documentID, fieldID, value); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } } if err := tx.Commit(r.Context()); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) }