feat(M3): 申请审批 + 邮件队列 — 公开申请、管理员审批自动建号、mail_logs 落库重试
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user