️ 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:
2025-07-26 09:48:23 +08:00
parent 9984f665d4
commit e659de23fb
46 changed files with 660 additions and 331 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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 检查是否点赞

View File

@ -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
}

View File

@ -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
}