feat: add app user routes (logout, user-info, avatar, tickets)
- Logout: clear device sessions on logout - User info: return expiry/balance based on billing type - Avatar upload: support jpg/png/gif/webp, max 2MB - App tickets: create, reply, close tickets for app users - Add ClosedAt field to Ticket model Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/internal/service"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupTicketRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/tickets", handleAppGetTickets)
|
||||
r.GET("/tickets/:id", handleAppGetTicket)
|
||||
r.POST("/tickets", handleAppCreateTicket)
|
||||
r.POST("/tickets/:id/reply", handleAppReplyTicket)
|
||||
r.PUT("/tickets/:id/close", handleAppCloseTicket)
|
||||
}
|
||||
|
||||
func handleAppGetTickets(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "该应用已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
statusFilter := c.Query("status")
|
||||
|
||||
query := database.DB.Model(&model.Ticket{}).Where("user_id = ? AND application_id = ?", userID, app.ID)
|
||||
|
||||
if statusFilter != "" {
|
||||
query = query.Where("status = ?", statusFilter)
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var tickets []model.Ticket
|
||||
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tickets).Error; err != nil {
|
||||
response.Error(c, 500, "获取工单列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"tickets": tickets,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_page": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
func handleAppGetTicket(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "该应用已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
ticketID := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ? AND application_id = ?", ticketID, userID, app.ID).
|
||||
Preload("Replies").
|
||||
First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"ticket": ticket,
|
||||
})
|
||||
}
|
||||
|
||||
func handleAppCreateTicket(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "该应用已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
Type string `json:"type"`
|
||||
Priority string `json:"priority"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Type == "" {
|
||||
req.Type = "other"
|
||||
}
|
||||
if req.Priority == "" {
|
||||
req.Priority = "normal"
|
||||
}
|
||||
|
||||
ticket := model.Ticket{
|
||||
UserID: userID.(uint),
|
||||
ApplicationID: &app.ID,
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Type: req.Type,
|
||||
Category: "other",
|
||||
Priority: req.Priority,
|
||||
Status: "open",
|
||||
AssignedTo: &app.UserID,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&ticket).Error; err != nil {
|
||||
log.Printf("[ERROR] Failed to create ticket: %v", err)
|
||||
response.Error(c, 500, "创建工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "create", "ticket", &ticket.ID, fmt.Sprintf("用户创建工单: %s", ticket.Title), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": ticket.ID,
|
||||
"message": "工单创建成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleAppReplyTicket(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "该应用已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
ticketID := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ? AND application_id = ?", ticketID, userID, app.ID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if ticket.Status == "closed" {
|
||||
response.Error(c, 400, "工单已关闭,无法回复")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.Error(c, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
reply := model.TicketReply{
|
||||
TicketID: parseUint(ticketID),
|
||||
UserID: userID.(uint),
|
||||
Content: req.Content,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&reply).Error; err != nil {
|
||||
response.Error(c, 500, "回复失败")
|
||||
return
|
||||
}
|
||||
|
||||
if ticket.Status == "resolved" || ticket.Status == "processing" {
|
||||
ticket.Status = "processing"
|
||||
database.DB.Save(&ticket)
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"id": reply.ID,
|
||||
"message": "回复成功",
|
||||
})
|
||||
}
|
||||
|
||||
func handleAppCloseTicket(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if service.GetApplicationDisabledStatus(app.ID) {
|
||||
response.Error(c, 403, "该应用已被禁用")
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
return
|
||||
}
|
||||
|
||||
ticketID := c.Param("id")
|
||||
var ticket model.Ticket
|
||||
if err := database.DB.Where("id = ? AND user_id = ? AND application_id = ?", ticketID, userID, app.ID).First(&ticket).Error; err != nil {
|
||||
response.Error(c, 404, "工单不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if ticket.Status == "closed" {
|
||||
response.Error(c, 400, "工单已关闭")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
ticket.Status = "closed"
|
||||
ticket.ClosedAt = &now
|
||||
|
||||
if err := database.DB.Save(&ticket).Error; err != nil {
|
||||
response.Error(c, 500, "关闭工单失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.LogOperation(c, "close", "ticket", &ticket.ID, fmt.Sprintf("用户关闭工单: %s", ticket.Title), nil)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"message": "工单已关闭",
|
||||
})
|
||||
}
|
||||
|
||||
func parseUint(s string) uint {
|
||||
var val uint
|
||||
fmt.Sscanf(s, "%d", &val)
|
||||
return val
|
||||
}
|
||||
Reference in New Issue
Block a user