- Integrated AI writing capabilities in the super article editor, allowing users to upload multiple reference files and generate drafts using prompts. - Updated the reading page to unify the display of book chapters and super articles, improving user navigation and experience. - Enhanced the "My" page with new UI elements, including gradient icons and improved layout for better visual appeal. - Added backend support for new article management endpoints, ensuring seamless integration with the front end. This update aims to streamline the article creation process and enhance the overall user interface across the application.
637 lines
18 KiB
Go
637 lines
18 KiB
Go
package handler
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"soul-api/internal/database"
|
||
"soul-api/internal/model"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type superArticleAuthorRow struct {
|
||
ID string `gorm:"column:id"`
|
||
Nickname string `gorm:"column:nickname"`
|
||
Avatar string `gorm:"column:avatar"`
|
||
VipName string `gorm:"column:vip_name"`
|
||
VipAvatar string `gorm:"column:vip_avatar"`
|
||
}
|
||
|
||
// resolveSuperArticleAuthorDisplay 展示用昵称/头像:普通资料优先,超级个体常用 vip_* 兜底。
|
||
func resolveSuperArticleAuthorDisplay(a superArticleAuthorRow) (nickname, avatar string) {
|
||
nickname = strings.TrimSpace(a.Nickname)
|
||
if nickname == "" {
|
||
nickname = strings.TrimSpace(a.VipName)
|
||
}
|
||
avatar = strings.TrimSpace(a.Avatar)
|
||
if avatar == "" {
|
||
avatar = strings.TrimSpace(a.VipAvatar)
|
||
}
|
||
return nickname, avatar
|
||
}
|
||
|
||
func loadSuperArticleAuthorMap(ids []string) map[string]superArticleAuthorRow {
|
||
out := map[string]superArticleAuthorRow{}
|
||
if len(ids) == 0 {
|
||
return out
|
||
}
|
||
var rows []superArticleAuthorRow
|
||
db := database.DB()
|
||
_ = db.Table("users").Select("id", "nickname", "avatar", "vip_name", "vip_avatar").Where("id IN ?", ids).Find(&rows).Error
|
||
for _, r := range rows {
|
||
key := strings.TrimSpace(r.ID)
|
||
if key == "" {
|
||
continue
|
||
}
|
||
out[key] = r
|
||
}
|
||
return out
|
||
}
|
||
|
||
const (
|
||
superArticleMaxImages = 9
|
||
superArticleMaxImageURLLen = 2048
|
||
)
|
||
|
||
func parseSuperArticleImagesJSON(raw string) []string {
|
||
raw = strings.TrimSpace(raw)
|
||
if raw == "" {
|
||
return nil
|
||
}
|
||
var arr []string
|
||
if err := json.Unmarshal([]byte(raw), &arr); err != nil {
|
||
return nil
|
||
}
|
||
out := make([]string, 0, len(arr))
|
||
for _, u := range arr {
|
||
u = strings.TrimSpace(u)
|
||
if u != "" {
|
||
out = append(out, u)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// superArticleFeedAllowedAuthorIDs 文章中出现过、且当前仍为超级个体的用户 ID(用于动态广场)
|
||
func superArticleFeedAllowedAuthorIDs(db *gorm.DB) []string {
|
||
var raw []string
|
||
_ = db.Model(&model.SuperArticle{}).
|
||
Where("audit_status = ?", model.SuperArticleAuditApproved).
|
||
Distinct("user_id").
|
||
Pluck("user_id", &raw).Error
|
||
out := make([]string, 0, len(raw))
|
||
seen := map[string]bool{}
|
||
for _, uid := range raw {
|
||
uid = strings.TrimSpace(uid)
|
||
if uid == "" || seen[uid] {
|
||
continue
|
||
}
|
||
seen[uid] = true
|
||
if _, ok := miniprogramSuperIndividualPerson(db, uid); ok {
|
||
out = append(out, uid)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// MiniprogramSuperArticleFeed GET /api/miniprogram/super/articles/feed?page=&pageSize=
|
||
// 动态广场:当前仍为超级个体的作者所发文章,按创建时间倒序分页。
|
||
func MiniprogramSuperArticleFeed(c *gin.Context) {
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 30 {
|
||
pageSize = 10
|
||
}
|
||
|
||
db := database.DB()
|
||
allowed := superArticleFeedAllowedAuthorIDs(db)
|
||
if len(allowed) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "list": []gin.H{}, "total": int64(0), "page": page, "pageSize": pageSize})
|
||
return
|
||
}
|
||
|
||
q := db.Model(&model.SuperArticle{}).
|
||
Where("user_id IN ?", allowed).
|
||
Where("audit_status = ?", model.SuperArticleAuditApproved)
|
||
var total int64
|
||
if err := q.Count(&total).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var rows []model.SuperArticle
|
||
if err := db.Where("user_id IN ?", allowed).
|
||
Where("audit_status = ?", model.SuperArticleAuditApproved).
|
||
Order("created_at DESC").
|
||
Offset((page - 1) * pageSize).
|
||
Limit(pageSize).
|
||
Find(&rows).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
|
||
idSet := map[string]bool{}
|
||
var authorIDs []string
|
||
for _, r := range rows {
|
||
uid := strings.TrimSpace(r.UserID)
|
||
if uid == "" || idSet[uid] {
|
||
continue
|
||
}
|
||
idSet[uid] = true
|
||
authorIDs = append(authorIDs, uid)
|
||
}
|
||
authorMap := loadSuperArticleAuthorMap(authorIDs)
|
||
|
||
list := make([]gin.H, 0, len(rows))
|
||
for _, r := range rows {
|
||
content := strings.TrimSpace(r.Content)
|
||
preview := content
|
||
if len([]rune(preview)) > 120 {
|
||
preview = string([]rune(preview)[:120]) + "..."
|
||
}
|
||
a := authorMap[strings.TrimSpace(r.UserID)]
|
||
an, aa := resolveSuperArticleAuthorDisplay(a)
|
||
list = append(list, gin.H{
|
||
"id": r.ID,
|
||
"userId": r.UserID,
|
||
"title": r.Title,
|
||
"content": r.Content,
|
||
"preview": preview,
|
||
"images": parseSuperArticleImagesJSON(r.Images),
|
||
"authorNickname": an,
|
||
"authorAvatar": aa,
|
||
"createdAt": r.CreatedAt,
|
||
})
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"list": list,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
})
|
||
}
|
||
|
||
func deriveSuperArticleTitleFromContent(content string) string {
|
||
content = strings.TrimSpace(content)
|
||
if content == "" {
|
||
return ""
|
||
}
|
||
parts := strings.SplitN(content, "\n", 2)
|
||
line := strings.TrimSpace(parts[0])
|
||
line = strings.TrimRight(line, "\r")
|
||
if line == "" {
|
||
return ""
|
||
}
|
||
rs := []rune(line)
|
||
if len(rs) > 40 {
|
||
return string(rs[:40])
|
||
}
|
||
return line
|
||
}
|
||
|
||
// finalizeSuperArticleTitleAndBody 发动态:标题可省略(由正文首行或默认文案补齐);正文与配图至少一项。
|
||
func finalizeSuperArticleTitleAndBody(title, content string, imageCount int) (outTitle, outBody, errMsg string) {
|
||
title = strings.TrimSpace(title)
|
||
content = strings.TrimSpace(content)
|
||
if content == "" && imageCount == 0 {
|
||
return "", "", "请填写文字或添加配图"
|
||
}
|
||
if len([]rune(content)) > 5000 {
|
||
return "", "", "正文最多 5000 字"
|
||
}
|
||
outTitle = title
|
||
if outTitle != "" && len([]rune(outTitle)) > 40 {
|
||
outTitle = string([]rune(outTitle)[:40])
|
||
}
|
||
if outTitle == "" {
|
||
outTitle = deriveSuperArticleTitleFromContent(content)
|
||
}
|
||
if outTitle == "" && imageCount > 0 {
|
||
outTitle = "分享图片"
|
||
}
|
||
if outTitle == "" {
|
||
outTitle = "动态"
|
||
}
|
||
if len([]rune(outTitle)) > 40 {
|
||
outTitle = string([]rune(outTitle)[:40])
|
||
}
|
||
return outTitle, content, ""
|
||
}
|
||
|
||
func normalizeSuperArticleImageURLs(in []string) ([]string, string) {
|
||
if len(in) == 0 {
|
||
return []string{}, ""
|
||
}
|
||
if len(in) > superArticleMaxImages {
|
||
return nil, "配图最多 9 张"
|
||
}
|
||
out := make([]string, 0, len(in))
|
||
for _, u := range in {
|
||
u = strings.TrimSpace(u)
|
||
if u == "" {
|
||
continue
|
||
}
|
||
if len(u) > superArticleMaxImageURLLen {
|
||
return nil, "图片链接过长"
|
||
}
|
||
low := strings.ToLower(u)
|
||
if !strings.HasPrefix(low, "http://") && !strings.HasPrefix(low, "https://") {
|
||
return nil, "图片链接须以 http(s):// 开头"
|
||
}
|
||
out = append(out, u)
|
||
}
|
||
if len(out) > superArticleMaxImages {
|
||
return nil, "配图最多 9 张"
|
||
}
|
||
return out, ""
|
||
}
|
||
|
||
// MiniprogramSuperArticleCreate POST /api/miniprogram/super/articles
|
||
// 仅超级个体本人可发文章。
|
||
func MiniprogramSuperArticleCreate(c *gin.Context) {
|
||
var req struct {
|
||
UserID string `json:"userId"`
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
Images []string `json:"images"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
req.UserID = strings.TrimSpace(req.UserID)
|
||
req.Title = strings.TrimSpace(req.Title)
|
||
req.Content = strings.TrimSpace(req.Content)
|
||
if req.UserID == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
|
||
imgURLs, imgErr := normalizeSuperArticleImageURLs(req.Images)
|
||
if imgErr != "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": imgErr})
|
||
return
|
||
}
|
||
imgJSONBytes, jerr := json.Marshal(imgURLs)
|
||
if jerr != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "配图数据无效"})
|
||
return
|
||
}
|
||
|
||
title, body, verr := finalizeSuperArticleTitleAndBody(req.Title, req.Content, len(imgURLs))
|
||
if verr != "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": verr})
|
||
return
|
||
}
|
||
req.Title, req.Content = title, body
|
||
|
||
db := database.DB()
|
||
if _, ok := miniprogramSuperIndividualPerson(db, req.UserID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅超级个体可发文章", "forbidden": true})
|
||
return
|
||
}
|
||
|
||
row := model.SuperArticle{
|
||
UserID: req.UserID,
|
||
Title: req.Title,
|
||
Content: req.Content,
|
||
Images: string(imgJSONBytes),
|
||
AuditStatus: model.SuperArticleAuditPending,
|
||
RejectReason: "",
|
||
}
|
||
if !SuperArticleAuditEnabled() {
|
||
row.AuditStatus = model.SuperArticleAuditApproved
|
||
row.RejectReason = ""
|
||
}
|
||
if err := db.Create(&row).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"id": row.ID}})
|
||
}
|
||
|
||
// MiniprogramSuperArticleList GET /api/miniprogram/super/articles?authorUserId=&page=&pageSize=
|
||
func MiniprogramSuperArticleList(c *gin.Context) {
|
||
authorUserID := strings.TrimSpace(c.Query("authorUserId"))
|
||
if authorUserID == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 authorUserId"})
|
||
return
|
||
}
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 30 {
|
||
pageSize = 10
|
||
}
|
||
|
||
db := database.DB()
|
||
if _, ok := miniprogramSuperIndividualPerson(db, authorUserID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "list": []gin.H{}, "total": 0, "page": page, "pageSize": pageSize})
|
||
return
|
||
}
|
||
|
||
var total int64
|
||
db.Model(&model.SuperArticle{}).
|
||
Where("user_id = ?", authorUserID).
|
||
Where("audit_status = ?", model.SuperArticleAuditApproved).
|
||
Count(&total)
|
||
var rows []model.SuperArticle
|
||
if err := db.Where("user_id = ?", authorUserID).
|
||
Where("audit_status = ?", model.SuperArticleAuditApproved).
|
||
Order("created_at DESC").
|
||
Offset((page - 1) * pageSize).
|
||
Limit(pageSize).
|
||
Find(&rows).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
|
||
authorMap := loadSuperArticleAuthorMap([]string{authorUserID})
|
||
author := authorMap[authorUserID]
|
||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||
list := make([]gin.H, 0, len(rows))
|
||
for _, r := range rows {
|
||
content := strings.TrimSpace(r.Content)
|
||
preview := content
|
||
if len([]rune(preview)) > 88 {
|
||
preview = string([]rune(preview)[:88]) + "..."
|
||
}
|
||
list = append(list, gin.H{
|
||
"id": r.ID,
|
||
"userId": r.UserID,
|
||
"title": r.Title,
|
||
"content": r.Content,
|
||
"preview": preview,
|
||
"images": parseSuperArticleImagesJSON(r.Images),
|
||
"authorNickname": an,
|
||
"authorAvatar": aa,
|
||
"createdAt": r.CreatedAt,
|
||
})
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"list": list,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
})
|
||
}
|
||
|
||
// MiniprogramSuperArticleMine GET /api/miniprogram/super/articles/mine?userId=&page=&pageSize=
|
||
// 本人全部动态(含待审/通过/驳回),仅超级个体可查。
|
||
func MiniprogramSuperArticleMine(c *gin.Context) {
|
||
userID := strings.TrimSpace(c.Query("userId"))
|
||
if userID == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 userId"})
|
||
return
|
||
}
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 30 {
|
||
pageSize = 10
|
||
}
|
||
|
||
db := database.DB()
|
||
if _, ok := miniprogramSuperIndividualPerson(db, userID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅超级个体可查看", "forbidden": true})
|
||
return
|
||
}
|
||
|
||
var total int64
|
||
db.Model(&model.SuperArticle{}).Where("user_id = ?", userID).Count(&total)
|
||
var rows []model.SuperArticle
|
||
if err := db.Where("user_id = ?", userID).
|
||
Order("created_at DESC").
|
||
Offset((page - 1) * pageSize).
|
||
Limit(pageSize).
|
||
Find(&rows).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
|
||
authorMap := loadSuperArticleAuthorMap([]string{userID})
|
||
author := authorMap[userID]
|
||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||
list := make([]gin.H, 0, len(rows))
|
||
for _, r := range rows {
|
||
content := strings.TrimSpace(r.Content)
|
||
preview := content
|
||
if len([]rune(preview)) > 88 {
|
||
preview = string([]rune(preview)[:88]) + "..."
|
||
}
|
||
ast := strings.TrimSpace(strings.ToLower(r.AuditStatus))
|
||
if ast == "" {
|
||
ast = model.SuperArticleAuditApproved
|
||
}
|
||
list = append(list, gin.H{
|
||
"id": r.ID,
|
||
"userId": r.UserID,
|
||
"title": r.Title,
|
||
"content": r.Content,
|
||
"preview": preview,
|
||
"images": parseSuperArticleImagesJSON(r.Images),
|
||
"authorNickname": an,
|
||
"authorAvatar": aa,
|
||
"createdAt": r.CreatedAt,
|
||
"auditStatus": ast,
|
||
"rejectReason": strings.TrimSpace(r.RejectReason),
|
||
})
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"list": list,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
})
|
||
}
|
||
|
||
// MiniprogramSuperArticleDetail GET /api/miniprogram/super/articles/:id
|
||
func MiniprogramSuperArticleDetail(c *gin.Context) {
|
||
idRaw := strings.TrimSpace(c.Param("id"))
|
||
id, err := strconv.ParseUint(idRaw, 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
viewer := strings.TrimSpace(c.Query("viewerUserId"))
|
||
db := database.DB()
|
||
var row model.SuperArticle
|
||
if err := db.First(&row, id).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "文章不存在"})
|
||
return
|
||
}
|
||
if _, ok := miniprogramSuperIndividualPerson(db, row.UserID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "文章不可见"})
|
||
return
|
||
}
|
||
if !model.SuperArticlePublicApproved(row.AuditStatus) {
|
||
if viewer == "" || viewer != strings.TrimSpace(row.UserID) {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "文章审核中或不可见"})
|
||
return
|
||
}
|
||
}
|
||
ast := strings.TrimSpace(strings.ToLower(row.AuditStatus))
|
||
if ast == "" {
|
||
ast = model.SuperArticleAuditApproved
|
||
}
|
||
authorMap := loadSuperArticleAuthorMap([]string{row.UserID})
|
||
author := authorMap[strings.TrimSpace(row.UserID)]
|
||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"data": gin.H{
|
||
"id": row.ID,
|
||
"userId": row.UserID,
|
||
"title": row.Title,
|
||
"content": row.Content,
|
||
"images": parseSuperArticleImagesJSON(row.Images),
|
||
"authorNickname": an,
|
||
"authorAvatar": aa,
|
||
"createdAt": row.CreatedAt,
|
||
"auditStatus": ast,
|
||
"rejectReason": strings.TrimSpace(row.RejectReason),
|
||
},
|
||
})
|
||
}
|
||
|
||
// MiniprogramSuperArticleUpdate PUT /api/miniprogram/super/articles/:id
|
||
// 仅作者本人;状态须为待审核或已驳回;提交后重置为待审核。
|
||
func MiniprogramSuperArticleUpdate(c *gin.Context) {
|
||
idRaw := strings.TrimSpace(c.Param("id"))
|
||
id, err := strconv.ParseUint(idRaw, 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
var req struct {
|
||
UserID string `json:"userId"`
|
||
Title string `json:"title"`
|
||
Content string `json:"content"`
|
||
Images []string `json:"images"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
req.UserID = strings.TrimSpace(req.UserID)
|
||
req.Title = strings.TrimSpace(req.Title)
|
||
req.Content = strings.TrimSpace(req.Content)
|
||
if req.UserID == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
|
||
imgURLs, imgErr := normalizeSuperArticleImageURLs(req.Images)
|
||
if imgErr != "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": imgErr})
|
||
return
|
||
}
|
||
imgJSONBytes, jerr := json.Marshal(imgURLs)
|
||
if jerr != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "配图数据无效"})
|
||
return
|
||
}
|
||
|
||
title, body, verr := finalizeSuperArticleTitleAndBody(req.Title, req.Content, len(imgURLs))
|
||
if verr != "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": verr})
|
||
return
|
||
}
|
||
req.Title, req.Content = title, body
|
||
|
||
db := database.DB()
|
||
var row model.SuperArticle
|
||
if err := db.First(&row, id).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "文章不存在"})
|
||
return
|
||
}
|
||
if strings.TrimSpace(row.UserID) != req.UserID {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "无权修改"})
|
||
return
|
||
}
|
||
if _, ok := miniprogramSuperIndividualPerson(db, req.UserID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅超级个体可修改", "forbidden": true})
|
||
return
|
||
}
|
||
st := strings.TrimSpace(strings.ToLower(row.AuditStatus))
|
||
if st == "" {
|
||
st = model.SuperArticleAuditApproved
|
||
}
|
||
if st != model.SuperArticleAuditPending && st != model.SuperArticleAuditRejected {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅待审核或已驳回的动态可修改"})
|
||
return
|
||
}
|
||
|
||
row.Title = req.Title
|
||
row.Content = req.Content
|
||
row.Images = string(imgJSONBytes)
|
||
if SuperArticleAuditEnabled() {
|
||
row.AuditStatus = model.SuperArticleAuditPending
|
||
row.RejectReason = ""
|
||
} else {
|
||
row.AuditStatus = model.SuperArticleAuditApproved
|
||
row.RejectReason = ""
|
||
}
|
||
if err := db.Save(&row).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||
}
|
||
|
||
// MiniprogramSuperArticleDelete 删除动态(作者本人 + 超级个体)。
|
||
// DELETE /api/miniprogram/super/articles/:id?userId=
|
||
// POST /api/miniprogram/super/articles/:id/delete?userId= (与 DELETE 等价,便于不支持 DELETE 的客户端调试)
|
||
func MiniprogramSuperArticleDelete(c *gin.Context) {
|
||
idRaw := strings.TrimSpace(c.Param("id"))
|
||
id, err := strconv.ParseUint(idRaw, 10, 64)
|
||
if err != nil || id == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||
return
|
||
}
|
||
userID := strings.TrimSpace(c.Query("userId"))
|
||
if userID == "" {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 userId"})
|
||
return
|
||
}
|
||
|
||
db := database.DB()
|
||
if _, ok := miniprogramSuperIndividualPerson(db, userID); !ok {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "仅超级个体可操作", "forbidden": true})
|
||
return
|
||
}
|
||
|
||
var row model.SuperArticle
|
||
if err := db.First(&row, id).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "动态不存在"})
|
||
return
|
||
}
|
||
if strings.TrimSpace(row.UserID) != userID {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "无权删除"})
|
||
return
|
||
}
|
||
|
||
if err := db.Delete(&row).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||
}
|