158 lines
5.0 KiB
Plaintext
158 lines
5.0 KiB
Plaintext
rules_version = '2';
|
|
|
|
service cloud.firestore {
|
|
match /databases/{database}/documents {
|
|
|
|
// --- HELPERS ---
|
|
function isSignedIn() {
|
|
return request.auth != null;
|
|
}
|
|
|
|
function isAdmin() {
|
|
return isSignedIn()
|
|
&& exists(/databases/$(database)/documents/users/$(request.auth.uid))
|
|
&& get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
|
|
}
|
|
|
|
// Toggle like on social_posts: only likedBy + likes may change; count must match list length.
|
|
function isSocialLikeReactionUpdate() {
|
|
return isSignedIn()
|
|
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['likedBy', 'likes'])
|
|
&& request.resource.data.likedBy is list
|
|
&& request.resource.data.likes == request.resource.data.likedBy.size();
|
|
}
|
|
|
|
// --- USERS ---
|
|
match /users/{userId} {
|
|
allow get: if true;
|
|
allow list: if isAdmin();
|
|
|
|
allow create: if isSignedIn() && request.auth.uid == userId;
|
|
|
|
allow update: if isAdmin() || (
|
|
isSignedIn()
|
|
&& request.auth.uid == userId
|
|
&& !request.resource.data.diff(resource.data).affectedKeys()
|
|
.hasAny(['rankLevel', 'role', 'isVerified', 'isBanned'])
|
|
);
|
|
|
|
match /{allPaths=**} {
|
|
allow read, write: if isSignedIn() && (request.auth.uid == userId || isAdmin());
|
|
}
|
|
}
|
|
|
|
// --- SOCIAL ---
|
|
match /social_posts/{postId} {
|
|
allow read: if true;
|
|
|
|
allow create: if isSignedIn()
|
|
&& request.resource.data.authorId == request.auth.uid;
|
|
|
|
allow update: if isAdmin() || (
|
|
isSignedIn()
|
|
&& resource.data.authorId == request.auth.uid
|
|
&& request.resource.data.authorId == resource.data.authorId
|
|
) || isSocialLikeReactionUpdate();
|
|
|
|
allow delete: if isAdmin()
|
|
|| (isSignedIn() && resource.data.authorId == request.auth.uid);
|
|
|
|
match /comments/{commentId} {
|
|
allow read: if true;
|
|
allow create: if isSignedIn()
|
|
&& request.resource.data.uid == request.auth.uid;
|
|
allow update, delete: if isAdmin()
|
|
|| (isSignedIn() && resource.data.uid == request.auth.uid);
|
|
}
|
|
}
|
|
|
|
// --- UPCOMING (Soon tab; artists manage own rows) ---
|
|
match /upcoming/{announcementId} {
|
|
allow read: if true;
|
|
|
|
allow create: if isSignedIn()
|
|
&& request.resource.data.authorId == request.auth.uid
|
|
&& request.resource.data.keys().hasAll([
|
|
'kind', 'authorId', 'authorName', 'seriesTitle'
|
|
])
|
|
&& request.resource.data.kind in ['new_series', 'chapter_drop']
|
|
&& (
|
|
request.resource.data.dateTbd == true
|
|
|| request.resource.data.targetDate is timestamp
|
|
);
|
|
|
|
allow update: if isAdmin() || (
|
|
isSignedIn()
|
|
&& resource.data.authorId == request.auth.uid
|
|
&& request.resource.data.authorId == resource.data.authorId
|
|
);
|
|
|
|
allow delete: if isAdmin()
|
|
|| (isSignedIn() && resource.data.authorId == request.auth.uid);
|
|
}
|
|
|
|
// --- MANGA & CHAPTERS ---
|
|
match /manga/{mangaId} {
|
|
allow read: if true;
|
|
|
|
// Anyone signed in can bump read counts; series author can edit portfolio fields.
|
|
allow update: if isAdmin()
|
|
|| (isSignedIn()
|
|
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['reads']))
|
|
|| (isSignedIn()
|
|
&& resource.data.keys().hasAll(['authorId'])
|
|
&& resource.data.authorId == request.auth.uid
|
|
&& request.resource.data.authorId == resource.data.authorId
|
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
|
.hasOnly(['coverUrl', 'bannerUrl', 'synopsis', 'aboutArtist', 'socials']));
|
|
|
|
allow write: if isAdmin();
|
|
|
|
match /chapters/{chapterId} {
|
|
allow read: if true;
|
|
|
|
allow create, update: if isAdmin();
|
|
|
|
allow delete: if isAdmin()
|
|
|| (isSignedIn()
|
|
&& exists(/databases/$(database)/documents/manga/$(mangaId))
|
|
&& get(/databases/$(database)/documents/manga/$(mangaId)).data.authorId == request.auth.uid);
|
|
|
|
match /page_interactions/{pageId} {
|
|
allow read: if true;
|
|
allow create, update: if isSignedIn();
|
|
|
|
match /comments/{commentId} {
|
|
allow read: if true;
|
|
allow create: if isSignedIn()
|
|
&& request.resource.data.uid == request.auth.uid;
|
|
allow update: if isAdmin()
|
|
|| (isSignedIn() && resource.data.uid == request.auth.uid);
|
|
allow delete: if isAdmin()
|
|
|| (isSignedIn() && resource.data.uid == request.auth.uid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- MARKETPLACE ---
|
|
match /marketplace/{itemId} {
|
|
allow read: if true;
|
|
allow write: if isAdmin();
|
|
}
|
|
|
|
// Sign-up validates a known code via get(); listing all codes is admin-only.
|
|
match /invite_codes/{code} {
|
|
allow get: if true;
|
|
allow list: if isAdmin();
|
|
allow create, update, delete: if isAdmin();
|
|
}
|
|
|
|
// --- SYSTEM BROADCAST ---
|
|
match /system/announcement {
|
|
allow read: if true;
|
|
allow write: if isAdmin();
|
|
}
|
|
}
|
|
}
|