This commit is contained in:
2026-06-22 08:55:57 +08:00
parent dbb87823e8
commit 6ade6e8fa9
325 changed files with 41131 additions and 855 deletions
+134
View File
@@ -60,6 +60,12 @@ func (h *Handler) Mount(r chi.Router) {
r.Post("/{slug}/archive", h.archiveProject)
r.Post("/{slug}/unarchive", h.unarchiveProject)
// 成员/角色 ACL(autonomy roadmap §11):list 需可读;add/remove 仅
// project-admin 或 global-admin(service 层 assertCanManageMembers 兜底)。
r.Get("/{slug}/members", h.listMembers)
r.Post("/{slug}/members", h.addMember)
r.Delete("/{slug}/members/{userID}", h.removeMember)
r.Post("/{slug}/requirements", h.createRequirement)
r.Get("/{slug}/requirements", h.listRequirements)
r.Get("/{slug}/requirements/{number}", h.getRequirement)
@@ -70,6 +76,7 @@ func (h *Handler) Mount(r chi.Router) {
r.Post("/{slug}/requirements/{number}/artifacts", h.createArtifact)
r.Get("/{slug}/requirements/{number}/artifacts", h.listArtifacts)
r.Get("/{slug}/requirements/{number}/artifacts/{phase}/{version}", h.getArtifact)
r.Post("/{slug}/requirements/{number}/artifacts/{phase}/{version}/approve", h.approveArtifact)
r.Post("/{slug}/issues", h.createIssue)
r.Get("/{slug}/issues", h.listIssues)
@@ -171,6 +178,29 @@ type artifactDTO struct {
SourceMessageID *int64 `json:"source_message_id,omitempty"`
CreatedBy string `json:"created_by"`
CreatedAt string `json:"created_at"`
Verdict string `json:"verdict"`
}
type memberDTO struct {
ProjectID string `json:"project_id"`
UserID string `json:"user_id"`
Role string `json:"role"`
AddedBy *string `json:"added_by,omitempty"`
CreatedAt string `json:"created_at"`
}
func toMemberDTO(m *Member) memberDTO {
d := memberDTO{
ProjectID: m.ProjectID.String(),
UserID: m.UserID.String(),
Role: string(m.Role),
CreatedAt: m.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"),
}
if m.AddedBy != nil {
s := m.AddedBy.String()
d.AddedBy = &s
}
return d
}
func toProjectDTO(p *Project) projectDTO {
@@ -240,6 +270,7 @@ func toArtifactDTO(a *Artifact) artifactDTO {
SourceMessageID: a.SourceMessageID,
CreatedBy: a.CreatedBy.String(),
CreatedAt: a.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"),
Verdict: string(a.Verdict),
}
}
@@ -364,6 +395,75 @@ func (h *Handler) toggleArchive(w http.ResponseWriter, r *http.Request, archive
w.WriteHeader(http.StatusNoContent)
}
// ===== Member endpoints(autonomy roadmap §11)=====
type addMemberReq struct {
UserID string `json:"user_id"`
Role string `json:"role"`
}
func (h *Handler) listMembers(w http.ResponseWriter, r *http.Request) {
c, err := h.callerFromCtx(r)
if err != nil {
writeErr(w, r, err)
return
}
out, err := h.projects.ListMembers(r.Context(), c, chi.URLParam(r, "slug"))
if err != nil {
writeErr(w, r, err)
return
}
dtos := make([]memberDTO, 0, len(out))
for _, m := range out {
dtos = append(dtos, toMemberDTO(m))
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"items": dtos})
}
func (h *Handler) addMember(w http.ResponseWriter, r *http.Request) {
c, err := h.callerFromCtx(r)
if err != nil {
writeErr(w, r, err)
return
}
var req addMemberReq
if err := decodeBody(r, &req); err != nil {
writeErr(w, r, err)
return
}
uid, err := uuid.Parse(req.UserID)
if err != nil {
writeErr(w, r, errs.New(errs.CodeInvalidInput, "user_id 非法"))
return
}
out, err := h.projects.AddMember(r.Context(), c, chi.URLParam(r, "slug"), AddMemberInput{
UserID: uid, Role: Role(req.Role),
})
if err != nil {
writeErr(w, r, err)
return
}
httpx.WriteJSON(w, http.StatusOK, toMemberDTO(out))
}
func (h *Handler) removeMember(w http.ResponseWriter, r *http.Request) {
c, err := h.callerFromCtx(r)
if err != nil {
writeErr(w, r, err)
return
}
uid, err := uuid.Parse(chi.URLParam(r, "userID"))
if err != nil {
writeErr(w, r, errs.New(errs.CodeInvalidInput, "user_id 非法"))
return
}
if err := h.projects.RemoveMember(r.Context(), c, chi.URLParam(r, "slug"), uid); err != nil {
writeErr(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// ===== Requirement endpoints =====
type createRequirementReq struct {
@@ -672,6 +772,40 @@ func (h *Handler) getArtifact(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, toArtifactDTO(out))
}
type approveArtifactReq struct {
Verdict string `json:"verdict"`
}
func (h *Handler) approveArtifact(w http.ResponseWriter, r *http.Request) {
c, err := h.callerFromCtx(r)
if err != nil {
writeErr(w, r, err)
return
}
num, err := numberParam(r)
if err != nil {
writeErr(w, r, err)
return
}
ver, err := strconv.Atoi(chi.URLParam(r, "version"))
if err != nil || ver <= 0 {
writeErr(w, r, errs.New(errs.CodeInvalidInput, "version 非法"))
return
}
var req approveArtifactReq
if err := decodeBody(r, &req); err != nil {
writeErr(w, r, err)
return
}
phase := Phase(chi.URLParam(r, "phase"))
out, err := h.artifacts.Approve(r.Context(), c, chi.URLParam(r, "slug"), num, phase, ver, Verdict(req.Verdict))
if err != nil {
writeErr(w, r, err)
return
}
httpx.WriteJSON(w, http.StatusOK, toArtifactDTO(out))
}
// ===== Issue endpoints =====
type createIssueReq struct {