Files
soul-yongping/soul-api/internal/handler/db_book.go

331 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"context"
"net/http"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// listSelectCols 列表/导出不加载 content大幅加速
var listSelectCols = []string{
"id", "section_title", "price", "is_free", "is_new",
"part_id", "part_title", "chapter_id", "chapter_title", "sort_order",
}
// sectionListItem 与前端 SectionListItem 一致(小写驼峰)
type sectionListItem struct {
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
IsFree *bool `json:"isFree,omitempty"`
IsNew *bool `json:"isNew,omitempty"` // stitch_soul标记最新新增
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.Select(listSelectCols).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,
IsNew: r.IsNew,
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,
"isNew": ch.IsNew,
"partId": ch.PartID,
"partTitle": ch.PartTitle,
"chapterId": ch.ChapterID,
"chapterTitle": ch.ChapterTitle,
},
})
return
case "export":
var rows []model.Chapter
if err := db.Select(listSelectCols).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, IsNew: r.IsNew,
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 {
Action string `json:"action"`
// reorder新顺序支持跨篇跨章时附带 partId/chapterId
IDs []string `json:"ids"`
Items []reorderItem `json:"items"`
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
IsNew *bool `json:"isNew"` // stitch_soul标记最新新增
EditionStandard *bool `json:"editionStandard"` // 是否属于普通版
EditionPremium *bool `json:"editionPremium"` // 是否属于增值版
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
return
}
if body.Action == "reorder" {
// 立即返回成功,后台异步执行排序更新
if len(body.Items) > 0 {
items := make([]reorderItem, len(body.Items))
copy(items, body.Items)
c.JSON(http.StatusOK, gin.H{"success": true})
go func() {
db := database.DB()
for i, it := range items {
if it.ID == "" {
continue
}
up := map[string]interface{}{"sort_order": i}
if it.PartID != "" {
up["part_id"] = it.PartID
}
if it.PartTitle != "" {
up["part_title"] = it.PartTitle
}
if it.ChapterID != "" {
up["chapter_id"] = it.ChapterID
}
if it.ChapterTitle != "" {
up["chapter_title"] = it.ChapterTitle
}
_ = db.WithContext(context.Background()).Model(&model.Chapter{}).Where("id = ?", it.ID).Updates(up).Error
}
}()
return
}
if len(body.IDs) > 0 {
ids := make([]string, len(body.IDs))
copy(ids, body.IDs)
c.JSON(http.StatusOK, gin.H{"success": true})
go func() {
db := database.DB()
for i, id := range ids {
if id != "" {
_ = db.WithContext(context.Background()).Model(&model.Chapter{}).Where("id = ?", id).Update("sort_order", i).Error
}
}
}()
return
}
}
if 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,
}
if body.IsNew != nil {
updates["is_new"] = *body.IsNew
}
if body.EditionStandard != nil {
updates["edition_standard"] = *body.EditionStandard
}
if body.EditionPremium != nil {
updates["edition_premium"] = *body.EditionPremium
}
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 reorderItem struct {
ID string `json:"id"`
PartID string `json:"partId"`
PartTitle string `json:"partTitle"`
ChapterID string `json:"chapterId"`
ChapterTitle string `json:"chapterTitle"`
}
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})
}