mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: add sidebar component with context and mobile support
- Implemented Sidebar component with collapsible functionality. - Added SidebarProvider for managing open state and keyboard shortcuts. - Created SidebarTrigger, SidebarRail, and various sidebar elements (Header, Footer, Content, etc.). - Integrated mobile responsiveness using Sheet component. - Added utility hooks for mobile detection. feat: create table component for structured data display - Developed Table component with subcomponents: TableHeader, TableBody, TableFooter, TableRow, TableCell, and TableCaption. - Enhanced styling for better readability and usability. feat: implement tabs component for navigation - Created Tabs component with TabsList, TabsTrigger, and TabsContent for tabbed navigation. - Ensured accessibility and responsive design. feat: add toggle group component for grouped toggle buttons - Developed ToggleGroup and ToggleGroupItem components for managing toggle states. - Integrated context for consistent styling and behavior. feat: create toggle component for binary state representation - Implemented Toggle component with variant and size options. - Enhanced user interaction with visual feedback. feat: add tooltip component for contextual information - Developed Tooltip, TooltipTrigger, and TooltipContent for displaying additional information on hover. - Integrated animations for a smoother user experience. feat: implement mobile detection hook - Created useIsMobile hook to determine if the user is on a mobile device. - Utilized matchMedia for responsive design adjustments.
This commit is contained in:
@ -2,6 +2,9 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
@ -9,8 +12,6 @@ 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 {
|
||||
@ -79,6 +80,46 @@ func UseAuth(block bool) app.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// UseRole 检查用户角色是否符合要求,必须在 UseAuth 之后使用
|
||||
// requiredRole 可以是 "admin", "editor", "user" 等
|
||||
// admin包含editor, editor包含user
|
||||
func UseRole(requiredRole string) app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
currentUserID := ctx.Value(constant.ContextKeyUserID)
|
||||
if currentUserID == nil {
|
||||
resps.Unauthorized(c, resps.ErrUnauthorized)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
userID := currentUserID.(uint)
|
||||
user, err := repo.User.GetUserByID(userID)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, resps.ErrInternalServerError)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
resps.Unauthorized(c, resps.ErrUnauthorized)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
roleHierarchy := map[string]int{
|
||||
constant.RoleUser: 1,
|
||||
constant.RoleEditor: 2,
|
||||
constant.RoleAdmin: 3,
|
||||
}
|
||||
userRoleLevel := roleHierarchy[user.Role]
|
||||
requiredRoleLevel := roleHierarchy[requiredRole]
|
||||
if userRoleLevel < requiredRoleLevel {
|
||||
resps.Forbidden(c, resps.ErrForbidden)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func isStatefulJwtValid(claims *utils.Claims) (bool, error) {
|
||||
if !claims.Stateful {
|
||||
return true, nil
|
||||
|
@ -1 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
)
|
||||
|
||||
func UseTrack() app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyRemoteAddr, c.ClientIP())
|
||||
ctx = context.WithValue(ctx, constant.ContextKeyUserAgent, c.UserAgent())
|
||||
c.Next(ctx)
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,18 @@ type Like struct {
|
||||
func (l *Like) AfterCreate(tx *gorm.DB) (err error) {
|
||||
switch l.TargetType {
|
||||
case constant.TargetTypePost:
|
||||
return tx.Model(&Post{}).Where("id = ?", l.TargetID).
|
||||
UpdateColumn("like_count", gorm.Expr("like_count + ?", 1)).Error
|
||||
// 点赞数+1
|
||||
if err := tx.Model(&Post{}).Where("id = ?", l.TargetID).
|
||||
UpdateColumn("like_count", gorm.Expr("like_count + ?", 1)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 查询最新 Post
|
||||
var post Post
|
||||
if err := tx.First(&post, l.TargetID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新热度
|
||||
return tx.Model(&post).UpdateColumn("heat", post.CalculateHeat()).Error
|
||||
case constant.TargetTypeComment:
|
||||
return tx.Model(&Comment{}).Where("id = ?", l.TargetID).
|
||||
UpdateColumn("like_count", gorm.Expr("like_count + ?", 1)).Error
|
||||
|
@ -22,7 +22,7 @@ type Post struct {
|
||||
LikeCount uint64
|
||||
CommentCount uint64
|
||||
ViewCount uint64
|
||||
Heat uint64 `gorm:"default:0"`
|
||||
Heat uint64
|
||||
}
|
||||
|
||||
// CalculateHeat 热度计算
|
||||
@ -34,17 +34,6 @@ func (p *Post) CalculateHeat() float64 {
|
||||
)
|
||||
}
|
||||
|
||||
// AfterUpdate 热度指标更新后更新热度
|
||||
func (p *Post) AfterUpdate(tx *gorm.DB) (err error) {
|
||||
if tx.Statement.Changed("LikeCount") || tx.Statement.Changed("CommentCount") || tx.Statement.Changed("ViewCount") {
|
||||
p.Heat = uint64(p.CalculateHeat())
|
||||
if err := tx.Model(p).Update("heat", p.Heat).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Post) ToDto() *dto.PostDto {
|
||||
return &dto.PostDto{
|
||||
ID: p.ID,
|
||||
@ -65,7 +54,7 @@ func (p *Post) ToDto() *dto.PostDto {
|
||||
LikeCount: p.LikeCount,
|
||||
CommentCount: p.CommentCount,
|
||||
ViewCount: p.ViewCount,
|
||||
Heat: p.Heat,
|
||||
Heat: uint64(p.CalculateHeat()),
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
User: p.User.ToDto(),
|
||||
|
@ -102,6 +102,7 @@ func (cr *CommentRepo) CreateComment(comment *model.Comment) (uint, error) {
|
||||
return err
|
||||
}
|
||||
commentID = comment.ID // 记录主键
|
||||
// 更新目标的评论数量
|
||||
switch comment.TargetType {
|
||||
case constant.TargetTypePost:
|
||||
var count int64
|
||||
@ -114,6 +115,15 @@ func (cr *CommentRepo) CreateComment(comment *model.Comment) (uint, error) {
|
||||
UpdateColumn("comment_count", count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 查询最新 Post
|
||||
var post model.Post
|
||||
if err := tx.Where("id = ?", comment.TargetID).First(&post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新热度
|
||||
if err := tx.Model(&post).UpdateColumn("heat", post.CalculateHeat()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errs.New(http.StatusBadRequest, "unsupported target type: "+comment.TargetType, nil)
|
||||
}
|
||||
|
@ -50,19 +50,6 @@ func (l *likeRepo) ToggleLike(userID, targetID uint, targetType string) (bool, e
|
||||
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).UpdateColumn("like_count", count).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
//case constant.TargetTypeComment:
|
||||
// if err := tx.Model(&model.Comment{}).Where("id = ?", targetID).UpdateColumn("like_count", count).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
//default:
|
||||
// return errors.New("invalid target type")
|
||||
//}
|
||||
return nil
|
||||
})
|
||||
return finalStatus, err
|
||||
|
@ -1,15 +1,15 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"gorm.io/gorm"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type postRepo struct{}
|
||||
@ -17,96 +17,100 @@ type postRepo struct{}
|
||||
var Post = &postRepo{}
|
||||
|
||||
func (p *postRepo) CreatePost(post *model.Post) error {
|
||||
if err := GetDB().Create(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if err := GetDB().Create(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRepo) DeletePost(id string) error {
|
||||
if id == "" {
|
||||
return errs.New(http.StatusBadRequest, "invalid post ID", nil)
|
||||
}
|
||||
if err := GetDB().Where("id = ?", id).Delete(&model.Post{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if id == "" {
|
||||
return errs.New(http.StatusBadRequest, "invalid post ID", nil)
|
||||
}
|
||||
if err := GetDB().Where("id = ?", id).Delete(&model.Post{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRepo) GetPostByID(id string) (*model.Post, error) {
|
||||
var post model.Post
|
||||
if err := GetDB().Where("id = ?", id).Preload("User").First(&post).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &post, nil
|
||||
var post model.Post
|
||||
if err := GetDB().Where("id = ?", id).Preload("User").First(&post).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
GetDB().Model(&post).UpdateColumn("view_count", gorm.Expr("view_count + ?", 1))
|
||||
GetDB().First(&post, post.ID)
|
||||
GetDB().Model(&post).UpdateColumn("heat", post.CalculateHeat())
|
||||
// TODO: 对用户进行追踪,实现更真实的访问次数计算,目前粗略地每次访问都+1
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (p *postRepo) UpdatePost(post *model.Post) error {
|
||||
if post.ID == 0 {
|
||||
return errs.New(http.StatusBadRequest, "invalid post ID", nil)
|
||||
}
|
||||
if err := GetDB().Save(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if post.ID == 0 {
|
||||
return errs.New(http.StatusBadRequest, "invalid post ID", nil)
|
||||
}
|
||||
if err := GetDB().Save(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRepo) ListPosts(currentUserID uint, keywords []string, labels []dto.LabelDto, labelRule string, page, size uint64, orderBy string, desc bool) ([]model.Post, int64, error) {
|
||||
if !slices.Contains(constant.OrderByEnumPost, orderBy) {
|
||||
return nil, 0, errs.New(http.StatusBadRequest, "invalid order_by parameter", nil)
|
||||
}
|
||||
query := GetDB().Model(&model.Post{}).Preload("User")
|
||||
if currentUserID > 0 {
|
||||
query = query.Where("is_private = ? OR (is_private = ? AND user_id = ?)", false, true, currentUserID)
|
||||
} else {
|
||||
query = query.Where("is_private = ?", false)
|
||||
}
|
||||
if !slices.Contains(constant.OrderByEnumPost, orderBy) {
|
||||
return nil, 0, errs.New(http.StatusBadRequest, "invalid order_by parameter", nil)
|
||||
}
|
||||
query := GetDB().Model(&model.Post{}).Preload("User")
|
||||
if currentUserID > 0 {
|
||||
query = query.Where("is_private = ? OR (is_private = ? AND user_id = ?)", false, true, currentUserID)
|
||||
} else {
|
||||
query = query.Where("is_private = ?", false)
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
var labelIds []uint
|
||||
for _, labelDto := range labels {
|
||||
label, _ := Label.GetLabelByKeyAndValue(labelDto.Key, labelDto.Value)
|
||||
labelIds = append(labelIds, label.ID)
|
||||
}
|
||||
if labelRule == "intersection" {
|
||||
query = query.Joins("JOIN post_labels ON post_labels.post_id = posts.id").
|
||||
Where("post_labels.label_id IN ?", labelIds).
|
||||
Group("posts.id").
|
||||
Having("COUNT(DISTINCT post_labels.label_id) = ?", len(labelIds))
|
||||
} else {
|
||||
query = query.Joins("JOIN post_labels ON post_labels.post_id = posts.id").
|
||||
Where("post_labels.label_id IN ?", labelIds)
|
||||
}
|
||||
}
|
||||
if len(labels) > 0 {
|
||||
var labelIds []uint
|
||||
for _, labelDto := range labels {
|
||||
label, _ := Label.GetLabelByKeyAndValue(labelDto.Key, labelDto.Value)
|
||||
labelIds = append(labelIds, label.ID)
|
||||
}
|
||||
if labelRule == "intersection" {
|
||||
query = query.Joins("JOIN post_labels ON post_labels.post_id = posts.id").
|
||||
Where("post_labels.label_id IN ?", labelIds).
|
||||
Group("posts.id").
|
||||
Having("COUNT(DISTINCT post_labels.label_id) = ?", len(labelIds))
|
||||
} else {
|
||||
query = query.Joins("JOIN post_labels ON post_labels.post_id = posts.id").
|
||||
Where("post_labels.label_id IN ?", labelIds)
|
||||
}
|
||||
}
|
||||
|
||||
if len(keywords) > 0 {
|
||||
for _, keyword := range keywords {
|
||||
if keyword != "" {
|
||||
query = query.Where("title LIKE ? OR content LIKE ?",
|
||||
"%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keywords) > 0 {
|
||||
for _, keyword := range keywords {
|
||||
if keyword != "" {
|
||||
query = query.Where("title LIKE ? OR content LIKE ?",
|
||||
"%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, 0, err
|
||||
}
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
items, _, err := PaginateQuery[model.Post](query, page, size, orderBy, desc)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
items, _, err := PaginateQuery[model.Post](query, page, size, orderBy, desc)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (p *postRepo) ToggleLikePost(postID uint, userID uint) (bool, error) {
|
||||
if postID == 0 || userID == 0 {
|
||||
return false, errs.New(http.StatusBadRequest, "invalid post ID or user ID", nil)
|
||||
}
|
||||
liked, err := Like.ToggleLike(userID, postID, constant.TargetTypePost)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return liked, nil
|
||||
if postID == 0 || userID == 0 {
|
||||
return false, errs.New(http.StatusBadRequest, "invalid post ID or user ID", nil)
|
||||
}
|
||||
liked, err := Like.ToggleLike(userID, postID, constant.TargetTypePost)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return liked, nil
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
)
|
||||
|
||||
func registerAdminRoutes(group *route.RouterGroup) {
|
||||
// Need Admin Middleware
|
||||
adminController := v1.NewAdminController()
|
||||
consoleGroup := group.Group("/admin").Use(middleware.UseAuth(true))
|
||||
{
|
||||
consoleGroup.POST("/oidc/o", adminController.CreateOidc)
|
||||
consoleGroup.DELETE("/oidc/o/:id", adminController.DeleteOidc)
|
||||
consoleGroup.GET("/oidc/o/:id", adminController.GetOidcByID)
|
||||
consoleGroup.GET("/oidc/list", adminController.ListOidc)
|
||||
consoleGroup.PUT("/oidc/o/:id", adminController.UpdateOidc)
|
||||
}
|
||||
// Need Admin Middleware
|
||||
adminController := v1.NewAdminController()
|
||||
consoleGroup := group.Group("/admin").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleAdmin))
|
||||
{
|
||||
consoleGroup.POST("/oidc/o", adminController.CreateOidc)
|
||||
consoleGroup.DELETE("/oidc/o/:id", adminController.DeleteOidc)
|
||||
consoleGroup.GET("/oidc/o/:id", adminController.GetOidcByID)
|
||||
consoleGroup.GET("/oidc/list", adminController.ListOidc)
|
||||
consoleGroup.PUT("/oidc/o/:id", adminController.UpdateOidc)
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
)
|
||||
|
||||
// page 页面API路由
|
||||
|
||||
func registerPageRoutes(group *route.RouterGroup) {
|
||||
postGroup := group.Group("/page").Use(middleware.UseAuth(true))
|
||||
postGroup := group.Group("/page").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleEditor))
|
||||
postGroupWithoutAuth := group.Group("/page").Use(middleware.UseAuth(false))
|
||||
{
|
||||
postGroupWithoutAuth.GET("/p/:id", v1.Page.Get)
|
||||
|
@ -4,13 +4,14 @@ import (
|
||||
"github.com/cloudwego/hertz/pkg/route"
|
||||
v1 "github.com/snowykami/neo-blog/internal/controller/v1"
|
||||
"github.com/snowykami/neo-blog/internal/middleware"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
)
|
||||
|
||||
// post 文章API路由
|
||||
|
||||
func registerPostRoutes(group *route.RouterGroup) {
|
||||
postController := v1.NewPostController()
|
||||
postGroup := group.Group("/post").Use(middleware.UseAuth(true))
|
||||
postGroup := group.Group("/post").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleEditor))
|
||||
postGroupWithoutAuth := group.Group("/post").Use(middleware.UseAuth(false))
|
||||
{
|
||||
postGroupWithoutAuth.GET("/p/:id", postController.Get)
|
||||
|
@ -4,6 +4,7 @@ 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/middleware"
|
||||
"github.com/snowykami/neo-blog/internal/router/apiv1"
|
||||
"github.com/snowykami/neo-blog/pkg/utils"
|
||||
)
|
||||
@ -26,6 +27,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())
|
||||
h.Use(recovery.Recovery(), middleware.UseTrack())
|
||||
apiv1.RegisterRoutes(h)
|
||||
}
|
||||
|
@ -1,154 +1,155 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
"github.com/snowykami/neo-blog/internal/ctxutils"
|
||||
"github.com/snowykami/neo-blog/internal/dto"
|
||||
"github.com/snowykami/neo-blog/internal/model"
|
||||
"github.com/snowykami/neo-blog/internal/repo"
|
||||
"github.com/snowykami/neo-blog/pkg/errs"
|
||||
)
|
||||
|
||||
type PostService struct{}
|
||||
|
||||
func NewPostService() *PostService {
|
||||
return &PostService{}
|
||||
return &PostService{}
|
||||
}
|
||||
|
||||
func (p *PostService) CreatePost(ctx context.Context, req *dto.CreateOrUpdatePostReq) (uint, error) {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
post := &model.Post{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
UserID: currentUser.ID,
|
||||
Labels: func() []model.Label {
|
||||
labelModels := make([]model.Label, 0)
|
||||
for _, labelID := range req.Labels {
|
||||
labelModel, err := repo.Label.GetLabelByID(strconv.Itoa(int(labelID)))
|
||||
if err == nil {
|
||||
labelModels = append(labelModels, *labelModel)
|
||||
}
|
||||
}
|
||||
return labelModels
|
||||
}(),
|
||||
IsPrivate: req.IsPrivate,
|
||||
}
|
||||
if err := repo.Post.CreatePost(post); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return post.ID, nil
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
post := &model.Post{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
UserID: currentUser.ID,
|
||||
Labels: func() []model.Label {
|
||||
labelModels := make([]model.Label, 0)
|
||||
for _, labelID := range req.Labels {
|
||||
labelModel, err := repo.Label.GetLabelByID(strconv.Itoa(int(labelID)))
|
||||
if err == nil {
|
||||
labelModels = append(labelModels, *labelModel)
|
||||
}
|
||||
}
|
||||
return labelModels
|
||||
}(),
|
||||
IsPrivate: req.IsPrivate,
|
||||
}
|
||||
if err := repo.Post.CreatePost(post); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return post.ID, nil
|
||||
}
|
||||
|
||||
func (p *PostService) DeletePost(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
|
||||
}
|
||||
if err := repo.Post.DeletePost(id); err != nil {
|
||||
return errs.ErrInternalServer
|
||||
}
|
||||
return nil
|
||||
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
|
||||
}
|
||||
if err := repo.Post.DeletePost(id); err != nil {
|
||||
return errs.ErrInternalServer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostService) GetPost(ctx context.Context, id string) (*dto.PostDto, error) {
|
||||
if id == "" {
|
||||
return nil, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if post.IsPrivate && (!ok || post.UserID != currentUser.ID) {
|
||||
return nil, errs.ErrForbidden
|
||||
}
|
||||
return post.ToDto(), nil
|
||||
if id == "" {
|
||||
return nil, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return nil, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if post.IsPrivate && (!ok || post.UserID != currentUser.ID) {
|
||||
return nil, errs.ErrForbidden
|
||||
}
|
||||
|
||||
return post.ToDto(), nil
|
||||
}
|
||||
|
||||
func (p *PostService) UpdatePost(ctx context.Context, id string, req *dto.CreateOrUpdatePostReq) (uint, error) {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
if id == "" {
|
||||
return 0, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return 0, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
if post.UserID != currentUser.ID {
|
||||
return 0, errs.ErrForbidden
|
||||
}
|
||||
post.Title = req.Title
|
||||
post.Content = req.Content
|
||||
post.IsPrivate = req.IsPrivate
|
||||
post.Labels = func() []model.Label {
|
||||
labelModels := make([]model.Label, len(req.Labels))
|
||||
for _, labelID := range req.Labels {
|
||||
labelModel, err := repo.Label.GetLabelByID(strconv.Itoa(int(labelID)))
|
||||
if err == nil {
|
||||
labelModels = append(labelModels, *labelModel)
|
||||
}
|
||||
}
|
||||
return labelModels
|
||||
}()
|
||||
if err := repo.Post.UpdatePost(post); err != nil {
|
||||
return 0, errs.ErrInternalServer
|
||||
}
|
||||
return post.ID, nil
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return 0, errs.ErrUnauthorized
|
||||
}
|
||||
if id == "" {
|
||||
return 0, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return 0, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
if post.UserID != currentUser.ID {
|
||||
return 0, errs.ErrForbidden
|
||||
}
|
||||
post.Title = req.Title
|
||||
post.Content = req.Content
|
||||
post.IsPrivate = req.IsPrivate
|
||||
post.Labels = func() []model.Label {
|
||||
labelModels := make([]model.Label, len(req.Labels))
|
||||
for _, labelID := range req.Labels {
|
||||
labelModel, err := repo.Label.GetLabelByID(strconv.Itoa(int(labelID)))
|
||||
if err == nil {
|
||||
labelModels = append(labelModels, *labelModel)
|
||||
}
|
||||
}
|
||||
return labelModels
|
||||
}()
|
||||
if err := repo.Post.UpdatePost(post); err != nil {
|
||||
return 0, errs.ErrInternalServer
|
||||
}
|
||||
return post.ID, nil
|
||||
}
|
||||
|
||||
func (p *PostService) ListPosts(ctx context.Context, req *dto.ListPostReq) ([]*dto.PostDto, int64, error) {
|
||||
postDtos := make([]*dto.PostDto, 0)
|
||||
currentUserID, _ := ctxutils.GetCurrentUserID(ctx)
|
||||
posts, total, err := repo.Post.ListPosts(currentUserID, req.Keywords, req.Labels, req.LabelRule, req.Page, req.Size, req.OrderBy, req.Desc)
|
||||
if err != nil {
|
||||
return nil, total, errs.New(errs.ErrInternalServer.Code, "failed to list posts", err)
|
||||
}
|
||||
for _, post := range posts {
|
||||
postDtos = append(postDtos, post.ToDtoWithShortContent(100))
|
||||
}
|
||||
return postDtos, total, nil
|
||||
postDtos := make([]*dto.PostDto, 0)
|
||||
currentUserID, _ := ctxutils.GetCurrentUserID(ctx)
|
||||
posts, total, err := repo.Post.ListPosts(currentUserID, req.Keywords, req.Labels, req.LabelRule, req.Page, req.Size, req.OrderBy, req.Desc)
|
||||
if err != nil {
|
||||
return nil, total, errs.New(errs.ErrInternalServer.Code, "failed to list posts", err)
|
||||
}
|
||||
for _, post := range posts {
|
||||
postDtos = append(postDtos, post.ToDtoWithShortContent(100))
|
||||
}
|
||||
return postDtos, total, nil
|
||||
}
|
||||
|
||||
func (p *PostService) ToggleLikePost(ctx context.Context, id string) (bool, error) {
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return false, errs.ErrUnauthorized
|
||||
}
|
||||
if id == "" {
|
||||
return false, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return false, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
if post.UserID == currentUser.ID {
|
||||
return false, errs.ErrForbidden
|
||||
}
|
||||
idInt, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return false, errs.New(errs.ErrBadRequest.Code, "invalid post ID", err)
|
||||
}
|
||||
liked, err := repo.Post.ToggleLikePost(uint(idInt), currentUser.ID)
|
||||
if err != nil {
|
||||
return false, errs.ErrInternalServer
|
||||
}
|
||||
return liked, nil
|
||||
currentUser, ok := ctxutils.GetCurrentUser(ctx)
|
||||
if !ok {
|
||||
return false, errs.ErrUnauthorized
|
||||
}
|
||||
if id == "" {
|
||||
return false, errs.ErrBadRequest
|
||||
}
|
||||
post, err := repo.Post.GetPostByID(id)
|
||||
if err != nil {
|
||||
return false, errs.New(errs.ErrNotFound.Code, "post not found", err)
|
||||
}
|
||||
if post.UserID == currentUser.ID {
|
||||
return false, errs.ErrForbidden
|
||||
}
|
||||
idInt, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return false, errs.New(errs.ErrBadRequest.Code, "invalid post ID", err)
|
||||
}
|
||||
liked, err := repo.Post.ToggleLikePost(uint(idInt), currentUser.ID)
|
||||
if err != nil {
|
||||
return false, errs.ErrInternalServer
|
||||
}
|
||||
return liked, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user