Files
go_blog/handlers/my_articles.go
T
kevinandClaude Fable 5 d64c839b10 feat: add role-based access control and user article management
- Add AdminRequired middleware to all /admin routes for proper authorization
- Restrict admin panel, comments, users, settings to admin role only
- Create separate /my/articles routes for regular users to manage their own articles
- Add MyArticlesPage handler for users to view/edit/delete their own content
- Update navigation menus with role-based visibility
- Admin dropdown shows only 'Admin Panel' link, other features in dashboard
- Regular users see 'My Articles' link in profile dropdown
- Add i18n translations for 'my_articles' and 'comment_manage' keys
- Fix permission boundary issues where author role could access admin settings

Security improvements:
- Platform settings now require admin role
- Comment moderation requires admin role
- User management requires admin role
- Regular users can only manage their own articles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 18:49:33 +08:00

170 lines
4.5 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 renders the logged-in user's article management page.
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 renders the article creation form for regular users.
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 handles the POST request to create a new article for regular users.
func MyArticleCreate(db *gorm.DB) gin.HandlerFunc {
return ArticleCreate(db) // Reuse the same logic
}
// MyArticleEditPage renders the article edit form for the logged-in user's own articles.
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,
Action: "/my/articles/" + id + "/edit",
TitleText: tr["article_edit_title"],
ArticleID: article.ID,
}, "")
}
}
// MyArticleUpdate handles the POST request to update the user's own article.
func MyArticleUpdate(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
}
f := parseArticleForm(c)
f.Action = "/my/articles/" + id + "/edit"
f.TitleText = tr["article_edit_title"]
f.ArticleID = article.ID
if f.Title == "" {
renderMyArticleForm(c, db, f, tr["article_title_required"])
return
}
if f.Content == "" {
renderMyArticleForm(c, db, f, tr["article_content_required"])
return
}
if f.Slug == "" {
f.Slug = generateSlug(f.Title)
if f.Slug == "" {
f.Slug = fallbackSlug(article.ID)
}
}
newStatus := statusFromForm(f.StatusStr)
// Stamp the publish time the first time an article is published.
wasPublished := article.Status == models.ArticlePublished
var publishedAt *time.Time = 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 {
renderMyArticleForm(c, db, f, tr["article_error"])
return
}
c.Redirect(http.StatusFound, "/my/articles")
}
}
// MyArticleDelete soft-deletes the user's own article.
func MyArticleDelete(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
session := sessions.Default(c)
userID := session.Get("user_id")
db.Where("id = ? AND author_id = ?", id, userID).Delete(&models.Article{})
c.Redirect(http.StatusFound, "/my/articles")
}
}
// renderMyArticleForm renders the article form for regular users.
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)
}