@fayz-ai/db 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/canonical-schema.json +132 -0
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/schema/spine.d.ts +42 -37
- package/dist/schema/spine.d.ts.map +1 -1
- package/migrations/000_baseline.sql +29667 -0
- package/migrations/001_the_tenant_has_apps.sql +314 -0
- package/migrations/002_the_spine_drops_the_vendor_and_the_dead.sql +129 -0
- package/migrations/003_the_role_rpcs_finish_and_the_mirrors_go.sql +135 -0
- package/migrations/004_the_projections_get_a_successor.sql +312 -0
- package/migrations/005_the_archetypes_anchor_to_the_unit.sql +90 -0
- package/migrations/006_the_helpers_read_the_source.sql +153 -0
- package/migrations/007_the_compat_machinery_leaves_with_the_tables.sql +77 -0
- package/migrations/008_the_switchers_see_across_accounts.sql +150 -0
- package/migrations/009_the_compat_aliases_nobody_adopted.sql +57 -0
- package/migrations/010_the_new_tenant_is_born_with_its_trade.sql +201 -0
- package/migrations/011_leadcontrol_joins_the_family.sql +116 -0
- package/migrations/012_chefcontrol_wears_its_own_mark.sql +28 -0
- package/migrations/013_the_workspace_switch_reaches_the_other_half.sql +156 -0
- package/migrations/014_the_chooser_can_read_what_it_offers.sql +91 -0
- package/migrations/015_the_unit_bindings_belong_to_one_workspace.sql +86 -0
- package/migrations/016_the_rule_stops_asking_every_row.sql +543 -0
- package/migrations/017_the_pin_is_checked_against_the_right_workspace.sql +139 -0
- package/migrations/018_the_bootstrap_says_what_you_reach.sql +114 -0
- package/migrations/019_distributing_one_row_stops_costing_the_table.sql +245 -0
- package/migrations/020_the_agenda_asks_where_someone_works.sql +81 -0
- package/migrations/022_the_line_says_which_document_it_belongs_to.sql +109 -0
- package/package.json +3 -2
- package/migrations/0000_legacy_quarantine.sql +0 -86
- package/migrations/0000b_straggler_sweep.sql +0 -37
- package/migrations/000_core_v1_convert.sql +0 -343
- package/migrations/001_core.sql +0 -372
- package/migrations/002_rls_user_tenant_ids.sql +0 -64
- package/migrations/003_invitations.sql +0 -17
- package/migrations/004_archetypes.sql +0 -343
- package/migrations/005_locations_archetype.sql +0 -18
- package/migrations/006_archetype_rls.sql +0 -74
- package/migrations/007_archetype_grants.sql +0 -4
- package/migrations/008_grants.sql +0 -5
- package/migrations/009_anon_hardening.sql +0 -19
- package/migrations/010_migration_ledger.sql +0 -36
- package/migrations/011_anon_write_revoke.sql +0 -12
- package/migrations/012_rename_migration_ledger.sql +0 -61
- package/migrations/013_sequences_rls.sql +0 -22
- package/migrations/014_plan_entitlements.sql +0 -28
- package/migrations/015_agent_guard.sql +0 -108
- package/migrations/016_agent_guard_actor.sql +0 -102
- package/migrations/017_core_addresses.sql +0 -115
- package/migrations/018_global_search.sql +0 -482
- package/migrations/019_tenant_members_person_id.sql +0 -20
- package/migrations/020_fayz_projects.sql +0 -63
- package/migrations/021_tenant_roles.sql +0 -74
- package/migrations/022_invite_acceptance.sql +0 -91
- package/migrations/023_team_visibility.sql +0 -75
- package/migrations/024_analytics_engine.sql +0 -317
- package/migrations/025_created_by.sql +0 -75
- package/migrations/026_audit_trail.sql +0 -73
- package/migrations/027_domain_events.sql +0 -266
- package/migrations/028_tenant_scoped_token.sql +0 -127
- package/migrations/029_connections.sql +0 -186
- package/migrations/030_effect_idempotency.sql +0 -159
- package/migrations/031_sync_run_message.sql +0 -39
- package/migrations/032_connection_secrets.sql +0 -227
- package/migrations/033_sync_schedule.sql +0 -651
- package/migrations/034_custom_fields.sql +0 -55
- package/migrations/035_field_registry.sql +0 -148
- package/migrations/036_analytics_run_batch.sql +0 -84
- package/migrations/037_sync_tick_one_at_a_time.sql +0 -256
- package/migrations/038_onboarding_responses.sql +0 -103
- package/migrations/039_unit_tree.sql +0 -270
- package/migrations/040_resource_grants.sql +0 -474
- package/migrations/041_scoped_columns.sql +0 -192
- package/migrations/042_unit_scope_policies.sql +0 -145
- package/migrations/043_view_invoker.sql +0 -81
- package/migrations/044_unit_member_facts.sql +0 -47
- package/migrations/045_unit_entry.sql +0 -236
- package/migrations/046_membership_visible_to_members.sql +0 -85
- package/migrations/047_tasks.sql +0 -266
- package/migrations/048_every_login_is_a_person.sql +0 -190
- package/migrations/049_bookable_people.sql +0 -126
package/migrations/001_core.sql
DELETED
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
-- core: minimal core schema for each SaaS project (industry-pool model)
|
|
2
|
-
-- All core entities live directly in the PUBLIC schema — no saas_core schema.
|
|
3
|
-
-- Plugin registry, verticals, billing live in Fayz platform DB — not here.
|
|
4
|
-
|
|
5
|
-
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
6
|
-
|
|
7
|
-
-- Tenants (organizations)
|
|
8
|
-
CREATE TABLE IF NOT EXISTS public.tenants (
|
|
9
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
10
|
-
name text NOT NULL,
|
|
11
|
-
slug text UNIQUE NOT NULL,
|
|
12
|
-
logo_url text,
|
|
13
|
-
plan text NOT NULL DEFAULT 'free',
|
|
14
|
-
vertical_id text,
|
|
15
|
-
settings jsonb NOT NULL DEFAULT '{}',
|
|
16
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
17
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
-- Profiles (extends auth.users)
|
|
21
|
-
CREATE TABLE IF NOT EXISTS public.profiles (
|
|
22
|
-
id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
23
|
-
email text,
|
|
24
|
-
full_name text,
|
|
25
|
-
avatar_url text,
|
|
26
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
27
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
-- Tenant members (org membership + role)
|
|
31
|
-
CREATE TABLE IF NOT EXISTS public.tenant_members (
|
|
32
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
33
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
34
|
-
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
35
|
-
role text NOT NULL DEFAULT 'owner',
|
|
36
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
37
|
-
UNIQUE(tenant_id, user_id)
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
-- Updated_at trigger
|
|
41
|
-
CREATE OR REPLACE FUNCTION public.handle_updated_at()
|
|
42
|
-
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
43
|
-
BEGIN NEW.updated_at = now(); RETURN NEW; END; $$;
|
|
44
|
-
|
|
45
|
-
DROP TRIGGER IF EXISTS tenants_updated_at ON public.tenants;
|
|
46
|
-
CREATE TRIGGER tenants_updated_at BEFORE UPDATE ON public.tenants
|
|
47
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
48
|
-
DROP TRIGGER IF EXISTS profiles_updated_at ON public.profiles;
|
|
49
|
-
CREATE TRIGGER profiles_updated_at BEFORE UPDATE ON public.profiles
|
|
50
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
51
|
-
|
|
52
|
-
-- Auto-create profile on signup
|
|
53
|
-
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
54
|
-
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$
|
|
55
|
-
BEGIN
|
|
56
|
-
INSERT INTO public.profiles (id, email, full_name, avatar_url)
|
|
57
|
-
VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'full_name', NEW.raw_user_meta_data->>'avatar_url')
|
|
58
|
-
ON CONFLICT (id) DO NOTHING;
|
|
59
|
-
RETURN NEW;
|
|
60
|
-
END; $$;
|
|
61
|
-
|
|
62
|
-
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
63
|
-
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users
|
|
64
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
|
65
|
-
|
|
66
|
-
-- Create tenant + owner atomically (bypasses RLS chicken-and-egg).
|
|
67
|
-
-- This public function IS the body — PostgREST exposes public directly, so
|
|
68
|
-
-- there is no saas_core wrapper indirection anymore.
|
|
69
|
-
CREATE OR REPLACE FUNCTION public.create_tenant_with_owner(
|
|
70
|
-
p_name text, p_slug text, p_user_id uuid,
|
|
71
|
-
p_vertical_id text DEFAULT NULL, p_plan text DEFAULT 'free', p_settings jsonb DEFAULT '{}'
|
|
72
|
-
)
|
|
73
|
-
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$
|
|
74
|
-
DECLARE v_id uuid; v_tenant jsonb;
|
|
75
|
-
BEGIN
|
|
76
|
-
INSERT INTO public.tenants (name, slug, plan, vertical_id, settings)
|
|
77
|
-
VALUES (p_name, p_slug, p_plan, p_vertical_id, p_settings) RETURNING id INTO v_id;
|
|
78
|
-
|
|
79
|
-
INSERT INTO public.tenant_members (tenant_id, user_id, role)
|
|
80
|
-
VALUES (v_id, p_user_id, 'owner');
|
|
81
|
-
|
|
82
|
-
SELECT to_jsonb(t) INTO v_tenant FROM public.tenants t WHERE t.id = v_id;
|
|
83
|
-
RETURN v_tenant;
|
|
84
|
-
END; $$;
|
|
85
|
-
|
|
86
|
-
-- Admin check (SECURITY DEFINER to avoid RLS recursion)
|
|
87
|
-
CREATE OR REPLACE FUNCTION public.is_tenant_admin(p_tenant_id uuid)
|
|
88
|
-
RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $$
|
|
89
|
-
SELECT EXISTS (
|
|
90
|
-
SELECT 1 FROM public.tenant_members
|
|
91
|
-
WHERE tenant_id = p_tenant_id AND user_id = auth.uid() AND role IN ('owner', 'admin')
|
|
92
|
-
);
|
|
93
|
-
$$;
|
|
94
|
-
|
|
95
|
-
-- Plans
|
|
96
|
-
CREATE TABLE IF NOT EXISTS public.plans (
|
|
97
|
-
id text PRIMARY KEY,
|
|
98
|
-
name text NOT NULL,
|
|
99
|
-
description text,
|
|
100
|
-
vertical_id text,
|
|
101
|
-
price_monthly integer DEFAULT 0,
|
|
102
|
-
price_yearly integer DEFAULT 0,
|
|
103
|
-
features jsonb DEFAULT '[]',
|
|
104
|
-
is_popular boolean DEFAULT false,
|
|
105
|
-
sort_order integer DEFAULT 0,
|
|
106
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
-- Permissions (RBAC)
|
|
110
|
-
CREATE TABLE IF NOT EXISTS public.permissions (
|
|
111
|
-
id text PRIMARY KEY,
|
|
112
|
-
category text NOT NULL,
|
|
113
|
-
description text,
|
|
114
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
-- Role permissions (default grants per role)
|
|
118
|
-
CREATE TABLE IF NOT EXISTS public.role_permissions (
|
|
119
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
120
|
-
role text NOT NULL,
|
|
121
|
-
permission_id text NOT NULL REFERENCES public.permissions(id) ON DELETE CASCADE,
|
|
122
|
-
actions text[] NOT NULL DEFAULT '{read}',
|
|
123
|
-
UNIQUE(role, permission_id)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
-- Tenant role overrides (per-tenant permission customization)
|
|
127
|
-
CREATE TABLE IF NOT EXISTS public.tenant_role_overrides (
|
|
128
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
129
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
130
|
-
role text NOT NULL,
|
|
131
|
-
permission_id text NOT NULL REFERENCES public.permissions(id) ON DELETE CASCADE,
|
|
132
|
-
granted boolean NOT NULL DEFAULT true,
|
|
133
|
-
UNIQUE(tenant_id, role, permission_id)
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
-- Invitations
|
|
137
|
-
CREATE TABLE IF NOT EXISTS public.invitations (
|
|
138
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
139
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
140
|
-
email text NOT NULL,
|
|
141
|
-
role text NOT NULL DEFAULT 'staff',
|
|
142
|
-
location_ids uuid[] DEFAULT '{}',
|
|
143
|
-
token text UNIQUE NOT NULL DEFAULT replace(gen_random_uuid()::text, '-', ''),
|
|
144
|
-
status text NOT NULL DEFAULT 'pending',
|
|
145
|
-
invited_by uuid REFERENCES auth.users(id),
|
|
146
|
-
accepted_at timestamptz,
|
|
147
|
-
expires_at timestamptz NOT NULL DEFAULT (now() + interval '7 days'),
|
|
148
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
-- Payment events (webhook log)
|
|
152
|
-
CREATE TABLE IF NOT EXISTS public.payment_events (
|
|
153
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
154
|
-
tenant_id uuid REFERENCES public.tenants(id) ON DELETE SET NULL,
|
|
155
|
-
stripe_event_id text UNIQUE,
|
|
156
|
-
event_type text NOT NULL,
|
|
157
|
-
payload jsonb DEFAULT '{}',
|
|
158
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
-- Locations (multi-branch)
|
|
162
|
-
CREATE TABLE IF NOT EXISTS public.locations (
|
|
163
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
164
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
165
|
-
kind text NOT NULL DEFAULT 'branch',
|
|
166
|
-
name text NOT NULL,
|
|
167
|
-
email text,
|
|
168
|
-
phone text,
|
|
169
|
-
address text,
|
|
170
|
-
city text,
|
|
171
|
-
state text,
|
|
172
|
-
country text DEFAULT 'BR',
|
|
173
|
-
postal_code text,
|
|
174
|
-
is_headquarters boolean DEFAULT false,
|
|
175
|
-
is_active boolean DEFAULT true,
|
|
176
|
-
tags text[] DEFAULT '{}',
|
|
177
|
-
notes text,
|
|
178
|
-
metadata jsonb DEFAULT '{}',
|
|
179
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
180
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
DROP TRIGGER IF EXISTS locations_updated_at ON public.locations;
|
|
184
|
-
CREATE TRIGGER locations_updated_at BEFORE UPDATE ON public.locations
|
|
185
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
186
|
-
|
|
187
|
-
-- Location members (staff assigned to locations)
|
|
188
|
-
CREATE TABLE IF NOT EXISTS public.location_members (
|
|
189
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
190
|
-
location_id uuid NOT NULL REFERENCES public.locations(id) ON DELETE CASCADE,
|
|
191
|
-
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
192
|
-
role text DEFAULT 'staff',
|
|
193
|
-
UNIQUE(location_id, user_id)
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
-- Audit logs
|
|
197
|
-
CREATE TABLE IF NOT EXISTS public.audit_logs (
|
|
198
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
199
|
-
tenant_id uuid REFERENCES public.tenants(id) ON DELETE SET NULL,
|
|
200
|
-
user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
201
|
-
action text NOT NULL,
|
|
202
|
-
entity_type text,
|
|
203
|
-
entity_id text,
|
|
204
|
-
metadata jsonb DEFAULT '{}',
|
|
205
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
-- Subscriptions (billing)
|
|
209
|
-
CREATE TABLE IF NOT EXISTS public.subscriptions (
|
|
210
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
211
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
212
|
-
plan text NOT NULL DEFAULT 'free',
|
|
213
|
-
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'past_due', 'cancelled', 'trialing')),
|
|
214
|
-
stripe_customer_id text,
|
|
215
|
-
stripe_subscription_id text,
|
|
216
|
-
current_period_start timestamptz,
|
|
217
|
-
current_period_end timestamptz,
|
|
218
|
-
cancel_at timestamptz,
|
|
219
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
220
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
DROP TRIGGER IF EXISTS subscriptions_updated_at ON public.subscriptions;
|
|
224
|
-
CREATE TRIGGER subscriptions_updated_at BEFORE UPDATE ON public.subscriptions
|
|
225
|
-
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
226
|
-
|
|
227
|
-
-- Invoices
|
|
228
|
-
CREATE TABLE IF NOT EXISTS public.invoices (
|
|
229
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
230
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
231
|
-
stripe_invoice_id text,
|
|
232
|
-
amount integer NOT NULL DEFAULT 0,
|
|
233
|
-
currency text NOT NULL DEFAULT 'brl',
|
|
234
|
-
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'open', 'paid', 'void', 'uncollectible')),
|
|
235
|
-
invoice_url text,
|
|
236
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
-- Notifications
|
|
240
|
-
CREATE TABLE IF NOT EXISTS public.notifications (
|
|
241
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
242
|
-
tenant_id uuid REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
243
|
-
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
244
|
-
title text NOT NULL,
|
|
245
|
-
body text,
|
|
246
|
-
type text DEFAULT 'info',
|
|
247
|
-
read boolean NOT NULL DEFAULT false,
|
|
248
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
-- RLS on all tables
|
|
252
|
-
ALTER TABLE public.tenants ENABLE ROW LEVEL SECURITY;
|
|
253
|
-
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
|
254
|
-
ALTER TABLE public.tenant_members ENABLE ROW LEVEL SECURITY;
|
|
255
|
-
ALTER TABLE public.plans ENABLE ROW LEVEL SECURITY;
|
|
256
|
-
ALTER TABLE public.permissions ENABLE ROW LEVEL SECURITY;
|
|
257
|
-
ALTER TABLE public.role_permissions ENABLE ROW LEVEL SECURITY;
|
|
258
|
-
ALTER TABLE public.tenant_role_overrides ENABLE ROW LEVEL SECURITY;
|
|
259
|
-
ALTER TABLE public.invitations ENABLE ROW LEVEL SECURITY;
|
|
260
|
-
ALTER TABLE public.payment_events ENABLE ROW LEVEL SECURITY;
|
|
261
|
-
ALTER TABLE public.locations ENABLE ROW LEVEL SECURITY;
|
|
262
|
-
ALTER TABLE public.location_members ENABLE ROW LEVEL SECURITY;
|
|
263
|
-
ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY;
|
|
264
|
-
ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY;
|
|
265
|
-
ALTER TABLE public.invoices ENABLE ROW LEVEL SECURITY;
|
|
266
|
-
ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;
|
|
267
|
-
|
|
268
|
-
-- Tenants: members can see, authenticated can create
|
|
269
|
-
DROP POLICY IF EXISTS "tenants_select" ON public.tenants;
|
|
270
|
-
CREATE POLICY "tenants_select" ON public.tenants FOR SELECT
|
|
271
|
-
USING (id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
272
|
-
DROP POLICY IF EXISTS "tenants_insert" ON public.tenants;
|
|
273
|
-
CREATE POLICY "tenants_insert" ON public.tenants FOR INSERT TO authenticated WITH CHECK (true);
|
|
274
|
-
DROP POLICY IF EXISTS "tenants_update" ON public.tenants;
|
|
275
|
-
CREATE POLICY "tenants_update" ON public.tenants FOR UPDATE
|
|
276
|
-
USING (public.is_tenant_admin(id));
|
|
277
|
-
|
|
278
|
-
-- Profiles: own profile only
|
|
279
|
-
DROP POLICY IF EXISTS "profiles_select" ON public.profiles;
|
|
280
|
-
CREATE POLICY "profiles_select" ON public.profiles FOR SELECT USING (id = auth.uid());
|
|
281
|
-
DROP POLICY IF EXISTS "profiles_insert" ON public.profiles;
|
|
282
|
-
CREATE POLICY "profiles_insert" ON public.profiles FOR INSERT WITH CHECK (id = auth.uid());
|
|
283
|
-
DROP POLICY IF EXISTS "profiles_update" ON public.profiles;
|
|
284
|
-
CREATE POLICY "profiles_update" ON public.profiles FOR UPDATE USING (id = auth.uid());
|
|
285
|
-
|
|
286
|
-
-- Tenant members: see own, insert own, admins manage
|
|
287
|
-
DROP POLICY IF EXISTS "members_select" ON public.tenant_members;
|
|
288
|
-
CREATE POLICY "members_select" ON public.tenant_members FOR SELECT USING (user_id = auth.uid());
|
|
289
|
-
DROP POLICY IF EXISTS "members_insert_own" ON public.tenant_members;
|
|
290
|
-
CREATE POLICY "members_insert_own" ON public.tenant_members FOR INSERT WITH CHECK (user_id = auth.uid());
|
|
291
|
-
DROP POLICY IF EXISTS "members_update" ON public.tenant_members;
|
|
292
|
-
CREATE POLICY "members_update" ON public.tenant_members FOR UPDATE USING (public.is_tenant_admin(tenant_id));
|
|
293
|
-
DROP POLICY IF EXISTS "members_delete" ON public.tenant_members;
|
|
294
|
-
CREATE POLICY "members_delete" ON public.tenant_members FOR DELETE USING (public.is_tenant_admin(tenant_id));
|
|
295
|
-
|
|
296
|
-
-- Plans/permissions: readable by all authenticated
|
|
297
|
-
DROP POLICY IF EXISTS "plans_select" ON public.plans;
|
|
298
|
-
CREATE POLICY "plans_select" ON public.plans FOR SELECT TO authenticated USING (true);
|
|
299
|
-
DROP POLICY IF EXISTS "perms_select" ON public.permissions;
|
|
300
|
-
CREATE POLICY "perms_select" ON public.permissions FOR SELECT TO authenticated USING (true);
|
|
301
|
-
DROP POLICY IF EXISTS "role_perms_select" ON public.role_permissions;
|
|
302
|
-
CREATE POLICY "role_perms_select" ON public.role_permissions FOR SELECT TO authenticated USING (true);
|
|
303
|
-
|
|
304
|
-
-- Tenant role overrides: members can see their tenant's overrides
|
|
305
|
-
DROP POLICY IF EXISTS "overrides_select" ON public.tenant_role_overrides;
|
|
306
|
-
CREATE POLICY "overrides_select" ON public.tenant_role_overrides FOR SELECT
|
|
307
|
-
USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
308
|
-
DROP POLICY IF EXISTS "overrides_manage" ON public.tenant_role_overrides;
|
|
309
|
-
CREATE POLICY "overrides_manage" ON public.tenant_role_overrides FOR ALL
|
|
310
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
311
|
-
|
|
312
|
-
-- Invitations: admins manage, anyone can read (to accept)
|
|
313
|
-
DROP POLICY IF EXISTS "invites_select" ON public.invitations;
|
|
314
|
-
CREATE POLICY "invites_select" ON public.invitations FOR SELECT TO authenticated USING (true);
|
|
315
|
-
DROP POLICY IF EXISTS "invites_insert" ON public.invitations;
|
|
316
|
-
CREATE POLICY "invites_insert" ON public.invitations FOR INSERT
|
|
317
|
-
WITH CHECK (public.is_tenant_admin(tenant_id));
|
|
318
|
-
DROP POLICY IF EXISTS "invites_update" ON public.invitations;
|
|
319
|
-
CREATE POLICY "invites_update" ON public.invitations FOR UPDATE
|
|
320
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
321
|
-
DROP POLICY IF EXISTS "invites_delete" ON public.invitations;
|
|
322
|
-
CREATE POLICY "invites_delete" ON public.invitations FOR DELETE
|
|
323
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
324
|
-
|
|
325
|
-
-- Locations: tenant members can see, admins manage
|
|
326
|
-
DROP POLICY IF EXISTS "locations_select" ON public.locations;
|
|
327
|
-
CREATE POLICY "locations_select" ON public.locations FOR SELECT
|
|
328
|
-
USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
329
|
-
DROP POLICY IF EXISTS "locations_manage" ON public.locations;
|
|
330
|
-
CREATE POLICY "locations_manage" ON public.locations FOR ALL
|
|
331
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
332
|
-
|
|
333
|
-
-- Location members: same as locations
|
|
334
|
-
DROP POLICY IF EXISTS "loc_members_select" ON public.location_members;
|
|
335
|
-
CREATE POLICY "loc_members_select" ON public.location_members FOR SELECT
|
|
336
|
-
USING (location_id IN (SELECT id FROM public.locations WHERE tenant_id IN (
|
|
337
|
-
SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()
|
|
338
|
-
)));
|
|
339
|
-
|
|
340
|
-
-- Subscriptions/invoices: members can see
|
|
341
|
-
DROP POLICY IF EXISTS "subs_select" ON public.subscriptions;
|
|
342
|
-
CREATE POLICY "subs_select" ON public.subscriptions FOR SELECT
|
|
343
|
-
USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
344
|
-
DROP POLICY IF EXISTS "invoices_select" ON public.invoices;
|
|
345
|
-
CREATE POLICY "invoices_select" ON public.invoices FOR SELECT
|
|
346
|
-
USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
347
|
-
|
|
348
|
-
-- Payment events: service_role only (webhook handler)
|
|
349
|
-
DROP POLICY IF EXISTS "events_service" ON public.payment_events;
|
|
350
|
-
CREATE POLICY "events_service" ON public.payment_events FOR ALL TO service_role USING (true);
|
|
351
|
-
|
|
352
|
-
-- Audit logs: tenant members can see their tenant's logs
|
|
353
|
-
DROP POLICY IF EXISTS "audit_select" ON public.audit_logs;
|
|
354
|
-
CREATE POLICY "audit_select" ON public.audit_logs FOR SELECT
|
|
355
|
-
USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
|
|
356
|
-
DROP POLICY IF EXISTS "audit_insert" ON public.audit_logs;
|
|
357
|
-
CREATE POLICY "audit_insert" ON public.audit_logs FOR INSERT TO authenticated WITH CHECK (true);
|
|
358
|
-
|
|
359
|
-
-- Notifications: own notifications
|
|
360
|
-
DROP POLICY IF EXISTS "notif_select" ON public.notifications;
|
|
361
|
-
CREATE POLICY "notif_select" ON public.notifications FOR SELECT USING (user_id = auth.uid());
|
|
362
|
-
DROP POLICY IF EXISTS "notif_update" ON public.notifications;
|
|
363
|
-
CREATE POLICY "notif_update" ON public.notifications FOR UPDATE USING (user_id = auth.uid());
|
|
364
|
-
|
|
365
|
-
-- Grant schema access (public is the only API schema now)
|
|
366
|
-
GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated, service_role;
|
|
367
|
-
GRANT SELECT ON ALL TABLES IN SCHEMA public TO anon;
|
|
368
|
-
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO anon, authenticated, service_role;
|
|
369
|
-
|
|
370
|
-
-- Expose ONLY the public schema to the PostgREST API
|
|
371
|
-
ALTER ROLE authenticator SET pgrst.db_schemas = 'public';
|
|
372
|
-
NOTIFY pgrst, 'reload config';
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
-- core: RLS for project tables (public schema)
|
|
2
|
-
-- Ensures every plugin/app table with tenant_id is scoped to the user's tenants.
|
|
3
|
-
|
|
4
|
-
-- Helper: get tenant IDs the current user belongs to
|
|
5
|
-
CREATE OR REPLACE FUNCTION public.user_tenant_ids()
|
|
6
|
-
RETURNS SETOF uuid
|
|
7
|
-
LANGUAGE sql STABLE SECURITY DEFINER AS $$
|
|
8
|
-
SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid();
|
|
9
|
-
$$;
|
|
10
|
-
|
|
11
|
-
-- Apply generic tenant-isolation RLS to public tables that have a tenant_id
|
|
12
|
-
-- column. Runs as a DO block so it auto-discovers tables.
|
|
13
|
-
--
|
|
14
|
-
-- Scope note: the core tables (tenants/tenant_members/locations/... created in
|
|
15
|
-
-- 001, and the archetype tables created later in 004) carry their OWN curated
|
|
16
|
-
-- policies and must NOT receive the generic isolation policy (e.g. payment_events
|
|
17
|
-
-- would otherwise become readable by tenant members). They are excluded below.
|
|
18
|
-
-- What remains covered: plugin (plg_*) and app tables that carry a tenant_id.
|
|
19
|
-
-- Child tables (order_items/appointment_items/*_items) have no tenant_id column,
|
|
20
|
-
-- so they are skipped here and instead get parent-FK policies in their own
|
|
21
|
-
-- migrations.
|
|
22
|
-
DO $$
|
|
23
|
-
DECLARE
|
|
24
|
-
t text;
|
|
25
|
-
core_tables text[] := ARRAY[
|
|
26
|
-
-- 001 core spine
|
|
27
|
-
'tenants', 'profiles', 'tenant_members', 'plans', 'permissions',
|
|
28
|
-
'role_permissions', 'tenant_role_overrides', 'invitations',
|
|
29
|
-
'payment_events', 'locations', 'location_members', 'audit_logs',
|
|
30
|
-
'subscriptions', 'invoices', 'notifications',
|
|
31
|
-
-- 004 archetypes (self-governed via 004/006 policies)
|
|
32
|
-
'people', 'categories', 'products', 'services', 'orders', 'order_items',
|
|
33
|
-
'transactions', 'appointments', 'appointment_items', 'schedules',
|
|
34
|
-
-- core, non-API helper tables (both ledger names kept for pre/post-rename compat;
|
|
35
|
-
-- `_migrations` is also covered by the NOT LIKE '\_%' filter below)
|
|
36
|
-
'sequences', 'documents', 'fayz_migration_ledger', '_migrations'
|
|
37
|
-
];
|
|
38
|
-
BEGIN
|
|
39
|
-
FOR t IN
|
|
40
|
-
SELECT c.table_name
|
|
41
|
-
FROM information_schema.columns c
|
|
42
|
-
JOIN information_schema.tables tbl ON tbl.table_name = c.table_name AND tbl.table_schema = c.table_schema
|
|
43
|
-
WHERE c.table_schema = 'public'
|
|
44
|
-
AND c.column_name = 'tenant_id'
|
|
45
|
-
AND tbl.table_type = 'BASE TABLE'
|
|
46
|
-
AND c.table_name NOT LIKE '\_%'
|
|
47
|
-
AND c.table_name <> ALL(core_tables)
|
|
48
|
-
LOOP
|
|
49
|
-
-- Enable RLS
|
|
50
|
-
EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);
|
|
51
|
-
|
|
52
|
-
-- Drop existing policies (idempotent)
|
|
53
|
-
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation_select ON public.%I', t);
|
|
54
|
-
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation_insert ON public.%I', t);
|
|
55
|
-
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation_update ON public.%I', t);
|
|
56
|
-
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation_delete ON public.%I', t);
|
|
57
|
-
|
|
58
|
-
-- Create tenant isolation policies
|
|
59
|
-
EXECUTE format('CREATE POLICY tenant_isolation_select ON public.%I FOR SELECT USING (tenant_id::uuid IN (SELECT public.user_tenant_ids()))', t);
|
|
60
|
-
EXECUTE format('CREATE POLICY tenant_isolation_insert ON public.%I FOR INSERT WITH CHECK (tenant_id::uuid IN (SELECT public.user_tenant_ids()))', t);
|
|
61
|
-
EXECUTE format('CREATE POLICY tenant_isolation_update ON public.%I FOR UPDATE USING (tenant_id::uuid IN (SELECT public.user_tenant_ids()))', t);
|
|
62
|
-
EXECUTE format('CREATE POLICY tenant_isolation_delete ON public.%I FOR DELETE USING (tenant_id::uuid IN (SELECT public.user_tenant_ids()))', t);
|
|
63
|
-
END LOOP;
|
|
64
|
-
END $$;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
-- Add missing columns to invitations table
|
|
2
|
-
ALTER TABLE public.invitations
|
|
3
|
-
ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'pending',
|
|
4
|
-
ADD COLUMN IF NOT EXISTS location_ids uuid[] DEFAULT '{}';
|
|
5
|
-
|
|
6
|
-
-- Add UPDATE and DELETE policies for invite management
|
|
7
|
-
DO $$ BEGIN
|
|
8
|
-
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'invites_update' AND tablename = 'invitations') THEN
|
|
9
|
-
CREATE POLICY "invites_update" ON public.invitations FOR UPDATE
|
|
10
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
11
|
-
END IF;
|
|
12
|
-
|
|
13
|
-
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'invites_delete' AND tablename = 'invitations') THEN
|
|
14
|
-
CREATE POLICY "invites_delete" ON public.invitations FOR DELETE
|
|
15
|
-
USING (public.is_tenant_admin(tenant_id));
|
|
16
|
-
END IF;
|
|
17
|
-
END $$;
|