205 lines
5.0 KiB
Go
205 lines
5.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const maxUploadSize = 50 << 20 // 50 MB
|
|
|
|
type DocumentHandler struct {
|
|
svc *services.DocumentService
|
|
}
|
|
|
|
func NewDocumentHandler(svc *services.DocumentService) *DocumentHandler {
|
|
return &DocumentHandler{svc: svc}
|
|
}
|
|
|
|
func (h *DocumentHandler) ListByCase(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
caseID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid case ID")
|
|
return
|
|
}
|
|
|
|
docs, err := h.svc.ListByCase(r.Context(), tenantID, caseID)
|
|
if err != nil {
|
|
internalError(w, "failed to list documents", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"documents": docs,
|
|
"total": len(docs),
|
|
})
|
|
}
|
|
|
|
func (h *DocumentHandler) Upload(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
userID, _ := auth.UserFromContext(r.Context())
|
|
|
|
caseID, err := uuid.Parse(r.PathValue("id"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid case ID")
|
|
return
|
|
}
|
|
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
|
|
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
|
|
writeError(w, http.StatusBadRequest, "file too large or invalid multipart form")
|
|
return
|
|
}
|
|
|
|
file, header, err := r.FormFile("file")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "missing file field")
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
title := r.FormValue("title")
|
|
if title == "" {
|
|
title = header.Filename
|
|
}
|
|
|
|
contentType := header.Header.Get("Content-Type")
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
|
|
input := services.CreateDocumentInput{
|
|
Title: title,
|
|
DocType: r.FormValue("doc_type"),
|
|
Filename: header.Filename,
|
|
ContentType: contentType,
|
|
Size: int(header.Size),
|
|
Data: file,
|
|
}
|
|
|
|
doc, err := h.svc.Create(r.Context(), tenantID, caseID, userID, input)
|
|
if err != nil {
|
|
if err.Error() == "case not found" {
|
|
writeError(w, http.StatusNotFound, "case not found")
|
|
return
|
|
}
|
|
internalError(w, "failed to upload document", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, doc)
|
|
}
|
|
|
|
func (h *DocumentHandler) Download(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
docID, err := uuid.Parse(r.PathValue("docId"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid document ID")
|
|
return
|
|
}
|
|
|
|
body, contentType, title, err := h.svc.Download(r.Context(), tenantID, docID)
|
|
if err != nil {
|
|
if err.Error() == "document not found" || err.Error() == "document has no file" {
|
|
writeError(w, http.StatusNotFound, "document not found")
|
|
return
|
|
}
|
|
internalError(w, "failed to download document", err)
|
|
return
|
|
}
|
|
defer body.Close()
|
|
|
|
w.Header().Set("Content-Type", contentType)
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, sanitizeFilename(title)))
|
|
io.Copy(w, body)
|
|
}
|
|
|
|
func (h *DocumentHandler) GetMeta(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
docID, err := uuid.Parse(r.PathValue("docId"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid document ID")
|
|
return
|
|
}
|
|
|
|
doc, err := h.svc.GetByID(r.Context(), tenantID, docID)
|
|
if err != nil {
|
|
internalError(w, "failed to get document metadata", err)
|
|
return
|
|
}
|
|
if doc == nil {
|
|
writeError(w, http.StatusNotFound, "document not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, doc)
|
|
}
|
|
|
|
func (h *DocumentHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
userID, _ := auth.UserFromContext(r.Context())
|
|
role := auth.UserRoleFromContext(r.Context())
|
|
|
|
docID, err := uuid.Parse(r.PathValue("docId"))
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid document ID")
|
|
return
|
|
}
|
|
|
|
// Check permission: owner/partner can delete any, associate can delete own
|
|
doc, err := h.svc.GetByID(r.Context(), tenantID, docID)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if doc == nil {
|
|
writeError(w, http.StatusNotFound, "document not found")
|
|
return
|
|
}
|
|
|
|
uploaderID := uuid.Nil
|
|
if doc.UploadedBy != nil {
|
|
uploaderID = *doc.UploadedBy
|
|
}
|
|
if !auth.CanDeleteDocument(role, uploaderID, userID) {
|
|
writeError(w, http.StatusForbidden, "insufficient permissions to delete this document")
|
|
return
|
|
}
|
|
|
|
if err := h.svc.Delete(r.Context(), tenantID, docID, userID); err != nil {
|
|
writeError(w, http.StatusNotFound, "document not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|