// Package uploadapi implementiert DOC-03s Backend-Voraussetzung: DOC-01 // liefert bislang nur einen Go-Service (internal/upload), keinen // HTTP-Endpunkt — dieselbe Lücke wie bei DOC-05/documentapi. Die // Drag-and-Drop-Oberfläche kann Uploads nur über die öffentliche // HTTP-Schnittstelle ansprechen, nicht über Go-Funktionsaufrufe. package uploadapi import ( "encoding/json" "errors" "net/http" "strconv" "gitea.perlbach24.de/scripte/nexarch/dms/internal/upload" ) type Server struct { svc *upload.Service } func NewServer(svc *upload.Service) *Server { return &Server{svc: svc} } func (s *Server) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /uploads", s.handleStart) mux.HandleFunc("PUT /uploads/{id}/chunk", s.handleChunk) mux.HandleFunc("GET /uploads/{id}", s.handleStatus) mux.HandleFunc("POST /uploads/{id}/complete", s.handleComplete) } type startRequest struct { Filename string `json:"filename"` MimeType string `json:"mimeType"` TotalSize int64 `json:"totalSize"` FolderID *string `json:"folderId,omitempty"` } type startResponse struct { SessionID string `json:"sessionId"` } func (s *Server) handleStart(w http.ResponseWriter, r *http.Request) { var req startRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "ungueltiger anfrage-body", http.StatusBadRequest) return } createdBy := r.Header.Get("X-User-Id") sessionID, err := s.svc.StartUpload(r.Context(), req.Filename, req.MimeType, req.TotalSize, req.FolderID, createdBy) if err != nil { writeUploadError(w, err) return } writeJSON(w, http.StatusCreated, startResponse{SessionID: sessionID}) } type chunkResponse struct { BytesReceived int64 `json:"bytesReceived"` } func (s *Server) handleChunk(w http.ResponseWriter, r *http.Request) { sessionID := r.PathValue("id") offset, err := strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64) if err != nil || offset < 0 { http.Error(w, "ungueltiger offset-parameter", http.StatusBadRequest) return } bytesReceived, err := s.svc.UploadChunk(r.Context(), sessionID, offset, r.Body) if err != nil { writeUploadError(w, err) return } writeJSON(w, http.StatusOK, chunkResponse{BytesReceived: bytesReceived}) } type statusResponse struct { SessionID string `json:"sessionId"` Filename string `json:"filename"` TotalSize int64 `json:"totalSize"` BytesReceived int64 `json:"bytesReceived"` Status string `json:"status"` DocumentID *string `json:"documentId,omitempty"` } func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) { sessionID := r.PathValue("id") sess, err := s.svc.Status(r.Context(), sessionID) if err != nil { writeUploadError(w, err) return } writeJSON(w, http.StatusOK, statusResponse{ SessionID: sess.ID, Filename: sess.Filename, TotalSize: sess.TotalSize, BytesReceived: sess.BytesReceived, Status: sess.Status, DocumentID: sess.DocumentID, }) } type completeResponse struct { DocumentID string `json:"documentId"` RevisionID string `json:"revisionId"` } func (s *Server) handleComplete(w http.ResponseWriter, r *http.Request) { sessionID := r.PathValue("id") documentID, revisionID, err := s.svc.Complete(r.Context(), sessionID, r.URL.Query().Get("confirm_duplicate") == "true") if err != nil { var dup *upload.DuplicateError if errors.As(err, &dup) { writeJSON(w, http.StatusConflict, map[string]string{"error": err.Error(), "documentId": dup.DocumentID, "revisionId": dup.RevisionID}) return } writeUploadError(w, err) return } writeJSON(w, http.StatusOK, completeResponse{DocumentID: documentID, RevisionID: revisionID}) } func writeUploadError(w http.ResponseWriter, err error) { switch { case errors.Is(err, upload.ErrSessionNotFound): http.Error(w, err.Error(), http.StatusNotFound) case errors.Is(err, upload.ErrFileTooLarge), errors.Is(err, upload.ErrDisallowedMimeType), errors.Is(err, upload.ErrInvalidOffset), errors.Is(err, upload.ErrInvalidSize): http.Error(w, err.Error(), http.StatusBadRequest) case errors.Is(err, upload.ErrIncompleteUpload): http.Error(w, err.Error(), http.StatusConflict) default: http.Error(w, err.Error(), http.StatusInternalServerError) } } func writeJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(body) }