- article.go:articleForm 加 json tags,新增 parseArticleFormJSON(绑定+
去白);ArticleCreate/ArticleUpdate/ArticleDelete 增加 redirectPath 参数,
校验失败改 APIError 400、DB 错误 500,成功 {ok,redirect}(原 renderForm
分支移除);id 改用 parseUintParam 数值化
- my_articles.go:MyArticleCreate 复用 ArticleCreate(db, /my/articles);
MyArticleUpdate/MyArticleDelete 保留 author_id 所有权约束(404
article_not_found);my 更新不触碰标签(表单无该字段,与旧行为一致)
- main.go:admin/my 文章旧 POST 路由移除,改 POST/PUT/DELETE
/api/admin/articles[/:id] 与 /api/my/articles[/:id]
- 模板:article_create.html/my_article_form.html 表单改 blogAPI 提交
(easyMDE.value() 同步正文、editor e.submitter 分流草稿/发布),错误内联
articleError/myArticleError;article_list.html/my_articles.html 删除改
blogDelete 委托(base.html 新公共函数,DELETE + reload 保筛选状态)
- main_test 冒烟补文章 CRUD 断言;go build/vet/test 全绿
186 lines
5.0 KiB
Go
186 lines
5.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"go_blog/models"
|
|
)
|
|
|
|
// MyArticlesPage 渲染已登录用户的文章管理页面。
|
|
func MyArticlesPage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var articles []models.Article
|
|
db.Where("author_id = ?", userID).Order("created_at DESC").Find(&articles)
|
|
|
|
data := DefaultData(c)
|
|
data["Title"] = tr["my_articles_title"]
|
|
data["Articles"] = articles
|
|
c.HTML(http.StatusOK, "my_articles", data)
|
|
}
|
|
}
|
|
|
|
// MyArticleCreatePage 为普通用户渲染文章创建表单。
|
|
func MyArticleCreatePage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
token := randomToken()
|
|
session := sessions.Default(c)
|
|
session.Set("pending_token", token)
|
|
session.Save()
|
|
|
|
renderMyArticleForm(c, db, articleForm{
|
|
Action: "/my/articles/new",
|
|
TitleText: tr["article_create_title"],
|
|
SessionToken: token,
|
|
}, "")
|
|
}
|
|
}
|
|
|
|
// MyArticleCreate 处理普通用户创建新文章的 POST 请求。
|
|
func MyArticleCreate(db *gorm.DB) gin.HandlerFunc {
|
|
return ArticleCreate(db, "/my/articles") // 复用创建逻辑,跳转到 my 列表
|
|
}
|
|
|
|
// MyArticleEditPage 为已登录用户自己的文章渲染编辑表单。
|
|
func MyArticleEditPage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
id := c.Param("id")
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var article models.Article
|
|
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
|
|
c.Redirect(http.StatusFound, "/my/articles")
|
|
return
|
|
}
|
|
|
|
renderMyArticleForm(c, db, articleForm{
|
|
Title: article.Title,
|
|
Slug: article.Slug,
|
|
Summary: article.Summary,
|
|
Content: article.Content,
|
|
Cover: article.Cover,
|
|
StatusStr: strconv.Itoa(article.Status),
|
|
IsTop: article.IsTop,
|
|
PublishedAt: formatPublishedAt(article.PublishedAt),
|
|
Action: "/my/articles/" + id + "/edit",
|
|
TitleText: tr["article_edit_title"],
|
|
ArticleID: article.ID,
|
|
}, "")
|
|
}
|
|
}
|
|
|
|
// MyArticleUpdate 处理更新用户自己文章的请求(带 author_id 所有权约束)。
|
|
func MyArticleUpdate(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := parseUintParam(c, "id")
|
|
if id == 0 {
|
|
APIError(c, http.StatusBadRequest, "api_invalid_request")
|
|
return
|
|
}
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var article models.Article
|
|
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
|
|
APIError(c, http.StatusNotFound, "article_not_found")
|
|
return
|
|
}
|
|
|
|
f, ok := parseArticleFormJSON(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if f.Title == "" {
|
|
APIError(c, http.StatusBadRequest, "article_title_required")
|
|
return
|
|
}
|
|
if f.Content == "" {
|
|
APIError(c, http.StatusBadRequest, "article_content_required")
|
|
return
|
|
}
|
|
|
|
if f.Slug == "" {
|
|
f.Slug = generateSlug(f.Title)
|
|
if f.Slug == "" {
|
|
f.Slug = fallbackSlug(article.ID)
|
|
}
|
|
}
|
|
|
|
newStatus := statusFromForm(f.StatusStr)
|
|
|
|
// 处理 published_at:若提供了表单值则使用,否则在首次发布时自动盖章。
|
|
var publishedAt *time.Time
|
|
if f.PublishedAt != "" {
|
|
// 用户提供了自定义发布时间
|
|
publishedAt = parsePublishedAt(f.PublishedAt)
|
|
} else {
|
|
// 文章首次发布时盖上发布时间。
|
|
wasPublished := article.Status == models.ArticlePublished
|
|
publishedAt = article.PublishedAt
|
|
if newStatus == models.ArticlePublished && !wasPublished && publishedAt == nil {
|
|
now := time.Now()
|
|
publishedAt = &now
|
|
}
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"title": f.Title,
|
|
"slug": f.Slug,
|
|
"summary": f.Summary,
|
|
"content": f.Content,
|
|
"cover": f.Cover,
|
|
"status": newStatus,
|
|
"is_top": f.IsTop,
|
|
"published_at": publishedAt,
|
|
}
|
|
|
|
if err := db.Model(&article).Updates(updates).Error; err != nil {
|
|
APIError(c, http.StatusInternalServerError, "article_error")
|
|
return
|
|
}
|
|
|
|
// 注意:my 表单无 tags 字段,此处不触碰标签(与旧行为一致)。
|
|
APIOK(c, "/my/articles", nil)
|
|
}
|
|
}
|
|
|
|
// MyArticleDelete 软删除用户自己的文章。
|
|
func MyArticleDelete(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := parseUintParam(c, "id")
|
|
if id == 0 {
|
|
APIError(c, http.StatusBadRequest, "api_invalid_request")
|
|
return
|
|
}
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
db.Where("id = ? AND author_id = ?", id, userID).Delete(&models.Article{})
|
|
APIOK(c, "/my/articles", nil)
|
|
}
|
|
}
|
|
|
|
// renderMyArticleForm 为普通用户渲染文章表单。
|
|
func renderMyArticleForm(c *gin.Context, db *gorm.DB, f articleForm, errMsg string) {
|
|
data := DefaultData(c)
|
|
data["Title"] = f.TitleText
|
|
if errMsg != "" {
|
|
data["Error"] = errMsg
|
|
}
|
|
applyFormToData(data, f)
|
|
c.HTML(http.StatusOK, "my_article_form", data)
|
|
}
|