mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-03 15:56:22 +00:00
⚡️ feat: refactor sorting parameters in post listing API and components
- Renamed `orderedBy` to `orderBy` and `reverse` to `desc` in ListPostsParams interface and related functions. - Updated all usages of the sorting parameters in the post listing logic to reflect the new naming convention. feat: add user-related API functions - Implemented `getLoginUser` and `getUserById` functions in the user API to fetch user details. - Enhanced user model to include `language` property. feat: integrate next-intl for internationalization - Added `next-intl` plugin to Next.js configuration for improved localization support. - Removed previous i18n implementation and replaced it with a new structure using JSON files for translations. - Created locale files for English, Japanese, and Chinese with basic translations. - Implemented a request configuration to handle user locales and messages dynamically. fix: clean up unused imports and code - Removed unused i18n utility functions and language settings from device context. - Cleaned up commented-out code in blog card component and sidebar. chore: update dependencies - Added `deepmerge` for merging locale messages. - Updated package.json and pnpm-lock.yaml to reflect new dependencies.
This commit is contained in:
@ -1 +1,91 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/service"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CommentController struct {
|
||||
service *service.CommentService
|
||||
}
|
||||
|
||||
func NewCommentController() *CommentController {
|
||||
return &CommentController{
|
||||
service: service.NewCommentService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *CommentController) CreateComment(ctx context.Context, c *app.RequestContext) {
|
||||
var req dto.CreateCommentReq
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
err := cc.service.CreateComment(ctx, &req)
|
||||
if err != nil {
|
||||
serviceErr := errs.AsServiceError(err)
|
||||
resps.Custom(c, serviceErr.Code, serviceErr.Message, nil)
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (cc *CommentController) UpdateComment(ctx context.Context, c *app.RequestContext) {
|
||||
var req dto.UpdateCommentReq
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
id := c.Param("id")
|
||||
idInt, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
req.CommentID = uint(idInt)
|
||||
|
||||
err = cc.service.UpdateComment(ctx, &req)
|
||||
if err != nil {
|
||||
serviceErr := errs.AsServiceError(err)
|
||||
resps.Custom(c, serviceErr.Code, serviceErr.Message, nil)
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (cc *CommentController) DeleteComment(ctx context.Context, c *app.RequestContext) {
|
||||
id := c.Param("id")
|
||||
err := cc.service.DeleteComment(ctx, id)
|
||||
if err != nil {
|
||||
serviceErr := errs.AsServiceError(err)
|
||||
resps.Custom(c, serviceErr.Code, serviceErr.Message, nil)
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, nil)
|
||||
}
|
||||
|
||||
func (cc *CommentController) GetComment(ctx context.Context, c *app.RequestContext) {
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
resps.BadRequest(c, resps.ErrParamInvalid)
|
||||
return
|
||||
}
|
||||
resp, err := cc.service.GetComment(ctx, id)
|
||||
if err != nil {
|
||||
serviceErr := errs.AsServiceError(err)
|
||||
resps.Custom(c, serviceErr.Code, serviceErr.Message, nil)
|
||||
return
|
||||
}
|
||||
resps.Ok(c, resps.Success, resp)
|
||||
}
|
||||
|
||||
func (cc *CommentController) GetCommentList(ctx context.Context, c *app.RequestContext) {
|
||||
// pagenation := ctxutils.GetPaginationParams(c)
|
||||
}
|
||||
|
||||
func (cc *CommentController) ReactComment(ctx context.Context, c *app.RequestContext) {}
|
||||
|
@ -1 +1,16 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
|
||||
type LikeController struct{}
|
||||
|
||||
func NewLikeController() *LikeController {
|
||||
return &LikeController{}
|
||||
}
|
||||
|
||||
func (lc *LikeController) ToggleLike(ctx context.Context, c *app.RequestContext) {
|
||||
// Implementation for creating a like
|
||||
}
|
||||
|
@ -96,21 +96,21 @@ func (p *PostController) Update(ctx context.Context, c *app.RequestContext) {
|
||||
func (p *PostController) List(ctx context.Context, c *app.RequestContext) {
|
||||
pagination := ctxutils.GetPaginationParams(c)
|
||||
fmt.Println(pagination)
|
||||
if pagination.OrderedBy == "" {
|
||||
pagination.OrderedBy = constant.OrderedByUpdatedAt
|
||||
if pagination.OrderBy == "" {
|
||||
pagination.OrderBy = constant.OrderByUpdatedAt
|
||||
}
|
||||
if pagination.OrderedBy != "" && !slices.Contains(constant.OrderedByEnumPost, pagination.OrderedBy) {
|
||||
if pagination.OrderBy != "" && !slices.Contains(constant.OrderByEnumPost, pagination.OrderBy) {
|
||||
resps.BadRequest(c, "无效的排序字段")
|
||||
return
|
||||
}
|
||||
keywords := c.Query("keywords")
|
||||
keywordsArray := strings.Split(keywords, ",")
|
||||
req := &dto.ListPostReq{
|
||||
Keywords: keywordsArray,
|
||||
Page: pagination.Page,
|
||||
Size: pagination.Size,
|
||||
OrderedBy: pagination.OrderedBy,
|
||||
Reverse: pagination.Reverse,
|
||||
Keywords: keywordsArray,
|
||||
Page: pagination.Page,
|
||||
Size: pagination.Size,
|
||||
OrderBy: pagination.OrderBy,
|
||||
Desc: pagination.Desc,
|
||||
}
|
||||
posts, err := p.service.ListPosts(ctx, req)
|
||||
if err != nil {
|
||||
|
@ -6,28 +6,28 @@ import (
|
||||
)
|
||||
|
||||
type PaginationParams struct {
|
||||
Page uint64
|
||||
Size uint64
|
||||
OrderedBy string
|
||||
Reverse bool // 默认是从大值到小值
|
||||
Page uint64
|
||||
Size uint64
|
||||
OrderBy string
|
||||
Desc bool // 默认是从大值到小值
|
||||
}
|
||||
|
||||
func GetPaginationParams(c *app.RequestContext) *PaginationParams {
|
||||
page := c.Query("page")
|
||||
size := c.Query("size")
|
||||
orderedBy := c.Query("ordered_by")
|
||||
reverse := c.Query("reverse")
|
||||
orderBy := c.Query("order_by")
|
||||
desc := c.Query("desc")
|
||||
if page == "" {
|
||||
page = "1"
|
||||
}
|
||||
if size == "" {
|
||||
size = "10"
|
||||
}
|
||||
var reverseBool bool
|
||||
if reverse == "" || reverse == "false" || reverse == "0" {
|
||||
reverseBool = false
|
||||
var descBool bool
|
||||
if desc == "" || desc == "false" || desc == "0" {
|
||||
descBool = false
|
||||
} else {
|
||||
reverseBool = true
|
||||
descBool = true
|
||||
}
|
||||
pageNum, err := strconv.ParseUint(page, 10, 64)
|
||||
if err != nil || pageNum < 1 {
|
||||
@ -38,9 +38,9 @@ func GetPaginationParams(c *app.RequestContext) *PaginationParams {
|
||||
sizeNum = 10
|
||||
}
|
||||
return &PaginationParams{
|
||||
Page: pageNum,
|
||||
Size: sizeNum,
|
||||
OrderedBy: orderedBy,
|
||||
Reverse: reverseBool,
|
||||
Page: pageNum,
|
||||
Size: sizeNum,
|
||||
OrderBy: orderBy,
|
||||
Desc: descBool,
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,25 @@
|
||||
package dto
|
||||
|
||||
type CommentDto struct {
|
||||
ID uint `json:"id"`
|
||||
TargetID uint `json:"target_id"`
|
||||
TargetType string `json:"target_type"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content"`
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
Depth int `json:"depth"` // 评论的层级深度
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
User UserDto `json:"user"` // 评论的
|
||||
}
|
||||
|
||||
type CreateCommentReq struct {
|
||||
TargetID uint `json:"target_id" binding:"required"` // 目标ID
|
||||
TargetType string `json:"target_type" binding:"required"` // 目标类型,如 "post", "page"
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
ReplyID uint `json:"reply_id"` // 回复的评论ID
|
||||
}
|
||||
|
||||
type UpdateCommentReq struct {
|
||||
CommentID uint `json:"comment_id" binding:"required"` // 评论ID
|
||||
Content string `json:"content" binding:"required"` // 评论内容
|
||||
}
|
||||
|
@ -29,16 +29,16 @@ type CreateOrUpdatePostReq struct {
|
||||
}
|
||||
|
||||
type ListPostReq struct {
|
||||
Keywords []string `json:"keywords"` // 关键词列表
|
||||
OrderedBy string `json:"ordered_by"` // 排序方式
|
||||
Page uint64 `json:"page"` // 页码
|
||||
Size uint64 `json:"size"`
|
||||
Reverse bool `json:"reverse"`
|
||||
Keywords []string `json:"keywords"` // 关键词列表
|
||||
OrderBy string `json:"order_by"` // 排序方式
|
||||
Page uint64 `json:"page"` // 页码
|
||||
Size uint64 `json:"size"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
||||
type ListPostResp struct {
|
||||
Posts []PostDto `json:"posts"`
|
||||
Total uint64 `json:"total"` // 总数
|
||||
OrderedBy string `json:"ordered_by"` // 排序方式
|
||||
Reverse bool `json:"reverse"`
|
||||
Posts []PostDto `json:"posts"`
|
||||
Total uint64 `json:"total"` // 总数
|
||||
OrderBy string `json:"order_by"` // 排序方式
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ type UserDto struct {
|
||||
Email string `json:"email"` // 邮箱
|
||||
Gender string `json:"gender"`
|
||||
Role string `json:"role"`
|
||||
Language string `json:"language"` // 语言
|
||||
}
|
||||
|
||||
type UserOidcConfigDto struct {
|
||||
|
@ -8,30 +8,42 @@ import (
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/resps"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func UseAuth(block bool) app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
// For cookie
|
||||
token := string(c.Cookie("token"))
|
||||
tokenFromCookie := string(c.Cookie("tokenFromCookie"))
|
||||
tokenFromHeader := strings.TrimPrefix(string(c.GetHeader("Authorization")), "Bearer ")
|
||||
refreshToken := string(c.Cookie("refresh_token"))
|
||||
|
||||
// 尝试用普通 token 认证
|
||||
tokenClaims, err := utils.Jwt.ParseJsonWebTokenWithoutState(token)
|
||||
if err == nil && tokenClaims != nil {
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyUserID, tokenClaims.UserID)
|
||||
c.Next(ctx)
|
||||
return
|
||||
// 尝试用普通 tokenFromCookie 认证
|
||||
if tokenFromCookie != "" {
|
||||
tokenClaims, err := utils.Jwt.ParseJsonWebTokenWithoutState(tokenFromCookie)
|
||||
if err == nil && tokenClaims != nil {
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyUserID, tokenClaims.UserID)
|
||||
c.Next(ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// token 失效 使用 refresh token 重新签发和鉴权
|
||||
// tokenFromCookie 认证失败,尝试用 Bearer tokenFromHeader 认证
|
||||
if tokenFromHeader != "" {
|
||||
tokenClaims, err := utils.Jwt.ParseJsonWebTokenWithoutState(tokenFromHeader)
|
||||
if err == nil && tokenClaims != nil {
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyUserID, tokenClaims.UserID)
|
||||
c.Next(ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
// tokenFromCookie 失效 使用 refresh tokenFromCookie 重新签发和鉴权
|
||||
refreshTokenClaims, err := utils.Jwt.ParseJsonWebTokenWithoutState(refreshToken)
|
||||
if err == nil && refreshTokenClaims != nil {
|
||||
ok, err := isStatefulJwtValid(refreshTokenClaims)
|
||||
if err == nil && ok {
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyUserID, refreshTokenClaims.UserID)
|
||||
// 生成新 token
|
||||
// 生成新 tokenFromCookie
|
||||
newTokenClaims := utils.Jwt.NewClaims(
|
||||
refreshTokenClaims.UserID,
|
||||
refreshTokenClaims.SessionKey,
|
||||
|
@ -11,6 +11,7 @@ type Comment struct {
|
||||
ReplyID uint `gorm:"index"` // 回复的评论ID
|
||||
Content string `gorm:"type:text"` // 评论内容
|
||||
Depth int `gorm:"default:0"` // 评论的层级深度
|
||||
IsPrivate bool `gorm:"default:false"` // 是否为私密评论,私密评论只有评论者和被评论对象所有者可见
|
||||
LikeCount uint64
|
||||
CommentCount uint64
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ type Post struct {
|
||||
CategoryID uint `gorm:"index"` // 帖子分类ID
|
||||
Category Category `gorm:"foreignKey:CategoryID;references:ID"` // 关联的分类
|
||||
Labels []Label `gorm:"many2many:post_labels;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` // 关联的标签
|
||||
IsPrivate bool `gorm:"default:false"` // 是否为私密帖子
|
||||
IsPrivate bool `gorm:"default:false"`
|
||||
LikeCount uint64
|
||||
CommentCount uint64
|
||||
ViewCount uint64
|
||||
@ -61,3 +61,14 @@ func (p *Post) ToDto() dto.PostDto {
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// ToDtoWithShortContent 返回一个简化的 DTO,内容可以根据需要截断
|
||||
func (p *Post) ToDtoWithShortContent(contentLength int) dto.PostDto {
|
||||
dtoPost := p.ToDto()
|
||||
if len(p.Content) > contentLength {
|
||||
dtoPost.Content = p.Content[:contentLength] + "..."
|
||||
} else {
|
||||
dtoPost.Content = p.Content
|
||||
}
|
||||
return dtoPost
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ type User struct {
|
||||
Email string `gorm:"uniqueIndex"`
|
||||
Gender string
|
||||
Role string `gorm:"default:'user'"`
|
||||
Language string `gorm:"default:'en'"`
|
||||
Password string // 密码,存储加密后的值
|
||||
}
|
||||
|
||||
@ -33,5 +34,6 @@ func (user *User) ToDto() *dto.UserDto {
|
||||
Email: user.Email,
|
||||
Gender: user.Gender,
|
||||
Role: user.Role,
|
||||
Language: user.Language,
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,33 @@
|
||||
package repo
|
||||
|
||||
import "github.com/snowykami/neo-blog/internal/model"
|
||||
|
||||
type CommentRepo struct {
|
||||
}
|
||||
|
||||
var Comment = &CommentRepo{}
|
||||
|
||||
func (cr *CommentRepo) CreateComment(comment *model.Comment) error {
|
||||
// Implementation for creating a comment
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *CommentRepo) UpdateComment(comment *model.Comment) error {
|
||||
// Implementation for updating a comment
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *CommentRepo) DeleteComment(commentID string) error {
|
||||
// Implementation for deleting a comment
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr *CommentRepo) GetComment(commentID string) (*model.Comment, error) {
|
||||
// Implementation for getting a comment by ID
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (cr *CommentRepo) ListComments(currentUserID uint, page, size uint, orderBy string, desc bool) ([]model.Comment, error) {
|
||||
// Implementation for listing comments for a post
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -101,10 +101,8 @@ func initPostgres(config DBConfig, gormConfig *gorm.Config) (db *gorm.DB, err er
|
||||
if config.Host == "" || config.User == "" || config.Password == "" || config.DBName == "" {
|
||||
err = errors.New("PostgreSQL configuration is incomplete: host, user, password, and dbname are required")
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode)
|
||||
|
||||
db, err = gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
return
|
||||
}
|
||||
|
@ -11,38 +11,53 @@ type likeRepo struct{}
|
||||
|
||||
var Like = &likeRepo{}
|
||||
|
||||
// Like 用户点赞,幂等
|
||||
func (l *likeRepo) Like(userID, targetID uint, targetType string) error {
|
||||
func (l *likeRepo) ToggleLike(userID, targetID uint, targetType string) error {
|
||||
err := l.checkTargetType(targetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var existingLike model.Like
|
||||
err = GetDB().Where("target_type = ? AND target_id = ? AND user_id = ?", targetType, targetID, userID).First(&existingLike).Error
|
||||
if err == nil {
|
||||
return GetDB().Transaction(func(tx *gorm.DB) error {
|
||||
// 判断是否已点赞
|
||||
isLiked, err := l.IsLiked(userID, targetID, targetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isLiked {
|
||||
// 已点赞,执行取消点赞逻辑
|
||||
if err := tx.Where("target_type = ? AND target_id = ? AND user_id = ?", targetType, targetID, userID).Delete(&model.Like{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 未点赞,执行新增点赞逻辑
|
||||
like := &model.Like{
|
||||
TargetType: targetType,
|
||||
TargetID: targetID,
|
||||
UserID: userID,
|
||||
}
|
||||
if err := tx.Create(like).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 重新计算点赞数量
|
||||
var count int64
|
||||
if err := tx.Model(&model.Like{}).Where("target_type = ? AND target_id = ?", targetType, targetID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新目标的点赞数量
|
||||
switch targetType {
|
||||
case constant.TargetTypePost:
|
||||
if err := tx.Model(&model.Post{}).Where("id = ?", targetID).Update("like_count", count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.TargetTypeComment:
|
||||
if err := tx.Model(&model.Comment{}).Where("id = ?", targetID).Update("like_count", count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("invalid target type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
like := &model.Like{
|
||||
TargetType: targetType,
|
||||
TargetID: targetID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return GetDB().Create(like).Error
|
||||
}
|
||||
|
||||
// UnLike 取消点赞
|
||||
func (l *likeRepo) UnLike(userID, targetID uint, targetType string) error {
|
||||
err := l.checkTargetType(targetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return GetDB().Where("target_type = ? AND target_id = ? AND user_id = ?",
|
||||
targetType, targetID, userID).Delete(&model.Like{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// IsLiked 检查是否点赞
|
||||
|
@ -1,7 +1,6 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
@ -48,16 +47,9 @@ func (p *postRepo) UpdatePost(post *model.Post) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRepo) ListPosts(currentUserID uint, keywords []string, page, size uint64, orderedBy string, reverse bool) ([]model.Post, error) {
|
||||
var posts []model.Post
|
||||
if !slices.Contains(constant.OrderedByEnumPost, orderedBy) {
|
||||
return nil, errs.New(http.StatusBadRequest, "invalid ordered_by parameter", nil)
|
||||
}
|
||||
order := orderedBy
|
||||
if reverse {
|
||||
order += " ASC"
|
||||
} else {
|
||||
order += " DESC"
|
||||
func (p *postRepo) ListPosts(currentUserID uint, keywords []string, page, size uint64, orderBy string, desc bool) ([]model.Post, error) {
|
||||
if !slices.Contains(constant.OrderByEnumPost, orderBy) {
|
||||
return nil, errs.New(http.StatusBadRequest, "invalid order_by parameter", nil)
|
||||
}
|
||||
query := GetDB().Model(&model.Post{}).Preload("User")
|
||||
if currentUserID > 0 {
|
||||
@ -65,7 +57,6 @@ func (p *postRepo) ListPosts(currentUserID uint, keywords []string, page, size u
|
||||
} else {
|
||||
query = query.Where("is_private = ?", false)
|
||||
}
|
||||
fmt.Println(keywords)
|
||||
if len(keywords) > 0 {
|
||||
for _, keyword := range keywords {
|
||||
if keyword != "" {
|
||||
@ -75,9 +66,20 @@ func (p *postRepo) ListPosts(currentUserID uint, keywords []string, page, size u
|
||||
}
|
||||
}
|
||||
}
|
||||
query = query.Order(order).Offset(int((page - 1) * size)).Limit(int(size))
|
||||
if err := query.Find(&posts).Error; err != nil {
|
||||
items, _, err := PaginateQuery[model.Post](query, page, size, orderBy, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return posts, nil
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (p *postRepo) ToggleLikePost(postID uint, userID uint) error {
|
||||
if postID == 0 || userID == 0 {
|
||||
return errs.New(http.StatusBadRequest, "invalid post ID or user ID", nil)
|
||||
}
|
||||
err := Like.ToggleLike(userID, postID, constant.TargetTypePost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1 +1,39 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func PaginateQuery[T any](db *gorm.DB, page, limit uint64, orderBy string, desc bool, conditions ...any) (items []T, total int64, err error) {
|
||||
countDB := db
|
||||
if len(conditions) > 0 {
|
||||
countDB = countDB.Where(conditions[0], conditions[1:]...)
|
||||
}
|
||||
err = countDB.Model(new(T)).Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = constant.PageLimitDefault
|
||||
}
|
||||
queryDB := db
|
||||
if len(conditions) > 0 {
|
||||
queryDB = queryDB.Where(conditions[0], conditions[1:]...)
|
||||
}
|
||||
if page > 0 {
|
||||
offset := (page - 1) * limit
|
||||
queryDB = queryDB.Offset(int(offset))
|
||||
}
|
||||
orderStr := orderBy
|
||||
if orderStr == "" {
|
||||
orderStr = "id"
|
||||
}
|
||||
if desc {
|
||||
orderStr += " DESC"
|
||||
} else {
|
||||
orderStr += " ASC"
|
||||
}
|
||||
err = queryDB.Limit(int(limit)).Order(orderStr).Find(&items).Error
|
||||
return
|
||||
}
|
||||
|
@ -1,7 +1,21 @@
|
||||
package apiv1
|
||||
|
||||
import "github.com/cloudwego/hertz/pkg/route"
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
)
|
||||
|
||||
func registerCommentRoutes(group *route.RouterGroup) {
|
||||
// TODO: Implement comment routes
|
||||
commentController := v1.NewCommentController()
|
||||
commentGroup := group.Group("/comments").Use(middleware.UseAuth(true))
|
||||
commentGroupWithoutAuth := group.Group("/comments").Use(middleware.UseAuth(false))
|
||||
{
|
||||
commentGroup.POST("/c", commentController.CreateComment)
|
||||
commentGroup.PUT("/c/:id", commentController.UpdateComment)
|
||||
commentGroup.DELETE("/c/:id", commentController.DeleteComment)
|
||||
commentGroup.PUT("/c/:id/react", commentController.ReactComment) // 暂时先不写
|
||||
commentGroupWithoutAuth.GET("/c/:id", commentController.GetComment)
|
||||
commentGroupWithoutAuth.GET("/c/list", commentController.GetCommentList)
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,15 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
"github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
)
|
||||
|
||||
func registerLikeRoutes(group *route.RouterGroup) {
|
||||
likeController := v1.NewLikeController()
|
||||
likeGroup := group.Group("/like").Use(middleware.UseAuth(true))
|
||||
{
|
||||
likeGroup.PUT("/toggle", likeController.ToggleLike)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ func registerPostRoutes(group *route.RouterGroup) {
|
||||
postGroupWithoutAuth.GET("/p/:id", postController.Get)
|
||||
postGroupWithoutAuth.GET("/list", postController.List)
|
||||
postGroup.POST("/p", postController.Create)
|
||||
postGroup.PUT("/p", postController.Update)
|
||||
postGroup.DELETE("/p", postController.Delete)
|
||||
postGroup.PUT("/p/:id", postController.Update)
|
||||
postGroup.DELETE("/p/:id", postController.Delete)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func registerUserRoutes(group *route.RouterGroup) {
|
||||
userGroupWithoutAuth.GET("/oidc/list", userController.OidcList)
|
||||
userGroupWithoutAuth.GET("/oidc/login/:name", userController.OidcLogin)
|
||||
userGroupWithoutAuth.GET("/u/:id", userController.GetUser)
|
||||
userGroup.GET("/u", userController.GetUser)
|
||||
userGroup.GET("/me", userController.GetUser)
|
||||
userGroup.POST("/logout", userController.Logout)
|
||||
userGroup.PUT("/u/:id", userController.UpdateUser)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ func RegisterRoutes(h *server.Hertz) {
|
||||
registerAdminRoutes(apiV1Group)
|
||||
registerFileRoutes(apiV1Group)
|
||||
registerLabelRoutes(apiV1Group)
|
||||
registerLikeRoutes(apiV1Group)
|
||||
registerPageRoutes(apiV1Group)
|
||||
registerPostRoutes(apiV1Group)
|
||||
registerUserRoutes(apiV1Group)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/app/middlewares/server/recovery"
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/router/apiv1"
|
||||
@ -25,5 +26,6 @@ func init() {
|
||||
server.WithHostPorts(":"+utils.Env.Get("PORT", "8888")),
|
||||
server.WithMaxRequestBodySize(utils.Env.GetAsInt("MAX_REQUEST_BODY_SIZE", 1048576000)), // 1000MiB
|
||||
)
|
||||
h.Use(recovery.Recovery())
|
||||
apiv1.RegisterRoutes(h)
|
||||
}
|
||||
|
@ -1 +1,36 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
)
|
||||
|
||||
type CommentService struct{}
|
||||
|
||||
func NewCommentService() *CommentService {
|
||||
return &CommentService{}
|
||||
}
|
||||
|
||||
func (cs *CommentService) CreateComment(ctx context.Context, req *dto.CreateCommentReq) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) UpdateComment(ctx context.Context, req *dto.UpdateCommentReq) error {
|
||||
// Implementation for updating a comment
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) DeleteComment(ctx context.Context, commentID string) error {
|
||||
// Implementation for deleting a comment
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dto.CommentDto, error) {
|
||||
// Implementation for getting a single comment
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
//func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) {
|
||||
// // Implementation for getting a list of comments
|
||||
// return nil, nil
|
||||
//}
|
||||
|
@ -127,12 +127,37 @@ func (p *PostService) UpdatePost(ctx context.Context, id string, req *dto.Create
|
||||
func (p *PostService) ListPosts(ctx context.Context, req *dto.ListPostReq) ([]dto.PostDto, error) {
|
||||
postDtos := make([]dto.PostDto, 0)
|
||||
currentUserID, _ := ctxutils.GetCurrentUserID(ctx)
|
||||
posts, err := repo.Post.ListPosts(currentUserID, req.Keywords, req.Page, req.Size, req.OrderedBy, req.Reverse)
|
||||
posts, err := repo.Post.ListPosts(currentUserID, req.Keywords, req.Page, req.Size, req.OrderBy, req.Desc)
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrInternalServer.Code, "failed to list posts", err)
|
||||
}
|
||||
for _, post := range posts {
|
||||
postDtos = append(postDtos, post.ToDto())
|
||||
postDtos = append(postDtos, post.ToDtoWithShortContent(100))
|
||||
}
|
||||
return postDtos, nil
|
||||
}
|
||||
|
||||
func (p *PostService) ToggleLikePost(ctx context.Context, id string) error {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return errs.ErrUnauthorized
|
||||
}
|
||||
if id == "" {
|
||||
return errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
if post.UserID == currentUser.ID {
|
||||
return errs.ErrForbidden
|
||||
}
|
||||
idInt, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return errs.New(errs.ErrBadRequest.Code, "invalid post ID", err)
|
||||
}
|
||||
if err := repo.Post.ToggleLikePost(uint(idInt), currentUser.ID); err != nil {
|
||||
return errs.ErrInternalServer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package tasks
|
||||
|
||||
// ClearSessionDaemon 定时任务:清理过期会话
|
||||
// ClearSessionDaemon 定时任务:清理过期会话 TODO: 定期清理过期会话key
|
||||
func ClearSessionDaemon() {}
|
||||
|
Reference in New Issue
Block a user