Resolve stage access from live instance assignments and filter document list/content for non-assigned roles. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
func (s *Server) repoRoot() string {
|
|
if s.cfg != nil && s.cfg.DataDir != "" {
|
|
// DataDir 形如 ./data/dev;工作区路径形如 data/dev/workspaces/...,相对仓库根目录
|
|
return filepath.Clean(filepath.Join(s.cfg.DataDir, "..", ".."))
|
|
}
|
|
return "."
|
|
}
|
|
|
|
// handleListProjectWorkflowDocuments 列出项目工作流受控文档。
|
|
func (s *Server) handleListProjectWorkflowDocuments(w http.ResponseWriter, r *http.Request) {
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil || projectID <= 0 {
|
|
http.Error(w, "invalid project id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
category := r.URL.Query().Get("category")
|
|
if category != "" && category != "workflow" {
|
|
http.Error(w, "unsupported category", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
user, ok := s.requireProjectAccess(w, r, projectID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if s.svc == nil || s.svc.DocGov() == nil {
|
|
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
docs, err := s.svc.DocGov().ListWorkflowDocumentsByProject(r.Context(), projectID)
|
|
if err != nil {
|
|
http.Error(w, "list project documents failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
docs = s.filterWorkflowDocsByStageAccess(r.Context(), projectID, user.ID, docs)
|
|
writeJSONResponse(w, http.StatusOK, docs)
|
|
}
|
|
|
|
// handleDocGovGetDocumentContent 读取受控文档 Markdown 正文。
|
|
func (s *Server) handleDocGovGetDocumentContent(w http.ResponseWriter, r *http.Request) {
|
|
docID := r.PathValue("doc_id")
|
|
if docID == "" {
|
|
http.Error(w, "doc_id required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if s.svc == nil || s.svc.DocGov() == nil {
|
|
http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
docRec, err := s.svc.DocGov().GetDocument(r.Context(), docID)
|
|
if err != nil || docRec == nil {
|
|
http.Error(w, "document not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
user, ok := s.requireProjectAccess(w, r, docRec.ProjectID)
|
|
if !ok {
|
|
return
|
|
}
|
|
if !s.allowWorkflowDocTaskAccess(r.Context(), docRec.ProjectID, user.ID, docRec.TaskKey) {
|
|
http.Error(w, `{"error":"WORKFLOW_ACCESS_DENIED","message":"无权访问该阶段文档"}`, http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
content, err := s.svc.DocGov().ReadDocumentContent(r.Context(), docID, s.repoRoot())
|
|
if err != nil {
|
|
http.Error(w, "read document content failed: "+err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if inm := r.Header.Get("If-None-Match"); inm != "" && inm == content.ContentSHA256 {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return
|
|
}
|
|
w.Header().Set("ETag", content.ContentSHA256)
|
|
writeJSONResponse(w, http.StatusOK, content)
|
|
}
|