159 lines
4.9 KiB
Go
159 lines
4.9 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"time"
|
||
|
||
"chat-agent-system/internal/contracts/vocabulary"
|
||
"chat-agent-system/internal/docgov"
|
||
"chat-agent-system/internal/projectinstance"
|
||
)
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// 文档正文保存与编辑租约
|
||
// -----------------------------------------------------------------------------
|
||
func (s *Server) handleDocGovSaveContent(w http.ResponseWriter, r *http.Request) {
|
||
user, ok := s.requirePrincipal(w, r)
|
||
if !ok {
|
||
return
|
||
}
|
||
var req SaveContentRequestDTO
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
writeDomainError(w, r, http.StatusBadRequest, err)
|
||
return
|
||
}
|
||
if s.gov() == nil {
|
||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||
return
|
||
}
|
||
// 成员级 DocAccessActionEdit(deny_only_submit);送审仍走 submit 职责门禁。
|
||
if !s.enforceDocGovWorkflowAction(w, r.Context(), user.ID, req.DocID, projectinstance.DocAccessActionEdit) {
|
||
return
|
||
}
|
||
role := vocabulary.RoleAuthor
|
||
res, err := s.gov().SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||
DocID: req.DocID,
|
||
ActorID: user.ID,
|
||
ActorRole: role,
|
||
ActorName: user.Name,
|
||
ActorIsMember: true, // enforceDocGovWorkflowAction 已确认成员可 edit
|
||
MarkdownBody: req.MarkdownBody,
|
||
ExpectedSHA256: req.ExpectedSHA256,
|
||
FencingToken: req.FencingToken,
|
||
})
|
||
if err != nil {
|
||
writeDocGovError(w, r, err)
|
||
return
|
||
}
|
||
writeJSONResponse(w, http.StatusOK, res)
|
||
}
|
||
func (s *Server) handleDocGovAcquireLease(w http.ResponseWriter, r *http.Request) {
|
||
user, ok := s.requirePrincipal(w, r)
|
||
if !ok {
|
||
return
|
||
}
|
||
var req LeaseRequestDTO
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
writeDomainError(w, r, http.StatusBadRequest, err)
|
||
return
|
||
}
|
||
if s.gov() == nil {
|
||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||
return
|
||
}
|
||
if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderHuman {
|
||
if !s.enforceDocGovWorkflowAction(w, r.Context(), user.ID, req.DocID, projectinstance.DocAccessActionEdit) {
|
||
return
|
||
}
|
||
if err := s.gov().CheckHumanCanEdit(r.Context(), req.DocID, user.ID, vocabulary.RoleAuthor, true); err != nil {
|
||
writeDocGovError(w, r, err)
|
||
return
|
||
}
|
||
req.HolderID = fmt.Sprintf("%d", user.ID)
|
||
}
|
||
workDir, werr := s.resolver().ResolveDocWorkDir(r.Context(), req.DocID)
|
||
if werr != nil {
|
||
workDir = ""
|
||
}
|
||
ttl := time.Duration(req.TTLSeconds) * time.Second
|
||
doc, err := s.gov().AcquireLease(r.Context(), docgov.LeaseAcquireRequest{
|
||
DocID: req.DocID,
|
||
HolderType: vocabulary.LeaseHolderType(req.HolderType),
|
||
HolderID: req.HolderID,
|
||
WorkDir: workDir,
|
||
TTL: ttl,
|
||
})
|
||
if err != nil {
|
||
writeDocGovError(w, r, err)
|
||
return
|
||
}
|
||
writeJSONResponse(w, http.StatusOK, doc)
|
||
}
|
||
|
||
func (s *Server) handleDocGovHeartbeatLease(w http.ResponseWriter, r *http.Request) {
|
||
user, ok := s.requirePrincipal(w, r)
|
||
if !ok {
|
||
return
|
||
}
|
||
var req LeaseRequestDTO
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
writeDomainError(w, r, http.StatusBadRequest, err)
|
||
return
|
||
}
|
||
if s.gov() == nil {
|
||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||
return
|
||
}
|
||
if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderHuman {
|
||
req.HolderID = fmt.Sprintf("%d", user.ID)
|
||
} else if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderSquad {
|
||
if !s.callerOwnsSquadLease(user.ID, req.HolderID) {
|
||
writeJSONResponse(w, http.StatusForbidden, map[string]string{
|
||
"error_code": "ERR_FORBIDDEN", "message": "无权续租该文档编辑锁",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
ttl := time.Duration(req.TTLSeconds) * time.Second
|
||
doc, err := s.gov().HeartbeatLease(r.Context(), req.DocID, req.HolderType, req.HolderID, ttl)
|
||
if err != nil {
|
||
writeDocGovError(w, r, err)
|
||
return
|
||
}
|
||
writeJSONResponse(w, http.StatusOK, doc)
|
||
}
|
||
|
||
func (s *Server) handleDocGovReleaseLease(w http.ResponseWriter, r *http.Request) {
|
||
user, ok := s.requirePrincipal(w, r)
|
||
if !ok {
|
||
return
|
||
}
|
||
var req LeaseRequestDTO
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
writeDomainError(w, r, http.StatusBadRequest, err)
|
||
return
|
||
}
|
||
if s.gov() == nil {
|
||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||
return
|
||
}
|
||
if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderHuman {
|
||
req.HolderID = fmt.Sprintf("%d", user.ID)
|
||
} else if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderSquad {
|
||
if !s.callerOwnsSquadLease(user.ID, req.HolderID) {
|
||
writeJSONResponse(w, http.StatusForbidden, map[string]string{
|
||
"error_code": "ERR_FORBIDDEN", "message": "无权释放该文档编辑锁",
|
||
})
|
||
return
|
||
}
|
||
}
|
||
doc, err := s.gov().ReleaseLease(r.Context(), req.DocID, req.HolderType, req.HolderID)
|
||
if err != nil {
|
||
writeDocGovError(w, r, err)
|
||
return
|
||
}
|
||
writeJSONResponse(w, http.StatusOK, doc)
|
||
}
|