veza/veza-backend-api/internal/handlers/social_group_handler.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.5 KiB
Go

package handlers
import (
"errors"
"net/http"
"strconv"
"veza-backend-api/internal/core/social"
"veza-backend-api/internal/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// GroupHandler handles social group operations
type GroupHandler struct {
service *social.Service
commonHandler *CommonHandler
}
// NewGroupHandler creates a new GroupHandler
func NewGroupHandler(service *social.Service, logger *zap.Logger) *GroupHandler {
return &GroupHandler{
service: service,
commonHandler: NewCommonHandler(logger),
}
}
// CreateGroupRequest is the DTO for group creation
type CreateGroupRequest struct {
Name string `json:"name" binding:"required,min=1,max=255" validate:"required,min=1,max=255"`
Description string `json:"description" binding:"max=2000" validate:"max=2000"`
IsPublic *bool `json:"is_public"`
}
// CreateGroup creates a new social group
func (h *GroupHandler) CreateGroup(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
var req CreateGroupRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
req.Name = utils.SanitizeText(req.Name, 255)
req.Description = utils.SanitizeText(req.Description, 2000)
isPublic := true
if req.IsPublic != nil {
isPublic = *req.IsPublic
}
group, err := h.service.CreateGroup(c.Request.Context(), userID, req.Name, req.Description, isPublic)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create group"})
return
}
RespondSuccess(c, http.StatusCreated, group)
}
// ListGroups returns all public groups
func (h *GroupHandler) ListGroups(c *gin.Context) {
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
if limit < 1 {
limit = 20
}
if limit > 100 {
limit = 100
}
if offset < 0 {
offset = 0
}
groups, total, err := h.service.ListGroups(c.Request.Context(), limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list groups"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{
"groups": groups,
"total": total,
})
}
// GetGroup returns a group by ID
func (h *GroupHandler) GetGroup(c *gin.Context) {
groupID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"})
return
}
group, err := h.service.GetGroup(c.Request.Context(), groupID)
if err != nil {
if errors.Is(err, social.ErrGroupNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get group"})
return
}
RespondSuccess(c, http.StatusOK, group)
}
// JoinGroup adds the authenticated user to a group
func (h *GroupHandler) JoinGroup(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
groupID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"})
return
}
if err := h.service.JoinGroup(c.Request.Context(), userID, groupID); err != nil {
if errors.Is(err, social.ErrGroupNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"})
return
}
if errors.Is(err, social.ErrAlreadyMember) {
c.JSON(http.StatusConflict, gin.H{"error": "Already a member"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to join group"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "Joined group successfully"})
}
// LeaveGroup removes the authenticated user from a group
func (h *GroupHandler) LeaveGroup(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
groupID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group ID"})
return
}
if err := h.service.LeaveGroup(c.Request.Context(), userID, groupID); err != nil {
if errors.Is(err, social.ErrGroupNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "Group not found"})
return
}
if errors.Is(err, social.ErrNotMember) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Not a member of this group"})
return
}
if errors.Is(err, social.ErrCannotLeaveOwned) {
c.JSON(http.StatusForbidden, gin.H{"error": "Creator cannot leave group"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to leave group"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "Left group successfully"})
}