Skip to content

Commit d03a1b4

Browse files
committed
backend notifications
1 parent 5e24a28 commit d03a1b4

File tree

9 files changed

+860
-40
lines changed

9 files changed

+860
-40
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"errors"
6+
"log/slog"
7+
8+
"github.com/generate/selfserve/internal/errs"
9+
"github.com/generate/selfserve/internal/httpx"
10+
"github.com/generate/selfserve/internal/models"
11+
"github.com/gofiber/fiber/v2"
12+
)
13+
14+
type NotificationsRepository interface {
15+
FindByUserID(ctx context.Context, userID string) ([]*models.Notification, error)
16+
MarkRead(ctx context.Context, id, userID string) error
17+
MarkAllRead(ctx context.Context, userID string) error
18+
UpsertDeviceToken(ctx context.Context, userID, token, platform string) error
19+
}
20+
21+
type NotificationsHandler struct {
22+
repo NotificationsRepository
23+
}
24+
25+
func NewNotificationsHandler(repo NotificationsRepository) *NotificationsHandler {
26+
return &NotificationsHandler{repo: repo}
27+
}
28+
29+
// ListNotifications godoc
30+
// @Summary List notifications
31+
// @Description Returns the most recent notifications for the authenticated user
32+
// @Tags notifications
33+
// @Produce json
34+
// @Success 200 {array} models.Notification
35+
// @Failure 500 {object} errs.HTTPError
36+
// @Security BearerAuth
37+
// @Router /notifications [get]
38+
func (h *NotificationsHandler) ListNotifications(c *fiber.Ctx) error {
39+
userID := c.Locals("userId").(string)
40+
41+
notifications, err := h.repo.FindByUserID(c.Context(), userID)
42+
if err != nil {
43+
slog.Error("failed to list notifications", "err", err)
44+
return errs.InternalServerError()
45+
}
46+
47+
if notifications == nil {
48+
notifications = []*models.Notification{}
49+
}
50+
51+
return c.JSON(notifications)
52+
}
53+
54+
// MarkRead godoc
55+
// @Summary Mark notification as read
56+
// @Description Marks a single notification as read for the authenticated user
57+
// @Tags notifications
58+
// @Param id path string true "Notification ID"
59+
// @Success 204
60+
// @Failure 404 {object} errs.HTTPError
61+
// @Failure 500 {object} errs.HTTPError
62+
// @Security BearerAuth
63+
// @Router /notifications/{id}/read [put]
64+
func (h *NotificationsHandler) MarkRead(c *fiber.Ctx) error {
65+
id := c.Params("id")
66+
if id == "" {
67+
return errs.BadRequest("id is required")
68+
}
69+
70+
userID := c.Locals("userId").(string)
71+
72+
if err := h.repo.MarkRead(c.Context(), id, userID); err != nil {
73+
if errors.Is(err, errs.ErrNotFoundInDB) {
74+
return errs.NotFound("notification", "id", id)
75+
}
76+
slog.Error("failed to mark notification as read", "err", err)
77+
return errs.InternalServerError()
78+
}
79+
80+
return c.SendStatus(fiber.StatusNoContent)
81+
}
82+
83+
// MarkAllRead godoc
84+
// @Summary Mark all notifications as read
85+
// @Description Marks all unread notifications as read for the authenticated user
86+
// @Tags notifications
87+
// @Success 204
88+
// @Failure 500 {object} errs.HTTPError
89+
// @Security BearerAuth
90+
// @Router /notifications/read-all [put]
91+
func (h *NotificationsHandler) MarkAllRead(c *fiber.Ctx) error {
92+
userID := c.Locals("userId").(string)
93+
94+
if err := h.repo.MarkAllRead(c.Context(), userID); err != nil {
95+
slog.Error("failed to mark all notifications as read", "err", err)
96+
return errs.InternalServerError()
97+
}
98+
99+
return c.SendStatus(fiber.StatusNoContent)
100+
}
101+
102+
// RegisterDeviceToken godoc
103+
// @Summary Register device token
104+
// @Description Registers an Expo push token so the user receives mobile push notifications
105+
// @Tags notifications
106+
// @Accept json
107+
// @Param request body models.RegisterDeviceTokenInput true "Device token"
108+
// @Success 204
109+
// @Failure 400 {object} errs.HTTPError
110+
// @Failure 500 {object} errs.HTTPError
111+
// @Security BearerAuth
112+
// @Router /device-tokens [post]
113+
func (h *NotificationsHandler) RegisterDeviceToken(c *fiber.Ctx) error {
114+
var input models.RegisterDeviceTokenInput
115+
if err := httpx.BindAndValidate(c, &input); err != nil {
116+
return err
117+
}
118+
119+
userID := c.Locals("userId").(string)
120+
121+
if err := h.repo.UpsertDeviceToken(c.Context(), userID, input.Token, input.Platform); err != nil {
122+
slog.Error("failed to register device token", "err", err)
123+
return errs.InternalServerError()
124+
}
125+
126+
return c.SendStatus(fiber.StatusNoContent)
127+
}

0 commit comments

Comments
 (0)