package api import ( "net/http" "strconv" "strings" "home-vue-go/internal/database" "home-vue-go/internal/ent" "home-vue-go/internal/ent/contact" "github.com/gin-gonic/gin" ) func GetContacts(db *database.Database) gin.HandlerFunc { return func(c *gin.Context) { ctx := c.Request.Context() contacts, err := db.Client.Contact.Query().Order(contact.BySortOrder(), contact.ByID()).All(ctx) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } result := make([]gin.H, len(contacts)) for i, contact := range contacts { result[i] = gin.H{ "id": contact.ID, "type": contact.Type, "icon": contact.Icon, "url": contact.URL, "qrCode": contact.QrCode, "hoverColor": contact.HoverColor, "sortOrder": contact.SortOrder, } } c.JSON(http.StatusOK, result) } } func ReorderContacts(db *database.Database) gin.HandlerFunc { return func(c *gin.Context) { var req struct { IDs []int `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "排序列表不能为空"}) return } seen := make(map[int]bool, len(req.IDs)) for _, id := range req.IDs { if id <= 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "排序列表包含无效ID"}) return } if seen[id] { c.JSON(http.StatusBadRequest, gin.H{"error": "排序列表包含重复ID"}) return } seen[id] = true } ctx := c.Request.Context() existingIDs, err := db.Client.Contact.Query().IDs(ctx) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if len(existingIDs) != len(req.IDs) { c.JSON(http.StatusBadRequest, gin.H{"error": "排序列表与当前联系方式数量不一致"}) return } existing := make(map[int]bool, len(existingIDs)) for _, id := range existingIDs { existing[id] = true } for _, id := range req.IDs { if !existing[id] { c.JSON(http.StatusBadRequest, gin.H{"error": "排序列表包含不存在的联系方式"}) return } } tx, err := db.Client.Tx(ctx) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } for index, id := range req.IDs { if _, err := tx.Contact.UpdateOneID(id).SetSortOrder((index + 1) * 10).Save(ctx); err != nil { _ = tx.Rollback() c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } if err := tx.Commit(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } contacts, err := db.Client.Contact.Query().Order(contact.BySortOrder(), contact.ByID()).All(ctx) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } result := make([]gin.H, len(contacts)) for i, contact := range contacts { result[i] = gin.H{ "id": contact.ID, "type": contact.Type, "icon": contact.Icon, "url": contact.URL, "qrCode": contact.QrCode, "hoverColor": contact.HoverColor, "sortOrder": contact.SortOrder, } } c.JSON(http.StatusOK, result) } } func CreateContact(db *database.Database) gin.HandlerFunc { return func(c *gin.Context) { var req struct { Type string `json:"type" binding:"required"` Icon string `json:"icon" binding:"required"` URL string `json:"url"` QrCode string `json:"qrCode"` HoverColor string `json:"hoverColor"` SortOrder int `json:"sortOrder"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } req.Type = strings.TrimSpace(req.Type) req.Icon = strings.TrimSpace(req.Icon) req.URL = strings.TrimSpace(req.URL) req.QrCode = strings.TrimSpace(req.QrCode) req.HoverColor = strings.TrimSpace(req.HoverColor) if req.Type == "" || req.Icon == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "类型和图标不能为空"}) return } if req.URL != "" && !validContactURL(req.URL) { c.JSON(http.StatusBadRequest, gin.H{"error": "联系方式URL必须是有效的http/https/mailto/tel地址"}) return } if req.QrCode != "" && !validQRCode(req.QrCode) { c.JSON(http.StatusBadRequest, gin.H{"error": "二维码必须使用本地上传路径或安全的http/https图片地址"}) return } if req.URL != "" && req.QrCode != "" { c.JSON(http.StatusBadRequest, gin.H{"error": "联系方式不能同时设置链接和二维码"}) return } if req.URL == "" && req.QrCode == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "链接或二维码不能为空"}) return } ctx := c.Request.Context() create := db.Client.Contact.Create(). SetType(req.Type). SetIcon(req.Icon). SetSortOrder(req.SortOrder) if req.URL != "" { create.SetURL(req.URL) } if req.QrCode != "" { create.SetQrCode(req.QrCode) } if req.HoverColor != "" { create.SetHoverColor(req.HoverColor) } contact, err := create.Save(ctx) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "id": contact.ID, "type": contact.Type, "icon": contact.Icon, "url": contact.URL, "qrCode": contact.QrCode, "hoverColor": contact.HoverColor, "sortOrder": contact.SortOrder, }) } } func UpdateContact(db *database.Database) gin.HandlerFunc { return func(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"}) return } var req struct { Type string `json:"type" binding:"required"` Icon string `json:"icon" binding:"required"` URL string `json:"url"` QrCode string `json:"qrCode"` HoverColor string `json:"hoverColor"` SortOrder int `json:"sortOrder"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } req.Type = strings.TrimSpace(req.Type) req.Icon = strings.TrimSpace(req.Icon) req.URL = strings.TrimSpace(req.URL) req.QrCode = strings.TrimSpace(req.QrCode) req.HoverColor = strings.TrimSpace(req.HoverColor) if req.Type == "" || req.Icon == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "类型和图标不能为空"}) return } if req.URL != "" && !validContactURL(req.URL) { c.JSON(http.StatusBadRequest, gin.H{"error": "联系方式URL必须是有效的http/https/mailto/tel地址"}) return } if req.QrCode != "" && !validQRCode(req.QrCode) { c.JSON(http.StatusBadRequest, gin.H{"error": "二维码必须使用本地上传路径或安全的http/https图片地址"}) return } if req.URL != "" && req.QrCode != "" { c.JSON(http.StatusBadRequest, gin.H{"error": "联系方式不能同时设置链接和二维码"}) return } if req.URL == "" && req.QrCode == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "链接或二维码不能为空"}) return } ctx := c.Request.Context() update := db.Client.Contact.UpdateOneID(id) update.SetType(req.Type) update.SetIcon(req.Icon) if req.URL != "" { update.SetURL(req.URL) } else { update.ClearURL() } if req.QrCode != "" { update.SetQrCode(req.QrCode) } else { update.ClearQrCode() } if req.HoverColor != "" { update.SetHoverColor(req.HoverColor) } else { update.ClearHoverColor() } update.SetSortOrder(req.SortOrder) contact, err := update.Save(ctx) if err != nil { if ent.IsNotFound(err) { c.JSON(http.StatusNotFound, gin.H{"error": "联系方式不存在"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "id": contact.ID, "type": contact.Type, "icon": contact.Icon, "url": contact.URL, "qrCode": contact.QrCode, "hoverColor": contact.HoverColor, "sortOrder": contact.SortOrder, }) } } func DeleteContact(db *database.Database) gin.HandlerFunc { return func(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"}) return } ctx := c.Request.Context() if err := db.Client.Contact.DeleteOneID(id).Exec(ctx); err != nil { if ent.IsNotFound(err) { c.JSON(http.StatusNotFound, gin.H{"error": "联系方式不存在"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) } }