Notification preferences (migration 048): - Per-member opt-out of muteable categories (bookings, classes, forms, admin alerts); billing is always-on. Opt-out model, default on. - Dispatcher reads prefs and marks muted outbox rows 'skipped' instead of sending; pure isMuted logic, fail-open on a prefs lookup error. - notification_preferences table (default-deny RLS); /me toggles and self-view via validated service-client actions. Alternate dues payment methods (migration 049): - Admin sets external pay-here URLs per platform (PayPal/Zeffy/Venmo) in /settings; members see active ones on /me and pay off-platform; the treasurer reconciles manually (platform pre-tagged). https-only. - dues_payment_methods table (SELECT space member, write admin/board). /me visual pass: - Restore the chip/preset profile editor (skills/interests/willing-to/ affiliations); promote ChipInput and presets to shared modules. - Gate the Stripe pay-dues button on whether the space has Stripe configured; show external links or a contact-admin note otherwise.
42 lines
2.3 KiB
SQL
42 lines
2.3 KiB
SQL
-- =============================================================================
|
|
-- 048: Member notification preferences (Product spine Phase 5)
|
|
-- =============================================================================
|
|
-- Per-member opt-out of muteable notification categories (bookings, classes,
|
|
-- forms, admin_alerts). Billing (dues renewed / payment-failed / lapsed) is
|
|
-- membership-critical and is never muteable, so it is never stored here. The
|
|
-- model is opt-out: absence of a row means the default, enabled.
|
|
--
|
|
-- The dispatcher cron reads these rows (service client) and marks a muted
|
|
-- outbox row 'skipped' instead of sending it; the member self-serve toggle on
|
|
-- /me writes them through a validated server action (service client). Both
|
|
-- paths use the service client, so there is NO client write policy and no
|
|
-- client read policy -- the same convention as notifications and
|
|
-- member_billing (member self-view goes through a validated action, not RLS).
|
|
-- RLS is enabled and default-deny for every non-service client.
|
|
--
|
|
-- Idempotent: CREATE TABLE/INDEX IF NOT EXISTS, DROP POLICY/TRIGGER IF EXISTS
|
|
-- then CREATE. Apply as-is (Supabase SQL editor / psql); re-runs are no-ops.
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS public.notification_preferences (
|
|
space_id uuid NOT NULL REFERENCES public.spaces(id) ON DELETE CASCADE,
|
|
member_id uuid NOT NULL REFERENCES public.space_members(id) ON DELETE CASCADE,
|
|
category text NOT NULL,
|
|
enabled boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (space_id, member_id, category)
|
|
);
|
|
|
|
ALTER TABLE public.notification_preferences ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- No client policy. Both the member self-view (getMyNotificationPreferences)
|
|
-- and the toggle write (setMyNotificationPreference) go through validated
|
|
-- service-client actions; the dispatcher reads via the service client too.
|
|
-- Default-deny for every non-service client.
|
|
|
|
DROP TRIGGER IF EXISTS trg_notification_prefs_touch ON public.notification_preferences;
|
|
CREATE TRIGGER trg_notification_prefs_touch
|
|
BEFORE UPDATE ON public.notification_preferences
|
|
FOR EACH ROW EXECUTE FUNCTION public.touch_updated_at();
|