NahdaAIنهضة
Supabase • المستوى الثالث • الدرس 3

تخزين الملفات والصور (Supabase Storage)

العودة لفهرس المستوى

أهداف الدرس

  • رفع الملفات بـ storage.from().upload().
  • الحصول على الرابط العام بـ getPublicUrl().
  • حذف الملفات بـ remove() وعرضها بـ list().
  • بناء Buckets وسياسات وصول آمنة.

1. ما هو Supabase Storage؟

Supabase Storage هو خدمة تخزين كائنية (Object Storage) مبنية على Amazon S3. يُستخدم لتخزين الملفات الكبيرة (صور، فيديو، PDF، ...) التي لا تُخزَّن عادةً في قاعدة البيانات.

  • • Buckets — حاويات الملفات (مثل: avatars، documents).
  • • Paths — مسار الملف داخل الـ Bucket (مثل: user-101/avatar.png).
  • • Policies — قواعد الوصول (Public / Private / RLS).
javascript
// بنية التخزين
Storage
├── avatars/              ← Bucket (Public)
│   ├── user-101/
│   │   └── avatar.png    ← ملف
│   └── user-102/
│       └── avatar.png
└── documents/            ← Bucket (Private)
    └── user-101/
        └── cv.pdf

2. إنشاء Bucket من لوحة التحكم

قبل رفع الملفات، أنشئ Bucket من Supabase Dashboard → Storage → New Bucket:

Public Bucket

أي شخص يملك الرابط يمكنه القراءة.

مناسب: الصور الشخصية، المنتجات

Private Bucket

يتطلب RLS/Policy للوصول.

مناسب: المستندات، الملفات الحساسة

3. رفع الملفات — upload

لرفع ملف، استخدم .upload(path, file):

javascript
// رفع ملف بسيط
const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user-101/avatar.png', file);

if (error) console.error(error);
else console.log('تم الرفع:', data.path);
upsert: true

استبدال الملف إن وُجد.

upload(path, file, {
  upsert: true
})
contentType

تحديد نوع المحتوى.

upload(path, file, {
  contentType: 'image/png'
})

4. الحصول على الرابط — getPublicUrl

بعد الرفع، تحتاج رابطاً لعرض الملف في الـ UI:

javascript
// رابط عام (للبكتات العامة)
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('user-101/avatar.png');

console.log(data.publicUrl);

// رابط موقَّع (للبكتات الخاصة)
const { data: signedData } = await supabase.storage
  .from('documents')
  .createSignedUrl('user-101/cv.pdf', 3600); // صالح لساعة

console.log(signedData.signedUrl);
الفرق
  • • getPublicUrl — للبكتات العامة (Public). متزامن.
  • • createSignedUrl — للبكتات الخاصة. غير متزامن (await).

5. الحذف والعرض والتنزيل

remove

حذف ملف أو عدة ملفات (يأخذ مصفوفة).

await supabase.storage
  .from('avatars')
  .remove(['path1', 'path2']);
list

عرض ملفات داخل مجلد.

await supabase.storage
  .from('avatars')
  .list('user-101');
download

تنزيل محتوى الملف كـ Blob.

const { data } = await supabase
  .storage.from('avatars')
  .download('user-101/a.png');
move / copy

نقل/نسخ ملف.

await supabase.storage
  .from('avatars')
  .move('old.png', 'new.png');

6. سياسات الوصول — Storage Policies

يمكنك حماية Bucket بقواعد RLS (مثل الجداول):

sql
-- سياسة: كل مستخدم يرفع صورته فقط
CREATE POLICY "Users upload own avatar"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars'
  AND auth.uid()::text = (storage.foldername(name))[1]
);

-- سياسة: قراءة عامة للصور
CREATE POLICY "Public avatar read"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');

-- سياسة: حذف الصورة لصاحبها فقط
CREATE POLICY "Users delete own avatar"
ON storage.objects FOR DELETE
USING (
  bucket_id = 'avatars'
  AND auth.uid()::text = (storage.foldername(name))[1]
);
متغيرات مساعدة
  • • storage.foldername(name) — يُرجع مسار المجلد كمصفوفة
  • • (storage.foldername(name))[1] — أول مجلد (UID المستخدم)
  • • bucket_id — اسم الـ Bucket
التطبيق العملي

تحدي الدرس: نظام رفع صور كامل

  1. أضف getPublicUrl لاستخراج رابط الملف.
  2. أضف deleteFile مع remove().
  3. أضف listUserFiles مع list().
  4. أضف downloadFile مع download().
  5. أضف اختباراً تلقائياً.
⏳ جارٍ التحميل...