add tags and category pages, then update backend apis
This commit is contained in:
@@ -65,8 +65,8 @@ func main() {
|
||||
// 公开接口
|
||||
api.POST("/auth/register", authHandler.Register)
|
||||
api.POST("/auth/login", authHandler.Login)
|
||||
api.GET("/auth/me", authHandler.GetCurrentUser) // 获取当前用户信息(需要 token 但通过 header 传递)
|
||||
api.GET("/comments", commentHandler.GetComments)
|
||||
api.POST("/comments", commentHandler.CreateComment) // 允许访客评论
|
||||
api.GET("/likes", likeHandler.GetLikeStatus)
|
||||
api.POST("/likes", likeHandler.ToggleLike) // 允许访客点赞(基于 IP Hash)
|
||||
|
||||
@@ -78,7 +78,8 @@ func main() {
|
||||
authGroup.GET("/auth/profile", authHandler.GetProfile)
|
||||
authGroup.PUT("/auth/profile", authHandler.UpdateProfile)
|
||||
|
||||
// 评论相关(用户删除自己的评论)
|
||||
// 评论相关(需要登录才能评论)
|
||||
authGroup.POST("/comments", commentHandler.CreateComment)
|
||||
authGroup.DELETE("/comments/:id", commentHandler.DeleteComment)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/novablog/server/internal/config"
|
||||
"github.com/novablog/server/internal/models"
|
||||
"github.com/novablog/server/internal/utils"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -37,6 +38,11 @@ func Initialize(cfg *config.Config) error {
|
||||
return fmt.Errorf("failed to migrate database: %w", err)
|
||||
}
|
||||
|
||||
// 创建默认管理员用户
|
||||
if err := createDefaultAdmin(); err != nil {
|
||||
return fmt.Errorf("failed to create default admin: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,4 +64,35 @@ func Close() error {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// createDefaultAdmin 创建默认管理员用户
|
||||
func createDefaultAdmin() error {
|
||||
// 检查是否已存在 admin 用户
|
||||
var count int64
|
||||
DB.Model(&models.User{}).Where("username = ?", "admin").Count(&count)
|
||||
if count > 0 {
|
||||
return nil // 已存在,跳过
|
||||
}
|
||||
|
||||
// 创建 admin 用户
|
||||
hashedPassword, err := utils.HashPassword("admin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
admin := models.User{
|
||||
Username: "admin",
|
||||
Email: "admin@novablog.local",
|
||||
Password: hashedPassword,
|
||||
Role: "admin",
|
||||
Nickname: "Administrator",
|
||||
}
|
||||
|
||||
if err := DB.Create(&admin).Error; err != nil {
|
||||
return fmt.Errorf("failed to create admin user: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("✅ Default admin user created: admin / admin")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,6 +136,45 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetCurrentUser 获取当前用户信息(通过 Authorization header)
|
||||
func (h *AuthHandler) GetCurrentUser(c *gin.Context) {
|
||||
// 从 header 获取 token
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "no token provided"})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 Bearer token
|
||||
tokenString := ""
|
||||
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
|
||||
tokenString = authHeader[7:]
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token format"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
claims, err := h.jwtManager.ParseToken(tokenString)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, claims.UserID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"user": user})
|
||||
}
|
||||
|
||||
// GetProfile 获取当前用户信息
|
||||
func (h *AuthHandler) GetProfile(c *gin.Context) {
|
||||
userID, _ := middleware.GetUserID(c)
|
||||
|
||||
@@ -28,14 +28,21 @@ type CreateCommentRequest struct {
|
||||
|
||||
// CreateComment 创建评论
|
||||
func (h *CommentHandler) CreateComment(c *gin.Context) {
|
||||
userID, _ := middleware.GetUserID(c)
|
||||
|
||||
var req CreateCommentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试从上下文获取用户 ID(如果有认证中间件)
|
||||
userID, isLoggedIn := middleware.GetUserID(c)
|
||||
|
||||
// 如果没有登录,返回错误(评论需要登录)
|
||||
if !isLoggedIn || userID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再评论"})
|
||||
return
|
||||
}
|
||||
|
||||
comment := models.Comment{
|
||||
PostID: req.PostID,
|
||||
UserID: userID,
|
||||
@@ -82,9 +89,11 @@ func (h *CommentHandler) GetComments(c *gin.Context) {
|
||||
|
||||
query.Count(&total)
|
||||
|
||||
if err := query.Preload("User").
|
||||
// 使用 Joins 显式加载用户信息,确保数据完整
|
||||
if err := query.
|
||||
Joins("LEFT JOIN users ON users.id = comments.user_id").
|
||||
Preload("Replies.User").
|
||||
Order("created_at DESC").
|
||||
Order("comments.created_at DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Find(&comments).Error; err != nil {
|
||||
@@ -92,6 +101,25 @@ func (h *CommentHandler) GetComments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 手动加载每个评论的用户信息(确保 Preload 正确工作)
|
||||
for i := range comments {
|
||||
if comments[i].UserID > 0 {
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, comments[i].UserID).Error; err == nil {
|
||||
comments[i].User = user
|
||||
}
|
||||
}
|
||||
// 加载回复的用户信息
|
||||
for j := range comments[i].Replies {
|
||||
if comments[i].Replies[j].UserID > 0 {
|
||||
var replyUser models.User
|
||||
if err := database.DB.First(&replyUser, comments[i].Replies[j].UserID).Error; err == nil {
|
||||
comments[i].Replies[j].User = replyUser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": comments,
|
||||
"pagination": gin.H{
|
||||
|
||||
BIN
server/novablog-server
Executable file
BIN
server/novablog-server
Executable file
Binary file not shown.
Reference in New Issue
Block a user