I have this structure on my Firestore database:
I have a collection named groups
.
Each document inside represents a group of users.
Each group has a map field, called users
.
Group1: { // Group name is the document ID
... // other fields
users: {
userID: {
nick: "myuser1"
},
...
}
}
Each user belongs to another collection, users
, each user document has
an array field with a list of groups he belongs to.
userID: { // document ID is the user ID
nick: "myuser1",
...,
..., // other fields
...,
groups: [
"Group1",
...
]
}
When adding a user to a group, I must ensure both group document and user document are updated in a single transaction, otherwise the information will be inconsistent.
The code lives inside a callable cloud function.
In order to ensure atomicity, I used a transaction, see below.
The thing is, it works…. most of the time. I’d say a 20% of times it does NOT work, it leaves the user with the group name inside its groups
array, but the group document is NOT updated. So, no atomicity.
Here’s the function, I am importing like this:
const functions = require('firebase-functions')
so I guess I’m using v1? Not sure about that.
exports.joinGroup = functions
.region(firebaseRegion)
.https.onCall(async (data, context) => {
const groupCollection = data.groupCollection
const userCollection = data.userCollection
const groupName = data.groupName
const token = data.token
try {
const res = await db.runTransaction(async t => {
const groupRef = db.collection(groupCollection).doc(groupName)
let docgr = await t.get(groupRef)
if (!docgr.exists) return false
const uid = context.auth.uid
const userRef = db.collection(userCollection).doc(uid)
const docUser = await t.get(userRef)
if (!docUser.exists) return false
const user = docUser.data()
const nick = user.nick
const him = {
nick
}
t.update(groupRef, {
[`users.${uid}`]: him
})
t.update(groupRef, {
invites: FieldValue.arrayRemove(token)
})
t.update(userRef, {
groups: FieldValue.arrayUnion(groupName)
})
return true
})
if (res) {
console.log('JOINGROUP JOINED', groupName)
return { ok: true }
} else {
const err = { code: 403, error: 'joinGroup failed' }
throw err
}
} catch (etrans) {
console.log('FAILED', etrans)
}
})