HealthCheckController.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { db, ObjectId } from './mongodb.js'
  2. import settings from '@overleaf/settings'
  3. import logger from '@overleaf/logger'
  4. import {
  5. fetchJson,
  6. fetchNothing,
  7. RequestFailedError,
  8. } from '@overleaf/fetch-utils'
  9. import { expressify } from '@overleaf/promise-utils'
  10. const { port } = settings.internal.notifications
  11. function makeUrl(userId, endPath = '') {
  12. return new URL(`/user/${userId}/${endPath}`, `http://127.0.0.1:${port}`)
  13. }
  14. async function makeNotification(notificationKey, userId) {
  15. const postOpts = {
  16. method: 'POST',
  17. json: {
  18. key: notificationKey,
  19. messageOpts: '',
  20. templateKey: 'f4g5',
  21. user_id: userId,
  22. },
  23. signal: AbortSignal.timeout(5000),
  24. }
  25. const url = makeUrl(userId)
  26. await fetchNothing(url, postOpts)
  27. }
  28. async function getUsersNotifications(userId) {
  29. const url = makeUrl(userId)
  30. try {
  31. return await fetchJson(url, {
  32. signal: AbortSignal.timeout(5000),
  33. })
  34. } catch (err) {
  35. if (err instanceof RequestFailedError) {
  36. logger.err({ err }, 'Non-2xx status code received')
  37. throw err
  38. }
  39. logger.err({ err }, 'Health Check: error getting notification')
  40. throw err
  41. }
  42. }
  43. async function userHasNotification(userId, notificationKey) {
  44. const body = await getUsersNotifications(userId)
  45. const hasNotification = body.some(
  46. notification =>
  47. notification.key === notificationKey && notification.user_id === userId
  48. )
  49. if (hasNotification) {
  50. return body
  51. } else {
  52. logger.err(
  53. { body, notificationKey },
  54. 'Health Check: notification not in response'
  55. )
  56. throw new Error('notification not found in response')
  57. }
  58. }
  59. async function cleanupNotifications(userId) {
  60. await db.notifications.deleteOne({ user_id: userId })
  61. }
  62. async function deleteNotification(userId, notificationId, notificationKey) {
  63. const deleteByIdUrl = makeUrl(userId, `notification/${notificationId}`)
  64. try {
  65. await fetchNothing(deleteByIdUrl, {
  66. signal: AbortSignal.timeout(5000),
  67. method: 'DELETE',
  68. })
  69. } catch (err) {
  70. logger.err(
  71. { err, url: deleteByIdUrl },
  72. 'Health Check: error cleaning up notification'
  73. )
  74. throw err
  75. }
  76. const deleteByKeyUrl = makeUrl(userId)
  77. try {
  78. await fetchNothing(deleteByKeyUrl, {
  79. signal: AbortSignal.timeout(5000),
  80. method: 'DELETE',
  81. body: {
  82. key: notificationKey,
  83. },
  84. })
  85. } catch (err) {
  86. logger.err(
  87. { err, url: deleteByKeyUrl },
  88. 'Health Check: error cleaning up notification'
  89. )
  90. throw err
  91. }
  92. }
  93. async function check(req, res) {
  94. const userId = new ObjectId().toString()
  95. let notificationKey = `smoke-test-notification-${new ObjectId()}`
  96. logger.debug({ userId, key: notificationKey }, 'Health Check: running')
  97. await makeNotification(notificationKey, userId)
  98. try {
  99. const body = await userHasNotification(userId, notificationKey)
  100. const notificationId = body[0]._id
  101. notificationKey = body[0].key
  102. logger.debug(
  103. { notificationId, notificationKey },
  104. 'Health Check: doing cleanup'
  105. )
  106. await deleteNotification(userId, notificationId, notificationKey)
  107. res.sendStatus(200)
  108. } catch (err) {
  109. logger.err({ err }, 'Health Check: error running health check')
  110. res.sendStatus(500)
  111. } finally {
  112. await cleanupNotifications(userId)
  113. }
  114. }
  115. export default {
  116. check: expressify(check),
  117. }