- Add Group and GroupMember models with CRUD service methods - Implement social group endpoints: create, list, get, join, leave - Add WishlistItem model with get/add/remove service methods - Add CartItem model with get/add/remove/checkout service methods - Create handlers for marketplace wishlist and cart operations - Register playlist export (JSON/CSV) and duplicate routes - Enable PLAYLIST_SHARE and NOTIFICATIONS feature flags Co-authored-by: Cursor <cursoragent@cursor.com>
174 lines
4.3 KiB
Go
174 lines
4.3 KiB
Go
package social
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var (
|
|
ErrGroupNotFound = errors.New("group not found")
|
|
ErrAlreadyMember = errors.New("already a member of this group")
|
|
ErrNotMember = errors.New("not a member of this group")
|
|
ErrCannotLeaveOwned = errors.New("group creator cannot leave, delete the group instead")
|
|
)
|
|
|
|
// CreateGroup creates a new social group
|
|
func (s *Service) CreateGroup(ctx context.Context, userID uuid.UUID, name, description string, isPublic bool) (*Group, error) {
|
|
group := &Group{
|
|
Name: name,
|
|
Description: description,
|
|
CreatorID: userID,
|
|
IsPublic: isPublic,
|
|
MemberCount: 1,
|
|
}
|
|
|
|
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(group).Error; err != nil {
|
|
return fmt.Errorf("failed to create group: %w", err)
|
|
}
|
|
|
|
// Add creator as admin member
|
|
member := &GroupMember{
|
|
GroupID: group.ID,
|
|
UserID: userID,
|
|
Role: "admin",
|
|
}
|
|
if err := tx.Create(member).Error; err != nil {
|
|
return fmt.Errorf("failed to add creator as member: %w", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.logger.Info("Group created",
|
|
zap.String("group_id", group.ID.String()),
|
|
zap.String("creator_id", userID.String()),
|
|
)
|
|
|
|
return group, nil
|
|
}
|
|
|
|
// ListGroups returns all public groups with pagination
|
|
func (s *Service) ListGroups(ctx context.Context, limit, offset int) ([]Group, int64, error) {
|
|
var groups []Group
|
|
var total int64
|
|
|
|
if err := s.db.WithContext(ctx).Model(&Group{}).Where("is_public = ?", true).Count(&total).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("failed to count groups: %w", err)
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).
|
|
Where("is_public = ?", true).
|
|
Order("member_count DESC, created_at DESC").
|
|
Limit(limit).
|
|
Offset(offset).
|
|
Find(&groups).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("failed to list groups: %w", err)
|
|
}
|
|
|
|
return groups, total, nil
|
|
}
|
|
|
|
// GetGroup returns a group by ID
|
|
func (s *Service) GetGroup(ctx context.Context, groupID uuid.UUID) (*Group, error) {
|
|
var group Group
|
|
if err := s.db.WithContext(ctx).Where("id = ?", groupID).First(&group).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrGroupNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get group: %w", err)
|
|
}
|
|
return &group, nil
|
|
}
|
|
|
|
// JoinGroup adds a user to a group
|
|
func (s *Service) JoinGroup(ctx context.Context, userID, groupID uuid.UUID) error {
|
|
// Check group exists
|
|
group, err := s.GetGroup(ctx, groupID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check not already a member
|
|
var count int64
|
|
s.db.WithContext(ctx).Model(&GroupMember{}).
|
|
Where("group_id = ? AND user_id = ?", groupID, userID).
|
|
Count(&count)
|
|
if count > 0 {
|
|
return ErrAlreadyMember
|
|
}
|
|
|
|
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
member := &GroupMember{
|
|
GroupID: groupID,
|
|
UserID: userID,
|
|
Role: "member",
|
|
}
|
|
if err := tx.Create(member).Error; err != nil {
|
|
return fmt.Errorf("failed to join group: %w", err)
|
|
}
|
|
|
|
// Increment member count
|
|
if err := tx.Model(&Group{}).Where("id = ?", groupID).
|
|
Update("member_count", gorm.Expr("member_count + 1")).Error; err != nil {
|
|
return fmt.Errorf("failed to update member count: %w", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.logger.Info("User joined group",
|
|
zap.String("user_id", userID.String()),
|
|
zap.String("group_id", groupID.String()),
|
|
zap.String("group_name", group.Name),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// LeaveGroup removes a user from a group
|
|
func (s *Service) LeaveGroup(ctx context.Context, userID, groupID uuid.UUID) error {
|
|
// Check group exists
|
|
group, err := s.GetGroup(ctx, groupID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Creator cannot leave
|
|
if group.CreatorID == userID {
|
|
return ErrCannotLeaveOwned
|
|
}
|
|
|
|
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
result := tx.Where("group_id = ? AND user_id = ?", groupID, userID).Delete(&GroupMember{})
|
|
if result.Error != nil {
|
|
return fmt.Errorf("failed to leave group: %w", result.Error)
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return ErrNotMember
|
|
}
|
|
|
|
// Decrement member count
|
|
if err := tx.Model(&Group{}).Where("id = ?", groupID).
|
|
Update("member_count", gorm.Expr("member_count - 1")).Error; err != nil {
|
|
return fmt.Errorf("failed to update member count: %w", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|