diff --git a/package-lock.json b/package-lock.json index 388f5df..fea6885 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "tidal-trappist", + "name": "novablog", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "tidal-trappist", + "name": "novablog", "version": "0.0.1", "dependencies": { "@astrojs/mdx": "^4.3.13", @@ -13,7 +13,8 @@ "@astrojs/vue": "^5.1.4", "@tailwindcss/typography": "^0.5.19", "astro": "^5.17.1", - "tailwindcss": "^3.4.19", + "marked": "^17.0.3", + "tailwindcss": "^3.4.0", "vue": "^3.5.29" } }, @@ -4633,6 +4634,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz", + "integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mdast-util-definitions": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", diff --git a/package.json b/package.json index 350f398..12ea264 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@astrojs/vue": "^5.1.4", "@tailwindcss/typography": "^0.5.19", "astro": "^5.17.1", + "marked": "^17.0.3", "tailwindcss": "^3.4.0", "vue": "^3.5.29" } -} \ No newline at end of file +} diff --git a/public/images/hello-world.jpg b/public/images/hello-world.jpg new file mode 100644 index 0000000..7df0e0c Binary files /dev/null and b/public/images/hello-world.jpg differ diff --git a/public/images/hello-world.svg b/public/images/hello-world.svg new file mode 100644 index 0000000..13841bc --- /dev/null +++ b/public/images/hello-world.svg @@ -0,0 +1,11 @@ + diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go index dfa43fe..8714f12 100644 --- a/server/cmd/server/main.go +++ b/server/cmd/server/main.go @@ -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) } diff --git a/server/internal/database/database.go b/server/internal/database/database.go index 460265b..c7da001 100644 --- a/server/internal/database/database.go +++ b/server/internal/database/database.go @@ -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() -} \ No newline at end of file +} + +// 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 +} diff --git a/server/internal/handlers/auth.go b/server/internal/handlers/auth.go index 6760ac8..72b5ef7 100644 --- a/server/internal/handlers/auth.go +++ b/server/internal/handlers/auth.go @@ -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) diff --git a/server/internal/handlers/comment.go b/server/internal/handlers/comment.go index d7b75d1..533056c 100644 --- a/server/internal/handlers/comment.go +++ b/server/internal/handlers/comment.go @@ -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{ diff --git a/server/novablog-server b/server/novablog-server new file mode 100755 index 0000000..5f98314 Binary files /dev/null and b/server/novablog-server differ diff --git a/src/components/CommentSection.vue b/src/components/CommentSection.vue index 0c3cddd..c03ec44 100644 --- a/src/components/CommentSection.vue +++ b/src/components/CommentSection.vue @@ -45,7 +45,7 @@
{{ comment.content }}
+{{ reply.content }}
++ 共 {totalCategories} 个分类,{allPosts.length} 篇文章 +
+暂无分类
+按标签浏览文章
++ 共 {sortedTags.length} 个标签,{allPosts.length} 篇文章 +
暂无标签