veza/veza-backend-api/internal/handlers/presence_handler.go
senke 9024fa92a0
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
v0.9.8 beta
2026-03-07 00:54:35 +01:00

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 }