From 0fb957a65e19b829fa30569a85d00e3afc83fd0a Mon Sep 17 00:00:00 2001 From: 230419 <230419@epvc.pt> Date: Mon, 4 May 2026 22:37:58 +0100 Subject: [PATCH] firestoreRules! regras de notificacoes --- firestore.rules | 25 +++++++++++++++++++++++++ src/App.jsx | 24 +++++++++++++----------- 2 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 firestore.rules diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..90a3d98 --- /dev/null +++ b/firestore.rules @@ -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; + } + } +} diff --git a/src/App.jsx b/src/App.jsx index 15a8606..8d25c0e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -16,7 +16,7 @@ import { } from 'firebase/auth'; import { collection, doc, onSnapshot, addDoc, updateDoc, - deleteDoc, writeBatch, setDoc, getDoc + deleteDoc, writeBatch, setDoc, getDoc, query, where } from 'firebase/firestore'; import { auth, db, appId } from './lib/firebase'; @@ -282,11 +282,12 @@ export default function App() { else setUserProfile({}); }, (err) => console.error(err)); - // Notificações - const notifCol = collection(db, 'artifacts', appId, 'users', user.uid, 'notifications'); - const unsubNotif = onSnapshot(notifCol, (snap) => { + // Notificações (coleção pública partilhada, filtrada por recipientUid) + const notifCol = collection(db, 'artifacts', appId, 'inboxNotifications'); + 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)); - }, (err) => console.error(err)); + }, (err) => console.error('Notif listener error:', err)); return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); unsubNotif(); }; }, [user]); @@ -635,19 +636,20 @@ export default function App() { updatedAt: new Date().getTime(), }); - // Notify the owner + // Notify the owner via coleção pública (inboxNotifications) if (sharedLookData.ownerUid && sharedLookData.ownerUid !== user.uid) { try { - const notificationsCol = collection(db, 'artifacts', appId, 'users', sharedLookData.ownerUid, 'notifications'); - await addDoc(notificationsCol, { + const inboxCol = collection(db, 'artifacts', appId, 'inboxNotifications'); + await addDoc(inboxCol, { type: 'look_copied', + recipientUid: sharedLookData.ownerUid, lookName: sharedLookData.lookName, copiedByEmail: userProfile?.username || user.email || 'Alguém', createdAt: new Date().getTime(), read: false }); } 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 () => { const batch = writeBatch(db); 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 }); }); await batch.commit(); @@ -1871,7 +1873,7 @@ export default function App() { {!notif.read && (