Skip to content

Commit d92512c

Browse files
committed
rolled back usage of /users/{userId}/last-connection/self document as this is impossible to create a firestore sub-collection when the parent collection (/users/{userId}) doesn't exist yet
Reverted to a /users/{userId} doc upsert creation at client authentication time, providing last connection date + creating the whole user doc entry serverside when it doesn't exist, so that user entry can be cleant and re-created after
1 parent 94c9832 commit d92512c

File tree

8 files changed

+71
-112
lines changed

8 files changed

+71
-112
lines changed

cloud/firestore/firestore.default.rules

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,23 +168,14 @@ service cloud.firestore {
168168
}
169169

170170
match /users/{userId} {
171-
allow list, create, delete: if false;
172-
allow get: if iAm(userId);
173-
/* TODO: update this to false after DVBE25
174-
(user should have had time to update to the new version of the app which, now, populates
175-
/users/{userId}/last-connection/self node which, in turn, copies userLastConnection to this document)
176-
*/
171+
allow list, delete: if false;
172+
allow get, create: if iAm(userId);
177173
allow update: if iAm(userId) && onlyAllowedUpdatedFields(["userLastConnection"]);
178174

179175
match /preferences/self {
180176
allow get, create, update: if iAm(userId);
181177
allow delete, list: if false;
182178
}
183-
match /last-connection/self {
184-
/* Don't know why, but we need to allow update in order to be able to create (at least in unit tests) */
185-
allow create, update: if iAm(userId);
186-
allow get, delete, list: if false;
187-
}
188179
/* Legacy node: but kept for backward compatibility until every users have migrated their tokens-wallet
189180
(this can take a lot of time)
190181
*/

cloud/firestore/firestore.default.rules.spec.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -80,24 +80,6 @@ const FIREBASE_MANAGED_COLLECTIONS = [
8080
data: () => ({pinnedEventIds: []}),
8181
updatedData: () => ({pinnedEventIds: ['42']})
8282
}]
83-
}, {
84-
name: '/users/{userId}/last-connection/self',
85-
docInitializations: [{
86-
name: 'alice',
87-
collection: '/users/alice/last-connection',
88-
path: '/users/alice/last-connection/self',
89-
newDocPath: '/users/alice/last-connection/other',
90-
data: () => ({userLastConnection: "2024-10-27T12:00:00Z"}),
91-
updatedData: () => ({userLastConnection: "2024-10-27T12:42:00Z"})
92-
}, {
93-
name: 'fred',
94-
collection: '/users/fred/last-connection',
95-
path: '/users/fred/last-connection/self',
96-
newDocPath: '/users/fred/last-connection/other',
97-
data: () => ({userLastConnection: "2024-10-27T12:00:00Z"}),
98-
updatedData: () => ({userLastConnection: "2024-10-27T12:42:00Z"})
99-
}]
100-
10183
}, {
10284
name: '/users/{userId}/events/{eventId}',
10385
docInitializations: [{
@@ -820,31 +802,6 @@ const COLLECTIONS: CollectionDescriptor[] = [{
820802
list: false, createDoc: false, delete: false, get: false, update: false, createNew: false
821803
}, 'alice')
822804
}
823-
}, {
824-
name: "/users/{userId}/last-connection",
825-
aroundTests: (userContext: UserContext) => match(userContext)
826-
.with({ name: "unauthenticated user" }, () => ({
827-
beforeEach: [],
828-
afterEach: [],
829-
}))
830-
.with({ name: "fred user" }, () => ({
831-
beforeEach: [],
832-
afterEach: [],
833-
})).run(),
834-
tests: (userContext: UserContext) => {
835-
ensureCollectionFollowAccessPermissions('/users/{userId}/last-connection/self', userContext,
836-
{
837-
createDoc: userContext.name === 'fred user',
838-
get: false, update: userContext.name === 'fred user',
839-
list: false, delete: false, createNew: false,
840-
}, 'fred')
841-
842-
ensureCollectionFollowAccessPermissions('/users/{userId}/last-connection/self', userContext,
843-
{
844-
get: false, update: false, createDoc: false,
845-
list: false, delete: false, createNew: false,
846-
}, 'alice')
847-
}
848805
}, {
849806
name: "/users/{userId}/preferences",
850807
aroundTests: (userContext: UserContext) => match(userContext)

cloud/functions/src/functions/firestore/onUserCreated.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,32 @@ export const onUserCreated = async (user: UserRecord, context: EventContext<{}>)
1414
await createUserInfos(user.uid);
1515
};
1616

17-
export async function createUserInfos(userId: string) {
18-
const publicUserToken = uuidv4();
19-
const user: User = {
20-
privateUserId: userId,
21-
publicUserToken,
22-
userCreation: new Date().toISOString() as ISODatetime,
23-
userLastConnection: new Date().toISOString() as ISODatetime,
24-
username: `Anonymous${generateRandom15DigitInteger()}`,
25-
totalFavs: {
26-
total: 0,
27-
perEventTotalFavs: {}
28-
},
29-
totalFeedbacks: {
30-
total: 0,
31-
perEventTotalFeedbacks: {}
32-
},
33-
_modelRemainingMigrations: [],
34-
_version: 4
35-
}
17+
export function defaultUserInfos(userId: string) {
18+
const publicUserToken = uuidv4();
19+
const now = new Date().toISOString() as ISODatetime
20+
const user: User = {
21+
privateUserId: userId,
22+
publicUserToken,
23+
userCreation: now,
24+
userLastConnection: now,
25+
username: `Anonymous${generateRandom15DigitInteger()}`,
26+
totalFavs: {
27+
total: 0,
28+
perEventTotalFavs: {}
29+
},
30+
totalFeedbacks: {
31+
total: 0,
32+
perEventTotalFeedbacks: {}
33+
},
34+
_modelRemainingMigrations: [],
35+
_version: 4
36+
}
37+
38+
return user;
39+
}
3640

41+
export async function createUserInfos(userId: string) {
42+
const user = defaultUserInfos(userId);
3743
await db.collection('users').doc(userId).set(user);
3844
}
3945

cloud/functions/src/functions/firestore/onUserLastConnectionCreated.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {DocumentSnapshot} from "firebase-functions/lib/v1/providers/firestore";
2+
import {EventContext} from "firebase-functions/lib/v1/cloud-functions";
3+
import {db} from "../../firebase";
4+
import {defaultUserInfos} from "./onUserCreated";
5+
import {User} from "../../../../../shared/user.firestore";
6+
7+
8+
export const onUserNodeUpserted = async (context: EventContext<{ userId: string }>) => {
9+
const userId = context.params.userId;
10+
const baseUser = defaultUserInfos(userId);
11+
12+
// Keeping db user infos and potentially overwriting it with default user info
13+
const userDoc = await db.doc(`/users/${userId}`).get() as DocumentSnapshot
14+
const dbUser = userDoc.data();
15+
16+
const mergedUser: User = {
17+
// enforcing all default user fields are defined
18+
...baseUser,
19+
// keeping db user state
20+
...dbUser,
21+
// Enforcing some fields that should never change over time...
22+
privateUserId: userId,
23+
// 👇Please, don't do this as this will lead to infinite user node-updated loop...
24+
// userLastConnection: new Date().toISOString() as ISODatetime,
25+
}
26+
27+
// important note: if mergedUser is the same than dbUser, nothing will be really called
28+
// (this is to avoid infinite loop on user node udpates
29+
await userDoc.ref.update(mergedUser)
30+
}

cloud/functions/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,17 @@ exports.onUserPrivateSpaceTalkFeedbackCreated = functions.firestore
103103
.onCreate(async (snapshot, context) => {
104104
(await import('./functions/firestore/onTalkFeedbackProvided')).onUserTalkFeedbackCreated(snapshot, context)
105105
});
106-
exports.onUserLastConnectionCreated = functions.firestore
107-
.document(`users/{userId}/last-connection/self`)
106+
exports.onUserNodeCreated = functions.firestore
107+
.document(`users/{userId}`)
108108
.onCreate(async (snapshot, context) => {
109-
(await import('./functions/firestore/onUserLastConnectionCreated')).onUserLastConnectionCreated(snapshot, context)
109+
console.log(`onUserNodeCreated called !`)
110+
return (await import('./functions/firestore/onUserNodeUpserted')).onUserNodeUpserted(context)
111+
});
112+
exports.onUserNodeUpdated = functions.firestore
113+
.document(`users/{userId}`)
114+
.onUpdate(async (snapshot, context) => {
115+
console.log(`onUserNodeUpdated called !`)
116+
return (await import('./functions/firestore/onUserNodeUpserted')).onUserNodeUpserted(context)
110117
});
111118

112119
// Schedulers

mobile/src/components/user/AuthenticatedUserContextProvider.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {db} from "@/state/firebase";
1010
import {User} from "firebase/auth";
1111
import {useUserTokensWallet} from "@/state/useUserTokensWallet";
1212
import {match, P} from "ts-pattern";
13-
import {UserLastConnection} from "../../../../shared/user-last-connection.firestore";
13+
import {User as FirestoreUser} from "../../../../shared/user.firestore";
1414
import {ISODatetime} from "../../../../shared/type-utils";
1515
1616
const props = defineProps({
@@ -22,12 +22,12 @@ const props = defineProps({
2222
2323
migrateData(props.user.uid);
2424
25-
const userLastConnexion: UserLastConnection = {
26-
privateUserId: props.user.uid,
25+
const userLastConnexion: Pick<FirestoreUser, "userLastConnection"> = {
2726
userLastConnection: new Date().toISOString() as ISODatetime,
2827
}
28+
const userDoc = doc(db, `/users/${props.user.uid}`)
2929
// Creating user last-connection doc with (always) last known user connection in it
30-
setDoc(doc(db, `/users/${props.user.uid}/last-connexion/self`), userLastConnexion);
30+
setDoc(userDoc, userLastConnexion, { merge: true })
3131
3232
3333
const { registerTalkFeedbacksViewerSecretToken, registerEventOrganizerSecretToken, registerPrivateSpaceSecretToken } = useUserTokensWallet();

shared/user-last-connection.firestore.d.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)