firestoreRules! regras de notificacoes
This commit is contained in:
25
firestore.rules
Normal file
25
firestore.rules
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/App.jsx
24
src/App.jsx
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user