Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { Query } from '@google-cloud/firestore'
- import * as admin from 'firebase-admin'
- import * as functions from 'firebase-functions'
- type Filter = (query: Query) => Query
- type FieldChanges = { [key: string]: number | { delta: number, filter?: Filter } }
- type DocumentChanges = { [id: string]: FieldChanges }
- /**
- * Updates counter document using delta value or creates counter
- * by calculating size of corresponding Firestore collection.
- *
- * @param counterCollection - Firestore collection that contains counters
- * @param collection - Firestore collection the size of which should be counted
- * @param documentChanges - Object that describes counter transformations
- */
- function applyCounterChanges(
- counterCollection: string,
- collection: string,
- documentChanges: DocumentChanges
- ) {
- const db = admin.firestore()
- const collectionRef = db.collection(collection)
- return db.runTransaction(async t => {
- const tasks = await Promise.all(Object.entries(documentChanges).map(async ([counterDocument, changes]) => {
- const counterRef = db.collection(counterCollection).doc(counterDocument)
- const doc = await t.get(counterRef)
- const entries = Object.entries(changes)
- const counts = await Promise.all(entries.map(async ([key, fieldChange]) => {
- const { delta, filter } = typeof fieldChange === 'number'
- ? { delta: fieldChange, filter: null }
- : fieldChange
- const savedCount = doc.get(key)
- return typeof savedCount === 'number'
- ? savedCount + delta
- : (await t.get(filter ? filter(collectionRef) : collectionRef)).size
- }))
- const data = entries.reduce<{ [key: string]: number }>((result, [key], index) => ({
- ...result,
- [key]: counts[index],
- }), {})
- return Object.keys(data).length > 0
- ? () => doc.exists
- ? t.update(counterRef, data)
- : t.create(counterRef, data)
- : undefined
- }))
- await Promise.all(tasks
- .map(task => task ? task() : undefined)
- .filter(promise => Boolean(promise)))
- })
- }
- /**
- * Creates Cloud Firestore Trigger that updates a correspodning counter of the collection.
- *
- * @param collection - Firestore collection name
- * @param fields - Array of document fields that should be counted
- */
- const createCounter = (collection: string, fields: string[] = []) =>
- functions.firestore
- .document(`${collection}/{id}`)
- .onWrite(change => applyCounterChanges('counters', collection, {
- [collection]: ['', ...fields].reduce<FieldChanges>((result, field_, index) => {
- const [before, after] = [
- { snapshot: change.before, delta: -1 },
- { snapshot: change.after, delta: 1 },
- ].map(({ snapshot, delta }) => {
- if (snapshot.exists) {
- const values = fields
- .slice(0, index)
- .map(field => ({ key: field, value: snapshot.get(field) }))
- return {
- key: values
- .map(({ key, value }) => `${key}.${value}`)
- .concat('total')
- .join('.'),
- value: {
- delta,
- filter: (query_: Query) => values
- .reduce((query, { key, value }) => query.where(key, '==', value), query_),
- },
- }
- }
- return {}
- })
- if (before.key === after.key) {
- return result
- }
- return {
- ...result,
- ...(before.key ? { [before.key]: before.value } : {}),
- ...(after.key ? { [after.key]: after.value } : {}),
- }
- }, {}),
- }))
- function populateCount(
- collection: string,
- field: string,
- relateCollection: string,
- relateField: string = collection
- ) {
- return functions.firestore
- .document(`${collection}/{id}`)
- .onWrite(change => {
- const [beforeRelateId, afterRelateId] = [change.before, change.after]
- .map(snapshot => snapshot.get(field))
- const [beforeChange, afterChange] = [[beforeRelateId, -1], [afterRelateId, 1]]
- .map(([relateId, delta]) => ({
- [relateId]: {
- [relateField]: {
- delta,
- filter: query => query.where(field, '==', relateId),
- }
- }
- }))
- const changes = !beforeRelateId && afterRelateId
- ? afterChange
- : beforeRelateId && !afterRelateId
- ? beforeChange
- : beforeRelateId !== afterRelateId
- ? { ...beforeChange, ...afterChange }
- : {}
- return applyCounterChanges(relateCollection, collection, changes)
- })
- }
- export const codesCounter = createCounter('codes')
- export const milestonesCounter = createCounter('milestones')
- export const contentsCounter = createCounter('contents', [
- 'milestone',
- 'type',
- ])
- export const iconsPopulateCount = populateCount('contents', 'icon', 'icons')
Add Comment
Please, Sign In to add comment