package handlers import ( "net/http" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // PresenceHandler handles user presence (v0.301 Lot P1) type PresenceHandler struct { presenceService *services.PresenceService commonHandler *CommonHandler logger *zap.Logger } // NewPresenceHandler creates a new PresenceHandler func NewPresenceHandler(presenceService *services.PresenceService, logger *zap.Logger) *PresenceHandler { return &PresenceHandler{ presenceService: presenceService, commonHandler: NewCommonHandler(logger), logger: logger, } } // GetPresence returns presence for a user // GET /users/:id/presence // P2: If target is invisible, returns offline for other viewers func (h *PresenceHandler) GetPresence(c *gin.Context) { userIDStr := c.Param("id") userID, err := uuid.Parse(userIDStr) if err != nil { RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid user id")) return } var viewerID *uuid.UUID if uid, ok := GetUserIDUUID(c); ok { viewerID = &uid } p, err := h.presenceService.GetPresenceForViewer(c.Request.Context(), userID, viewerID) if err != nil { h.logger.Error("Failed to get presence", zap.Error(err), zap.String("user_id", userID.String())) RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to get presence", err)) return } if p == nil { RespondSuccess(c, http.StatusOK, gin.H{ "user_id": userID.String(), "status": "offline", "last_seen_at": nil, "status_message": nil, "track_id": nil, "track_title": nil, }) return } resp := gin.H{ "user_id": p.UserID.String(), "status": p.Status, "last_seen_at": p.LastSeenAt, "status_message": p.StatusMsg, "track_id": nil, "track_title": nil, } if p.TrackID != nil { resp["track_id"] = p.TrackID.String() } if p.TrackTitle != "" { resp["track_title"] = p.TrackTitle } if viewerID != nil && *viewerID == userID { resp["invisible"] = p.Invisible } RespondSuccess(c, http.StatusOK, resp) } // UpdatePresenceRequest is the body for PUT /users/me/presence (P2) type UpdatePresenceRequest struct { Status *string `json:"status"` StatusMsg *string `json:"status_message"` TrackID *uuid.UUID `json:"track_id"` TrackTitle *string `json:"track_title"` Invisible *bool `json:"invisible"` } // UpdatePresence updates the current user's presence // PUT /users/me/presence func (h *PresenceHandler) UpdatePresence(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok { return } var req UpdatePresenceRequest if err := c.ShouldBindJSON(&req); err != nil { RespondWithAppError(c, apperrors.NewValidationError("invalid request body")) return } input := &services.UpdatePresenceInput{ Status: req.Status, StatusMsg: req.StatusMsg, TrackID: req.TrackID, TrackTitle: req.TrackTitle, Invisible: req.Invisible, } // If no fields provided, default to online if input.Status == nil && input.StatusMsg == nil && input.TrackID == nil && input.TrackTitle == nil && input.Invisible == nil { input.Status = ptr("online") } if err := h.presenceService.UpdatePresenceFull(c.Request.Context(), userID, input); err != nil { h.logger.Error("Failed to update presence", zap.Error(err), zap.String("user_id", userID.String())) RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to update presence", err)) return } RespondSuccess(c, http.StatusOK, gin.H{"message": "Presence updated"}) } func ptr(s string) *string { return &s }