دليل تصميم مخطط Supabase RLS متعدد المستأجرين للإنتاج
دليل تصميم مخطط Supabase RLS متعدد المستأجرين للإنتاج
لقد أطلقت ثلاث تطبيقات SaaS متعددة المستأجرين على Supabase في العامين الماضيين. كان الأول كارثة. ليس لأن Supabase خذلني -- لم يخذل -- لكن لأنني أساء فهم كيفية تفاعل Row Level Security (RLS) مع تخطيط الاستعلام على نطاق واسع. كان الثاني أفضل. والثالث يتعامل مع أكثر من 2000 مستأجر مع أوقات استعلام أقل من 50ms عبر الجداول التي تحتوي على ملايين الصفوف.
هذه المقالة هي كل ما كنت أتمنى أن يخبرني به شخص ما قبل المشروع الأول. سنبني مخطط متعدد المستأجرين حقيقي من الصفر، وسنربط سياسات RLS التي تعمل فعلاً بأداء عالٍ، وسنغطي الحالات الحدية التي تظهر فقط عندما يكون لديك حركة مرور حقيقية تضرب قاعدة البيانات الخاصة بك.
جدول المحتويات
- نهج تعدد المستأجرين في Supabase
- الأساس: مخطط المستأجر والمستخدم
- تصميم سياسات RLS التي لا تقتل الأداء
- نمط tenant_id: القيام به بشكل صحيح
- مطالبات JWT مقابل بحث قاعدة البيانات
- التعامل مع العمليات عبر المستأجرين
- استراتيجية الترحيل وتطور المخطط
- معايير الأداء والتحسين
- تقسية الأمان للإنتاج
- مثال مخطط حقيقي
- الأسئلة الشائعة

