-- ============================================================================ -- 0004_water_log_admin.sql -- -- Adds the three water-log tables that the Drizzle schema -- (db/schema/water-log.ts) declares but 0001_init.sql never created. -- The corresponding actions live in src/actions/water-log/* and were -- stubbed as "Water log is not configured in the SaaS rebuild" before -- this migration; calling them raised "relation does not exist" and -- /api/water-admin-auth returned a 500 "Server error" to the field. -- -- water_admin_settings — per-brand admin PIN + portal config -- water_admin_sessions — /water/admin sign-in sessions -- water_audit_log — who changed what, when -- -- All three are brand-scoped with RLS, matching the 0001 pattern -- (`brand_id = current_brand_id() OR is_platform_admin()`). -- ============================================================================ -- ============================================================================ -- water_admin_settings -- ============================================================================ CREATE TABLE IF NOT EXISTS water_admin_settings ( brand_id UUID PRIMARY KEY REFERENCES brands(id) ON DELETE CASCADE, -- Hashed admin PIN, format: scrypt$N$r$p$salt_b64$hash_b64 -- (see src/lib/water-log-pin.ts). Nullable so a brand can exist -- without an admin PIN until one is generated. pin_hash TEXT, enabled BOOLEAN NOT NULL DEFAULT true, session_duration_hours INTEGER NOT NULL DEFAULT 4, can_edit_entries BOOLEAN NOT NULL DEFAULT true, can_delete_entries BOOLEAN NOT NULL DEFAULT true, can_export_csv BOOLEAN NOT NULL DEFAULT true, alert_phone TEXT, alerts_enabled BOOLEAN NOT NULL DEFAULT false, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_by UUID REFERENCES admin_users(id) ); -- ============================================================================ -- water_admin_sessions -- ============================================================================ CREATE TABLE IF NOT EXISTS water_admin_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE, admin_user_id UUID NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE, -- Snapshot of the PIN hash used to sign in, so a subsequent PIN -- rotation invalidates the session without us having to walk every row. pin_hash_used TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS water_admin_sessions_admin_idx ON water_admin_sessions (admin_user_id, expires_at); -- ============================================================================ -- water_audit_log -- ============================================================================ CREATE TABLE IF NOT EXISTS water_audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE, actor_id UUID REFERENCES admin_users(id), actor_label TEXT NOT NULL, action TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id UUID, details JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS water_audit_log_brand_recent_idx ON water_audit_log (brand_id, created_at); -- ============================================================================ -- RLS -- ============================================================================ ALTER TABLE water_admin_settings ENABLE ROW LEVEL SECURITY; ALTER TABLE water_admin_settings FORCE ROW LEVEL SECURITY; ALTER TABLE water_admin_sessions ENABLE ROW LEVEL SECURITY; ALTER TABLE water_admin_sessions FORCE ROW LEVEL SECURITY; ALTER TABLE water_audit_log ENABLE ROW LEVEL SECURITY; ALTER TABLE water_audit_log FORCE ROW LEVEL SECURITY; -- ============================================================================ -- Policies -- ============================================================================ -- water_admin_settings: PK is brand_id, so a single per-brand row. DROP POLICY IF EXISTS tenant_isolation ON water_admin_settings; CREATE POLICY tenant_isolation ON water_admin_settings FOR ALL USING (brand_id = current_brand_id() OR is_platform_admin()) WITH CHECK (brand_id = current_brand_id() OR is_platform_admin()); -- water_admin_sessions: keyed off brand_id directly. DROP POLICY IF EXISTS tenant_isolation ON water_admin_sessions; CREATE POLICY tenant_isolation ON water_admin_sessions FOR ALL USING (brand_id = current_brand_id() OR is_platform_admin()) WITH CHECK (brand_id = current_brand_id() OR is_platform_admin()); -- water_audit_log: keyed off brand_id directly. DROP POLICY IF EXISTS tenant_isolation ON water_audit_log; CREATE POLICY tenant_isolation ON water_audit_log FOR ALL USING (brand_id = current_brand_id() OR is_platform_admin()) WITH CHECK (brand_id = current_brand_id() OR is_platform_admin());