veza/veza-backend-api/internal/core/social/group_service.go
senke ecac9c3b03 feat(backend): add social groups, wishlist, cart, and playlist export endpoints
- 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>
2026-02-12 22:48:50 +01:00

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
}