rules_version = '2'; service cloud.firestore { match /databases/{db}/documents { function signed() { return request.auth != null; } function isAdmin() { return signed() && exists(/databases/$(db)/documents/admins/$(request.auth.uid)); } function me() { return request.auth.uid; } // অ্যাডমিন তালিকা: শুধু Firebase Console থেকে বানাতে হবে match /admins/{id} { allow read: if signed() && request.auth.uid == id; allow write: if false; } // সাইট সেটিংস (সবাই পড়তে পারবে, শুধু অ্যাডমিন লিখতে পারবে) match /settings/{id} { allow read: if true; allow write: if isAdmin(); } // ভিডিও match /videos/{vid} { allow read: if true; allow create, delete: if isAdmin(); allow update: if isAdmin() || (request.resource.data.diff(resource.data).affectedKeys().hasOnly(['views']) && request.resource.data.views == resource.data.views + 1); // পেইড ভিডিওর আসল লিংক: কেনা থাকলে বা অ্যাডমিন হলে তবেই পড়া যাবে match /private/{d} { allow read: if isAdmin() || (signed() && exists(/databases/$(db)/documents/purchases/$(me() + '_' + vid))); allow write: if isAdmin(); } } // ইউজার match /users/{uid} { allow read: if signed() && (me() == uid || isAdmin()); allow create: if signed() && me() == uid && request.resource.data.balance == 0; allow update: if isAdmin() || (signed() && me() == uid && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['balance', 'name']) && request.resource.data.balance <= resource.data.balance && request.resource.data.balance >= 0); allow delete: if isAdmin(); } // ভিডিও কেনা: ব্যালেন্স কমানো + purchase তৈরি একসাথে (batch) না হলে হবে না match /purchases/{pid} { allow read: if signed() && (resource.data.uid == me() || isAdmin()); allow create: if signed() && request.resource.data.uid == me() && pid == me() + '_' + request.resource.data.vid && request.resource.data.price == get(/databases/$(db)/documents/videos/$(request.resource.data.vid)).data.price && request.resource.data.price > 0 && getAfter(/databases/$(db)/documents/users/$(me())).data.balance == get(/databases/$(db)/documents/users/$(me())).data.balance - request.resource.data.price; allow update, delete: if isAdmin(); } // ডিপোজিট রিকোয়েস্ট match /deposits/{id} { allow read: if signed() && (resource.data.uid == me() || isAdmin()); allow create: if signed() && request.resource.data.uid == me() && request.resource.data.status == 'pending' && request.resource.data.amount > 0; allow update, delete: if isAdmin(); } // উইথড্র রিকোয়েস্ট: ব্যালেন্স কমানো + রিকোয়েস্ট একসাথে match /withdrawals/{id} { allow read: if signed() && (resource.data.uid == me() || isAdmin()); allow create: if signed() && request.resource.data.uid == me() && request.resource.data.status == 'pending' && request.resource.data.amount > 0 && getAfter(/databases/$(db)/documents/users/$(me())).data.balance == get(/databases/$(db)/documents/users/$(me())).data.balance - request.resource.data.amount; allow update, delete: if isAdmin(); } // নোটিফিকেশন match /notifications/{id} { allow read: if resource.data.to == 'all' || (signed() && resource.data.to == me()) || isAdmin(); allow write: if isAdmin(); } // লাইভ চ্যাট match /chats/{uid} { allow read, write: if signed() && (me() == uid || isAdmin()); match /msgs/{id} { allow read: if signed() && (me() == uid || isAdmin()); allow create: if signed() && ( (me() == uid && request.resource.data.from == 'user') || (isAdmin() && request.resource.data.from == 'admin')); allow update, delete: if isAdmin(); } } } }