@fayz-ai/db 0.13.0 → 0.15.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.
Files changed (198) hide show
  1. package/canonical-schema.json +106 -23
  2. package/dist/index.cjs +4 -4
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -4
  7. package/dist/index.js.map +1 -1
  8. package/dist/schema/spine.d.ts +42 -37
  9. package/dist/schema/spine.d.ts.map +1 -1
  10. package/migrations/000_baseline.sql +29667 -0
  11. package/migrations/001_the_tenant_has_apps.sql +314 -0
  12. package/migrations/002_the_spine_drops_the_vendor_and_the_dead.sql +129 -0
  13. package/migrations/003_the_role_rpcs_finish_and_the_mirrors_go.sql +135 -0
  14. package/migrations/004_the_projections_get_a_successor.sql +312 -0
  15. package/migrations/005_the_archetypes_anchor_to_the_unit.sql +90 -0
  16. package/migrations/006_the_helpers_read_the_source.sql +153 -0
  17. package/migrations/007_the_compat_machinery_leaves_with_the_tables.sql +77 -0
  18. package/migrations/008_the_switchers_see_across_accounts.sql +150 -0
  19. package/migrations/009_the_compat_aliases_nobody_adopted.sql +57 -0
  20. package/migrations/010_the_new_tenant_is_born_with_its_trade.sql +201 -0
  21. package/migrations/011_leadcontrol_joins_the_family.sql +120 -0
  22. package/migrations/012_chefcontrol_wears_its_own_mark.sql +28 -0
  23. package/migrations/013_the_workspace_switch_reaches_the_other_half.sql +156 -0
  24. package/migrations/014_the_chooser_can_read_what_it_offers.sql +91 -0
  25. package/migrations/015_the_unit_bindings_belong_to_one_workspace.sql +86 -0
  26. package/migrations/016_the_rule_stops_asking_every_row.sql +543 -0
  27. package/migrations/017_the_pin_is_checked_against_the_right_workspace.sql +139 -0
  28. package/migrations/018_the_bootstrap_says_what_you_reach.sql +114 -0
  29. package/migrations/019_distributing_one_row_stops_costing_the_table.sql +245 -0
  30. package/migrations/020_the_agenda_asks_where_someone_works.sql +81 -0
  31. package/migrations/022_the_line_says_which_document_it_belongs_to.sql +109 -0
  32. package/migrations/029_o_administrador_enxerga_o_que_existe.sql +167 -0
  33. package/migrations/031_ticketcontrol_joins_the_family.sql +109 -0
  34. package/migrations/032_a_marca_e_um_conjunto_de_tokens.sql +291 -0
  35. package/migrations/041_a_entrega_tem_onde_guardar_a_politica.sql +53 -0
  36. package/migrations/042_o_token_de_marca_fecha_a_porta_anon.sql +17 -0
  37. package/migrations/043_fiscalcontrol_joins_the_family.sql +75 -0
  38. package/migrations/044_o_teste_gratis_dura_trinta_dias.sql +15 -0
  39. package/migrations/046_the_tick_advances_the_flows.sql +77 -0
  40. package/migrations/047_the_tick_sweeps_the_offers.sql +74 -0
  41. package/migrations/052_fullcontrol_joins_the_family.sql +213 -0
  42. package/migrations/053_o_erp_para_de_exigir_o_que_nao_desenha.sql +37 -0
  43. package/package.json +1 -1
  44. package/migrations/0000_legacy_quarantine.sql +0 -86
  45. package/migrations/0000b_straggler_sweep.sql +0 -37
  46. package/migrations/000_core_v1_convert.sql +0 -343
  47. package/migrations/001_core.sql +0 -372
  48. package/migrations/002_rls_user_tenant_ids.sql +0 -64
  49. package/migrations/003_invitations.sql +0 -17
  50. package/migrations/004_archetypes.sql +0 -343
  51. package/migrations/005_locations_archetype.sql +0 -18
  52. package/migrations/006_archetype_rls.sql +0 -74
  53. package/migrations/007_archetype_grants.sql +0 -4
  54. package/migrations/008_grants.sql +0 -5
  55. package/migrations/009_anon_hardening.sql +0 -19
  56. package/migrations/010_migration_ledger.sql +0 -46
  57. package/migrations/011_anon_write_revoke.sql +0 -12
  58. package/migrations/012_rename_migration_ledger.sql +0 -61
  59. package/migrations/013_sequences_rls.sql +0 -22
  60. package/migrations/014_plan_entitlements.sql +0 -28
  61. package/migrations/015_agent_guard.sql +0 -108
  62. package/migrations/016_agent_guard_actor.sql +0 -102
  63. package/migrations/017_core_addresses.sql +0 -115
  64. package/migrations/018_global_search.sql +0 -482
  65. package/migrations/019_tenant_members_person_id.sql +0 -20
  66. package/migrations/020_fayz_projects.sql +0 -63
  67. package/migrations/021_tenant_roles.sql +0 -74
  68. package/migrations/022_invite_acceptance.sql +0 -91
  69. package/migrations/023_team_visibility.sql +0 -75
  70. package/migrations/024_analytics_engine.sql +0 -317
  71. package/migrations/025_created_by.sql +0 -75
  72. package/migrations/026_audit_trail.sql +0 -73
  73. package/migrations/027_domain_events.sql +0 -266
  74. package/migrations/028_tenant_scoped_token.sql +0 -127
  75. package/migrations/029_connections.sql +0 -186
  76. package/migrations/030_effect_idempotency.sql +0 -159
  77. package/migrations/031_sync_run_message.sql +0 -39
  78. package/migrations/032_connection_secrets.sql +0 -227
  79. package/migrations/033_sync_schedule.sql +0 -651
  80. package/migrations/034_custom_fields.sql +0 -55
  81. package/migrations/035_field_registry.sql +0 -148
  82. package/migrations/036_analytics_run_batch.sql +0 -84
  83. package/migrations/037_sync_tick_one_at_a_time.sql +0 -256
  84. package/migrations/038_onboarding_responses.sql +0 -103
  85. package/migrations/039_unit_tree.sql +0 -270
  86. package/migrations/040_resource_grants.sql +0 -474
  87. package/migrations/041_scoped_columns.sql +0 -192
  88. package/migrations/042_unit_scope_policies.sql +0 -145
  89. package/migrations/043_view_invoker.sql +0 -90
  90. package/migrations/044_unit_member_facts.sql +0 -47
  91. package/migrations/045_unit_entry.sql +0 -236
  92. package/migrations/046_membership_visible_to_members.sql +0 -85
  93. package/migrations/047_tasks.sql +0 -266
  94. package/migrations/048_every_login_is_a_person.sql +0 -190
  95. package/migrations/049_bookable_people.sql +0 -126
  96. package/migrations/050_authz_core.sql +0 -230
  97. package/migrations/051_authz_helpers.sql +0 -330
  98. package/migrations/052_authz_fanout.sql +0 -253
  99. package/migrations/053_authz_catalog_templates.sql +0 -402
  100. package/migrations/060_tenancy_seam.sql +0 -132
  101. package/migrations/061_create_tenant.sql +0 -125
  102. package/migrations/062_invitations.sql +0 -329
  103. package/migrations/063_legacy_tenancy_retired.sql +0 -568
  104. package/migrations/064_agent_guard_authz.sql +0 -118
  105. package/migrations/064b_supersede_unit_scope.sql +0 -205
  106. package/migrations/065_scaffold_sweep.sql +0 -139
  107. package/migrations/066_shadow_report.sql +0 -122
  108. package/migrations/070_permission_catalog.sql +0 -205
  109. package/migrations/071_v1_permission_map.sql +0 -266
  110. package/migrations/072_role_rpcs.sql +0 -126
  111. package/migrations/073_legacy_rbac_retire.sql +0 -471
  112. package/migrations/075_company_legal_entities.sql +0 -169
  113. package/migrations/076_tenant_settings.sql +0 -251
  114. package/migrations/080_people_roles.sql +0 -269
  115. package/migrations/081_people_identity.sql +0 -271
  116. package/migrations/082_people_membership_link.sql +0 -142
  117. package/migrations/083_people_data_migration.sql +0 -75
  118. package/migrations/084_people_registrations_tokens.sql +0 -282
  119. package/migrations/085_catalog_products.sql +0 -333
  120. package/migrations/086_catalog_pricing.sql +0 -194
  121. package/migrations/087_catalog_packages.sql +0 -134
  122. package/migrations/090_orders_venda.sql +0 -166
  123. package/migrations/091_orders_scaffold.sql +0 -92
  124. package/migrations/095_config_field_rules.sql +0 -261
  125. package/migrations/096_audit_append_only.sql +0 -256
  126. package/migrations/097_sequences_authorized.sql +0 -111
  127. package/migrations/098_holidays.sql +0 -80
  128. package/migrations/099_access_grants.sql +0 -293
  129. package/migrations/100_migration_schema.sql +0 -217
  130. package/migrations/101_migration_allowlist.sql +0 -202
  131. package/migrations/102_migration_rpcs.sql +0 -933
  132. package/migrations/103_migration_reconcile_ponr.sql +0 -132
  133. package/migrations/105_policies_to_authenticated.sql +0 -71
  134. package/migrations/106_function_search_path.sql +0 -55
  135. package/migrations/107_core_table_prefix.sql +0 -361
  136. package/migrations/108_authz_write_guards.sql +0 -149
  137. package/migrations/109_catalog_migration_promoted_columns.sql +0 -126
  138. package/migrations/110_people_kind_vocabulary.sql +0 -120
  139. package/migrations/111_migration_excluded_is_a_decision.sql +0 -259
  140. package/migrations/111_replacing_a_view_keeps_its_dependents.sql +0 -132
  141. package/migrations/112_money_precision_standard.sql +0 -132
  142. package/migrations/113_addresses_units_companies.sql +0 -114
  143. package/migrations/114_migrations_ledger_immutable.sql +0 -65
  144. package/migrations/115_registration_honours_field_rules.sql +0 -172
  145. package/migrations/116_field_rules_check_needs_a_tenant.sql +0 -72
  146. package/migrations/117_authz_reconciliation_schedule.sql +0 -82
  147. package/migrations/118_migration_catalog_writers.sql +0 -450
  148. package/migrations/119_migration_config_writers.sql +0 -193
  149. package/migrations/120_migration_facet_writer.sql +0 -139
  150. package/migrations/121_migration_financial_writers.sql +0 -294
  151. package/migrations/122_migration_inventory_writers.sql +0 -245
  152. package/migrations/123_migration_order_item_writer.sql +0 -144
  153. package/migrations/124_migration_ledger_writers.sql +0 -170
  154. package/migrations/125_migration_authz_people_writers.sql +0 -205
  155. package/migrations/126_owner_scoping_is_configuration.sql +0 -247
  156. package/migrations/127_scaffold_audit_hook.sql +0 -98
  157. package/migrations/128_shadow_tables_enforced.sql +0 -93
  158. package/migrations/129_order_kind_terminal_states.sql +0 -57
  159. package/migrations/130_every_login_is_a_person.sql +0 -196
  160. package/migrations/131_migration_invoice_split.sql +0 -127
  161. package/migrations/132_audit_prune_scheduled.sql +0 -28
  162. package/migrations/133_unit_is_a_session_property.sql +0 -155
  163. package/migrations/134_field_rules_on_read.sql +0 -341
  164. package/migrations/135_distribution_polarity.sql +0 -158
  165. package/migrations/136_unit_id_means_locality.sql +0 -358
  166. package/migrations/137_vendor_named_spine_objects.sql +0 -126
  167. package/migrations/138_grants_inherit_down_the_tree.sql +0 -178
  168. package/migrations/139_distributable_is_a_scaffold_option.sql +0 -109
  169. package/migrations/140_masking_is_a_scaffold_option.sql +0 -85
  170. package/migrations/141_renamed_objects_keep_a_compat_name.sql +0 -192
  171. package/migrations/142_a_person_has_addresses.sql +0 -212
  172. package/migrations/143_a_professional_has_a_rate.sql +0 -75
  173. package/migrations/144_a_product_can_be_sold_in_sizes.sql +0 -57
  174. package/migrations/145_the_app_says_which_tenant_it_is_in.sql +0 -123
  175. package/migrations/146_products_stock_has_one_writer.sql +0 -67
  176. package/migrations/147_every_archetype_has_a_kind_registry.sql +0 -388
  177. package/migrations/148_the_legacy_archetype_stops_being_writable.sql +0 -154
  178. package/migrations/149_the_event_log_gets_a_consumer.sql +0 -331
  179. package/migrations/150_the_app_is_configured_in_the_database.sql +0 -285
  180. package/migrations/151_stock_balance_is_an_archetype.sql +0 -144
  181. package/migrations/152_a_file_is_an_archetype.sql +0 -222
  182. package/migrations/153_a_plugin_calls_a_service_through_one_door.sql +0 -199
  183. package/migrations/154_the_plan_is_a_ceiling.sql +0 -258
  184. package/migrations/155_a_tenant_can_leave.sql +0 -271
  185. package/migrations/156_the_service_door_has_a_server_side.sql +0 -335
  186. package/migrations/157_nobody_creates_objects_in_public.sql +0 -74
  187. package/migrations/158_the_registry_owns_the_status_vocabulary.sql +0 -153
  188. package/migrations/159_the_plan_catalogue_gets_its_first_rows.sql +0 -132
  189. package/migrations/160_the_vertical_decides_what_starts_on.sql +0 -246
  190. package/migrations/161_a_tenant_leaves_with_its_data.sql +0 -363
  191. package/migrations/162_every_name_has_one_creator_for_real.sql +0 -126
  192. package/migrations/163_a_species_nobody_declared_goes_to_quarantine.sql +0 -184
  193. package/migrations/164_the_sequence_table_joins_the_register.sql +0 -26
  194. package/migrations/165_a_view_is_not_a_way_in.sql +0 -90
  195. package/migrations/166_the_platform_billing_leftovers_go.sql +0 -72
  196. package/migrations/167_a_view_gets_back_what_its_table_grants.sql +0 -112
  197. package/migrations/168_the_grant_census_stops_crying_wolf.sql +0 -33
  198. package/migrations/169_the_census_asks_postgres_instead_of_guessing.sql +0 -45
