初始提交:姜十三论坛 Jiang13 Forum

轻量自用论坛,Go 单二进制 + React SPA 内嵌 + SQLite。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
freefire
2026-06-15 21:08:52 +08:00
commit e1c1708715
140 changed files with 16115 additions and 0 deletions

244
handler/admin.go Normal file
View File

@@ -0,0 +1,244 @@
package handler
import (
"net/http"
"path/filepath"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/jiang13/forum/model"
"github.com/jiang13/forum/service"
)
// --- 后台管理页面 ---
func (h *Handlers) adminPageData(c *gin.Context, title, activeNav string, data gin.H) gin.H {
if data == nil {
data = gin.H{}
}
data["ActiveNav"] = activeNav
return h.pageData(c, title, data)
}
func (h *Handlers) AdminLoginPage(c *gin.Context) {
c.HTML(http.StatusOK, "admin/login.html", h.pageData(c, "后台登录", gin.H{
"ActiveNav": "login",
"QueryBanned": c.Query("banned"),
}))
}
func (h *Handlers) AdminDashboard(c *gin.Context) {
var userCount, postCount, boardCount, commentCount int64
model.DB.Model(&model.User{}).Count(&userCount)
model.DB.Model(&model.Post{}).Count(&postCount)
model.DB.Model(&model.Board{}).Count(&boardCount)
model.DB.Model(&model.Comment{}).Count(&commentCount)
recentPosts, _, _ := h.Post.List(service.PostListQuery{Page: 1, Size: 8})
c.HTML(http.StatusOK, "admin/dashboard.html", h.adminPageData(c, "仪表盘", "dashboard", gin.H{
"UserCount": userCount,
"PostCount": postCount,
"BoardCount": boardCount,
"CommentCount": commentCount,
"OnlineCount": h.Online.Count(),
"RecentPosts": recentPosts,
}))
}
func (h *Handlers) AdminBoardsPage(c *gin.Context) {
boards, _ := h.Board.ListWithStats()
c.HTML(http.StatusOK, "admin/boards.html", h.adminPageData(c, "板块管理", "boards", gin.H{
"Boards": boards,
}))
}
func (h *Handlers) AdminPostsPage(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
keyword := strings.TrimSpace(c.Query("keyword"))
posts, total, _ := h.Post.List(service.PostListQuery{Page: page, Size: 20, Keyword: keyword})
c.HTML(http.StatusOK, "admin/posts.html", h.adminPageData(c, "帖子管理", "posts", gin.H{
"Posts": posts,
"Total": total,
"Page": page,
"Keyword": keyword,
"TotalPages": calcTotalPages(total, 20),
}))
}
func (h *Handlers) AdminCommentsPage(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
comments, total, _ := h.Comment.ListRecent(page, 20)
c.HTML(http.StatusOK, "admin/comments.html", h.adminPageData(c, "评论管理", "comments", gin.H{
"Comments": comments,
"Total": total,
"Page": page,
"TotalPages": calcTotalPages(total, 20),
}))
}
func (h *Handlers) AdminUsersPage(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
users, total, _ := h.User.ListUsers(page, 20)
c.HTML(http.StatusOK, "admin/users.html", h.adminPageData(c, "用户管理", "users", gin.H{
"Users": users,
"Total": total,
"Page": page,
"TotalPages": calcTotalPages(total, 20),
}))
}
func (h *Handlers) AdminSettingsPage(c *gin.Context) {
c.HTML(http.StatusOK, "admin/settings.html", h.adminPageData(c, "系统设置", "settings", gin.H{
"FilterPath": h.Cfg.FilterWordsPath(),
"DataDir": h.Cfg.DataDir,
"DBPath": h.Cfg.DBPath(),
"Port": h.Cfg.Port,
}))
}
func calcTotalPages(total int64, size int) int {
if total == 0 {
return 1
}
pages := int(total) / size
if int(total)%size > 0 {
pages++
}
if pages < 1 {
return 1
}
return pages
}
// --- 后台 API ---
func (h *Handlers) AdminAPICreateBoard(c *gin.Context) {
sortOrder, _ := strconv.Atoi(c.PostForm("sort_order"))
board, err := h.Board.Create(c.PostForm("name"), c.PostForm("description"), sortOrder)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "板块已创建", "id": board.ID})
}
func (h *Handlers) AdminAPIUpdateBoard(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
sortOrder, _ := strconv.Atoi(c.PostForm("sort_order"))
if err := h.Board.Update(uint(id), c.PostForm("name"), c.PostForm("description"), sortOrder); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "板块已更新"})
}
func (h *Handlers) AdminAPIDeleteBoard(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Board.Delete(uint(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "板块已删除"})
}
func (h *Handlers) AdminAPIPinPost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
pinned := c.PostForm("pinned") == "true" || c.PostForm("pinned") == "1"
if err := h.Post.SetPinned(uint(id), pinned); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
msg := "已取消置顶"
if pinned {
msg = "已置顶"
}
c.JSON(http.StatusOK, gin.H{"message": msg, "pinned": pinned})
}
func (h *Handlers) AdminAPIDeletePost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Post.Delete(0, uint(id), true); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "帖子已删除"})
}
func (h *Handlers) AdminAPIDeleteComment(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Comment.AdminDelete(uint(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "评论已删除"})
}
func (h *Handlers) AdminAPIBanUser(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
banned := c.PostForm("banned") == "true" || c.PostForm("banned") == "1"
if err := h.User.BanUser(uint(id), banned); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
msg := "已解除禁言"
if banned {
msg = "已禁言"
}
c.JSON(http.StatusOK, gin.H{"message": msg})
}
func (h *Handlers) AdminAPIBackup(c *gin.Context) {
path, err := h.Backup.ExportSQLite()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
filename := filepath.Base(path)
c.JSON(http.StatusOK, gin.H{
"message": "备份成功",
"path": path,
"filename": filename,
"download": "/admin/api/backup/download/" + filename,
})
}
func (h *Handlers) AdminDownloadBackup(c *gin.Context) {
name := c.Param("name")
if !strings.HasPrefix(name, "jiang13_backup_") || !strings.HasSuffix(name, ".db") {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的备份文件名"})
return
}
path := filepath.Join(h.Cfg.DataDir, name)
c.FileAttachment(path, name)
}
func (h *Handlers) AdminAPILogin(c *gin.Context) {
var req struct {
Username string `json:"username" form:"username" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
token, user, err := h.Auth.Login(req.Username, req.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
if user.Role != model.RoleAdmin {
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员账号才能登录后台"})
return
}
h.setAuthCookie(c, token)
c.JSON(http.StatusOK, gin.H{
"message": "登录成功",
"user": gin.H{
"id": user.ID, "nickname": user.Nickname, "role": user.Role,
},
})
}
func (h *Handlers) AdminAPILogout(c *gin.Context) {
h.APILogout(c)
}

237
handler/api.go Normal file
View File

@@ -0,0 +1,237 @@
package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/jiang13/forum/model"
"github.com/jiang13/forum/service"
)
// APIMe 当前登录用户
func (h *Handlers) APIMe(c *gin.Context) {
uid := h.currentUserID(c)
if uid == 0 {
c.JSON(http.StatusOK, gin.H{"user": nil})
return
}
user, err := h.User.GetByID(uid)
if err != nil {
c.JSON(http.StatusOK, gin.H{"user": nil})
return
}
c.JSON(http.StatusOK, gin.H{
"user": user,
})
}
// APIBoards 板块列表(含帖子数)
func (h *Handlers) APIBoards(c *gin.Context) {
boards, err := h.Board.ListWithStats()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if boards == nil {
boards = []service.BoardWithStats{}
}
c.JSON(http.StatusOK, gin.H{"boards": boards})
}
// APIStats 论坛概览统计
func (h *Handlers) APIStats(c *gin.Context) {
var userCount, postCount, boardCount int64
model.DB.Model(&model.User{}).Count(&userCount)
model.DB.Model(&model.Post{}).Count(&postCount)
model.DB.Model(&model.Board{}).Count(&boardCount)
c.JSON(http.StatusOK, gin.H{
"users": userCount, "posts": postCount, "boards": boardCount,
})
}
// APIAdminCreateBoard 管理员创建板块JSON
func (h *Handlers) APIAdminCreateBoard(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
SortOrder int `json:"sort_order"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
board, err := h.Board.Create(req.Name, req.Description, req.SortOrder)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "板块已创建", "board": board})
}
// APIAdminUpdateBoard 管理员更新板块
func (h *Handlers) APIAdminUpdateBoard(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
var req struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
SortOrder int `json:"sort_order"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
if err := h.Board.Update(uint(id), req.Name, req.Description, req.SortOrder); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
board, _ := h.Board.GetByID(uint(id))
c.JSON(http.StatusOK, gin.H{"message": "板块已更新", "board": board})
}
// APIAdminDeleteBoard 管理员删除板块
func (h *Handlers) APIAdminDeleteBoard(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Board.Delete(uint(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "板块已删除"})
}
// APIPosts 帖子列表(分页)
func (h *Handlers) APIPosts(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "30"))
boardID, _ := strconv.ParseUint(c.Query("board_id"), 10, 64)
keyword := c.Query("keyword")
q := service.PostListQuery{
BoardID: uint(boardID),
Page: page,
Size: size,
Keyword: keyword,
}
items, total, err := h.Post.ListItems(q)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if items == nil {
items = []service.PostListItem{}
}
c.JSON(http.StatusOK, gin.H{
"posts": items,
"total": total,
"page": page,
"size": size,
"has_more": int64(page*size) < total,
})
}
// APIPostDetail 帖子详情
func (h *Handlers) APIPostDetail(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
post, err := h.Post.GetByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
}
uid := h.currentUserID(c)
if uid == 0 {
post.Content = service.RedactMembersOnlyHTML(post.Content)
}
comments, _ := h.Comment.ListByPost(uint(id), uid, h.isAdmin(c), post.UserID, h.parseGuestCommentIDs(c))
c.JSON(http.StatusOK, gin.H{
"post": post,
"comment_count": len(comments),
"liked": h.Post.IsLiked(uid, uint(id)),
"favorited": h.Post.IsFavorited(uid, uint(id)),
})
}
// APIPostComments 楼层列表
func (h *Handlers) APIPostComments(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
post, err := h.Post.GetByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "帖子不存在"})
return
}
comments, err := h.Comment.ListByPost(uint(id), h.currentUserID(c), h.isAdmin(c), post.UserID, h.parseGuestCommentIDs(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if comments == nil {
comments = []model.Comment{}
}
c.JSON(http.StatusOK, gin.H{"comments": comments, "total": len(comments)})
}
// APIHotPosts 热门 TOP
func (h *Handlers) APIHotPosts(c *gin.Context) {
items, err := h.Post.HotPosts(10)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"posts": items})
}
// APINotifications 最新动态通知
func (h *Handlers) APINotifications(c *gin.Context) {
posts, _, _ := h.Post.List(service.PostListQuery{Page: 1, Size: 8})
type notice struct {
ID uint `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
CreatedAt string `json:"created_at"`
}
list := make([]notice, 0, len(posts))
for _, p := range posts {
list = append(list, notice{
ID: p.ID, Title: p.Title, Type: "post",
CreatedAt: p.CreatedAt.Format("01-02 15:04"),
})
}
c.JSON(http.StatusOK, gin.H{"notifications": list})
}
// APIOnline 当前浏览统计
func (h *Handlers) APIOnline(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"count": h.Online.Count(),
"members": h.Online.CountMembers(),
"guests": h.Online.CountGuests(),
"users": h.Online.List(20),
})
}
// APIPresence 上报浏览心跳(会员与游客均可)
func (h *Handlers) APIPresence(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"count": h.Online.Count(),
"members": h.Online.CountMembers(),
"guests": h.Online.CountGuests(),
})
}
// APIFavorites 我的收藏
func (h *Handlers) APIFavorites(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
favs, total, err := h.Post.ListFavorites(h.currentUserID(c), page, size)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if favs == nil {
favs = []model.PostFavorite{}
}
c.JSON(http.StatusOK, gin.H{"favorites": favs, "total": total, "page": page})
}
func (h *Handlers) APIPing(c *gin.Context) {
h.APIPresence(c)
}

