From 3f0c3a66b44352307a8ee397d597f1bb3d2a0fa8 Mon Sep 17 00:00:00 2001 From: WEIREN FENG Date: Wed, 14 Oct 2026 16:24:29 -0700 Subject: [PATCH] Profiles: a renamed organizer is renamed on their meetups too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A meetup keeps its organizer's name and photo, shown as "Organized by …". onUserUpdated copied a new name onto posts, comments, rosters and the rest, but not onto the meetups the person organizes, which kept the old name until the meetup itself was edited. It now updates those too. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/__tests__/organizer-name-sync.test.ts | 49 +++++++++++++++++++ functions/src/users.ts | 9 ++++ 2 files changed, 58 insertions(+) create mode 100644 functions/src/__tests__/organizer-name-sync.test.ts diff --git a/functions/src/__tests__/organizer-name-sync.test.ts b/functions/src/__tests__/organizer-name-sync.test.ts new file mode 100644 index 0000000..d37657d --- /dev/null +++ b/functions/src/__tests__/organizer-name-sync.test.ts @@ -0,0 +1,49 @@ +import "./setup"; +import { afterAll, beforeEach, describe, expect, it } from "vitest"; +import { db } from "../platform"; +import { onUserUpdated } from "../users"; +import { captureSnapshot, deliverWritten, newEventId, purge } from "./helpers"; + +// A meetup keeps its organizer's name and photo, shown as "Organized by …". +// onUserUpdated copied a new name onto posts, comments, rosters and the rest, +// but not onto the meetups the person organizes, which kept the old name until +// the meetup itself was edited. + +const ORGANIZER = "rename-organizer"; +const SOMEONE_ELSE = "rename-someone-else"; +const USER = `users/${ORGANIZER}`; +const THEIRS = "meetups/rename-theirs"; +const NOT_THEIRS = "meetups/rename-not-theirs"; +const POST = "posts/rename-post"; +const OLD_PHOTO = "https://res.cloudinary.com/demo/image/upload/old.jpg"; +const NEW_PHOTO = "https://res.cloudinary.com/demo/image/upload/new.jpg"; + +async function wipe() { + await purge(USER, THEIRS, NOT_THEIRS, POST); +} + +beforeEach(async () => { + await wipe(); + await db.doc(USER).set({ displayName: "Old Name", avatarUrl: OLD_PHOTO }); + await db.doc(THEIRS).set({ organizerId: ORGANIZER, organizerName: "Old Name", organizerAvatar: OLD_PHOTO, title: "Theirs", status: "upcoming" }); + await db.doc(NOT_THEIRS).set({ organizerId: SOMEONE_ELSE, organizerName: "Someone Else", organizerAvatar: OLD_PHOTO, title: "Not theirs", status: "upcoming" }); + await db.doc(POST).set({ authorId: ORGANIZER, authorName: "Old Name", authorAvatar: OLD_PHOTO, text: "a post" }); +}); +afterAll(wipe); + +describe("a renamed organizer", () => { + it("is named, with the new photo, on the meetups they organize", async () => { + const before = await captureSnapshot(USER); + await db.doc(USER).update({ displayName: "New Name", avatarUrl: NEW_PHOTO }); + const after = await captureSnapshot(USER); + + await deliverWritten(onUserUpdated, before, after, { userId: ORGANIZER }, newEventId("user")); + + const theirs = (await db.doc(THEIRS).get()).data(); + expect([theirs?.organizerName, theirs?.organizerAvatar]).toEqual(["New Name", NEW_PHOTO]); + const post = (await db.doc(POST).get()).data(); + expect([post?.authorName, post?.authorAvatar]).toEqual(["New Name", NEW_PHOTO]); + const notTheirs = (await db.doc(NOT_THEIRS).get()).data(); + expect(notTheirs?.organizerName, "someone else's meetup changed").toBe("Someone Else"); + }); +}); diff --git a/functions/src/users.ts b/functions/src/users.ts index bd7c3da..62fd2de 100644 --- a/functions/src/users.ts +++ b/functions/src/users.ts @@ -194,6 +194,14 @@ export const onUserUpdated = onDocumentWritten( if (nameChanged) checkinFields.userName = after.displayName; if (avatarChanged) checkinFields.userAvatar = after.avatarUrl; + // The meetups they organize carry their name and photo as well, shown as + // "Organized by …". updateMeetupCallable refreshed those only when the + // meetup itself was edited, so a renamed organizer kept the old name on + // every meetup they had not touched since. + const organizerFields: Record = {}; + if (nameChanged) organizerFields.organizerName = after.displayName; + if (avatarChanged) organizerFields.organizerAvatar = after.avatarUrl; + const syncTasks: Array<[string, () => Promise]> = [ ["posts", () => syncCollection(db.collection("posts").where("authorId", "==", userId), postFields)], ["comments", () => syncCollection(db.collectionGroup("comments").where("authorId", "==", userId), commentFields)], @@ -203,6 +211,7 @@ export const onUserUpdated = onDocumentWritten( ["family", () => syncCollection(db.collectionGroup("family").where("userId", "==", userId), familyFields)], ["followers", () => syncCollection(db.collectionGroup("followers").where("userId", "==", userId), followerMirrorFields)], ["checkins", () => syncCollection(db.collectionGroup("checkins").where("userId", "==", userId), checkinFields)], + ["meetups", () => syncCollection(db.collection("meetups").where("organizerId", "==", userId), organizerFields)], ]; const syncFailures = await Promise.all(