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();
}
}
}
}
