package store import ( "context" "database/sql" "encoding/json" "errors" "g.fc.run/misc/byre/internal/api" ) func (s *Store) UpdateNotificationPrefs(ctx context.Context, login string, prefs api.NotificationPrefs) (api.User, error) { b, err := json.Marshal(prefs) if err != nil { return api.User{}, err } err = s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { _, err := tx.ExecContext(ctx, `UPDATE users SET notification_prefs = ? WHERE login = ?`, string(b), login) if err != nil { return err } return emit(api.Event{Kind: "user.updated", Actor: login, Payload: map[string]any{"login": login}}) }) if err != nil { return api.User{}, err } return s.GetUser(ctx, login) } type VAPIDKeys struct { Public string Private string } func (s *Store) GetVAPIDKeys(ctx context.Context) (VAPIDKeys, error) { k := VAPIDKeys{} err := s.db.QueryRowContext(ctx, `SELECT public_key, private_key FROM vapid_keys WHERE id = 1`).Scan(&k.Public, &k.Private) if errors.Is(err, sql.ErrNoRows) { return k, ErrNotFound } return k, err } func (s *Store) InitVAPIDKeys(ctx context.Context, k VAPIDKeys) (VAPIDKeys, error) { err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { _, err := tx.ExecContext(ctx, `INSERT INTO vapid_keys (id, public_key, private_key) VALUES (1, ?, ?) ON CONFLICT (id) DO NOTHING`, k.Public, k.Private) return err }) if err != nil { return VAPIDKeys{}, err } return s.GetVAPIDKeys(ctx) } type PushSubscription struct { Endpoint string UserLogin string P256dh string Auth string Subscriber string } func (s *Store) AddPushSubscription(ctx context.Context, sub PushSubscription) error { return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { _, err := tx.ExecContext(ctx, ` INSERT INTO push_subscriptions (endpoint, user_login, p256dh, auth, subscriber, created_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (endpoint) DO UPDATE SET user_login = excluded.user_login, p256dh = excluded.p256dh, auth = excluded.auth, subscriber = excluded.subscriber `, sub.Endpoint, sub.UserLogin, sub.P256dh, sub.Auth, sub.Subscriber, now()) if err != nil { return err } return emit(api.Event{Kind: "push.subscribed", Actor: sub.UserLogin, Payload: map[string]any{"login": sub.UserLogin}}) }) } func (s *Store) RemovePushSubscription(ctx context.Context, endpoint, login, actor string) error { return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { res, err := tx.ExecContext(ctx, `DELETE FROM push_subscriptions WHERE endpoint = ? AND user_login = ?`, endpoint, login) if err != nil { return err } n, err := res.RowsAffected() if err != nil { return err } if n == 0 { return nil } return emit(api.Event{Kind: "push.unsubscribed", Actor: actor, Payload: map[string]any{"login": login}}) }) } type PushTarget struct { Subscription PushSubscription Prefs api.NotificationPrefs } func (s *Store) PushTargets(ctx context.Context) ([]PushTarget, error) { rows, err := s.db.QueryContext(ctx, ` SELECT p.endpoint, p.user_login, p.p256dh, p.auth, p.subscriber, u.notification_prefs FROM push_subscriptions p JOIN users u ON u.login = p.user_login ORDER BY p.created_at `) if err != nil { return nil, err } defer rows.Close() targets := []PushTarget{} for rows.Next() { t := PushTarget{} prefs := "" err = rows.Scan(&t.Subscription.Endpoint, &t.Subscription.UserLogin, &t.Subscription.P256dh, &t.Subscription.Auth, &t.Subscription.Subscriber, &prefs) if err != nil { return nil, err } err = json.Unmarshal([]byte(prefs), &t.Prefs) if err != nil { return nil, err } targets = append(targets, t) } return targets, rows.Err() }