343
handler/handlers.go Normal file
View File

@@ -0,0 +1,343 @@
package handler
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/jiang13/forum/config"
"github.com/jiang13/forum/middleware"
"github.com/jiang13/forum/model"
"github.com/jiang13/forum/service"
)
// Handlers 聚合所有 HTTP 处理器
type Handlers struct {
Cfg *config.Config
Auth *service.AuthService
User *service.UserService
Board *service.BoardService
Post *service.PostService
Comment *service.CommentService
Backup *service.BackupService
Filter *service.SensitiveFilter
Limiter *service.RateLimiter
Online *service.OnlineService
}
func (h *Handlers) setAuthCookie(c *gin.Context, token string) {
c.SetCookie(middleware.CookieName, token, int(service.TokenExpire.Seconds()), "/", "", false, true)
}
func (h *Handlers) currentUserID(c *gin.Context) uint {
if v, ok := c.Get(middleware.CtxUserID); ok {
return v.(uint)
}
return 0
}
func (h *Handlers) isAdmin(c *gin.Context) bool {
if v, ok := c.Get(middleware.CtxRole); ok {
return v == model.RoleAdmin
}
return false
}
func (h *Handlers) parseGuestCommentIDs(c *gin.Context) []uint {
raw := c.Query("my_ids")
if raw == "" {
return nil
}
var ids []uint
for _, part := range strings.Split(raw, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
v, err := strconv.ParseUint(part, 10, 64)
if err == nil && v > 0 {
ids = append(ids, uint(v))
}
}
return ids
}
func (h *Handlers) pageData(c *gin.Context, title string, data gin.H) gin.H {
if data == nil {
data = gin.H{}
}
data["Title"] = title
data["SiteName"] = "姜十三论坛"
data["SiteEN"] = "Jiang13 Forum"
if uid := h.currentUserID(c); uid > 0 {
data["CurrentUserID"] = uid
if u, err := h.User.GetByID(uid); err == nil {
data["CurrentUser"] = u
}
}
data["IsAdmin"] = h.isAdmin(c)
return data
}
// --- 页面路由 ---
func (h *Handlers) IndexPage(c *gin.Context) {
boards, _ := h.Board.List()
posts, _, _ := h.Post.List(service.PostListQuery{Page: 1, Size: 10})
c.HTML(http.StatusOK, "index.html", h.pageData(c, "首页", gin.H{
"Boards": boards, "Posts": posts,
}))
}
func (h *Handlers) LoginPage(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", h.pageData(c, "登录", nil))
}
func (h *Handlers) RegisterPage(c *gin.Context) {
c.HTML(http.StatusOK, "register.html", h.pageData(c, "注册", nil))
}
func (h *Handlers) BoardPage(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
board, err := h.Board.GetByID(uint(id))
if err != nil {
c.HTML(http.StatusNotFound, "error.html", h.pageData(c, "板块不存在", gin.H{"Message": "板块不存在"}))
return
}
posts, total, _ := h.Post.List(service.PostListQuery{BoardID: uint(id), Page: page, Size: 20})
c.HTML(http.StatusOK, "board.html", h.pageData(c, board.Name, gin.H{
"Board": board, "Posts": posts, "Total": total, "Page": page,
}))
}
func (h *Handlers) PostPage(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
post, err := h.Post.GetByID(uint(id))
if err != nil {
c.HTML(http.StatusNotFound, "error.html", h.pageData(c, "帖子不存在", gin.H{"Message": "帖子不存在"}))
return
}
comments, _ := h.Comment.ListByPost(uint(id), h.currentUserID(c), h.isAdmin(c), post.UserID, nil)
uid := h.currentUserID(c)
c.HTML(http.StatusOK, "post.html", h.pageData(c, post.Title, gin.H{
"Post": post, "Comments": comments,
"Liked": h.Post.IsLiked(uid, uint(id)),
"Favorited": h.Post.IsFavorited(uid, uint(id)),
}))
}
func (h *Handlers) PostNewPage(c *gin.Context) {
boards, _ := h.Board.List()
c.HTML(http.StatusOK, "post_new.html", h.pageData(c, "发帖", gin.H{"Boards": boards}))
}
func (h *Handlers) PostEditPage(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
post, err := h.Post.GetByID(uint(id))
if err != nil || (!h.isAdmin(c) && post.UserID != h.currentUserID(c)) {
c.Redirect(http.StatusFound, "/")
return
}
c.HTML(http.StatusOK, "post_edit.html", h.pageData(c, "编辑帖子", gin.H{"Post": post}))
}
func (h *Handlers) ProfilePage(c *gin.Context) {
user, _ := h.User.GetByID(h.currentUserID(c))
c.HTML(http.StatusOK, "profile.html", h.pageData(c, "个人主页", gin.H{"ProfileUser": user}))
}
func (h *Handlers) UserProfilePage(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
user, err := h.User.GetByID(uint(id))
if err != nil {
c.HTML(http.StatusNotFound, "error.html", h.pageData(c, "用户不存在", gin.H{"Message": "用户不存在"}))
return
}
c.HTML(http.StatusOK, "user_profile.html", h.pageData(c, user.Nickname, gin.H{"ProfileUser": user}))
}
func (h *Handlers) FavoritesPage(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
favs, total, _ := h.Post.ListFavorites(h.currentUserID(c), page, 20)
c.HTML(http.StatusOK, "favorites.html", h.pageData(c, "我的收藏", gin.H{
"Favorites": favs, "Total": total, "Page": page,
}))
}
// --- API ---
func (h *Handlers) APIRegister(c *gin.Context) {
var req struct {
Username string `json:"username" form:"username" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
Nickname string `json:"nickname" form:"nickname"`
}
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
user, err := h.Auth.Register(req.Username, req.Password, req.Nickname)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
token, _, _ := h.Auth.Login(req.Username, req.Password)
h.setAuthCookie(c, token)
c.JSON(http.StatusOK, gin.H{"message": "注册成功", "user_id": user.ID})
}
func (h *Handlers) APILogin(c *gin.Context) {
var req struct {
Username string `json:"username" form:"username" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
token, user, err := h.Auth.Login(req.Username, req.Password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
h.setAuthCookie(c, token)
c.JSON(http.StatusOK, gin.H{"message": "登录成功", "user": gin.H{"id": user.ID, "nickname": user.Nickname}})
}
func (h *Handlers) APILogout(c *gin.Context) {
c.SetCookie(middleware.CookieName, "", -1, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{"message": "已退出"})
}
func (h *Handlers) APIUpdateProfile(c *gin.Context) {
nickname := c.PostForm("nickname")
if err := h.User.UpdateNickname(h.currentUserID(c), nickname); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user, _ := h.User.GetByID(h.currentUserID(c))
c.JSON(http.StatusOK, gin.H{"message": "昵称已更新", "user": user})
}
func (h *Handlers) APIUpdatePassword(c *gin.Context) {
oldPass := c.PostForm("old_password")
newPass := c.PostForm("new_password")
if err := h.User.UpdatePassword(h.currentUserID(c), oldPass, newPass); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "密码已修改"})
}
func (h *Handlers) APIUploadAvatar(c *gin.Context) {
file, err := c.FormFile("avatar")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请选择头像文件"})
return
}
url, err := h.User.UploadAvatar(h.currentUserID(c), file, h.Cfg.UploadDir())
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "头像已更新", "avatar": url})
}
func (h *Handlers) APICreatePost(c *gin.Context) {
boardID, _ := strconv.ParseUint(c.PostForm("board_id"), 10, 64)
title := c.PostForm("title")
content := c.PostForm("content")
tags := c.PostForm("tags")
post, err := h.Post.Create(h.currentUserID(c), uint(boardID), title, content, tags)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "发帖成功", "post_id": post.ID})
}
func (h *Handlers) APIUpdatePost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
err := h.Post.Update(h.currentUserID(c), uint(id), h.isAdmin(c),
c.PostForm("title"), c.PostForm("content"), c.PostForm("tags"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "帖子已更新"})
}
func (h *Handlers) APIDeletePost(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Post.Delete(h.currentUserID(c), uint(id), h.isAdmin(c)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "帖子已删除"})
}
func (h *Handlers) APIToggleLike(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
liked, err := h.Post.ToggleLike(h.currentUserID(c), uint(id))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var post model.Post
model.DB.First(&post, id)
c.JSON(http.StatusOK, gin.H{"liked": liked, "like_count": post.LikeCount})
}
func (h *Handlers) APIToggleFavorite(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
faved, err := h.Post.ToggleFavorite(h.currentUserID(c), uint(id))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"favorited": faved})
}
func (h *Handlers) APICreateComment(c *gin.Context) {
postID, _ := strconv.ParseUint(c.Param("id"), 10, 64)
content := c.PostForm("content")
var replyTo *uint
if rt := c.PostForm("reply_to"); rt != "" {
v, _ := strconv.ParseUint(rt, 10, 64)
u := uint(v)
replyTo = &u
}
isPrivate := c.PostForm("is_private") == "1" || c.PostForm("is_private") == "true"
uid := h.currentUserID(c)
in := service.CommentCreateInput{
UserID: uid,
PostID: uint(postID),
Content: content,
ReplyTo: replyTo,
IsPrivate: isPrivate,
}
if uid == 0 {
in.GuestNick = c.PostForm("guest_nick")
in.GuestEmail = c.PostForm("guest_email")
in.GuestURL = c.PostForm("guest_url")
}
comment, err := h.Comment.Create(in)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "评论成功", "floor": comment.Floor, "id": comment.ID})
}
func (h *Handlers) APIDeleteComment(c *gin.Context) {
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := h.Comment.Delete(h.currentUserID(c), uint(id), h.isAdmin(c)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "评论已删除"})
}