FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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.logger.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)
|
||||
}
|
||||
Reference in New Issue
Block a user