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