metadataapi/tagapi/bulkeditapi als Backend-Voraussetzung. MetadataForm passt sich der Felddefinition an und warnt vor Verwerfen ungespeicherter Aenderungen, TagInput schlaegt Tags vor und ist per Pfeiltasten/Enter bedienbar, Massenbearbeitung wendet Tags auf beliebig grosse Auswahlen in einem Aufruf an. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
// Package bulkeditapi implementiert MET-03s Backend-Voraussetzung für die
|
|
// Massenbearbeitung: EIN HTTP-Endpunkt über internal/bulkedit (DOC-15).
|
|
// Keine eigene Massenbearbeitungslogik.
|
|
package bulkeditapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/bulkedit"
|
|
)
|
|
|
|
type Server struct {
|
|
pool *pgxpool.Pool
|
|
lockChecker bulkedit.LockChecker
|
|
}
|
|
|
|
func NewServer(pool *pgxpool.Pool, lockChecker bulkedit.LockChecker) *Server {
|
|
return &Server{pool: pool, lockChecker: lockChecker}
|
|
}
|
|
|
|
func (s *Server) RegisterRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("POST /bulk-edit", s.handleApply)
|
|
}
|
|
|
|
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 metadataChangeDTO struct {
|
|
FieldID string `json:"field_id"`
|
|
Value *string `json:"value"`
|
|
}
|
|
|
|
type applyRequest struct {
|
|
Actor string `json:"actor"`
|
|
DocumentIDs []string `json:"document_ids"`
|
|
AddTagIDs []string `json:"add_tag_ids"`
|
|
RemoveTagIDs []string `json:"remove_tag_ids"`
|
|
MetadataChanges []metadataChangeDTO `json:"metadata_changes"`
|
|
}
|
|
|
|
type skippedDTO struct {
|
|
DocumentID string `json:"document_id"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
type applyResult struct {
|
|
Applied []string `json:"applied"`
|
|
Skipped []skippedDTO `json:"skipped"`
|
|
}
|
|
|
|
// Akzeptanzkriterium 3 (MET-03) / Akzeptanzkriterium 1 (DOC-15):
|
|
// Massenbearbeitung über eine Dokumentauswahl, gesperrte Dokumente werden
|
|
// einzeln übersprungen statt die Operation als Ganzes abzulehnen.
|
|
func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
|
|
var req applyRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "ungueltiger request-body: "+err.Error())
|
|
return
|
|
}
|
|
if req.Actor == "" {
|
|
writeError(w, http.StatusBadRequest, "fehlendes feld \"actor\"")
|
|
return
|
|
}
|
|
|
|
changes := make([]bulkedit.MetadataChange, len(req.MetadataChanges))
|
|
for i, c := range req.MetadataChanges {
|
|
changes[i] = bulkedit.MetadataChange{FieldID: c.FieldID, Value: c.Value}
|
|
}
|
|
|
|
result, err := bulkedit.Apply(r.Context(), s.pool, s.lockChecker, req.Actor, bulkedit.Request{
|
|
DocumentIDs: req.DocumentIDs, AddTagIDs: req.AddTagIDs, RemoveTagIDs: req.RemoveTagIDs, MetadataChanges: changes,
|
|
})
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
dto := applyResult{Applied: result.Applied, Skipped: make([]skippedDTO, len(result.Skipped))}
|
|
for i, sk := range result.Skipped {
|
|
dto.Skipped[i] = skippedDTO{DocumentID: sk.DocumentID, Reason: sk.Reason}
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(dto)
|
|
}
|