Skip to content

Commit 793dfe7

Browse files
feat: automatically track lastUpdated time on all firestore docs (#227)
- it is often helpful to query for “new” firestore docs (for sync or backup) - this commit adds an automatic server timestamp to all subject documents and top level project documents when updated or created so that this can be queried - non-breaking change because without this it just will not store those fields. - update tests for last updated - update default rules to allow anonymous updates to last updated for parent/project doc - [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent d705dc3 commit 793dfe7

3 files changed

Lines changed: 33 additions & 6 deletions

File tree

firebase/firebase.rules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ service cloud.firestore {
1717
match /testing/{studyID} {
1818
allow create: if isAuthenticated() && !exists(/databases/$(database)/documents/testing/$(studyID));
1919
allow read: if isAuthenticated(); // Add this line
20+
allow update: if isAuthenticated() &&
21+
request.resource.data.diff(resource.data).affectedKeys().hasOnly(['lastUpdated']);
2022
match /data/{partID} {
2123
allow create: if isAuthenticated() &&
2224
request.resource.data.firebaseAnonAuthID == request.auth.uid;
@@ -36,6 +38,8 @@ service cloud.firestore {
3638
match /real/{studyID} {
3739
allow create: if isAuthenticated() && !exists(/databases/$(database)/documents/real/$(studyID));
3840
allow read: if isAuthenticated(); // Add this line
41+
allow update: if isAuthenticated() &&
42+
request.resource.data.diff(resource.data).affectedKeys().hasOnly(['lastUpdated']);
3943
match /data/{partID} {
4044
allow create: if isAuthenticated() &&
4145
request.resource.data.firebaseAnonAuthID == request.auth.uid;

src/core/stores/firestore-db.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
updateDoc,
2828
getDoc,
2929
Timestamp,
30+
serverTimestamp,
3031
connectFirestoreEmulator,
3132
} from 'firebase/firestore'
3233
import appconfig from '@/core/config'
@@ -148,7 +149,11 @@ export const updateSubjectDataRecord = async (data, docid) => {
148149
try {
149150
validateFirestoreData(data)
150151
const docRef = doc(db, `${mode}/${appconfig.projectRef}/data/`, docid)
151-
await setDoc(docRef, data, { merge: true })
152+
await setDoc(docRef, { ...data, lastUpdated: serverTimestamp() }, { merge: true })
153+
154+
// Also update the parent project document's lastUpdated field
155+
const projectRef = doc(db, mode, appconfig.projectRef)
156+
await setDoc(projectRef, { lastUpdated: serverTimestamp() }, { merge: true })
152157
} catch (e) {
153158
log.error('FIRESTORE-DB: Error updating document:', e.message)
154159
throw e // Re-throw to allow caller to handle the error
@@ -167,9 +172,17 @@ export const updatePrivateSubjectDataRecord = async (data, docid) => {
167172
// is it weird to have a aync method that doesn't return anything?
168173
try {
169174
const docRef = doc(db, `${mode}/${appconfig.projectRef}/data/${docid}/private/`, 'private_data')
170-
setDoc(docRef, data, {
171-
merge: true,
172-
})
175+
await setDoc(
176+
docRef,
177+
{ ...data, lastUpdated: serverTimestamp() },
178+
{
179+
merge: true,
180+
}
181+
)
182+
183+
// Also update the parent project document's lastUpdated field
184+
const projectRef = doc(db, mode, appconfig.projectRef)
185+
await setDoc(projectRef, { lastUpdated: serverTimestamp() }, { merge: true })
173186
} catch (e) {
174187
log.error('FIRESTORE-DB: Error updating private document', e)
175188
throw e
@@ -232,6 +245,7 @@ export const createDoc = async (data) => {
232245
projectRef: appconfig.projectRef,
233246
codeName: appconfig.codeName,
234247
codeNameURL: appconfig.codeNameURL,
248+
lastUpdated: serverTimestamp(),
235249
})
236250
log.log('FIRESTORE-DB: New experiment registered with ID: ', `${mode}/${appconfig.projectRef}`)
237251
}
@@ -242,11 +256,13 @@ export const createDoc = async (data) => {
242256
const docRef = await addDoc(collection(db, `${mode}/${appconfig.projectRef}/data`), {
243257
...data,
244258
firebaseAnonAuthID: user.uid,
259+
lastUpdated: serverTimestamp(),
245260
})
246261

247262
// Update the document with its own ID
248263
await updateDoc(docRef, {
249264
firebaseDocID: docRef.id,
265+
lastUpdated: serverTimestamp(),
250266
})
251267

252268
data.firebaseAnonAuthID = user.uid
@@ -277,7 +293,13 @@ export const createPrivateDoc = async (data, docId) => {
277293
await setDoc(docRef, {
278294
...data,
279295
firebaseAnonAuthID: user.uid,
296+
lastUpdated: serverTimestamp(),
280297
})
298+
299+
// Also update the parent project document's lastUpdated field
300+
const projectRef = doc(db, mode, appconfig.projectRef)
301+
await setDoc(projectRef, { lastUpdated: serverTimestamp() }, { merge: true })
302+
281303
log.log(`FIRESTORE-DB: Private document written with ID: `, docRef.id)
282304
return docRef.id
283305
} catch (e) {

tests/vitest/core/stores/firestore-db.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ vi.mock('firebase/firestore', () => ({
2828
Timestamp: {
2929
now: vi.fn(() => ({ seconds: 1234567890, nanoseconds: 123456789 })),
3030
},
31+
serverTimestamp: vi.fn(() => ({ seconds: 1234567890, nanoseconds: 123456789 })),
3132
connectFirestoreEmulator: vi.fn(),
3233
}))
3334

@@ -67,9 +68,9 @@ import {
6768
fsnow,
6869
} from '@/core/stores/firestore-db'
6970

70-
import { getAuth, signInAnonymously } from 'firebase/auth'
71+
import { signInAnonymously } from 'firebase/auth'
7172

72-
import { doc, getDoc, addDoc, setDoc, updateDoc, Timestamp } from 'firebase/firestore'
73+
import { getDoc, addDoc, setDoc, updateDoc, Timestamp } from 'firebase/firestore'
7374

7475
describe('firestore-db', () => {
7576
beforeEach(() => {

0 commit comments

Comments
 (0)