@@ -1,343 +0,0 @@
1
- -- ============================================================================
2
- -- 000_core_v1_convert.sql — in-place converter: saas_core -> public
3
- --
4
- -- For EXISTING pools that already hold live data in the saas_core schema.
5
- -- Moves every core table into public, renames the three entities that changed
6
- -- (persons->people, bookings->appointments, booking_items->appointment_items),
7
- -- re-authors the core functions/triggers/policies in public terms, then drops
8
- -- the saas_core schema and re-points PostgREST at public only.
9
- --
10
- -- Idempotent + partial-re-run safe: guarded by the existence of the saas_core
11
- -- schema, and every table move/rename is individually guarded with to_regclass.
12
- --
13
- -- Relies on Postgres semantics:
14
- -- * ALTER TABLE ... SET SCHEMA preserves data / PKs / FKs / indexes /
15
- -- policies / grants (only the containing schema changes).
16
- -- * ALTER TABLE ... RENAME cascades FK / index / policy references.
17
- -- * Function BODIES and trigger function references do NOT auto-follow a
18
- -- schema move -> they are re-emitted below before saas_core is dropped.
19
- --
20
- -- ORDERING NOTE: 0000_legacy_quarantine.sql MUST be applied BEFORE this file.
21
- -- On any pool that already holds a legacy public.<target> table (e.g. a bespoke
22
- -- public.appointments, or a real public.subscriptions), that collision would
23
- -- block the corresponding move/rename here (each is guarded on target absence),
24
- -- leaving the live saas_core.<source> stranded for the CASCADE below to destroy.
25
- -- The quarantine step moves such collisions into legacy_pre_pools first.
26
- -- ============================================================================
27
-
28
- DO $convert$
29
- DECLARE
30
- t text;
31
- r record;
32
- v_roles text;
33
- v_using text;
34
- v_check text;
35
- v_sql text;
36
- core_tables text[] := ARRAY[
37
- 'tenants', 'profiles', 'tenant_members', 'plans', 'permissions',
38
- 'role_permissions', 'tenant_role_overrides', 'invitations',
39
- 'payment_events', 'locations', 'location_members', 'audit_logs',
40
- 'subscriptions', 'invoices', 'notifications',
41
- 'persons', 'categories', 'products', 'services', 'orders', 'order_items',
42
- 'transactions', 'bookings', 'booking_items', 'schedules',
43
- -- may or may not exist depending on installed plugins (stay core, unprefixed)
44
- 'sequences', 'documents'
45
- ];
46
- -- post-move / post-rename public names carrying an updated_at trigger
47
- ts_tables text[] := ARRAY[
48
- 'tenants', 'profiles', 'locations', 'subscriptions', 'people', 'categories',
49
- 'products', 'services', 'orders', 'transactions', 'appointments', 'schedules'
50
- ];
51
- -- post-move / post-rename public names governed by generic tenant isolation
52
- archetype_tables text[] := ARRAY[
53
- 'people', 'categories', 'products', 'services',
54
- 'orders', 'transactions', 'appointments', 'schedules'
55
- ];
56
- -- every core table that must be re-policied in public terms
57
- policy_tables text[];
58
- BEGIN
59
- -- Only act on pools that still have the legacy schema.
60
- IF NOT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'saas_core') THEN
61
- RETURN;
62
- END IF;
63
-
64
- -- --------------------------------------------------------------------------
65
- -- 1. Move every core table saas_core.* -> public.* (guarded, idempotent).
66
- -- --------------------------------------------------------------------------
67
- FOREACH t IN ARRAY core_tables LOOP
68
- IF to_regclass('saas_core.' || quote_ident(t)) IS NOT NULL
69
- AND to_regclass('public.' || quote_ident(t)) IS NULL THEN
70
- EXECUTE format('ALTER TABLE saas_core.%I SET SCHEMA public', t);
71
- END IF;
72
- END LOOP;
73
-
74
- -- --------------------------------------------------------------------------
75
- -- 2. Rename the three entities that changed (only if source present, target free).
76
- -- --------------------------------------------------------------------------
77
- IF to_regclass('public.persons') IS NOT NULL AND to_regclass('public.people') IS NULL THEN
78
- ALTER TABLE public.persons RENAME TO people;
79
- END IF;
80
- IF to_regclass('public.bookings') IS NOT NULL AND to_regclass('public.appointments') IS NULL THEN
81
- ALTER TABLE public.bookings RENAME TO appointments;
82
- END IF;
83
- IF to_regclass('public.booking_items') IS NOT NULL AND to_regclass('public.appointment_items') IS NULL THEN
84
- ALTER TABLE public.booking_items RENAME TO appointment_items;
85
- END IF;
86
-
87
- -- --------------------------------------------------------------------------
88
- -- 3. Re-emit the core functions in public (bodies do not follow a move).
89
- -- MUST run after the moves/renames: LANGUAGE sql bodies are validated at
90
- -- CREATE time, so public.tenant_members etc. have to exist already.
91
- -- --------------------------------------------------------------------------
92
- EXECUTE $q$
93
- CREATE OR REPLACE FUNCTION public.handle_updated_at()
94
- RETURNS trigger LANGUAGE plpgsql AS $fn$
95
- BEGIN NEW.updated_at = now(); RETURN NEW; END; $fn$;
96
- $q$;
97
-
98
- EXECUTE $q$
99
- CREATE OR REPLACE FUNCTION public.handle_new_user()
100
- RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $fn$
101
- BEGIN
102
- INSERT INTO public.profiles (id, email, full_name, avatar_url)
103
- VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'full_name', NEW.raw_user_meta_data->>'avatar_url')
104
- ON CONFLICT (id) DO NOTHING;
105
- RETURN NEW;
106
- END; $fn$;
107
- $q$;
108
-
109
- EXECUTE $q$
110
- CREATE OR REPLACE FUNCTION public.create_tenant_with_owner(
111
- p_name text, p_slug text, p_user_id uuid,
112
- p_vertical_id text DEFAULT NULL, p_plan text DEFAULT 'free', p_settings jsonb DEFAULT '{}'
113
- )
114
- RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $fn$
115
- DECLARE v_id uuid; v_tenant jsonb;
116
- BEGIN
117
- INSERT INTO public.tenants (name, slug, plan, vertical_id, settings)
118
- VALUES (p_name, p_slug, p_plan, p_vertical_id, p_settings) RETURNING id INTO v_id;
119
- INSERT INTO public.tenant_members (tenant_id, user_id, role)
120
- VALUES (v_id, p_user_id, 'owner');
121
- SELECT to_jsonb(t) INTO v_tenant FROM public.tenants t WHERE t.id = v_id;
122
- RETURN v_tenant;
123
- END; $fn$;
124
- $q$;
125
-
126
- EXECUTE $q$
127
- CREATE OR REPLACE FUNCTION public.is_tenant_admin(p_tenant_id uuid)
128
- RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $fn$
129
- SELECT EXISTS (
130
- SELECT 1 FROM public.tenant_members
131
- WHERE tenant_id = p_tenant_id AND user_id = auth.uid() AND role IN ('owner', 'admin')
132
- );
133
- $fn$;
134
- $q$;
135
-
136
- EXECUTE $q$
137
- CREATE OR REPLACE FUNCTION public.user_tenant_ids()
138
- RETURNS SETOF uuid LANGUAGE sql STABLE SECURITY DEFINER AS $fn$
139
- SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid();
140
- $fn$;
141
- $q$;
142
-
143
- -- Re-point the auth.users trigger at the public function.
144
- DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
145
- CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users
146
- FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
147
-
148
- -- --------------------------------------------------------------------------
149
- -- 4. Re-create updated_at triggers to call public.handle_updated_at.
150
- -- --------------------------------------------------------------------------
151
- FOREACH t IN ARRAY ts_tables LOOP
152
- IF to_regclass('public.' || quote_ident(t)) IS NOT NULL THEN
153
- EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', t || '_updated_at', t);
154
- EXECUTE format(
155
- 'CREATE TRIGGER %I BEFORE UPDATE ON public.%I FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at()',
156
- t || '_updated_at', t
157
- );
158
- END IF;
159
- END LOOP;
160
- -- Legacy trigger names carried over by the RENAME.
161
- IF to_regclass('public.people') IS NOT NULL THEN
162
- DROP TRIGGER IF EXISTS persons_updated_at ON public.people;
163
- END IF;
164
- IF to_regclass('public.appointments') IS NOT NULL THEN
165
- DROP TRIGGER IF EXISTS bookings_updated_at ON public.appointments;
166
- END IF;
167
-
168
- -- --------------------------------------------------------------------------
169
- -- 5. Re-emit RLS policies in public terms.
170
- -- Drop EVERY existing policy on the moved tables (their bodies still
171
- -- reference saas_core.*) and recreate the canonical public policy set —
172
- -- identical end-state to a fresh baseline (001 + 006).
173
- -- --------------------------------------------------------------------------
174
- policy_tables := core_tables
175
- || ARRAY['people', 'appointments', 'appointment_items']::text[];
176
-
177
- FOR r IN
178
- SELECT policyname, tablename FROM pg_policies
179
- WHERE schemaname = 'public' AND tablename = ANY(policy_tables)
180
- LOOP
181
- EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', r.policyname, r.tablename);
182
- END LOOP;
183
-
184
- -- tenants
185
- CREATE POLICY "tenants_select" ON public.tenants FOR SELECT
186
- USING (id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
187
- CREATE POLICY "tenants_insert" ON public.tenants FOR INSERT TO authenticated WITH CHECK (true);
188
- CREATE POLICY "tenants_update" ON public.tenants FOR UPDATE USING (public.is_tenant_admin(id));
189
-
190
- -- profiles
191
- CREATE POLICY "profiles_select" ON public.profiles FOR SELECT USING (id = auth.uid());
192
- CREATE POLICY "profiles_insert" ON public.profiles FOR INSERT WITH CHECK (id = auth.uid());
193
- CREATE POLICY "profiles_update" ON public.profiles FOR UPDATE USING (id = auth.uid());
194
-
195
- -- tenant_members
196
- CREATE POLICY "members_select" ON public.tenant_members FOR SELECT USING (user_id = auth.uid());
197
- CREATE POLICY "members_insert_own" ON public.tenant_members FOR INSERT WITH CHECK (user_id = auth.uid());
198
- CREATE POLICY "members_update" ON public.tenant_members FOR UPDATE USING (public.is_tenant_admin(tenant_id));
199
- CREATE POLICY "members_delete" ON public.tenant_members FOR DELETE USING (public.is_tenant_admin(tenant_id));
200
-
201
- -- plans / permissions / role_permissions
202
- CREATE POLICY "plans_select" ON public.plans FOR SELECT TO authenticated USING (true);
203
- CREATE POLICY "perms_select" ON public.permissions FOR SELECT TO authenticated USING (true);
204
- CREATE POLICY "role_perms_select" ON public.role_permissions FOR SELECT TO authenticated USING (true);
205
-
206
- -- tenant_role_overrides
207
- CREATE POLICY "overrides_select" ON public.tenant_role_overrides FOR SELECT
208
- USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
209
- CREATE POLICY "overrides_manage" ON public.tenant_role_overrides FOR ALL
210
- USING (public.is_tenant_admin(tenant_id));
211
-
212
- -- invitations
213
- CREATE POLICY "invites_select" ON public.invitations FOR SELECT TO authenticated USING (true);
214
- CREATE POLICY "invites_insert" ON public.invitations FOR INSERT WITH CHECK (public.is_tenant_admin(tenant_id));
215
- CREATE POLICY "invites_update" ON public.invitations FOR UPDATE USING (public.is_tenant_admin(tenant_id));
216
- CREATE POLICY "invites_delete" ON public.invitations FOR DELETE USING (public.is_tenant_admin(tenant_id));
217
-
218
- -- payment_events
219
- CREATE POLICY "events_service" ON public.payment_events FOR ALL TO service_role USING (true);
220
-
221
- -- locations
222
- CREATE POLICY "locations_select" ON public.locations FOR SELECT
223
- USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
224
- CREATE POLICY "locations_manage" ON public.locations FOR ALL USING (public.is_tenant_admin(tenant_id));
225
-
226
- -- location_members
227
- CREATE POLICY "loc_members_select" ON public.location_members FOR SELECT
228
- USING (location_id IN (SELECT id FROM public.locations WHERE tenant_id IN (
229
- SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()
230
- )));
231
-
232
- -- subscriptions / invoices
233
- CREATE POLICY "subs_select" ON public.subscriptions FOR SELECT
234
- USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
235
- CREATE POLICY "invoices_select" ON public.invoices FOR SELECT
236
- USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
237
-
238
- -- audit_logs
239
- CREATE POLICY "audit_select" ON public.audit_logs FOR SELECT
240
- USING (tenant_id IN (SELECT tenant_id FROM public.tenant_members WHERE user_id = auth.uid()));
241
- CREATE POLICY "audit_insert" ON public.audit_logs FOR INSERT TO authenticated WITH CHECK (true);
242
-
243
- -- notifications
244
- CREATE POLICY "notif_select" ON public.notifications FOR SELECT USING (user_id = auth.uid());
245
- CREATE POLICY "notif_update" ON public.notifications FOR UPDATE USING (user_id = auth.uid());
246
-
247
- -- archetype tables (generic tenant isolation via SECURITY DEFINER helper)
248
- FOREACH t IN ARRAY archetype_tables LOOP
249
- EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);
250
- EXECUTE format('CREATE POLICY "%s_select" ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t, t);
251
- EXECUTE format('CREATE POLICY "%s_insert" ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t, t);
252
- EXECUTE format('CREATE POLICY "%s_update" ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t, t);
253
- EXECUTE format('CREATE POLICY "%s_delete" ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t, t);
254
- END LOOP;
255
-
256
- -- order_items (parent-FK policies)
257
- CREATE POLICY "order_items_select" ON public.order_items FOR SELECT TO authenticated
258
- USING (order_id IN (SELECT id FROM public.orders WHERE tenant_id IN (SELECT public.user_tenant_ids())));
259
- CREATE POLICY "order_items_insert" ON public.order_items FOR INSERT TO authenticated
260
- WITH CHECK (order_id IN (SELECT id FROM public.orders WHERE tenant_id IN (SELECT public.user_tenant_ids())));
261
- CREATE POLICY "order_items_update" ON public.order_items FOR UPDATE TO authenticated
262
- USING (order_id IN (SELECT id FROM public.orders WHERE tenant_id IN (SELECT public.user_tenant_ids())));
263
- CREATE POLICY "order_items_delete" ON public.order_items FOR DELETE TO authenticated
264
- USING (order_id IN (SELECT id FROM public.orders WHERE tenant_id IN (SELECT public.user_tenant_ids())));
265
-
266
- -- appointment_items (parent-FK policies; booking_id column preserved by rename)
267
- CREATE POLICY "appointment_items_select" ON public.appointment_items FOR SELECT TO authenticated
268
- USING (booking_id IN (SELECT id FROM public.appointments WHERE tenant_id IN (SELECT public.user_tenant_ids())));
269
- CREATE POLICY "appointment_items_insert" ON public.appointment_items FOR INSERT TO authenticated
270
- WITH CHECK (booking_id IN (SELECT id FROM public.appointments WHERE tenant_id IN (SELECT public.user_tenant_ids())));
271
- CREATE POLICY "appointment_items_update" ON public.appointment_items FOR UPDATE TO authenticated
272
- USING (booking_id IN (SELECT id FROM public.appointments WHERE tenant_id IN (SELECT public.user_tenant_ids())));
273
- CREATE POLICY "appointment_items_delete" ON public.appointment_items FOR DELETE TO authenticated
274
- USING (booking_id IN (SELECT id FROM public.appointments WHERE tenant_id IN (SELECT public.user_tenant_ids())));
275
-
276
- -- --------------------------------------------------------------------------
277
- -- 5b. Re-point APP-table policies that still call saas_core.* helpers.
278
- -- App-created tables (unknown to this converter — e.g. conversations,
279
- -- conversation_messages) can carry RLS policies whose USING / WITH CHECK
280
- -- bodies reference saas_core.user_tenant_ids() (FKs are OID-based and
281
- -- survive the schema move, but policy bodies are text). DROP SCHEMA
282
- -- saas_core CASCADE would cascade-drop such a policy, leaving its table
283
- -- RLS-enabled with ZERO policies (a full lock-out). Rewrite each in
284
- -- public terms first — GENERIC, driven by pg_policies text, never a list.
285
- -- Idempotent: after the rewrite no body contains 'saas_core.', so a
286
- -- re-run selects nothing.
287
- -- --------------------------------------------------------------------------
288
- FOR r IN
289
- SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual, with_check
290
- FROM pg_policies
291
- WHERE schemaname = 'public'
292
- AND (
293
- coalesce(qual, '') LIKE '%saas_core.%'
294
- OR coalesce(with_check, '') LIKE '%saas_core.%'
295
- )
296
- LOOP
297
- v_using := replace(coalesce(r.qual, ''), 'saas_core.', 'public.');
298
- v_check := replace(coalesce(r.with_check, ''), 'saas_core.', 'public.');
299
-
300
- -- Render the TO role list. PUBLIC is a keyword, never quote it.
301
- SELECT string_agg(CASE WHEN role = 'public' THEN 'public' ELSE quote_ident(role) END, ', ')
302
- INTO v_roles
303
- FROM unnest(r.roles) AS role;
304
-
305
- v_sql := format(
306
- 'CREATE POLICY %I ON public.%I AS %s FOR %s TO %s',
307
- r.policyname, r.tablename, r.permissive, r.cmd, coalesce(v_roles, 'public')
308
- );
309
- IF r.qual IS NOT NULL THEN
310
- v_sql := v_sql || format(' USING (%s)', v_using);
311
- END IF;
312
- IF r.with_check IS NOT NULL THEN
313
- v_sql := v_sql || format(' WITH CHECK (%s)', v_check);
314
- END IF;
315
-
316
- EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', r.policyname, r.tablename);
317
- EXECUTE v_sql;
318
- RAISE NOTICE 're-pointed policy %.% to public.* helpers', r.tablename, r.policyname;
319
- END LOOP;
320
-
321
- -- --------------------------------------------------------------------------
322
- -- 6. Drop the now-empty legacy schema (any residue is old wrappers/functions).
323
- -- SAFETY GATE: refuse to CASCADE if any BASE TABLE is still in saas_core —
324
- -- that means a move/rename above silently skipped (a name collision the
325
- -- quarantine step missed), and dropping now would destroy live data.
326
- -- --------------------------------------------------------------------------
327
- IF EXISTS (
328
- SELECT 1 FROM information_schema.tables
329
- WHERE table_schema = 'saas_core' AND table_type = 'BASE TABLE'
330
- ) THEN
331
- RAISE EXCEPTION 'saas_core still has tables after move — aborting before CASCADE (check name collisions / quarantine)';
332
- END IF;
333
-
334
- DROP SCHEMA IF EXISTS saas_core CASCADE;
335
-
336
- -- --------------------------------------------------------------------------
337
- -- 7. Point PostgREST at public only.
338
- -- --------------------------------------------------------------------------
339
- EXECUTE 'ALTER ROLE authenticator SET pgrst.db_schemas = ''public''';
340
- END;
341
- $convert$;
342
-
343
- NOTIFY pgrst, 'reload config';