Skip to content

Commit 94c9832

Browse files
committed
considering that userLastConnection should now be provided into a dedicated document, so that a new /users/{userId} document can be created when it doesn't exist (yet)
1 parent 90790fe commit 94c9832

File tree

7 files changed

+100
-8
lines changed

7 files changed

+100
-8
lines changed

cloud/firestore/firestore.default.rules

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,21 @@ service cloud.firestore {
170170
match /users/{userId} {
171171
allow list, create, delete: if false;
172172
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+
*/
173177
allow update: if iAm(userId) && onlyAllowedUpdatedFields(["userLastConnection"]);
174178

175179
match /preferences/self {
176180
allow get, create, update: if iAm(userId);
177181
allow delete, list: if false;
178182
}
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+
}
179188
/* Legacy node: but kept for backward compatibility until every users have migrated their tokens-wallet
180189
(this can take a lot of time)
181190
*/

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ 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+
83101
}, {
84102
name: '/users/{userId}/events/{eventId}',
85103
docInitializations: [{
@@ -802,6 +820,31 @@ const COLLECTIONS: CollectionDescriptor[] = [{
802820
list: false, createDoc: false, delete: false, get: false, update: false, createNew: false
803821
}, 'alice')
804822
}
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+
}
805848
}, {
806849
name: "/users/{userId}/preferences",
807850
aroundTests: (userContext: UserContext) => match(userContext)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function createUserInfos(userId: string) {
2020
privateUserId: userId,
2121
publicUserToken,
2222
userCreation: new Date().toISOString() as ISODatetime,
23+
userLastConnection: new Date().toISOString() as ISODatetime,
2324
username: `Anonymous${generateRandom15DigitInteger()}`,
2425
totalFavs: {
2526
total: 0,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {QueryDocumentSnapshot} from "firebase-functions/lib/v1/providers/firestore";
2+
import {EventContext} from "firebase-functions/lib/v1/cloud-functions";
3+
import {db} from "../../firebase";
4+
import {firestore} from "firebase-admin";
5+
import {User} from "../../../../../shared/user.firestore";
6+
import DocumentSnapshot = firestore.DocumentSnapshot;
7+
import {UserLastConnection} from "../../../../../shared/user-last-connection.firestore";
8+
import {createUserInfos} from "./onUserCreated";
9+
10+
export const onUserLastConnectionCreated = async (snapshot: QueryDocumentSnapshot, context: EventContext<{ userId: string }>) => {
11+
const userId = context.params.userId;
12+
// Checking if user infos have been created: if not, creating it
13+
// This can happen in multiple cases:
14+
// - onUserCreated() has not been called (yet)
15+
// - user has been considered outdated and has been cleant (this allows to re-create a well formed user data in case
16+
// someone doesn't connect to voxxrin for a very long time)
17+
const userDoc = await db.doc(`/users/${userId}`).get() as DocumentSnapshot<User>
18+
const user = userDoc.data();
19+
const userLastConnection = snapshot.data() as UserLastConnection;
20+
if(!userDoc.exists || !user) {
21+
await createUserInfos(userId)
22+
}
23+
24+
// Updating user last connection in main document
25+
await userDoc.ref.update("userLastConnection", userLastConnection.userLastConnection)
26+
}

cloud/functions/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ 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`)
108+
.onCreate(async (snapshot, context) => {
109+
(await import('./functions/firestore/onUserLastConnectionCreated')).onUserLastConnectionCreated(snapshot, context)
110+
});
106111

107112
// Schedulers
108113
exports.refreshSlowPacedTalkStatsCron = functions.pubsub

mobile/src/components/user/AuthenticatedUserContextProvider.vue

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
</template>
44

55
<script setup lang="ts">
6-
import {PropType, watch} from "vue";
6+
import {PropType} from "vue";
77
import {migrateData} from "@/data-migrations/migrate-data";
8-
import {collection, doc, updateDoc} from "firebase/firestore";
8+
import {doc, setDoc} from "firebase/firestore";
99
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";
14+
import {ISODatetime} from "../../../../shared/type-utils";
1315
1416
const props = defineProps({
1517
user: {
@@ -20,12 +22,12 @@ const props = defineProps({
2022
2123
migrateData(props.user.uid);
2224
23-
const userRef = doc(collection(db, 'users'), props.user.uid)
24-
// Letting some time to the server to create the new user node the first time the user authenticates
25-
// ... so that we can then update last connection date
26-
setTimeout(() => {
27-
updateDoc(userRef, "userLastConnection", new Date().toISOString())
28-
}, 30000);
25+
const userLastConnexion: UserLastConnection = {
26+
privateUserId: props.user.uid,
27+
userLastConnection: new Date().toISOString() as ISODatetime,
28+
}
29+
// 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);
2931
3032
3133
const { registerTalkFeedbacksViewerSecretToken, registerEventOrganizerSecretToken, registerPrivateSpaceSecretToken } = useUserTokensWallet();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {ISODatetime} from "./type-utils";
2+
3+
export type UserLastConnection = {
4+
privateUserId: string;
5+
userLastConnection: ISODatetime;
6+
}

0 commit comments

Comments
 (0)