gohorsejobs/backend/internal/handlers/notification_handler.go
Tiago Yamamoto 63023b922f feat(notifications): implementar sistema de notificações e FCM
- Migration 017: tabelas notifications e fcm_tokens
- Models: Notification, FCMToken
- NotificationService: CRUD, push notifications helper
- FCMService: Firebase Cloud Messaging integration
- NotificationHandler: endpoints REST
- Rotas autenticadas: /api/v1/notifications/*

Endpoints:
- GET /api/v1/notifications
- GET /api/v1/notifications/unread-count
- PUT /api/v1/notifications/read-all
- PUT /api/v1/notifications/{id}/read
- DELETE /api/v1/notifications/{id}
- POST /api/v1/notifications/fcm-token
- DELETE /api/v1/notifications/fcm-token
2025-12-27 11:24:27 -03:00

237 lines
7 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/rede5/gohorsejobs/backend/internal/models"
"github.com/rede5/gohorsejobs/backend/internal/services"
)
type NotificationHandler struct {
service *services.NotificationService
}
func NewNotificationHandler(service *services.NotificationService) *NotificationHandler {
return &NotificationHandler{service: service}
}
// GetNotifications lists notifications for the authenticated user
// @Summary List Notifications
// @Description Get all notifications for the current user
// @Tags Notifications
// @Produce json
// @Param limit query int false "Limit results (default 50)"
// @Param offset query int false "Offset for pagination"
// @Param unreadOnly query bool false "Only show unread"
// @Success 200 {array} models.Notification
// @Router /api/v1/notifications [get]
func (h *NotificationHandler) GetNotifications(w http.ResponseWriter, r *http.Request) {
// Get user ID from context (set by auth middleware)
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
unreadOnly := r.URL.Query().Get("unreadOnly") == "true"
if limit == 0 {
limit = 50
}
notifications, err := h.service.GetByUserID(userID, limit, offset, unreadOnly)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(notifications)
}
// GetUnreadCount returns the count of unread notifications
// @Summary Get Unread Count
// @Description Get count of unread notifications
// @Tags Notifications
// @Produce json
// @Success 200 {object} map[string]int
// @Router /api/v1/notifications/unread-count [get]
func (h *NotificationHandler) GetUnreadCount(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
count, err := h.service.GetUnreadCount(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]int{"count": count})
}
// MarkAsRead marks a notification as read
// @Summary Mark as Read
// @Description Mark a specific notification as read
// @Tags Notifications
// @Param id path int true "Notification ID"
// @Success 200
// @Router /api/v1/notifications/{id}/read [put]
func (h *NotificationHandler) MarkAsRead(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid notification ID", http.StatusBadRequest)
return
}
if err := h.service.MarkAsRead(id, userID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// MarkAllAsRead marks all notifications as read
// @Summary Mark All as Read
// @Description Mark all notifications as read for the current user
// @Tags Notifications
// @Success 200
// @Router /api/v1/notifications/read-all [put]
func (h *NotificationHandler) MarkAllAsRead(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := h.service.MarkAllAsRead(userID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// DeleteNotification deletes a notification
// @Summary Delete Notification
// @Description Delete a specific notification
// @Tags Notifications
// @Param id path int true "Notification ID"
// @Success 204
// @Router /api/v1/notifications/{id} [delete]
func (h *NotificationHandler) DeleteNotification(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid notification ID", http.StatusBadRequest)
return
}
if err := h.service.Delete(id, userID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
// RegisterFCMToken registers a device token for push notifications
// @Summary Register FCM Token
// @Description Register a device token for push notifications
// @Tags Notifications
// @Accept json
// @Param token body models.RegisterFCMTokenRequest true "FCM Token"
// @Success 200
// @Router /api/v1/notifications/fcm-token [post]
func (h *NotificationHandler) RegisterFCMToken(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req models.RegisterFCMTokenRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if req.Token == "" {
http.Error(w, "Token is required", http.StatusBadRequest)
return
}
if err := h.service.RegisterFCMToken(userID, req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// UnregisterFCMToken removes a device token
// @Summary Unregister FCM Token
// @Description Remove a device token from push notifications
// @Tags Notifications
// @Accept json
// @Param token body object{token string} true "Token to remove"
// @Success 200
// @Router /api/v1/notifications/fcm-token [delete]
func (h *NotificationHandler) UnregisterFCMToken(w http.ResponseWriter, r *http.Request) {
userID := getUserIDFromContext(r)
if userID == 0 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req struct {
Token string `json:"token"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if err := h.service.UnregisterFCMToken(userID, req.Token); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// getUserIDFromContext extracts user ID from context (set by auth middleware)
func getUserIDFromContext(r *http.Request) int {
// Check for user ID in context, set by auth middleware
if userID, ok := r.Context().Value("userID").(int); ok {
return userID
}
// Fallback: check header for testing
if userIDStr := r.Header.Get("X-User-ID"); userIDStr != "" {
if id, err := strconv.Atoi(userIDStr); err == nil {
return id
}
}
return 0
}