Files
usernode/internal/api/approval.go
T

105 lines
3.5 KiB
Go

package api
import (
"errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"ws_usernode/internal/config"
"ws_usernode/internal/model"
"ws_usernode/internal/service"
)
// ApprovalHandler 新账号申请与审批接口(PLAN F4):
// 公开提交(POST /approvals)、管理员列表(GET /approvals)、审批(POST /approvals/:id/review)。
type ApprovalHandler struct {
svc *service.ApprovalService
cfg *config.Config
h *Handler // 访问审计 helper
}
// ApprovalSubmitRequest 提交申请(用户名不含 ext_ 前缀)。
type ApprovalSubmitRequest struct {
Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required"`
Supervisor string `json:"supervisor"`
Purpose string `json:"purpose"`
}
// Submit POST /approvals —— 公开提交新账号申请。
func (h *ApprovalHandler) Submit(c *gin.Context) {
var req ApprovalSubmitRequest
if err := c.ShouldBindJSON(&req); err != nil {
fail(c, http.StatusBadRequest, "请求参数不合法: "+err.Error())
return
}
a, err := h.svc.Submit(c.Request.Context(), req.Username, req.Email, req.Supervisor, req.Purpose)
if err != nil {
h.h.audit(c, "approval.submit", "approval", "", map[string]any{"username": req.Username, "err": err.Error()}, model.ResultFailed)
switch {
case errors.Is(err, service.ErrUsernameTaken):
fail(c, http.StatusConflict, err.Error())
default:
fail(c, http.StatusBadRequest, err.Error())
}
return
}
h.h.audit(c, "approval.submit", "approval", strconv.FormatUint(uint64(a.ID), 10), map[string]any{"username": a.UsernameRequested}, model.ResultSuccess)
ok(c, a)
}
// List GET /approvals —— 申请单列表(admin),按状态筛选。
func (h *ApprovalHandler) List(c *gin.Context) {
rows, err := h.svc.List(c.Request.Context(), c.Query("status"))
if err != nil {
fail(c, http.StatusInternalServerError, err.Error())
return
}
ok(c, gin.H{"items": rows})
}
// ApprovalReviewRequest 审批请求:通过 → approve=true;拒绝 → approve=false + 理由。
type ApprovalReviewRequest struct {
Approve bool `json:"approve"`
Reason string `json:"reason"`
}
// Review POST /approvals/:id/review —— 审批(通过 → 自动建号 / 拒绝 + 理由)。
func (h *ApprovalHandler) Review(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
fail(c, http.StatusBadRequest, "无效的申请单 ID")
return
}
var req ApprovalReviewRequest
if err := c.ShouldBindJSON(&req); err != nil {
fail(c, http.StatusBadRequest, "请求参数不合法: "+err.Error())
return
}
reviewerID := uint(0)
if sess := sessionFrom(c); sess != nil {
reviewerID = sess.RefID
}
a, err := h.svc.Review(c.Request.Context(), uint(id), req.Approve, reviewerID, req.Reason)
if err != nil {
h.h.audit(c, "approval.review", "approval", c.Param("id"), map[string]any{"approve": req.Approve, "err": err.Error()}, model.ResultFailed)
switch {
case errors.Is(err, service.ErrApprovalNotFound):
fail(c, http.StatusNotFound, err.Error())
case errors.Is(err, service.ErrApprovalReviewed):
fail(c, http.StatusConflict, err.Error())
case errors.Is(err, service.ErrReasonRequired):
fail(c, http.StatusBadRequest, err.Error())
case errors.Is(err, service.ErrUsernameTaken):
fail(c, http.StatusConflict, err.Error())
default:
fail(c, http.StatusInternalServerError, err.Error())
}
return
}
h.h.audit(c, "approval.review", "approval", c.Param("id"), map[string]any{"approve": req.Approve, "reason": req.Reason, "status": a.Status}, model.ResultSuccess)
ok(c, a)
}