package handler import ( "net/http" "soul-api/internal/database" "soul-api/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // sectionListItem 与前端 SectionListItem 一致(小写驼峰) type sectionListItem struct { ID string `json:"id"` Title string `json:"title"` Price float64 `json:"price"` IsFree *bool `json:"isFree,omitempty"` PartID string `json:"partId"` PartTitle string `json:"partTitle"` ChapterID string `json:"chapterId"` ChapterTitle string `json:"chapterTitle"` FilePath *string `json:"filePath,omitempty"` } // DBBookAction GET/POST/PUT /api/db/book func DBBookAction(c *gin.Context) { db := database.DB() switch c.Request.Method { case http.MethodGet: action := c.Query("action") id := c.Query("id") switch action { case "list": var rows []model.Chapter if err := db.Order("sort_order ASC, id ASC").Find(&rows).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error(), "sections": []sectionListItem{}}) return } sections := make([]sectionListItem, 0, len(rows)) for _, r := range rows { price := 1.0 if r.Price != nil { price = *r.Price } sections = append(sections, sectionListItem{ ID: r.ID, Title: r.SectionTitle, Price: price, IsFree: r.IsFree, PartID: r.PartID, PartTitle: r.PartTitle, ChapterID: r.ChapterID, ChapterTitle: r.ChapterTitle, }) } c.JSON(http.StatusOK, gin.H{"success": true, "sections": sections, "total": len(sections)}) return case "read": if id == "" { c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id"}) return } var ch model.Chapter if err := db.Where("id = ?", id).First(&ch).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusOK, gin.H{"success": false, "error": "章节不存在"}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } price := 1.0 if ch.Price != nil { price = *ch.Price } c.JSON(http.StatusOK, gin.H{ "success": true, "section": gin.H{ "id": ch.ID, "title": ch.SectionTitle, "price": price, "content": ch.Content, "partId": ch.PartID, "partTitle": ch.PartTitle, "chapterId": ch.ChapterID, "chapterTitle": ch.ChapterTitle, }, }) return case "export": var rows []model.Chapter if err := db.Order("sort_order ASC, id ASC").Find(&rows).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } sections := make([]sectionListItem, 0, len(rows)) for _, r := range rows { price := 1.0 if r.Price != nil { price = *r.Price } sections = append(sections, sectionListItem{ ID: r.ID, Title: r.SectionTitle, Price: price, IsFree: r.IsFree, PartID: r.PartID, PartTitle: r.PartTitle, ChapterID: r.ChapterID, ChapterTitle: r.ChapterTitle, }) } c.JSON(http.StatusOK, gin.H{"success": true, "sections": sections}) return default: c.JSON(http.StatusOK, gin.H{"success": false, "error": "无效的 action"}) return } case http.MethodPost: var body struct { Action string `json:"action"` Data []importItem `json:"data"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"}) return } switch body.Action { case "sync": c.JSON(http.StatusOK, gin.H{"success": true, "message": "同步完成(Gin 无文件源时可从 DB 已存在数据视为已同步)"}) return case "import": imported, failed := 0, 0 for _, item := range body.Data { price := 1.0 if item.Price != nil { price = *item.Price } isFree := false if item.IsFree != nil { isFree = *item.IsFree } wordCount := len(item.Content) status := "published" ch := model.Chapter{ ID: item.ID, PartID: strPtr(item.PartID, "part-1"), PartTitle: strPtr(item.PartTitle, "未分类"), ChapterID: strPtr(item.ChapterID, "chapter-1"), ChapterTitle: strPtr(item.ChapterTitle, "未分类"), SectionTitle: item.Title, Content: item.Content, WordCount: &wordCount, IsFree: &isFree, Price: &price, Status: &status, } err := db.Where("id = ?", item.ID).First(&model.Chapter{}).Error if err == gorm.ErrRecordNotFound { err = db.Create(&ch).Error } else if err == nil { err = db.Model(&model.Chapter{}).Where("id = ?", item.ID).Updates(map[string]interface{}{ "section_title": ch.SectionTitle, "content": ch.Content, "word_count": ch.WordCount, "is_free": ch.IsFree, "price": ch.Price, }).Error } if err != nil { failed++ continue } imported++ } c.JSON(http.StatusOK, gin.H{"success": true, "message": "导入完成", "imported": imported, "failed": failed}) return default: c.JSON(http.StatusOK, gin.H{"success": false, "error": "无效的 action"}) return } case http.MethodPut: var body struct { ID string `json:"id"` Title string `json:"title"` Content string `json:"content"` Price *float64 `json:"price"` IsFree *bool `json:"isFree"` } if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" { c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id 或请求体无效"}) return } price := 1.0 if body.Price != nil { price = *body.Price } isFree := false if body.IsFree != nil { isFree = *body.IsFree } wordCount := len(body.Content) updates := map[string]interface{}{ "section_title": body.Title, "content": body.Content, "word_count": wordCount, "price": price, "is_free": isFree, } err := db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(updates).Error if err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) return } c.JSON(http.StatusOK, gin.H{"success": false, "error": "不支持的请求方法"}) } type importItem struct { ID string `json:"id"` Title string `json:"title"` Content string `json:"content"` Price *float64 `json:"price"` IsFree *bool `json:"isFree"` PartID *string `json:"partId"` PartTitle *string `json:"partTitle"` ChapterID *string `json:"chapterId"` ChapterTitle *string `json:"chapterTitle"` } func strPtr(s *string, def string) string { if s != nil && *s != "" { return *s } return def } // DBBookDelete DELETE /api/db/book func DBBookDelete(c *gin.Context) { id := c.Query("id") if id == "" { c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id"}) return } if err := database.DB().Where("id = ?", id).Delete(&model.Chapter{}).Error; err != nil { c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) }