Files
Onsol-GO/firestore.rules
T
2026-04-23 23:58:59 -05:00

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