diff --git a/internal/db/db.go b/internal/db/db.go index 1e16b96..25bd167 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{}, &video.Like{}) + return db.AutoMigrate(&account.Account{}, &video.Video{}, &video.Like{}, &video.Comment{}) } func CloseDB(db *gorm.DB) error { diff --git a/internal/http/router.go b/internal/http/router.go index 499b8d3..1c0ed9b 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -59,12 +59,26 @@ func SetRouter(db *gorm.DB) *gin.Engine { protectedLikeGroup.POST("/unlike", likeHandler.Unlike) protectedLikeGroup.POST("/isLiked", likeHandler.IsLiked) } - + // comment + commentRepository := video.NewCommentRepository(db) + commentService := video.NewCommentService(commentRepository, videoRepository) + commentHandler := video.NewCommentHandler(commentService, accountService) + commentGroup := r.Group("/comment") + { + commentGroup.POST("/listAll", commentHandler.GetAllComments) + } + protectedCommentGroup := commentGroup.Group("") + protectedCommentGroup.Use(middleware.JWTAuth(accountRepository)) + { + protectedCommentGroup.POST("/publish", commentHandler.PublishComment) + protectedCommentGroup.POST("/delete", commentHandler.DeleteComment) + } // feed feedRepository := feed.NewFeedRepository(db) feedService := feed.NewFeedService(feedRepository, likeRepository) feedHandler := feed.NewFeedHandler(feedService) feedGroup := r.Group("/feed") + feedGroup.Use(middleware.SoftJWTAuth(accountRepository)) { feedGroup.POST("/listLatest", feedHandler.ListLatest) feedGroup.POST("/listLikesCount", feedHandler.ListLikesCount) diff --git a/internal/video/comment_entity.go b/internal/video/comment_entity.go new file mode 100644 index 0000000..c0cd55e --- /dev/null +++ b/internal/video/comment_entity.go @@ -0,0 +1,25 @@ +package video + +import "time" + +type Comment struct { + ID uint `gorm:"primaryKey" json:"id"` + Username string `gorm:"index" json:"username"` + VideoID uint `gorm:"index" json:"video_id"` + AuthorID uint `gorm:"index" json:"author_id"` + Content string `gorm:"type:text" json:"content"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` +} + +type PublishCommentRequest struct { + VideoID uint `json:"video_id"` + Content string `json:"content"` +} + +type DeleteCommentRequest struct { + CommentID uint `json:"comment_id"` +} + +type GetAllCommentsRequest struct { + VideoID uint `json:"video_id"` +} diff --git a/internal/video/comment_handler.go b/internal/video/comment_handler.go new file mode 100644 index 0000000..2bf872a --- /dev/null +++ b/internal/video/comment_handler.go @@ -0,0 +1,93 @@ +package video + +import ( + "feedsystem_video_go/internal/account" + "feedsystem_video_go/internal/middleware" + + "github.com/gin-gonic/gin" +) + +type CommentHandler struct { + service *CommentService + accountService *account.AccountService +} + +func NewCommentHandler(service *CommentService, accountService *account.AccountService) *CommentHandler { + return &CommentHandler{service: service, accountService: accountService} +} +func (h *CommentHandler) PublishComment(c *gin.Context) { + var req PublishCommentRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.Content == "" { + c.JSON(400, gin.H{"error": "content is required"}) + return + } + if req.VideoID <= 0 { + c.JSON(400, gin.H{"error": "video_id is required"}) + return + } + authorId, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + user, err := h.accountService.FindByID(c.Request.Context(), authorId) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + comment := &Comment{ + Username: user.Username, + VideoID: req.VideoID, + AuthorID: authorId, + Content: req.Content, + } + if err := h.service.Publish(c.Request.Context(), comment); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"message": "comment published successfully"}) +} + +func (h *CommentHandler) DeleteComment(c *gin.Context) { + var req DeleteCommentRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if req.CommentID <= 0 { + c.JSON(400, gin.H{"error": "comment_id is required"}) + return + } + if err := h.service.Delete(c.Request.Context(), req.CommentID, accountID); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"message": "comment deleted successfully"}) +} + +func (h *CommentHandler) GetAllComments(c *gin.Context) { + var req GetAllCommentsRequest + 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 + } + comments, err := h.service.GetAll(c.Request.Context(), req.VideoID) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + c.JSON(200, comments) +} diff --git a/internal/video/comment_repo.go b/internal/video/comment_repo.go new file mode 100644 index 0000000..aa010bd --- /dev/null +++ b/internal/video/comment_repo.go @@ -0,0 +1,51 @@ +package video + +import ( + "context" + + "gorm.io/gorm" +) + +type CommentRepository struct { + db *gorm.DB +} + +func NewCommentRepository(db *gorm.DB) *CommentRepository { + return &CommentRepository{db: db} +} + +func (r *CommentRepository) CreateComment(ctx context.Context, comment *Comment) error { + return r.db.WithContext(ctx).Create(comment).Error +} + +func (r *CommentRepository) DeleteComment(ctx context.Context, comment *Comment) error { + return r.db.WithContext(ctx).Delete(comment).Error +} + +func (r *CommentRepository) GetAllComments(ctx context.Context, videoID uint) ([]Comment, error) { + var comments []Comment + err := r.db.WithContext(ctx).Where("video_id = ?", videoID).Find(&comments).Error + return comments, err +} + +func (r *CommentRepository) IsExist(ctx context.Context, id uint) (bool, error) { + var comment Comment + if err := r.db.WithContext(ctx).First(&comment, id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return false, nil + } + return false, err + } + return true, nil +} + +func (r *CommentRepository) GetByID(ctx context.Context, id uint) (*Comment, error) { + var comment Comment + if err := r.db.WithContext(ctx).First(&comment, id).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &comment, nil +} diff --git a/internal/video/comment_service.go b/internal/video/comment_service.go new file mode 100644 index 0000000..4d89b06 --- /dev/null +++ b/internal/video/comment_service.go @@ -0,0 +1,51 @@ +package video + +import ( + "context" + "errors" +) + +type CommentService struct { + repo *CommentRepository + VideoRepository *VideoRepository +} + +func NewCommentService(repo *CommentRepository, videoRepo *VideoRepository) *CommentService { + return &CommentService{repo: repo, VideoRepository: videoRepo} +} + +func (s *CommentService) Publish(ctx context.Context, comment *Comment) error { + exists, err := s.VideoRepository.IsExist(ctx, comment.VideoID) + if err != nil { + return err + } + if !exists { + return errors.New("video not found") + } + return s.repo.CreateComment(ctx, comment) +} + +func (s *CommentService) Delete(ctx context.Context, commentID uint, accountID uint) error { + comment, err := s.repo.GetByID(ctx, commentID) + if err != nil { + return err + } + if comment == nil { + return errors.New("comment not found") + } + if comment.AuthorID != accountID { + return errors.New("permission denied") + } + return s.repo.DeleteComment(ctx, comment) +} + +func (s *CommentService) GetAll(ctx context.Context, videoID uint) ([]Comment, error) { + exists, err := s.VideoRepository.IsExist(ctx, videoID) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.New("video not found") + } + return s.repo.GetAllComments(ctx, videoID) +}