- 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.5 KiB
Go
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"})
|
|
}
|