原型阶段

This commit is contained in:
2026-06-09 23:44:31 +08:00
parent ea1d0fb3bd
commit 01b3375395
22 changed files with 1077 additions and 27 deletions
+119 -8
View File
@@ -38,13 +38,14 @@ type Handler struct {
projects ProjectService
requirements RequirementService
issues IssueService
prototypes PrototypeService
resolver middleware.SessionResolver
users AdminLookup
}
// NewHandler 构造 Handler。
func NewHandler(p ProjectService, r RequirementService, i IssueService, resolver middleware.SessionResolver, users AdminLookup) *Handler {
return &Handler{projects: p, requirements: r, issues: i, resolver: resolver, users: users}
func NewHandler(p ProjectService, r RequirementService, i IssueService, proto PrototypeService, resolver middleware.SessionResolver, users AdminLookup) *Handler {
return &Handler{projects: p, requirements: r, issues: i, prototypes: proto, resolver: resolver, users: users}
}
// Mount 把所有 PM 路由挂到 r 上,并装入 Auth 中间件。
@@ -66,6 +67,9 @@ func (h *Handler) Mount(r chi.Router) {
r.Post("/{slug}/requirements/{number}/phase", h.changeRequirementPhase)
r.Post("/{slug}/requirements/{number}/close", h.closeRequirement)
r.Post("/{slug}/requirements/{number}/reopen", h.reopenRequirement)
r.Post("/{slug}/requirements/{number}/prototypes", h.createPrototype)
r.Get("/{slug}/requirements/{number}/prototypes", h.listPrototypes)
r.Get("/{slug}/requirements/{number}/prototypes/{version}", h.getPrototype)
r.Post("/{slug}/issues", h.createIssue)
r.Get("/{slug}/issues", h.listIssues)
@@ -157,6 +161,16 @@ type issueDTO struct {
UpdatedAt string `json:"updated_at"`
}
type prototypeDTO struct {
ID string `json:"id"`
RequirementID string `json:"requirement_id"`
Version int `json:"version"`
Content string `json:"content"`
Note string `json:"note"`
CreatedBy string `json:"created_by"`
CreatedAt string `json:"created_at"`
}
func toProjectDTO(p *Project) projectDTO {
d := projectDTO{
ID: p.ID.String(), Slug: p.Slug, Name: p.Name, Description: p.Description,
@@ -217,6 +231,15 @@ func toIssueDTO(i *Issue) issueDTO {
return d
}
func toPrototypeDTO(p *Prototype) prototypeDTO {
return prototypeDTO{
ID: p.ID.String(), RequirementID: p.RequirementID.String(),
Version: p.Version, Content: p.Content, Note: p.Note,
CreatedBy: p.CreatedBy.String(),
CreatedAt: p.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"),
}
}
// ===== Project endpoints =====
type createProjectReq struct {
@@ -314,8 +337,10 @@ func (h *Handler) updateProject(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, toProjectDTO(out))
}
func (h *Handler) archiveProject(w http.ResponseWriter, r *http.Request) { h.toggleArchive(w, r, true) }
func (h *Handler) unarchiveProject(w http.ResponseWriter, r *http.Request) { h.toggleArchive(w, r, false) }
func (h *Handler) archiveProject(w http.ResponseWriter, r *http.Request) { h.toggleArchive(w, r, true) }
func (h *Handler) unarchiveProject(w http.ResponseWriter, r *http.Request) {
h.toggleArchive(w, r, false)
}
func (h *Handler) toggleArchive(w http.ResponseWriter, r *http.Request, archive bool) {
c, err := h.callerFromCtx(r)
@@ -529,8 +554,12 @@ func (h *Handler) changeRequirementPhase(w http.ResponseWriter, r *http.Request)
httpx.WriteJSON(w, http.StatusOK, toRequirementDTO(out))
}
func (h *Handler) closeRequirement(w http.ResponseWriter, r *http.Request) { h.toggleReqStatus(w, r, true) }
func (h *Handler) reopenRequirement(w http.ResponseWriter, r *http.Request) { h.toggleReqStatus(w, r, false) }
func (h *Handler) closeRequirement(w http.ResponseWriter, r *http.Request) {
h.toggleReqStatus(w, r, true)
}
func (h *Handler) reopenRequirement(w http.ResponseWriter, r *http.Request) {
h.toggleReqStatus(w, r, false)
}
func (h *Handler) toggleReqStatus(w http.ResponseWriter, r *http.Request, doClose bool) {
c, err := h.callerFromCtx(r)
@@ -555,6 +584,86 @@ func (h *Handler) toggleReqStatus(w http.ResponseWriter, r *http.Request, doClos
w.WriteHeader(http.StatusNoContent)
}
// ===== Prototype endpoints =====
type createPrototypeReq struct {
Content string `json:"content"`
Note string `json:"note"`
}
func (h *Handler) createPrototype(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
}
var req createPrototypeReq
if err := decodeBody(r, &req); err != nil {
writeErr(w, r, err)
return
}
out, err := h.prototypes.Create(r.Context(), c, chi.URLParam(r, "slug"), num, CreatePrototypeInput{
Content: req.Content, Note: req.Note,
})
if err != nil {
writeErr(w, r, err)
return
}
httpx.WriteJSON(w, http.StatusCreated, toPrototypeDTO(out))
}
func (h *Handler) listPrototypes(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
}
out, err := h.prototypes.List(r.Context(), c, chi.URLParam(r, "slug"), num)
if err != nil {
writeErr(w, r, err)
return
}
dtos := make([]prototypeDTO, 0, len(out))
for _, x := range out {
dtos = append(dtos, toPrototypeDTO(x))
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"prototypes": dtos})
}
func (h *Handler) getPrototype(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
}
out, err := h.prototypes.GetByVersion(r.Context(), c, chi.URLParam(r, "slug"), num, ver)
if err != nil {
writeErr(w, r, err)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"prototype": toPrototypeDTO(out)})
}
// ===== Issue endpoints =====
type createIssueReq struct {
@@ -751,8 +860,10 @@ func (h *Handler) assignIssue(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, toIssueDTO(out))
}
func (h *Handler) closeIssue(w http.ResponseWriter, r *http.Request) { h.toggleIssueStatus(w, r, true) }
func (h *Handler) reopenIssue(w http.ResponseWriter, r *http.Request) { h.toggleIssueStatus(w, r, false) }
func (h *Handler) closeIssue(w http.ResponseWriter, r *http.Request) { h.toggleIssueStatus(w, r, true) }
func (h *Handler) reopenIssue(w http.ResponseWriter, r *http.Request) {
h.toggleIssueStatus(w, r, false)
}
func (h *Handler) toggleIssueStatus(w http.ResponseWriter, r *http.Request, doClose bool) {
c, err := h.callerFromCtx(r)