diff --git a/internal/db/db.go b/internal/db/db.go index 25bd167..30e1202 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -3,6 +3,7 @@ package db import ( "feedsystem_video_go/internal/account" "feedsystem_video_go/internal/config" + "feedsystem_video_go/internal/social" "feedsystem_video_go/internal/video" "fmt" @@ -23,7 +24,7 @@ func NewDB(dbcfg config.DatabaseConfig) (*gorm.DB, error) { } func AutoMigrate(db *gorm.DB) error { - return db.AutoMigrate(&account.Account{}, &video.Video{}, &video.Like{}, &video.Comment{}) + return db.AutoMigrate(&account.Account{}, &video.Video{}, &video.Like{}, &video.Comment{}, &social.Social{}) } func CloseDB(db *gorm.DB) error { diff --git a/internal/http/router.go b/internal/http/router.go index 1c0ed9b..d4614a4 100644 --- a/internal/http/router.go +++ b/internal/http/router.go @@ -4,6 +4,7 @@ import ( "feedsystem_video_go/internal/account" "feedsystem_video_go/internal/feed" "feedsystem_video_go/internal/middleware" + "feedsystem_video_go/internal/social" "feedsystem_video_go/internal/video" "github.com/gin-gonic/gin" @@ -73,6 +74,19 @@ func SetRouter(db *gorm.DB) *gin.Engine { protectedCommentGroup.POST("/publish", commentHandler.PublishComment) protectedCommentGroup.POST("/delete", commentHandler.DeleteComment) } + // social + socialRepository := social.NewSocialRepository(db) + socialService := social.NewSocialService(socialRepository, accountRepository) + socialHandler := social.NewSocialHandler(socialService) + socialGroup := r.Group("/social") + protectedSocialGroup := socialGroup.Group("") + protectedSocialGroup.Use(middleware.JWTAuth(accountRepository)) + { + protectedSocialGroup.POST("/follow", socialHandler.Follow) + protectedSocialGroup.POST("/unfollow", socialHandler.Unfollow) + protectedSocialGroup.POST("/getAllFollowers", socialHandler.GetAllFollowers) + protectedSocialGroup.POST("/getAllVloggers", socialHandler.GetAllVloggers) + } // feed feedRepository := feed.NewFeedRepository(db) feedService := feed.NewFeedService(feedRepository, likeRepository) diff --git a/internal/social/entity.go b/internal/social/entity.go new file mode 100644 index 0000000..6fc5842 --- /dev/null +++ b/internal/social/entity.go @@ -0,0 +1,33 @@ +package social + +import "feedsystem_video_go/internal/account" + +type Social struct { + ID uint `gorm:"primaryKey"` + FollowerID uint `gorm:"index"` + VloggerID uint `gorm:"index"` +} + +type FollowRequest struct { + VloggerID uint `json:"vlogger_id"` +} + +type UnfollowRequest struct { + VloggerID uint `json:"vlogger_id"` +} + +type GetAllFollowersRequest struct { + VloggerID uint `json:"vlogger_id"` +} + +type GetAllFollowersResponse struct { + Followers []*account.Account `json:"followers"` +} + +type GetAllVloggersRequest struct { + FollowerID uint `json:"follower_id"` +} + +type GetAllVloggersResponse struct { + Vloggers []*account.Account `json:"vloggers"` +} diff --git a/internal/social/handler.go b/internal/social/handler.go new file mode 100644 index 0000000..7d213e3 --- /dev/null +++ b/internal/social/handler.go @@ -0,0 +1,118 @@ +package social + +import ( + "feedsystem_video_go/internal/middleware" + "net/http" + + "github.com/gin-gonic/gin" +) + +type SocialHandler struct { + service *SocialService +} + +func NewSocialHandler(service *SocialService) *SocialHandler { + return &SocialHandler{service: service} +} + +func (h *SocialHandler) Follow(c *gin.Context) { + var req FollowRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if req.VloggerID <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "vlogger_id is required"}) + return + } + FollowerID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + social := &Social{ + FollowerID: FollowerID, + VloggerID: req.VloggerID, + } + if err := h.service.Follow(c.Request.Context(), social); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "followed"}) +} + +func (h *SocialHandler) Unfollow(c *gin.Context) { + var req UnfollowRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if req.VloggerID <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "vlogger_id is required"}) + return + } + FollowerID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + social := &Social{ + FollowerID: FollowerID, + VloggerID: req.VloggerID, + } + if err := h.service.Unfollow(c.Request.Context(), social); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "unfollowed"}) +} + +func (h *SocialHandler) GetAllFollowers(c *gin.Context) { + var req GetAllFollowersRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + vloggerID := req.VloggerID + if vloggerID == 0 { + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + vloggerID = accountID + } + + followers, err := h.service.GetAllFollowers(c.Request.Context(), vloggerID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, GetAllFollowersResponse{Followers: followers}) +} + +func (h *SocialHandler) GetAllVloggers(c *gin.Context) { + var req GetAllVloggersRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + followerID := req.FollowerID + if followerID == 0 { + accountID, err := middleware.GetAccountID(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + followerID = accountID + } + + vloggers, err := h.service.GetAllVloggers(c.Request.Context(), followerID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, GetAllVloggersResponse{Vloggers: vloggers}) +} diff --git a/internal/social/repo.go b/internal/social/repo.go new file mode 100644 index 0000000..6dd5928 --- /dev/null +++ b/internal/social/repo.go @@ -0,0 +1,91 @@ +package social + +import ( + "context" + "feedsystem_video_go/internal/account" + + "gorm.io/gorm" +) + +type SocialRepository struct { + db *gorm.DB +} + +func NewSocialRepository(db *gorm.DB) *SocialRepository { + return &SocialRepository{db: db} +} + +func (r *SocialRepository) Follow(ctx context.Context, social *Social) error { + return r.db.WithContext(ctx).Create(social).Error +} + +func (r *SocialRepository) Unfollow(ctx context.Context, social *Social) error { + return r.db.WithContext(ctx). + Where("follower_id = ? AND vlogger_id = ?", social.FollowerID, social.VloggerID). + Delete(&Social{}).Error +} + +func (r *SocialRepository) GetAllFollowers(ctx context.Context, VloggerID uint) ([]*account.Account, error) { + var relations []Social + if err := r.db.WithContext(ctx). + Model(&Social{}). + Where("vlogger_id = ?", VloggerID). + Find(&relations).Error; err != nil { + return nil, err + } + + followerIDs := make([]uint, 0, len(relations)) + for _, rel := range relations { + followerIDs = append(followerIDs, rel.FollowerID) + } + if len(followerIDs) == 0 { + return []*account.Account{}, nil + } + + var followers []*account.Account + if err := r.db.WithContext(ctx). + Model(&account.Account{}). + Where("id IN ?", followerIDs). + Find(&followers).Error; err != nil { + return nil, err + } + return followers, nil +} + +func (r *SocialRepository) GetAllVloggers(ctx context.Context, FollowerID uint) ([]*account.Account, error) { + var relations []Social + if err := r.db.WithContext(ctx). + Model(&Social{}). + Where("follower_id = ?", FollowerID). + Find(&relations).Error; err != nil { + return nil, err + } + + vloggerIDs := make([]uint, 0, len(relations)) + for _, rel := range relations { + vloggerIDs = append(vloggerIDs, rel.VloggerID) + } + if len(vloggerIDs) == 0 { + return []*account.Account{}, nil + } + + var vloggers []*account.Account + if err := r.db.WithContext(ctx). + Model(&account.Account{}). + Where("id IN ?", vloggerIDs). + Find(&vloggers).Error; err != nil { + return nil, err + } + return vloggers, nil +} + +func (r *SocialRepository) IsFollowed(ctx context.Context, social *Social) (bool, error) { + var count int64 + if err := r.db.WithContext(ctx). + Model(&Social{}). + Where("follower_id = ? AND vlogger_id = ?", social.FollowerID, social.VloggerID). + Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} diff --git a/internal/social/service.go b/internal/social/service.go new file mode 100644 index 0000000..b68ed97 --- /dev/null +++ b/internal/social/service.go @@ -0,0 +1,82 @@ +package social + +import ( + "context" + "errors" + "feedsystem_video_go/internal/account" +) + +type SocialService struct { + repo *SocialRepository + accountrepo *account.AccountRepository +} + +func NewSocialService(repo *SocialRepository, accountrepo *account.AccountRepository) *SocialService { + return &SocialService{repo: repo, accountrepo: accountrepo} +} + +func (s *SocialService) Follow(ctx context.Context, social *Social) error { + _, err := s.accountrepo.FindByID(ctx, social.FollowerID) + if err != nil { + return err + } + _, err = s.accountrepo.FindByID(ctx, social.VloggerID) + if err != nil { + return err + } + isFollowed, err := s.repo.IsFollowed(ctx, social) + if err != nil { + return err + } + if isFollowed { + return errors.New("already followed") + } + return s.repo.Follow(ctx, social) +} + +func (s *SocialService) Unfollow(ctx context.Context, social *Social) error { + _, err := s.accountrepo.FindByID(ctx, social.FollowerID) + if err != nil { + return err + } + _, err = s.accountrepo.FindByID(ctx, social.VloggerID) + if err != nil { + return err + } + isFollowed, err := s.repo.IsFollowed(ctx, social) + if err != nil { + return err + } + if !isFollowed { + return errors.New("not followed") + } + return s.repo.Unfollow(ctx, social) +} + +func (s *SocialService) GetAllFollowers(ctx context.Context, VloggerID uint) ([]*account.Account, error) { + _, err := s.accountrepo.FindByID(ctx, VloggerID) + if err != nil { + return nil, err + } + return s.repo.GetAllFollowers(ctx, VloggerID) +} + +func (s *SocialService) GetAllVloggers(ctx context.Context, FollowerID uint) ([]*account.Account, error) { + _, err := s.accountrepo.FindByID(ctx, FollowerID) + if err != nil { + return nil, err + } + return s.repo.GetAllVloggers(ctx, FollowerID) +} + +func (s *SocialService) IsFollowed(ctx context.Context, social *Social) (bool, error) { + _, err := s.accountrepo.FindByID(ctx, social.FollowerID) + if err != nil { + return false, err + } + _, err = s.accountrepo.FindByID(ctx, social.VloggerID) + if err != nil { + return false, err + } + return s.repo.IsFollowed(ctx, social) +}