From eeafe9d32a5e9706ef54e1a35f18f02f802212cf Mon Sep 17 00:00:00 2001 From: Leon <147289645+LeoninCS@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:30:16 +0800 Subject: [PATCH] feat(video): add like/unlike/isliked/getlikescount APIs for videos --- internal/db/db.go | 2 +- internal/http/router.go | 15 +++++ internal/video/like_entity.go | 10 +++ internal/video/like_handler.go | 117 +++++++++++++++++++++++++++++++++ internal/video/like_repo.go | 47 +++++++++++++ internal/video/like_service.go | 38 +++++++++++ 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 internal/video/like_entity.go create mode 100644 internal/video/like_handler.go create mode 100644 internal/video/like_repo.go create mode 100644 internal/video/like_service.go diff --git a/internal/db/db.go b/internal/db/db.go index bd4746b..1e16b96 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -23,7 +23,7 @@ func NewDB(dbcfg config.DatabaseConfig) (*gorm.DB, error) { } func AutoMigrate(db *gorm.DB) error { - return db.AutoMigrate(&account.Account{}, &video.Video{}) + return db.AutoMigrate(&account.Account{}, &video.Video{}, &video.Like{}) } func CloseDB(db *gorm.DB) error { diff --git a/internal/http/router.go b/internal/http/router.go index 5c7e379..5d1c04c 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -44,6 +44,21 @@ func SetRouter(db *gorm.DB) *gin.Engine { { protectedVideoGroup.POST("/publish", videoHandler.PublishVideo) } + // like + likeRepository := video.NewLikeRepository(db) + likeService := video.NewLikeService(likeRepository) + likeHandler := video.NewLikeHandler(likeService) + likeGroup := r.Group("/like") + { + likeGroup.POST("/getLikesCount", likeHandler.GetLikesCount) + } + protectedLikeGroup := likeGroup.Group("") + protectedLikeGroup.Use(middleware.JWTAuth(accountRepository)) + { + protectedLikeGroup.POST("/like", likeHandler.Like) + protectedLikeGroup.POST("/unlike", likeHandler.Unlike) + protectedLikeGroup.POST("/isLiked", likeHandler.IsLiked) + } // feed feedRepository := feed.NewFeedRepository(db) diff --git a/internal/video/like_entity.go b/internal/video/like_entity.go new file mode 100644 index 0000000..042ec68 --- /dev/null +++ b/internal/video/like_entity.go @@ -0,0 +1,10 @@ +package video + +import "time" + +type Like struct { + ID uint `gorm:"primaryKey"` + VideoID uint `gorm:"index, not null"` + AccountID uint `gorm:"index, not null"` + CreatedAt time.Time +} diff --git a/internal/video/like_handler.go b/internal/video/like_handler.go new file mode 100644 index 0000000..1ddea16 --- /dev/null +++ b/internal/video/like_handler.go @@ -0,0 +1,117 @@ +package video + +import ( + "feedsystem_video_go/internal/middleware" + + "github.com/gin-gonic/gin" +) + +type LikeRequest struct { + VideoID uint `json:"video_id"` +} + +type LikeHandler struct { + service *LikeService +} + +func NewLikeHandler(service *LikeService) *LikeHandler { + return &LikeHandler{service: service} +} + +func (lh *LikeHandler) Like(c *gin.Context) { + var req LikeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.VideoID <= 0 { + c.JSON(400, gin.H{"error": "video_id is required"}) + return + } + + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + like := &Like{ + VideoID: req.VideoID, + AccountID: accountID, + } + if err := lh.service.Like(c.Request.Context(), like); err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"message": "like success"}) +} + +func (lh *LikeHandler) Unlike(c *gin.Context) { + var req LikeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.VideoID <= 0 { + c.JSON(400, gin.H{"error": "video_id is required"}) + return + } + + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + like := &Like{ + VideoID: req.VideoID, + AccountID: accountID, + } + if err := lh.service.Unlike(c.Request.Context(), like); err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"message": "unlike success"}) +} + +func (lh *LikeHandler) IsLiked(c *gin.Context) { + var req LikeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.VideoID <= 0 { + c.JSON(400, gin.H{"error": "video_id is required"}) + return + } + + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + isLiked, err := lh.service.IsLiked(c.Request.Context(), req.VideoID, accountID) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"is_liked": isLiked}) +} + +func (lh *LikeHandler) GetLikesCount(c *gin.Context) { + var req LikeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.VideoID <= 0 { + c.JSON(400, gin.H{"error": "video_id is required"}) + return + } + likesCount, err := lh.service.GetLikesCount(c.Request.Context(), req.VideoID) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"likes_count": likesCount}) +} diff --git a/internal/video/like_repo.go b/internal/video/like_repo.go new file mode 100644 index 0000000..cd690ac --- /dev/null +++ b/internal/video/like_repo.go @@ -0,0 +1,47 @@ +package video + +import ( + "context" + + "gorm.io/gorm" +) + +type LikeRepository struct { + db *gorm.DB +} + +func NewLikeRepository(db *gorm.DB) *LikeRepository { + return &LikeRepository{db: db} +} + +func (r *LikeRepository) Like(ctx context.Context, like *Like) error { + return r.db.WithContext(ctx).Create(like).Error +} + +func (r *LikeRepository) Unlike(ctx context.Context, like *Like) error { + return r.db.WithContext(ctx). + Where("video_id = ? AND account_id = ?", like.VideoID, like.AccountID). + Delete(&Like{}).Error +} + +func (r *LikeRepository) IsLiked(ctx context.Context, videoID, accountID uint) (bool, error) { + var count int64 + err := r.db.WithContext(ctx).Model(&Like{}). + Where("video_id = ? AND account_id = ?", videoID, accountID). + Count(&count).Error + if err != nil { + return false, err + } + return count > 0, nil +} + +func (r *LikeRepository) GetLikesCount(ctx context.Context, videoID uint) (int64, error) { + var count int64 + err := r.db.WithContext(ctx).Model(&Like{}). + Where("video_id = ?", videoID). + Count(&count).Error + if err != nil { + return 0, err + } + return count, nil +} diff --git a/internal/video/like_service.go b/internal/video/like_service.go new file mode 100644 index 0000000..1419513 --- /dev/null +++ b/internal/video/like_service.go @@ -0,0 +1,38 @@ +package video + +import ( + "context" + "errors" + "time" +) + +type LikeService struct { + repo *LikeRepository +} + +func NewLikeService(repo *LikeRepository) *LikeService { + return &LikeService{repo: repo} +} + +func (s *LikeService) Like(ctx context.Context, like *Like) error { + if isLiked, err := s.IsLiked(ctx, like.VideoID, like.AccountID); err == nil && isLiked { + return errors.New("user has liked this video") + } + like.CreatedAt = time.Now() + return s.repo.Like(ctx, like) +} + +func (s *LikeService) Unlike(ctx context.Context, like *Like) error { + if isLiked, err := s.IsLiked(ctx, like.VideoID, like.AccountID); err == nil && !isLiked { + return errors.New("user has not liked this video") + } + return s.repo.Unlike(ctx, like) +} + +func (s *LikeService) IsLiked(ctx context.Context, videoID, accountID uint) (bool, error) { + return s.repo.IsLiked(ctx, videoID, accountID) +} + +func (s *LikeService) GetLikesCount(ctx context.Context, videoID uint) (int64, error) { + return s.repo.GetLikesCount(ctx, videoID) +}