Guest User

Untitled

a guest
Jul 19th, 2018
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.79 KB | None | 0 0
  1. import { Query } from '@google-cloud/firestore'
  2. import * as admin from 'firebase-admin'
  3. import * as functions from 'firebase-functions'
  4.  
  5. type Filter = (query: Query) => Query
  6. type FieldChanges = { [key: string]: number | { delta: number, filter?: Filter } }
  7. type DocumentChanges = { [id: string]: FieldChanges }
  8.  
  9. /**
  10. * Updates counter document using delta value or creates counter
  11. * by calculating size of corresponding Firestore collection.
  12. *
  13. * @param counterCollection - Firestore collection that contains counters
  14. * @param collection - Firestore collection the size of which should be counted
  15. * @param documentChanges - Object that describes counter transformations
  16. */
  17. function applyCounterChanges(
  18. counterCollection: string,
  19. collection: string,
  20. documentChanges: DocumentChanges
  21. ) {
  22. const db = admin.firestore()
  23.  
  24. const collectionRef = db.collection(collection)
  25.  
  26. return db.runTransaction(async t => {
  27. const tasks = await Promise.all(Object.entries(documentChanges).map(async ([counterDocument, changes]) => {
  28. const counterRef = db.collection(counterCollection).doc(counterDocument)
  29. const doc = await t.get(counterRef)
  30.  
  31. const entries = Object.entries(changes)
  32. const counts = await Promise.all(entries.map(async ([key, fieldChange]) => {
  33. const { delta, filter } = typeof fieldChange === 'number'
  34. ? { delta: fieldChange, filter: null }
  35. : fieldChange
  36.  
  37. const savedCount = doc.get(key)
  38. return typeof savedCount === 'number'
  39. ? savedCount + delta
  40. : (await t.get(filter ? filter(collectionRef) : collectionRef)).size
  41. }))
  42. const data = entries.reduce<{ [key: string]: number }>((result, [key], index) => ({
  43. ...result,
  44. [key]: counts[index],
  45. }), {})
  46.  
  47. return Object.keys(data).length > 0
  48. ? () => doc.exists
  49. ? t.update(counterRef, data)
  50. : t.create(counterRef, data)
  51. : undefined
  52. }))
  53.  
  54. await Promise.all(tasks
  55. .map(task => task ? task() : undefined)
  56. .filter(promise => Boolean(promise)))
  57. })
  58. }
  59.  
  60. /**
  61. * Creates Cloud Firestore Trigger that updates a correspodning counter of the collection.
  62. *
  63. * @param collection - Firestore collection name
  64. * @param fields - Array of document fields that should be counted
  65. */
  66. const createCounter = (collection: string, fields: string[] = []) =>
  67. functions.firestore
  68. .document(`${collection}/{id}`)
  69. .onWrite(change => applyCounterChanges('counters', collection, {
  70. [collection]: ['', ...fields].reduce<FieldChanges>((result, field_, index) => {
  71. const [before, after] = [
  72. { snapshot: change.before, delta: -1 },
  73. { snapshot: change.after, delta: 1 },
  74. ].map(({ snapshot, delta }) => {
  75. if (snapshot.exists) {
  76. const values = fields
  77. .slice(0, index)
  78. .map(field => ({ key: field, value: snapshot.get(field) }))
  79. return {
  80. key: values
  81. .map(({ key, value }) => `${key}.${value}`)
  82. .concat('total')
  83. .join('.'),
  84. value: {
  85. delta,
  86. filter: (query_: Query) => values
  87. .reduce((query, { key, value }) => query.where(key, '==', value), query_),
  88. },
  89. }
  90. }
  91. return {}
  92. })
  93.  
  94. if (before.key === after.key) {
  95. return result
  96. }
  97.  
  98. return {
  99. ...result,
  100. ...(before.key ? { [before.key]: before.value } : {}),
  101. ...(after.key ? { [after.key]: after.value } : {}),
  102. }
  103. }, {}),
  104. }))
  105.  
  106. function populateCount(
  107. collection: string,
  108. field: string,
  109. relateCollection: string,
  110. relateField: string = collection
  111. ) {
  112. return functions.firestore
  113. .document(`${collection}/{id}`)
  114. .onWrite(change => {
  115. const [beforeRelateId, afterRelateId] = [change.before, change.after]
  116. .map(snapshot => snapshot.get(field))
  117.  
  118. const [beforeChange, afterChange] = [[beforeRelateId, -1], [afterRelateId, 1]]
  119. .map(([relateId, delta]) => ({
  120. [relateId]: {
  121. [relateField]: {
  122. delta,
  123. filter: query => query.where(field, '==', relateId),
  124. }
  125. }
  126. }))
  127.  
  128. const changes = !beforeRelateId && afterRelateId
  129. ? afterChange
  130. : beforeRelateId && !afterRelateId
  131. ? beforeChange
  132. : beforeRelateId !== afterRelateId
  133. ? { ...beforeChange, ...afterChange }
  134. : {}
  135.  
  136. return applyCounterChanges(relateCollection, collection, changes)
  137. })
  138. }
  139.  
  140. export const codesCounter = createCounter('codes')
  141.  
  142. export const milestonesCounter = createCounter('milestones')
  143.  
  144. export const contentsCounter = createCounter('contents', [
  145. 'milestone',
  146. 'type',
  147. ])
  148.  
  149. export const iconsPopulateCount = populateCount('contents', 'icon', 'icons')
Add Comment
Please, Sign In to add comment