mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
* feat(notifications): add system notifications toggle in settings Add a per-user, per-workspace toggle to enable/disable native OS notification banners. Reuses the existing notification-preferences endpoint by introducing a `system_notifications` key alongside the inbox event groups; the realtime handler reads the cached preference and skips desktopAPI.showNotification when muted. Co-authored-by: multica-agent <github@multica.ai> * fix(notifications): fetch system_notifications pref lazily Settings is the only mounted reader of notificationPreferenceOptions, so a fresh app start (or any session that never visits Settings) left the cache empty and the muted preference silently fell back to default "all". Switch the inbox:new handler to ensureQueryData so the value is fetched on first use and cached for subsequent events. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/multica-ai/multica/server/internal/logger"
|
|
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
|
)
|
|
|
|
// validNotifGroups is the set of notification preference group keys that the
|
|
// API accepts. Keys not in this set are rejected. `system_notifications` is
|
|
// not an inbox event group — it's a delivery-channel toggle controlling
|
|
// whether native OS notification banners fire — but it shares the same
|
|
// preferences map so a single endpoint covers all user notification
|
|
// preferences.
|
|
var validNotifGroups = map[string]bool{
|
|
"assignments": true,
|
|
"status_changes": true,
|
|
"comments": true,
|
|
"updates": true,
|
|
"agent_activity": true,
|
|
"system_notifications": true,
|
|
}
|
|
|
|
// validNotifValues is the set of allowed preference values per group.
|
|
var validNotifValues = map[string]bool{
|
|
"all": true,
|
|
"muted": true,
|
|
}
|
|
|
|
func (h *Handler) GetNotificationPreferences(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := requireUserID(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
workspaceID := ctxWorkspaceID(r.Context())
|
|
|
|
pref, err := h.Queries.GetNotificationPreference(r.Context(), db.GetNotificationPreferenceParams{
|
|
WorkspaceID: parseUUID(workspaceID),
|
|
UserID: parseUUID(userID),
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"workspace_id": workspaceID,
|
|
"preferences": map[string]any{},
|
|
})
|
|
return
|
|
}
|
|
slog.Warn("GetNotificationPreference failed", append(logger.RequestAttrs(r), "error", err)...)
|
|
writeError(w, http.StatusInternalServerError, "failed to get notification preferences")
|
|
return
|
|
}
|
|
|
|
var prefs map[string]string
|
|
if err := json.Unmarshal(pref.Preferences, &prefs); err != nil {
|
|
prefs = map[string]string{}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"workspace_id": workspaceID,
|
|
"preferences": prefs,
|
|
})
|
|
}
|
|
|
|
type updateNotifPrefRequest struct {
|
|
Preferences map[string]string `json:"preferences"`
|
|
}
|
|
|
|
func (h *Handler) UpdateNotificationPreferences(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := requireUserID(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
workspaceID := ctxWorkspaceID(r.Context())
|
|
|
|
var req updateNotifPrefRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.Preferences == nil {
|
|
writeError(w, http.StatusBadRequest, "preferences field is required")
|
|
return
|
|
}
|
|
|
|
for k, v := range req.Preferences {
|
|
if !validNotifGroups[k] {
|
|
writeError(w, http.StatusBadRequest, "invalid preference group: "+k)
|
|
return
|
|
}
|
|
if !validNotifValues[v] {
|
|
writeError(w, http.StatusBadRequest, "invalid preference value: "+v)
|
|
return
|
|
}
|
|
}
|
|
|
|
prefsJSON, err := json.Marshal(req.Preferences)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to marshal preferences")
|
|
return
|
|
}
|
|
|
|
pref, err := h.Queries.UpsertNotificationPreference(r.Context(), db.UpsertNotificationPreferenceParams{
|
|
WorkspaceID: parseUUID(workspaceID),
|
|
UserID: parseUUID(userID),
|
|
Preferences: prefsJSON,
|
|
})
|
|
if err != nil {
|
|
slog.Warn("UpsertNotificationPreference failed", append(logger.RequestAttrs(r), "error", err)...)
|
|
writeError(w, http.StatusInternalServerError, "failed to update notification preferences")
|
|
return
|
|
}
|
|
|
|
var prefs map[string]string
|
|
if err := json.Unmarshal(pref.Preferences, &prefs); err != nil {
|
|
prefs = map[string]string{}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"workspace_id": workspaceID,
|
|
"preferences": prefs,
|
|
})
|
|
}
|