- FDN-02: Rollback-fähige Down-Migrationen (024-026), archivdms seed dev CLI - FDN-03: internal/objectstore Interface + lokaler WORM-Treiber, signierte Download-URLs - FDN-07: go.mod/go.sum vervollständigt (fehlender go-ldap/v3-Eintrag), CI-Pipeline (.gitea/workflows/ci.yml, bereits in FDN-01 committet) damit lauffähig - FDN-08: Request-ID-Middleware, /metrics-Endpoint, Panic-Recovery, Login/Logout/Me technisches Logging inkl. Access-Log je Anfrage
76 lines
2.7 KiB
Go
76 lines
2.7 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// ocrWordResponse is the wire shape of one OCR word box. It is a dedicated DTO
|
|
// (not storage.OCRWord, which carries no json tags and exposes the internal row
|
|
// id / document_id) so the overlay renderer gets a compact, stable payload.
|
|
// Coordinates are in the original file's coordinate space — see
|
|
// internal/ocr/coords.go.
|
|
type ocrWordResponse struct {
|
|
Text string `json:"text"`
|
|
Left int `json:"left"`
|
|
Top int `json:"top"`
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
Confidence float64 `json:"confidence"`
|
|
Page int `json:"page"`
|
|
Block int `json:"block"`
|
|
Par int `json:"par"`
|
|
Line int `json:"line"`
|
|
}
|
|
|
|
// handleListDocumentOCRWords returns the stored word-level bounding boxes of a
|
|
// document (GET /api/documents/{id}/ocr-words), Phase 3 of the OCR
|
|
// text-highlight/overlay feature.
|
|
//
|
|
// Tenant/ACL: ocr_words has no tenant_id column, access is only ever mediated
|
|
// through document_id. The handler therefore performs the exact same ownership
|
|
// check as handleDocumentAuditLog / handleGetDocumentFile — GetDocument(id,
|
|
// tenantID) filters WHERE tenant_id and yields 404 for both "unknown id" and
|
|
// "foreign tenant", so the endpoint never reveals whether a document exists
|
|
// outside the caller's tenant — BEFORE any ocr_words row is read.
|
|
//
|
|
// Pure read: no audit entry, consistent with the other document GET handlers.
|
|
// Empty result serializes as [] (never null).
|
|
func (s *Server) handleListDocumentOCRWords(w http.ResponseWriter, r *http.Request) {
|
|
sess := sessionFromCtx(r.Context())
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil || sess.TenantID == nil {
|
|
writeError(w, http.StatusBadRequest, "invalid document id")
|
|
return
|
|
}
|
|
// ACL/tenant check first — must precede the ocr_words lookup.
|
|
if _, err := s.store.GetDocument(r.Context(), id, *sess.TenantID); err != nil {
|
|
writeError(w, http.StatusNotFound, "document not found")
|
|
return
|
|
}
|
|
|
|
words, err := s.store.ListOCRWords(r.Context(), id)
|
|
if err != nil {
|
|
s.reqLog(r.Context()).Error("list ocr words failed", "document_id", id, "tenant_id", *sess.TenantID, "err", err)
|
|
writeError(w, http.StatusInternalServerError, "list ocr words failed")
|
|
return
|
|
}
|
|
|
|
out := make([]ocrWordResponse, 0, len(words))
|
|
for _, wd := range words {
|
|
out = append(out, ocrWordResponse{
|
|
Text: wd.Word,
|
|
Left: wd.Left,
|
|
Top: wd.Top,
|
|
Width: wd.Width,
|
|
Height: wd.Height,
|
|
Confidence: wd.Confidence,
|
|
Page: wd.Page,
|
|
Block: wd.Block,
|
|
Par: wd.Par,
|
|
Line: wd.Line,
|
|
})
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|