firestoreRules! regras de notificacoes

This commit is contained in:
2026-05-04 22:37:58 +01:00
parent 54a7ce0e8e
commit 0fb957a65e
2 changed files with 38 additions and 11 deletions

25
firestore.rules Normal file
View File

@@ -0,0 +1,25 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ── Dados privados por utilizador ──────────────────────────────────────
match /artifacts/{appId}/users/{userId}/{document=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// ── Looks partilhados (qualquer utilizador autenticado pode ler/criar) ─
match /artifacts/{appId}/sharedLooks/{lookId} {
allow read: if request.auth != null;
allow create: if request.auth != null;
}
// ── Caixa de entrada de notificações cross-user ────────────────────────
// Qualquer utilizador autenticado pode criar notificações (para outro user)
// Só o destinatário pode ler e atualizar (marcar como lida) as suas
match /artifacts/{appId}/inboxNotifications/{notifId} {
allow create: if request.auth != null;
allow read, update: if request.auth != null
&& request.auth.uid == resource.data.recipientUid;
}
}
}

View File

@@ -16,7 +16,7 @@ import {
} from 'firebase/auth'; } from 'firebase/auth';
import { import {
collection, doc, onSnapshot, addDoc, updateDoc, collection, doc, onSnapshot, addDoc, updateDoc,
deleteDoc, writeBatch, setDoc, getDoc deleteDoc, writeBatch, setDoc, getDoc, query, where
} from 'firebase/firestore'; } from 'firebase/firestore';
import { auth, db, appId } from './lib/firebase'; import { auth, db, appId } from './lib/firebase';
@@ -282,11 +282,12 @@ export default function App() {
else setUserProfile({}); else setUserProfile({});
}, (err) => console.error(err)); }, (err) => console.error(err));
// Notificações // Notificações (coleção pública partilhada, filtrada por recipientUid)
const notifCol = collection(db, 'artifacts', appId, 'users', user.uid, 'notifications'); const notifCol = collection(db, 'artifacts', appId, 'inboxNotifications');
const unsubNotif = onSnapshot(notifCol, (snap) => { const notifQuery = query(notifCol, where('recipientUid', '==', user.uid));
const unsubNotif = onSnapshot(notifQuery, (snap) => {
setNotifications(snap.docs.map(d => ({ id: d.id, ...d.data() })).sort((a, b) => b.createdAt - a.createdAt)); setNotifications(snap.docs.map(d => ({ id: d.id, ...d.data() })).sort((a, b) => b.createdAt - a.createdAt));
}, (err) => console.error(err)); }, (err) => console.error('Notif listener error:', err));
return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); unsubNotif(); }; return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); unsubNotif(); };
}, [user]); }, [user]);
@@ -635,19 +636,20 @@ export default function App() {
updatedAt: new Date().getTime(), updatedAt: new Date().getTime(),
}); });
// Notify the owner // Notify the owner via coleção pública (inboxNotifications)
if (sharedLookData.ownerUid && sharedLookData.ownerUid !== user.uid) { if (sharedLookData.ownerUid && sharedLookData.ownerUid !== user.uid) {
try { try {
const notificationsCol = collection(db, 'artifacts', appId, 'users', sharedLookData.ownerUid, 'notifications'); const inboxCol = collection(db, 'artifacts', appId, 'inboxNotifications');
await addDoc(notificationsCol, { await addDoc(inboxCol, {
type: 'look_copied', type: 'look_copied',
recipientUid: sharedLookData.ownerUid,
lookName: sharedLookData.lookName, lookName: sharedLookData.lookName,
copiedByEmail: userProfile?.username || user.email || 'Alguém', copiedByEmail: userProfile?.username || user.email || 'Alguém',
createdAt: new Date().getTime(), createdAt: new Date().getTime(),
read: false read: false
}); });
} catch (notifErr) { } catch (notifErr) {
console.warn('Não foi possível enviar notificação ao dono do look:', notifErr); console.error('Não foi possível enviar notificação ao dono do look:', notifErr);
} }
} }
@@ -1812,7 +1814,7 @@ export default function App() {
onClick={async () => { onClick={async () => {
const batch = writeBatch(db); const batch = writeBatch(db);
notifications.filter(n => !n.read).forEach(n => { notifications.filter(n => !n.read).forEach(n => {
const ref = doc(db, 'artifacts', appId, 'users', user.uid, 'notifications', n.id); const ref = doc(db, 'artifacts', appId, 'inboxNotifications', n.id);
batch.update(ref, { read: true }); batch.update(ref, { read: true });
}); });
await batch.commit(); await batch.commit();
@@ -1871,7 +1873,7 @@ export default function App() {
{!notif.read && ( {!notif.read && (
<button <button
onClick={async () => { onClick={async () => {
const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'notifications', notif.id); const docRef = doc(db, 'artifacts', appId, 'inboxNotifications', notif.id);
await updateDoc(docRef, { read: true }); await updateDoc(docRef, { read: true });
}} }}
className="shrink-0 p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900/40 rounded-xl transition-all" className="shrink-0 p-2 text-primary-600 hover:bg-primary-100 dark:hover:bg-primary-900/40 rounded-xl transition-all"