mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
9af942a169
* feat(store): push notification schema and settings store (TASK-3937 part A)
Tables push_devices, user_notifications, user_notification_settings
(sqlite 155 + generated NUL triggers 156, pg 124), the settings model and
store, the migrated.go left-out/refusal entries, and the cascade tests.
The enqueue side is part B.
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
* test(store): ScanNUL cost figure counts the push tables' columns (TASK-3937 part A)
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
* fix(store): index the push queue's actor, say retention is the dispatcher's, cover the queue in the purge test (TASK-3937 part A r1)
- idx_user_notifications_actor on both dialects: deleting a user runs
actor_user_id's ON DELETE SET NULL against the queue.
- 155's comment no longer claims a 7-day retention that nothing
implements yet: the sweep lands with the dispatcher (TASK-3939).
- user_notifications joins wsChildTables and is seeded by
seedFullWorkspace, so the no-orphan and no-over-purge tests cover it.
Shown able to fail: with workspace_id's cascade removed, the purge
test reports "table user_notifications left 1 rows".
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
* fix(store): isolate push_devices' user cascade in a test; dispatcher prose says it is TASK-3939's (TASK-3937 part A r3)
- TestPushDevices_UserCascade binds a device to another user's session
and deletes the device's user, so only push_devices.user_id's cascade
can clear the row. TestPushTables_Cascades could not: account deletion
removes the user's sessions first. Shown able to fail: with the cascade
removed from 155, the delete is refused (FOREIGN KEY constraint failed).
- TestPushTables_Cascades' header lists exactly what it kills.
- migrated.go, 155, models and nulcolumns no longer describe a
dispatcher or 7-day sweep as if they ran today: both land with
TASK-3939, and until then nothing reads or deletes the queue.
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
* docs(store): the push-delivery note names the census test; five groups are left out (TASK-3937 part A)
Rebased onto 393d986ea, which carries BUG-3958's
TestMigratedEveryLiveTableIsClassified. Part A's first commit now
classifies its tables there: user_notification_settings refused,
push_devices and user_notifications excluded as "push-delivery". Shown
able to fail: without the push_devices entry, the census test reports
"[push_devices]". The header's group count goes from four to five
with the PUSH DELIVERY STATE group.
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
* docs(store): push prose describes the enqueue, registration and dispatcher as owed, not built (TASK-3937 part A r4 L1)
Ruled a fix once part B could not merge in the same window as A
(r4 hunter L1). 155's header, migrated.go's push-delivery notes and
the settings model now say the enqueue is part B's, registration
TASK-3938's and the dispatcher TASK-3939's. nulExcluded's push reasons
gain a header saying they are the writers' contract, and each names the
unit that owes it: device columns TASK-3938, queue columns part B, the
claim and send columns TASK-3939. user_notification_settings keeps
TASK-3937; PutNotificationSettings writes it today.
Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
56 lines
2.2 KiB
Go
56 lines
2.2 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/PerpetualSoftware/pad/internal/models"
|
|
)
|
|
|
|
// GetNotificationSettings returns userID's push settings, or the defaults when
|
|
// they have never saved any (TASK-3937). A missing row is not an error.
|
|
func (s *Store) GetNotificationSettings(userID string) (models.NotificationSettings, error) {
|
|
var out models.NotificationSettings
|
|
err := s.db.QueryRow(s.q(`
|
|
SELECT push_enabled, notify_assigned, notify_comments, notify_replies,
|
|
notify_reminders, notify_mentions, include_agent_activity
|
|
FROM user_notification_settings WHERE user_id = ?`), userID).Scan(
|
|
&out.PushEnabled, &out.NotifyAssigned, &out.NotifyComments, &out.NotifyReplies,
|
|
&out.NotifyReminders, &out.NotifyMentions, &out.IncludeAgentActivity)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return models.DefaultNotificationSettings(), nil
|
|
}
|
|
if err != nil {
|
|
return models.NotificationSettings{}, fmt.Errorf("get notification settings: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// PutNotificationSettings replaces userID's settings wholesale: a field left
|
|
// false is stored false, so a client that turns one off and sends the rest
|
|
// unchanged cannot lose the choice (TASK-3937).
|
|
func (s *Store) PutNotificationSettings(userID string, in models.NotificationSettings) error {
|
|
b := s.dialect.BoolToInt
|
|
_, err := s.db.Exec(s.q(`
|
|
INSERT INTO user_notification_settings
|
|
(user_id, push_enabled, notify_assigned, notify_comments, notify_replies,
|
|
notify_reminders, notify_mentions, include_agent_activity, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT (user_id) DO UPDATE SET
|
|
push_enabled = excluded.push_enabled,
|
|
notify_assigned = excluded.notify_assigned,
|
|
notify_comments = excluded.notify_comments,
|
|
notify_replies = excluded.notify_replies,
|
|
notify_reminders = excluded.notify_reminders,
|
|
notify_mentions = excluded.notify_mentions,
|
|
include_agent_activity = excluded.include_agent_activity,
|
|
updated_at = excluded.updated_at`),
|
|
userID, b(in.PushEnabled), b(in.NotifyAssigned), b(in.NotifyComments), b(in.NotifyReplies),
|
|
b(in.NotifyReminders), b(in.NotifyMentions), b(in.IncludeAgentActivity), now())
|
|
if err != nil {
|
|
return fmt.Errorf("put notification settings: %w", err)
|
|
}
|
|
return nil
|
|
}
|