| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- package admin
- import (
- "errors"
- "strconv"
- "github.com/Wei-Shaw/sub2api/internal/pkg/response"
- middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- // TicketHandler 工单:用户端(JWT 上下文取用户)与管理端共用同一 handler。
- type TicketHandler struct {
- service *service.TicketService
- }
- func NewTicketHandler(svc *service.TicketService) *TicketHandler {
- return &TicketHandler{service: svc}
- }
- func (h *TicketHandler) requireService(c *gin.Context) bool {
- if h == nil || h.service == nil {
- response.ErrorFrom(c, errors.New("ticket service unavailable"))
- return false
- }
- return true
- }
- func ticketUserID(c *gin.Context) (int64, bool) {
- subject, ok := middleware2.GetAuthSubjectFromContext(c)
- if !ok || subject.UserID <= 0 {
- response.Unauthorized(c, "Unauthorized")
- return 0, false
- }
- return subject.UserID, true
- }
- func ticketID(c *gin.Context) (int64, bool) {
- id, err := strconv.ParseInt(c.Param("id"), 10, 64)
- if err != nil || id <= 0 {
- response.BadRequest(c, "Invalid ticket ID")
- return 0, false
- }
- return id, true
- }
- func ticketErr(c *gin.Context, err error) {
- switch {
- case errors.Is(err, service.ErrTicketNotFound):
- response.NotFound(c, "Ticket not found")
- case errors.Is(err, service.ErrTicketClosed):
- response.BadRequest(c, "Ticket is closed")
- default:
- response.ErrorFrom(c, err)
- }
- }
- // ListMine 用户工单列表。
- // GET /tickets
- func (h *TicketHandler) ListMine(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- userID, ok := ticketUserID(c)
- if !ok {
- return
- }
- items, err := h.service.ListMine(c.Request.Context(), userID)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- if items == nil {
- items = []service.SupportTicket{}
- }
- response.Success(c, gin.H{"items": items, "count": len(items)})
- }
- type ticketCreateRequest struct {
- Subject string `json:"subject"`
- Body string `json:"body"`
- }
- // Create 用户建单。
- // POST /tickets
- func (h *TicketHandler) Create(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- userID, ok := ticketUserID(c)
- if !ok {
- return
- }
- var req ticketCreateRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- t, err := h.service.Create(c.Request.Context(), userID, req.Subject, req.Body)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, t)
- }
- // GetMine 用户查看自己的工单。
- // GET /tickets/:id
- func (h *TicketHandler) GetMine(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- userID, ok := ticketUserID(c)
- if !ok {
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- t, err := h.service.GetMine(c.Request.Context(), userID, id)
- if err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, t)
- }
- type ticketReplyRequest struct {
- Body string `json:"body"`
- }
- // ReplyMine 用户追问。
- // POST /tickets/:id/replies
- func (h *TicketHandler) ReplyMine(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- userID, ok := ticketUserID(c)
- if !ok {
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- var req ticketReplyRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- r, err := h.service.ReplyMine(c.Request.Context(), userID, id, req.Body)
- if err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, r)
- }
- // CloseMine 用户关闭。
- // POST /tickets/:id/close
- func (h *TicketHandler) CloseMine(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- userID, ok := ticketUserID(c)
- if !ok {
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- if err := h.service.CloseMine(c.Request.Context(), userID, id); err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, gin.H{"message": "Ticket closed successfully"})
- }
- // ListAll 管理端列表:?status=&search=&limit=&offset=。
- // GET /api/v1/admin/tickets
- func (h *TicketHandler) ListAll(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- limit := 50
- if raw := c.Query("limit"); raw != "" {
- if v, err := strconv.Atoi(raw); err == nil && v > 0 && v <= 500 {
- limit = v
- }
- }
- offset := 0
- if raw := c.Query("offset"); raw != "" {
- if v, err := strconv.Atoi(raw); err == nil && v >= 0 {
- offset = v
- }
- }
- items, err := h.service.ListAll(c.Request.Context(), c.Query("status"), c.Query("search"), limit, offset)
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- if items == nil {
- items = []service.SupportTicket{}
- }
- response.Success(c, gin.H{"items": items, "count": len(items)})
- }
- // GetAny 管理端查看。
- // GET /api/v1/admin/tickets/:id
- func (h *TicketHandler) GetAny(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- t, err := h.service.GetAny(c.Request.Context(), id)
- if err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, t)
- }
- // ReplyAny 管理员回复(状态置 answered)。
- // POST /api/v1/admin/tickets/:id/replies
- func (h *TicketHandler) ReplyAny(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- subject, ok := middleware2.GetAuthSubjectFromContext(c)
- if !ok {
- response.Unauthorized(c, "Unauthorized")
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- var req ticketReplyRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- r, err := h.service.ReplyAny(c.Request.Context(), subject.UserID, id, req.Body)
- if err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, r)
- }
- // CloseAny 管理员关闭。
- // POST /api/v1/admin/tickets/:id/close
- func (h *TicketHandler) CloseAny(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- id, ok := ticketID(c)
- if !ok {
- return
- }
- if err := h.service.CloseAny(c.Request.Context(), id); err != nil {
- ticketErr(c, err)
- return
- }
- response.Success(c, gin.H{"message": "Ticket closed successfully"})
- }
- // Stats 未关闭工单数(管理端角标)。
- // GET /api/v1/admin/tickets/stats
- func (h *TicketHandler) Stats(c *gin.Context) {
- if !h.requireService(c) {
- return
- }
- n, err := h.service.OpenCount(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, gin.H{"open": n})
- }
|