126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
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 }
|