- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
103 lines
3.9 KiB
SQL
103 lines
3.9 KiB
SQL
-- Task #423: tasks.organization_id NOT NULL + RLS policies
|
|
-- ============================================================
|
|
|
|
-- Step 1: backfill any NULL organization_id in tasks from parent form
|
|
UPDATE tasks
|
|
SET organization_id = forms.organization_id
|
|
FROM forms
|
|
WHERE tasks.form_id = forms.id
|
|
AND tasks.organization_id IS NULL;
|
|
|
|
-- Step 2: enforce NOT NULL (safe after backfill above)
|
|
ALTER TABLE tasks
|
|
ALTER COLUMN organization_id SET NOT NULL;
|
|
|
|
-- ============================================================
|
|
-- Step 3: Row-Level Security policies for tenant tables.
|
|
--
|
|
-- Policy expression for DIRECT organization_id tables:
|
|
-- USING (
|
|
-- current_setting('app.is_superadmin', true) = 'true'
|
|
-- OR organization_id = NULLIF(current_setting('app.current_org_id', true), '')::int
|
|
-- )
|
|
--
|
|
-- Policy expression for JOIN-based tables (no direct organization_id):
|
|
-- USING (
|
|
-- current_setting('app.is_superadmin', true) = 'true'
|
|
-- OR <fk_col> IN (SELECT id FROM <parent> WHERE organization_id = ...)
|
|
-- )
|
|
--
|
|
-- Policies are INACTIVE until `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`
|
|
-- is also issued (done at startup when ENABLE_RLS=true).
|
|
-- ============================================================
|
|
|
|
-- Direct organization_id tables
|
|
DO $$
|
|
DECLARE t text;
|
|
DECLARE tbls text[] := ARRAY[
|
|
'tasks','users','forms','task_relations','user_notifications',
|
|
'message_reads','bookmarks','bookmark_folders','bots','bot_subscriptions',
|
|
'data_tables','invitations','user_custom_fields','device_tokens',
|
|
'web_push_subscriptions','organization_api_keys','automations',
|
|
'task_reminders','task_audit_log','conversations','global_fields',
|
|
'custom_tab_modules','external_services','notification_subscriptions'
|
|
];
|
|
BEGIN
|
|
FOREACH t IN ARRAY tbls LOOP
|
|
IF EXISTS (SELECT FROM pg_tables WHERE schemaname='public' AND tablename=t) THEN
|
|
EXECUTE format('DROP POLICY IF EXISTS tenant_iso ON %I', t);
|
|
EXECUTE format(
|
|
'CREATE POLICY tenant_iso ON %I USING ('
|
|
' current_setting(''app.is_superadmin'', true) = ''true'''
|
|
' OR organization_id = NULLIF(current_setting(''app.current_org_id'', true), '''')::int'
|
|
')', t);
|
|
END IF;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- Join-based: form_fields, form_statuses, form_tabs, status_transitions
|
|
-- (no direct organization_id — join through forms)
|
|
DO $$
|
|
DECLARE t text;
|
|
DECLARE tbls text[] := ARRAY['form_fields','form_statuses','form_tabs','status_transitions'];
|
|
BEGIN
|
|
FOREACH t IN ARRAY tbls LOOP
|
|
IF EXISTS (SELECT FROM pg_tables WHERE schemaname='public' AND tablename=t) THEN
|
|
EXECUTE format('DROP POLICY IF EXISTS tenant_iso ON %I', t);
|
|
EXECUTE format(
|
|
'CREATE POLICY tenant_iso ON %I USING ('
|
|
' current_setting(''app.is_superadmin'', true) = ''true'''
|
|
' OR form_id IN ('
|
|
' SELECT id FROM forms'
|
|
' WHERE organization_id = NULLIF(current_setting(''app.current_org_id'', true), '''')::int'
|
|
' )'
|
|
')', t);
|
|
END IF;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- Join-based: task_messages, task_field_values, field_history
|
|
-- (no direct organization_id — join through tasks, which is now NOT NULL)
|
|
DO $$
|
|
DECLARE t text;
|
|
DECLARE tbls text[] := ARRAY['task_messages','task_field_values','field_history'];
|
|
BEGIN
|
|
FOREACH t IN ARRAY tbls LOOP
|
|
IF EXISTS (SELECT FROM pg_tables WHERE schemaname='public' AND tablename=t) THEN
|
|
EXECUTE format('DROP POLICY IF EXISTS tenant_iso ON %I', t);
|
|
EXECUTE format(
|
|
'CREATE POLICY tenant_iso ON %I USING ('
|
|
' current_setting(''app.is_superadmin'', true) = ''true'''
|
|
' OR task_id IN ('
|
|
' SELECT id FROM tasks'
|
|
' WHERE organization_id = NULLIF(current_setting(''app.current_org_id'', true), '''')::int'
|
|
' )'
|
|
')', t);
|
|
END IF;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- NOTE: ENABLE ROW LEVEL SECURITY is NOT issued here.
|
|
-- It is applied at runtime by server/index.ts when ENABLE_RLS=true.
|
|
-- This keeps the migration safe on environments not yet using withTenant.
|