From d9b1c7bc0da0578e0251085ad32a82848ba21511 Mon Sep 17 00:00:00 2001 From: Ben Furber Date: Wed, 18 Sep 2024 16:23:03 +0100 Subject: [PATCH] feat: add function for pins after user updates --- .../src/userUpdates/hasKeyDetailsChanged.ts | 40 ---- functions/src/userUpdates/index.ts | 8 +- .../userUpdates/updateDiscussionComments.ts | 16 +- functions/src/userUpdates/updateMapPins.ts | 62 ++++++ ...eyDetailsChanged.test.ts => utils.test.ts} | 184 +++++++++++------- functions/src/userUpdates/utils.ts | 75 +++++++ 6 files changed, 267 insertions(+), 118 deletions(-) delete mode 100644 functions/src/userUpdates/hasKeyDetailsChanged.ts create mode 100644 functions/src/userUpdates/updateMapPins.ts rename functions/src/userUpdates/{hasKeyDetailsChanged.test.ts => utils.test.ts} (55%) create mode 100644 functions/src/userUpdates/utils.ts diff --git a/functions/src/userUpdates/hasKeyDetailsChanged.ts b/functions/src/userUpdates/hasKeyDetailsChanged.ts deleted file mode 100644 index ded54ae56c..0000000000 --- a/functions/src/userUpdates/hasKeyDetailsChanged.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { valuesAreDeepEqual } from '../Utils' - -import type { IUserDB } from '../models' - -export const hasDetailsChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean[] => { - return [ - prevUser.displayName !== user.displayName, - prevUser.location?.countryCode !== user.location?.countryCode, - hasUserImageChanged(prevUser, user), - prevUser.badges?.verified !== user.badges?.verified, - prevUser.badges?.supporter !== user.badges?.supporter, - ] -} - -export const hasKeyDetailsChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean => { - const detailsChanged = hasDetailsChanged(prevUser, user) - return !!detailsChanged.find((detail) => detail === true) -} - -export const hasUserImageChanged = ( - prevUser: IUserDB, - user: IUserDB, -): boolean => { - if (!prevUser.userImage && !user.userImage) return false - - if (prevUser.userImage && user.userImage) { - return !valuesAreDeepEqual(prevUser.userImage, user.userImage) - } - - if (prevUser.userImage && !user.userImage) return true - if (!prevUser.userImage && user.userImage) return true - - return false -} diff --git a/functions/src/userUpdates/index.ts b/functions/src/userUpdates/index.ts index b9bbd0b55f..03526132c3 100644 --- a/functions/src/userUpdates/index.ts +++ b/functions/src/userUpdates/index.ts @@ -4,6 +4,7 @@ import { db } from '../Firebase/firestoreDB' import { DB_ENDPOINTS } from '../models' import { backupUser } from './backupUser' import { updateDiscussionComments } from './updateDiscussionComments' +import { updateMapPins } from './updateMapPins' import type { IDBDocChange, IUserDB } from '../models' @@ -17,12 +18,12 @@ import type { IDBDocChange, IUserDB } from '../models' export const handleUserUpdates = functions .runWith({ memory: '512MB' }) .firestore.document(`${DB_ENDPOINTS.users}/{id}`) - .onUpdate(async (change, context) => { + .onUpdate(async (change, _) => { await backupUser(change) await updateDocuments(change) }) -const isUserCountryDifferent = (prevInfo, info) => { +const isUserCountryDifferent = (prevInfo: IUserDB, info: IUserDB) => { const prevCountryCode = prevInfo.location?.countryCode const newCountryCode = info.location?.countryCode const prevCountry = prevInfo.country @@ -48,6 +49,7 @@ async function updateDocuments(change: IDBDocChange) { } await updateDiscussionComments(prevInfo, info) + await updateMapPins(prevInfo, info) const didDelete = prevDeleted !== deleted && deleted if (didDelete) { @@ -142,6 +144,8 @@ async function updatePostsCountry(userId: string, country: IUserDB['country']) { return false } +// update _lastActive for any action --> content creation/editing + async function deleteMapPin(_id: string) { const pin = await db.collection(DB_ENDPOINTS.mappins).doc(_id).get() diff --git a/functions/src/userUpdates/updateDiscussionComments.ts b/functions/src/userUpdates/updateDiscussionComments.ts index 0b096ba1d0..9c8df29138 100644 --- a/functions/src/userUpdates/updateDiscussionComments.ts +++ b/functions/src/userUpdates/updateDiscussionComments.ts @@ -1,7 +1,7 @@ import { DB_ENDPOINTS } from 'oa-shared' import { db } from '../Firebase/firestoreDB' -import { hasKeyDetailsChanged } from './hasKeyDetailsChanged' +import { getCreatorImage, hasDetailsForCommentsChanged } from './utils' import type { IDiscussion, IUserDB } from '../models' @@ -9,7 +9,7 @@ export const updateDiscussionComments = async ( prevUser: IUserDB, user: IUserDB, ) => { - if (!hasKeyDetailsChanged(prevUser, user)) return + if (!hasDetailsForCommentsChanged(prevUser, user)) return const snapshot = await db .collection(DB_ENDPOINTS.discussions) @@ -17,11 +17,12 @@ export const updateDiscussionComments = async ( .get() if (!snapshot.empty) { - const { _id, badges, userImage, location } = user + const { _id, badges, country, userImage, location } = user const creatorImage = getCreatorImage(userImage) + const creatorCountry = location?.countryCode || country || '' const userDetails = { - creatorCountry: location?.countryCode || '', + creatorCountry, creatorImage, isUserVerified: !!badges?.verified, isUserSupporter: !!badges?.supporter, @@ -47,10 +48,3 @@ export const updateDiscussionComments = async ( return console.log(`Updated ${updatedCommentCount} discussion comments`) } } - -const getCreatorImage = (userImage: IUserDB['userImage']) => { - if (userImage && userImage.downloadUrl) { - return userImage.downloadUrl - } - return null -} diff --git a/functions/src/userUpdates/updateMapPins.ts b/functions/src/userUpdates/updateMapPins.ts new file mode 100644 index 0000000000..15ff04cdc5 --- /dev/null +++ b/functions/src/userUpdates/updateMapPins.ts @@ -0,0 +1,62 @@ +import { DB_ENDPOINTS } from 'oa-shared' + +import { db } from '../Firebase/firestoreDB' +import { + getCreatorImage, + getFirstCoverImage, + hasDetailsForMapPinChanged, +} from './utils' + +import type { IUserDB } from '../models' + +export const updateMapPins = async (prevUser: IUserDB, user: IUserDB) => { + if (!hasDetailsForMapPinChanged(prevUser, user)) { + return + } + + const snapshot = await db + .collection(DB_ENDPOINTS.mappins) + .where('_id', '==', user._id) + .get() + + if (snapshot.empty) { + return + } + + const { + _id, + _lastActive, + about, + badges, + country, + coverImages, + displayName, + isContactableByPublic, + profileType, + subType, + userImage, + location, + } = user + const creatorImage = getCreatorImage(userImage) + const coverImage = getFirstCoverImage(coverImages) + const countryCode = location?.countryCode || country || '' + + const creator = { + _lastActive, + about, + badges, + countryCode, + coverImage, + displayName, + isContactableByPublic, + profileType, + subType, + userImage: creatorImage, + } + + // Only one expected + for (const doc of snapshot.docs) { + await doc.ref.update({ creator }) + } + return console.log(`Updated ${_id}'s mapPin`) +} diff --git a/functions/src/userUpdates/hasKeyDetailsChanged.test.ts b/functions/src/userUpdates/utils.test.ts similarity index 55% rename from functions/src/userUpdates/hasKeyDetailsChanged.test.ts rename to functions/src/userUpdates/utils.test.ts index b3ede40f7c..6739fcb3f1 100644 --- a/functions/src/userUpdates/hasKeyDetailsChanged.test.ts +++ b/functions/src/userUpdates/utils.test.ts @@ -1,12 +1,80 @@ import { hasDetailsChanged, - hasKeyDetailsChanged, + hasDetailsForCommentsChanged, + hasDetailsForMapPinChanged, + hasLocationDetailsChanged, hasUserImageChanged, -} from './hasKeyDetailsChanged' +} from './utils' import type { IUserDB } from '../../../src/models' -describe('hasKeyDetailsChanged', () => { +describe('hasDetailsChanged', () => { + it("returns false for every field that's the same", () => { + const user = { + displayName: 'same', + userImage: { + downloadUrl: 'https://more.same/image.jpg', + }, + badges: { + verified: true, + supporter: false, + }, + } as IUserDB + + expect(hasDetailsChanged(user, user)).toEqual([false, false, false, false]) + }) + + it("returns true for every field that's different", () => { + const prevUser = { + displayName: 'old', + userImage: { + downloadUrl: 'https://more.old/image.jpg', + }, + badges: { + verified: true, + supporter: true, + }, + } as IUserDB + + const user = { + displayName: 'new', + userImage: { + downloadUrl: 'https://more.new/image.jpg', + }, + badges: { + verified: false, + supporter: false, + }, + } as IUserDB + + expect(hasDetailsChanged(prevUser, user)).toEqual([true, true, true, true]) + }) +}) + +describe('hasLocationDetailsChanged', () => { + it('returns true when details have changed', () => { + const prevUser = { + location: { countryCode: 'mw' }, + country: 'mw', + } as IUserDB + + const user = { + location: { countryCode: 'uk' }, + country: 'uk', + } as IUserDB + expect(hasLocationDetailsChanged(prevUser, user)).toEqual([true, true]) + }) + + it('returns false when details are the same', () => { + const user = { + location: { countryCode: 'uk' }, + country: 'uk', + } as IUserDB + expect(hasLocationDetailsChanged(user, user)).toEqual([false, false]) + }) +}) + +describe('hasDetailsForCommentsChanged', () => { it('returns true when details have changed', () => { const prevUser = { displayName: 'old name', @@ -17,12 +85,11 @@ describe('hasKeyDetailsChanged', () => { userImage: { downloadUrl: 'http//etc.', }, - location: { countryCode: 'USA' }, badges: { verified: true }, } as IUserDB - expect(hasKeyDetailsChanged(prevUser, user)).toEqual(true) + expect(hasDetailsForCommentsChanged(prevUser, user)).toEqual(true) }) it('returns false when details are the same', () => { @@ -39,11 +106,56 @@ describe('hasKeyDetailsChanged', () => { }, } as IUserDB - expect(hasKeyDetailsChanged(user, user)).toEqual(false) + expect(hasDetailsForCommentsChanged(user, user)).toEqual(false) }) }) -describe('hasKeyDetailsChanged', () => { +describe('hasDetailsForMapPinChanged', () => { + it('returns true when details have changed', () => { + const prevUser = { + _lastActive: 'yesterday', + about: 'Old description', + displayName: 'id_name', + isContactableByPublic: true, + profileType: 'member', + subType: null, + } as IUserDB + const user = { + _authID: '', + _lastActive: 'today', + about: 'Super new description', + coverImages: [], + displayName: 'id name', + isContactableByPublic: false, + links: [], + profileType: 'workspace', + subType: 'shredder', + userName: 'idname', + verified: false, + } as IUserDB + + expect(hasDetailsForMapPinChanged(prevUser, user)).toEqual(true) + }) + + it('returns false when details are the same', () => { + const user = { + _authID: '', + _lastActive: 'today', + about: 'Super new description', + coverImages: [], + displayName: 'id name', + isContactableByPublic: false, + profileType: 'workspace', + subType: 'shredder', + userName: 'idname', + verified: false, + } as IUserDB + + expect(hasDetailsForMapPinChanged(user, user)).toEqual(false) + }) +}) + +describe('hasUserImageChanged', () => { it('returns false when userImage array is missing from both', () => { const user = {} as IUserDB expect(hasUserImageChanged(user, user)).toEqual(false) @@ -84,61 +196,3 @@ describe('hasKeyDetailsChanged', () => { expect(hasUserImageChanged(prevUser, user)).toEqual(true) }) }) - -describe('hasDetailsChanged', () => { - it("returns false for every field that's the same", () => { - const user = { - displayName: 'same', - location: { countryCode: 'same' }, - userImage: { - downloadUrl: 'https://more.same/image.jpg', - }, - badges: { - verified: true, - supporter: false, - }, - } as IUserDB - - expect(hasDetailsChanged(user, user)).toEqual([ - false, - false, - false, - false, - false, - ]) - }) - - it("returns truw for every field that's different", () => { - const prevUser = { - displayName: 'old', - location: { countryCode: 'old' }, - userImage: { - downloadUrl: 'https://more.old/image.jpg', - }, - badges: { - verified: true, - supporter: true, - }, - } as IUserDB - - const user = { - displayName: 'new', - location: { countryCode: 'new' }, - userImage: { - downloadUrl: 'https://more.new/image.jpg', - }, - badges: { - verified: false, - supporter: false, - }, - } as IUserDB - - expect(hasDetailsChanged(prevUser, user)).toEqual([ - true, - true, - true, - true, - true, - ]) - }) -}) diff --git a/functions/src/userUpdates/utils.ts b/functions/src/userUpdates/utils.ts new file mode 100644 index 0000000000..91ba552cf6 --- /dev/null +++ b/functions/src/userUpdates/utils.ts @@ -0,0 +1,75 @@ +import { valuesAreDeepEqual } from '../Utils' + +import type { IUserDB } from '../models' + +export const hasDetailsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean[] => { + return [ + prevUser.displayName !== user.displayName, + hasUserImageChanged(prevUser, user), + prevUser.badges?.verified !== user.badges?.verified, + prevUser.badges?.supporter !== user.badges?.supporter, + ] +} + +export const hasLocationDetailsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean[] => { + return [ + prevUser.location?.countryCode !== user.location?.countryCode, + prevUser.country !== user.country, + ] +} + +export const hasDetailsForCommentsChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + const detailsChanged = [ + ...hasDetailsChanged(prevUser, user), + ...hasLocationDetailsChanged(prevUser, user), + ] + return !!detailsChanged.find((detail) => detail === true) +} + +export const hasDetailsForMapPinChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + const detailsChanged = [ + prevUser._lastActive !== user._lastActive, + prevUser.about !== user.about, + prevUser.displayName !== user.displayName, + prevUser.isContactableByPublic !== user.isContactableByPublic, + prevUser.profileType !== user.profileType, + prevUser.subType !== user.subType, + ...hasDetailsChanged(prevUser, user), + ...hasLocationDetailsChanged(prevUser, user), + ] + return !!detailsChanged.find((detail) => detail === true) +} + +export const hasUserImageChanged = ( + prevUser: IUserDB, + user: IUserDB, +): boolean => { + if (!prevUser.userImage && !user.userImage) return false + + if (prevUser.userImage && user.userImage) { + return !valuesAreDeepEqual(prevUser.userImage, user.userImage) + } + + if (prevUser.userImage && !user.userImage) return true + if (!prevUser.userImage && user.userImage) return true +} + +export const getCreatorImage = (userImage: IUserDB['userImage']) => { + return userImage?.downloadUrl || null +} + +export const getFirstCoverImage = (coverImages: IUserDB['coverImages']) => { + return coverImages?.[0]?.downloadUrl || null +}