From d385bbd90eb6fb26d8a1ecb3507755a155ce99db Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Tue, 3 May 2022 21:36:25 -0700 Subject: [PATCH] Add script to denormalize avatars into contracts/comments --- .../src/scripts/denormalize-avatar-urls.ts | 101 ++++++++++++++++++ functions/src/scripts/denormalize.ts | 48 +++++++++ 2 files changed, 149 insertions(+) create mode 100644 functions/src/scripts/denormalize-avatar-urls.ts create mode 100644 functions/src/scripts/denormalize.ts diff --git a/functions/src/scripts/denormalize-avatar-urls.ts b/functions/src/scripts/denormalize-avatar-urls.ts new file mode 100644 index 00000000..148c836b --- /dev/null +++ b/functions/src/scripts/denormalize-avatar-urls.ts @@ -0,0 +1,101 @@ +// Script for lining up users and contracts/comments to make sure the denormalized avatar URLs in the contracts and +// comments match the user avatar URLs. + +import * as admin from 'firebase-admin' +import { initAdmin } from './script-init' +import { + DocumentCorrespondence, + findDiffs, + describeDiff, + applyDiff, +} from './denormalize' +import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore' + +initAdmin() +const firestore = admin.firestore() + +async function getUsersById( + transaction: Transaction +): Promise> { + const results = new Map() + const users = await transaction.get(firestore.collection('users')) + users.forEach((doc) => { + results.set(doc.get('id'), doc) + }) + console.log(`Found ${results.size} unique users.`) + return results +} + +async function getContractsByUserId( + transaction: Transaction +): Promise> { + let n = 0 + const results = new Map() + const contracts = await transaction.get(firestore.collection('contracts')) + contracts.forEach((doc) => { + let creatorId = doc.get('creatorId') + let creatorContracts = results.get(creatorId) || [] + creatorContracts.push(doc) + results.set(creatorId, creatorContracts) + n++ + }) + console.log(`Found ${n} contracts from ${results.size} unique users.`) + return results +} + +async function getCommentsByUserId( + transaction: Transaction +): Promise> { + let n = 0 + let results = new Map() + let comments = await transaction.get(firestore.collectionGroup('comments')) + comments.forEach((doc) => { + let userId = doc.get('userId') + let userComments = results.get(userId) || [] + userComments.push(doc) + results.set(userId, userComments) + n++ + }) + console.log(`Found ${n} comments from ${results.size} unique users.`) + return results +} + +if (require.main === module) { + admin.firestore().runTransaction(async (transaction) => { + let [usersById, contractsByUserId, commentsByUserId] = await Promise.all([ + getUsersById(transaction), + getContractsByUserId(transaction), + getCommentsByUserId(transaction), + ]) + + let usersContracts = Array.from( + usersById.entries(), + ([id, doc]): DocumentCorrespondence => { + return [doc, contractsByUserId.get(id) || []] + } + ) + let contractDiffs = findDiffs( + usersContracts, + 'avatarUrl', + 'creatorAvatarUrl' + ) + console.log(`Found ${contractDiffs.length} contracts with mismatches.`) + contractDiffs.forEach((d) => { + console.log(describeDiff(d)) + applyDiff(transaction, d) + }) + + let usersComments = Array.from( + usersById.entries(), + ([id, doc]): DocumentCorrespondence => { + return [doc, commentsByUserId.get(id) || []] + } + ) + let commentDiffs = findDiffs(usersComments, 'avatarUrl', 'userAvatarUrl') + console.log(`Found ${commentDiffs.length} comments with mismatches.`) + commentDiffs.forEach((d) => { + console.log(describeDiff(d)) + applyDiff(transaction, d) + }) + }) +} diff --git a/functions/src/scripts/denormalize.ts b/functions/src/scripts/denormalize.ts new file mode 100644 index 00000000..aa5f00ab --- /dev/null +++ b/functions/src/scripts/denormalize.ts @@ -0,0 +1,48 @@ +// Helper functions for maintaining the relationship between fields in one set of documents and denormalized copies in +// another set of documents. + +import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore' + +export type DocumentValue = { + doc: DocumentSnapshot + field: string + val: any +} +export type DocumentCorrespondence = [DocumentSnapshot, DocumentSnapshot[]] +export type DocumentDiff = { + src: DocumentValue + dest: DocumentValue +} + +export function findDiffs( + docs: DocumentCorrespondence[], + srcPath: string, + destPath: string +): DocumentDiff[] { + let diffs = [] + for (let [srcDoc, destDocs] of docs) { + const srcVal = srcDoc.get(srcPath) + for (let destDoc of destDocs) { + const destVal = destDoc.get(destPath) + if (destVal !== srcVal) { + diffs.push({ + src: { doc: srcDoc, field: srcPath, val: srcVal }, + dest: { doc: destDoc, field: destPath, val: destVal }, + }) + } + } + } + return diffs +} + +export function describeDiff(diff: DocumentDiff): string { + function describeDocVal(x: DocumentValue): string { + return `${x.doc.ref.path}.${x.field}: ${x.val}` + } + return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}` +} + +export function applyDiff(transaction: Transaction, diff: DocumentDiff) { + let { src, dest } = diff + transaction.update(dest.doc.ref, dest.field, src.val) +}