نهج تعدد المستأجرين في Supabase
قبل أن نكتب سطراً واحداً من SQL، دعنا نوضح النهج الثلاثة لتعدد المستأجرين ولماذا أحدها يفوز في معظم مشاريع Supabase.
| النهج | مستوى العزل | التعقيد | التكلفة لكل مستأجر | الأفضل لـ |
|---|---|---|---|---|
| قاعدة بيانات لكل مستأجر | الأعلى | عالي جداً | 25+ دولار/شهر لكل مستأجر | مؤسسات، الامتثال الثقيل |
| مخطط لكل مستأجر | عالي | عالي | 5-15 دولار/شهر لكل مستأجر | SaaS متوسط السوق |
| مخطط مشترك + RLS | متوسط | متوسط | قروش لكل مستأجر | معظم تطبيقات SaaS |
قاعدة البيانات لكل مستأجر هي ما ستستخدمه إذا كنت تبيع البنوك أو شركات الرعاية الصحية التي تتطلب حرفياً بنية تحتية منفصلة. Supabase لا تجعل هذا سهلاً -- ستدير عدة مشاريع Supabase.
مخطط لكل مستأجر (باستخدام مخططات PostgreSQL مثل tenant_123.projects) يبدو جذاباً لكنه يتحول إلى كابوس صيانة. كل ترحيل يعمل ضد كل مخطط. حاولت هذا مرة واحدة. مع 400 مستأجر، استغرق ترحيل ALTER TABLE بسيط 45 دقيقة.
المخطط المشترك مع RLS هو البقعة الحلوة لـ 90% من تطبيقات SaaS. مجموعة واحدة من الجداول، مجموعة واحدة من الترحيلات، وسياسات RLS في PostgreSQL تتعامل مع العزل. هذا ما نبنيه هنا.
الأساس: مخطط المستأجر والمستخدم
دعنا نبدأ بالجداول الأساسية. سأريكم المخطط الذي أستخدمه في الإنتاج، وليس لعبة تعليمية.
-- تفعيل توليد UUID
create extension if not exists "uuid-ossp";
-- المستأجرون (المؤسسات، مساحات العمل، مهما كنت تسميهم)
create table public.tenants (
id uuid primary key default uuid_generate_v4(),
name text not null,
slug text unique not null,
plan text not null default 'free' check (plan in ('free', 'pro', 'enterprise')),
settings jsonb not null default '{}',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- جدول الانضمام: يربط auth.users بـ tenants
create table public.tenant_memberships (
id uuid primary key default uuid_generate_v4(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
user_id uuid not null references auth.users(id) on delete cascade,
role text not null default 'member' check (role in ('owner', 'admin', 'member', 'viewer')),
created_at timestamptz not null default now(),
unique(tenant_id, user_id)
);
-- حاسم: الفهارس التي ستعتمد عليها سياسات RLS
create index idx_tenant_memberships_user_id on public.tenant_memberships(user_id);
create index idx_tenant_memberships_tenant_id on public.tenant_memberships(tenant_id);
create index idx_tenant_memberships_user_tenant on public.tenant_memberships(user_id, tenant_id);
شيئان يجب ملاحظتهما. أولاً، أستخدم جدول انضمام (tenant_memberships) بدلاً من وضع tenant_id مباشرة على ملف تعريف المستخدم. يمكن للمستخدمين الانتماء إلى عدة مستأجرين -- هذا متطلب في العالم الحقيقي لتقريباً كل تطبيق SaaS. ثانياً، تلك الفهارس ليست اختيارية. بدونها، يقوم كل فحص RLS بمسح متسلسل على جدول الانضمام. لقد رأيت هذا يضيف 200+ ms للاستعلامات بمجرد حصولك على بضعة آلاف من الانضمامات.
تصميم سياسات RLS التي لا تقتل الأداء
هنا حيث تفشل معظم البرامج التعليمية. يظهرون لك سياسة بسيطة مثل:
-- لا تفعل هذا في الإنتاج
create policy "Users can view their tenant's data"
on public.projects for select
using (
tenant_id in (
select tenant_id from public.tenant_memberships
where user_id = auth.uid()
)
);
هذا يعمل. سيمر الاختبارات. ثم تنشره، تحصل على 500 مستخدم، وتتساءل لماذا يستغرق لوحة المعلومات الخاصة بك 4 ثوان للتحميل.
المشكلة هي أن PostgreSQL يقيّم هذا الاستعلام الفرعي لكل صف واحد. يمكن لمخطط الاستعلام أحياناً تحسينه، لكنه غالباً لا يفعل -- خاصة مع JOINs المعنية.
نمط دالة Security Definer
إليك ما يعمل فعلاً في الإنتاج:
-- إنشاء دالة تعيد معرفات المستأجرين للمستخدم
-- SECURITY DEFINER يعني أنها تعمل بأذونات منشئ الدالة
-- هذا حاسم: فهو يتجاوز RLS على جدول الانضمام نفسه
create or replace function public.get_user_tenant_ids()
returns setof uuid
language sql
security definer
stable
set search_path = public
as $$
select tenant_id
from public.tenant_memberships
where user_id = auth.uid();
$$;
-- الآن استخدمها في السياسات
create policy "tenant_isolation_select"
on public.projects for select
using (tenant_id in (select public.get_user_tenant_ids()));
create policy "tenant_isolation_insert"
on public.projects for insert
with check (tenant_id in (select public.get_user_tenant_ids()));
create policy "tenant_isolation_update"
on public.projects for update
using (tenant_id in (select public.get_user_tenant_ids()))
with check (tenant_id in (select public.get_user_tenant_ids()));
create policy "tenant_isolation_delete"
on public.projects for delete
using (tenant_id in (select public.get_user_tenant_ids()));
لماذا هذا أسرع؟ تخبر كلمة STABLE PostgreSQL أن الدالة تعيد النتيجة نفسها ضمن بيان واحد. يمكن لمخطط الاستعلام استدعاؤها مرة واحدة وإعادة استخدام النتيجة. في المقاييس الخاصة بي، هذا يقلل من تكلفة RLS بمقدار 60-80% على الاستعلامات التي تلمس صفوفاً متعددة.
فخ search_path
هل ترى set search_path = public على الدالة؟ هذا ليس اختياري. بدونه، قد يتمكن مستخدم خبيث من إنشاء دالة في مخطط آخر يظلل auth.uid() ويتجاوز أمانك. كتبت فريق Supabase عن هذا، لكن من السهل تفويتها.

نمط tenant_id: القيام به بشكل صحيح
كل جدول يحتوي على بيانات خاصة بالمستأجر يحتاج إلى عمود tenant_id. بدون استثناءات. حتى لو كان الجدول لديه مفتاح خارجي لجدول آخر ذي نطاق مستأجر. إليك السبب:
-- جدول المشاريع الخاص بك
create table public.projects (
id uuid primary key default uuid_generate_v4(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
name text not null,
created_at timestamptz not null default now()
);
-- المهام تنتمي إلى المشاريع. قد تعتقد أن tenant_id زائد هنا.
create table public.tasks (
id uuid primary key default uuid_generate_v4(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
project_id uuid not null references public.projects(id) on delete cascade,
title text not null,
status text not null default 'todo',
created_at timestamptz not null default now()
);
-- فهرس لـ RLS + أنماط الاستعلام الشائعة
create index idx_projects_tenant on public.projects(tenant_id);
create index idx_tasks_tenant on public.tasks(tenant_id);
create index idx_tasks_project on public.tasks(project_id);
create index idx_tasks_tenant_project on public.tasks(tenant_id, project_id);
نعم، tenant_id على tasks غير طبيعي. نعم، إنها الخطوة الصحيحة. بدونها، ستحتاج السياسة RLS على tasks إلى JOIN إلى projects للتحقق من المستأجر -- وهذا JOIN يحدث في كل استعلام. مع tenant_id غير الطبيعي، فحص RLS هو مجرد بحث فهرس بسيط.
أفرض الاتساق مع محفز:
create or replace function public.verify_tenant_consistency()
returns trigger
language plpgsql
as $$
begin
if NEW.tenant_id != (
select tenant_id from public.projects where id = NEW.project_id
) then
raise exception 'tenant_id mismatch: task tenant does not match project tenant';
end if;
return NEW;
end;
$$;
create trigger check_task_tenant
before insert or update on public.tasks
for each row execute function public.verify_tenant_consistency();
مطالبات JWT مقابل بحث قاعدة البيانات
يسمح لك Supabase بتضمين مطالبات مخصصة في رمز JWT. يستخدمها بعض الناس لتخزين معرف المستأجر الحالي:
-- القراءة من JWT (سريعة، لكن لديها تحذيرات)
auth.jwt() -> 'app_metadata' ->> 'current_tenant_id'
مقابل البحث عنها من قاعدة البيانات في كل مرة:
-- بحث قاعدة البيانات (أبطأ، لكن دائماً حالي)
select tenant_id from tenant_memberships where user_id = auth.uid()
| جانب | مطالبات JWT | بحث قاعدة البيانات |
|---|---|---|
| السرعة | ~0.1ms | ~1-5ms |
| الحداثة | قديمة حتى تحديث الرمز | دائماً حالية |
| التبديل متعدد المستأجرين | يتطلب تحديث الرمز | فوري |
| الأمان عند الإلغاء | تأخير حتى انتهاء JWT | فوري |
| تعقيد التنفيذ | أعلى (بحاجة Edge Function) | أقل |
توصيتي: استخدم بحث قاعدة البيانات مع نمط دالة security definer لمعظم التطبيقات. الفرق في الأداء ضئيل عندما يكون لديك فهارس مناسبة، وتتجنب فئة كاملة من الأخطاء حول الرموز القديمة.
إذا كنت تخدم 10,000+ مستخدم متزامن وكنت تحلق ميلي ثانية، فنعم، انقل معرف المستأجر النشط إلى JWT. ستحتاج إلى Supabase Edge Function (أو خطاف) لتعيين المطالبة عندما يقوم المستخدمون بتبديل المستأجرين، وستحتاج إلى التعامل مع تدفق تحديث الرمز على العميل.
التعامل مع العمليات عبر المستأجرين
بعض العمليات تحتاج بشرعية إلى عبور حدود المستأجرين. لوحات معلومات الإدارة، أنظمة الفواتير، تجميع التحليلات. إليك كيفية التعامل معهم بأمان.
مفتاح دور الخدمة (استخدم بحذر)
مفتاح دور الخدمة Supabase يتجاوز RLS بالكامل. استخدمه فقط في الكود من جانب الخادم -- لا تعرضه أبداً للعميل.
// من جانب الخادم فقط (Next.js API route, Edge Function, إلخ.)
import { createClient } from '@supabase/supabase-js';
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // لا تعرض هذا أبداً
);
// هذا يتجاوز RLS - استخدمه بحذر شديد
const { data } = await supabaseAdmin
.from('tenants')
.select('id, name, plan')
.eq('plan', 'enterprise');
إذا كنت تبني على Next.js (نفعل الكثير من هذا)، فإن مسارات API والمكونات من جانب الخادم الخاصة بك هي المكان الصحيح لعمليات دور الخدمة.
دوال قاعدة البيانات للوصول المتحكم فيه عبر المستأجرين
للتحكم الأكثر دقة، أنشئ وظائف محددة:
create or replace function public.admin_get_tenant_stats(target_tenant_id uuid)
returns json
language plpgsql
security definer
set search_path = public
as $$
declare
result json;
caller_role text;
begin
-- التحقق من أن المتصل هو مسؤول المستأجر المستهدف
select role into caller_role
from tenant_memberships
where user_id = auth.uid() and tenant_id = target_tenant_id;
if caller_role not in ('owner', 'admin') then
raise exception 'Unauthorized: requires admin role';
end if;
select json_build_object(
'project_count', (select count(*) from projects where tenant_id = target_tenant_id),
'task_count', (select count(*) from tasks where tenant_id = target_tenant_id),
'member_count', (select count(*) from tenant_memberships where tenant_id = target_tenant_id)
) into result;
return result;
end;
$$;
استراتيجية الترحيل وتطور المخطط
إضافة tenant_id إلى جداول موجودة هي الترحيل الذي يخشاه الجميع. إليك النهج الذي يقلل من وقت التوقف:
-- الخطوة 1: إضافة عمود قابل للقيمة الفارغة
alter table public.some_existing_table
add column tenant_id uuid references public.tenants(id);
-- الخطوة 2: الملء (افعل هذا على دفعات للجداول الكبيرة)
update public.some_existing_table set tenant_id = (
select tenant_id from public.projects
where projects.id = some_existing_table.project_id
)
where tenant_id is null;
-- الخطوة 3: إضافة قيد NOT NULL
alter table public.some_existing_table
alter column tenant_id set not null;
-- الخطوة 4: إضافة فهرس
create index concurrently idx_some_table_tenant
on public.some_existing_table(tenant_id);
-- الخطوة 5: تفعيل RLS وإضافة السياسات
alter table public.some_existing_table enable row level security;
create policy "tenant_isolation" on public.some_existing_table
for all using (tenant_id in (select public.get_user_tenant_ids()));
كلمة concurrently على إنشاء الفهرس حاسمة -- بدونها، ستقفل الجدول طوال بناء الفهرس. على جدول يحتوي على مليون صف، قد يكون هذا دقائق من وقت التوقف.
معايير الأداء والتحسين
قمت بتشغيل معايير على خطة Supabase Pro ($25/شهر، اعتباراً من أوائل 2025) مع 500,000 صف في جدول tasks منتشرة عبر 1000 مستأجر.
| نمط الاستعلام | بدون RLS | RLS ساذج | RLS محسّن | التكلفة الإضافية |
|---|---|---|---|---|
| اختيار 50 صف (مستأجر واحد) | 2.1ms | 18.4ms | 3.8ms | +81% |
| إدراج صف واحد | 1.2ms | 4.1ms | 1.9ms | +58% |
| العد مع التصفية | 3.4ms | 22.1ms | 5.2ms | +53% |
| الانضمام عبر جدولين | 4.8ms | 45.2ms | 8.1ms | +69% |
"RLS محسّن" يعني: وظائف security definer، فهارس مركبة مناسبة، tenant_id غير طبيعي على الجداول الفرعية، وتقلب دالة STABLE.
النهج الساذج (استعلامات فرعية مضمنة، فهارس مفقودة) يجعل RLS يبدو بطيئاً. المحسّن، التكلفة الإضافية مقبولة تماماً للأحمال الإنتاجية.
نصائح تحسين إضافية
- استخدم فهارس مركبة مع
tenant_idكعمود الرصاص:create index on tasks(tenant_id, status, created_at) - قسّم الجداول الكبيرة حسب
tenant_idإذا كان لأي مستأجر واحد ملايين الصفوف - استخدم
pg_stat_statementsللعثور على استعلامات بطيئة -- Supabase تعرضها في لوحة التحكم تحت Database > Query Performance - فكر في العروض المادية للتحليلات عبر المستأجرين بدلاً من تشغيل التجميعات المكلفة في الوقت الفعلي
تقسية الأمان للإنتاج
RLS هو دفاعك الأساسي، لكنه لا ينبغي أن يكون الوحيد.
1. الإنكار الافتراضي
دائماً فعّل RLS بدون سياسة افتراضية -- هذا يعني أنه إذا نسيت إضافة سياسة لجدول جديد، فهي مغلقة افتراضياً بدلاً من كونها مفتوحة على مصراعيها.
alter table public.new_table enable row level security;
-- لا تضيف سياسة تصريح حتى تفكر في أنماط الوصول
2. سجل التدقيق
create table public.audit_log (
id bigint generated always as identity primary key,
tenant_id uuid not null,
user_id uuid,
action text not null,
table_name text not null,
record_id uuid,
old_data jsonb,
new_data jsonb,
created_at timestamptz not null default now()
);
-- محفز تدقيق عام
create or replace function public.audit_trigger_func()
returns trigger
language plpgsql
security definer
set search_path = public
as $$
begin
if TG_OP = 'DELETE' then
insert into audit_log(tenant_id, user_id, action, table_name, record_id, old_data)
values (OLD.tenant_id, auth.uid(), TG_OP, TG_TABLE_NAME, OLD.id, to_jsonb(OLD));
return OLD;
else
insert into audit_log(tenant_id, user_id, action, table_name, record_id, new_data, old_data)
values (
NEW.tenant_id, auth.uid(), TG_OP, TG_TABLE_NAME, NEW.id,
to_jsonb(NEW),
case when TG_OP = 'UPDATE' then to_jsonb(OLD) else null end
);
return NEW;
end if;
end;
$$;
3. تحديد معدل على الحافة
RLS يمنع تسرب البيانات، لكنه لا يمنع الإساءة. استخدم Supabase Edge Functions أو Next.js middleware الخاص بك لتحديد المعدل. إذا كنت تبني باستخدام Astro، يمكنك التعامل مع هذا في نقاط نهايتك من جانب الخادم.
4. اختبر سياساتك
اكتب اختبارات حقيقية. استخدم بيئة تطوير Supabase المحلية (supabase start) واختبر أن:
- المستخدم A لا يمكنه رؤية بيانات مستأجر المستخدم B
- إزالة العضوية تلغي الوصول فوراً
- دور الخدمة يتجاوز RLS بشكل صحيح
- سياسات الإدراج/التحديث تمنع التلاعب بـ tenant_id
// مثال على الاختبار مع Vitest
import { describe, it, expect } from 'vitest';
import { createClient } from '@supabase/supabase-js';
describe('RLS Policies', () => {
it('should prevent cross-tenant data access', async () => {
const userA = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: { headers: { Authorization: `Bearer ${tokenA}` } }
});
const { data, error } = await userA
.from('projects')
.select('*')
.eq('tenant_id', TENANT_B_ID);
expect(data).toHaveLength(0); // RLS يجب أن تصفي هذه
});
});
مثال مخطط حقيقي
إليك مخطط مختصر لكن كامل لتطبيق إدارة المشاريع SaaS. هذا قريب من ما أنشرته في الإنتاج.
-- تفعيل RLS على جميع الجداول
alter table public.tenants enable row level security;
alter table public.tenant_memberships enable row level security;
alter table public.projects enable row level security;
alter table public.tasks enable row level security;
-- المستأجرون: يمكن للمستخدمين رؤية المستأجرين الذين ينتمون إليهم
create policy "view_own_tenants" on public.tenants for select
using (id in (select public.get_user_tenant_ids()));
-- فقط المالكون يمكنهم تحديث إعدادات المستأجر
create policy "owners_update_tenants" on public.tenants for update
using (
id in (
select tenant_id from public.tenant_memberships
where user_id = auth.uid() and role = 'owner'
)
);
-- الانضمام: يمكن للأعضاء رؤية أعضاء آخرين في مستأجريهم
create policy "view_tenant_members" on public.tenant_memberships for select
using (tenant_id in (select public.get_user_tenant_ids()));
-- فقط المسؤولون + يمكنهم إدارة الانضمامات
create policy "admins_manage_members" on public.tenant_memberships for insert
with check (
tenant_id in (
select tenant_id from public.tenant_memberships
where user_id = auth.uid() and role in ('owner', 'admin')
)
);
-- المشاريع والمهام: عزل مستأجر قياسي
create policy "tenant_projects" on public.projects for all
using (tenant_id in (select public.get_user_tenant_ids()))
with check (tenant_id in (select public.get_user_tenant_ids()));
create policy "tenant_tasks" on public.tasks for all
using (tenant_id in (select public.get_user_tenant_ids()))
with check (tenant_id in (select public.get_user_tenant_ids()));
الأسئلة الشائعة
هل تضيف Supabase RLS زمن انتظار كبير للاستعلامات؟
مع سياسات محسّنة (وظائف security definer، فهارس مناسبة، tenant_id غير طبيعي)، التكلفة الإضافية عادة ما تكون 50-80% على وقت الاستعلام الخام. بالنسبة للاستعلام الذي يستغرق 3ms بدون RLS، توقع 5-6ms معه. هذا مقبول تماماً للاستخدام الإنتاجي. النهج الساذج يمكن أن يضيف 10-20x من التكلفة الإضافية، وهذا لماذا يعتبر التحسين حاسماً.
هل يمكنني استخدام RLS مع اشتراكات Supabase Realtime؟ نعم. Supabase Realtime تحترم سياسات RLS. عندما يشترك العميل في التغييرات على جدول، سيتلقى فقط الأحداث للصفوف المأذون به برؤيتها. هذا يعمل تلقائياً -- لا تحتاج إلى إضافة أي منطق تصفية إضافي على العميل. فقط تأكد من أن سياسات RLS الخاصة بك فعالة، لأنها يتم تقييمها لكل بث.
كيف أتعامل مع تبديل المستأجر في واجهة المستخدم؟
خزّن معرف المستأجر النشط في حالة تطبيقك (React context, Zustand store, إلخ.) ومررها كمرشح في استعلاماتك. بما أن RLS بالفعل يحد من النتائج للمستأجرين الذين ينتمي المستخدم إليهم، التبديل هو مجرد مسألة تغيير tenant_id الذي تصفيه. لا حاجة لتحديث الرمز إذا كنت تستخدم نهج البحث في قاعدة البيانات.
هل يجب أن أستخدم مصادقة Supabase المدمجة أو مزود خارجي مثل Clerk؟
مصادقة Supabase تندمج بشكل طبيعي مع RLS من خلال auth.uid(). إذا استخدمت مزوداً خارجياً، ستحتاج إلى مزامنة المستخدمين إلى Supabase واستخدام مطالبات JWT مخصصة أو جدول تعيين. أنا سأبقى مع Supabase Auth ما لم تكن لديك سبب معين لعدم القيام بذلك -- إنه يوفر عمل دمج كبير.
كم عدد المستأجرين الذي يمكن لمشروع Supabase واحد التعامل معه؟ مع تعدد المستأجرين للمخطط المشترك، قمت شخصياً بتشغيل 2000+ مستأجر على خطة Supabase Pro ($25/شهر). الحد العملي يعتمد على إجمالي حجم البيانات وأنماط الاستعلام، وليس عدد المستأجرين. يمكن لمثيل Supabase Pro مع 2 vCPU و 1GB RAM التعامل مع الجداول التي تحتوي على عشرات الملايين من الصفوف إذا كانت فهارسك صحيحة.
ماذا يحدث إذا نسيت تفعيل RLS على جدول جديد؟
إذا كنت تستخدم عميل Supabase مع مفتاح anon، فأي جدول بدون RLS مفعّل يمكن الوصول إليه من قبل أي شخص لديه هذا المفتاح. هذا هو أكبر عيب في Supabase. قم بإعداد فحص CI يتحقق من أن جميع الجداول في مخطط public لديها RLS مفعّل. يمكنك الاستعلام عن pg_tables المنضمة إلى pg_class لأتمتة هذا الفحص.
هل يمكنني استخدام RLS مع Supabase Edge Functions؟ نعم. يمكن لـ Edge Functions إنشاء عميل Supabase باستخدام مفتاح anon (ينطبق RLS) أو مفتاح دور الخدمة (RLS قاهر). استخدم مفتاح anon مع JWT المستخدم للعمليات التي تواجه المستخدم، ومفتاح دور الخدمة للمهام الإدارية التي تحتاج إلى وصول عبر المستأجرين.
كيف أهاجر من تطبيق مستأجر واحد إلى متعدد المستأجرين؟
أضف tenant_id كعمود قابل للقيمة الفارغة، ثم ملأ جميع البيانات الموجودة (جميع الصفوف تحصل على نفس معرف المستأجر لأنها كانت مستأجر واحد)، ثم أضف قيد NOT NULL، والفهارس، وسياسات RLS. اختبر بشكل شامل مع البيانات الموجودة قبل تفعيل RLS -- سياسة واحدة خاطئة يمكن أن تقفل جميع المستخدمين الخاصين بك. استخدم بيئة تطوير Supabase المحلية لإعادة تمثيل الترحيل.