postgres.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. const { Blob } = require('overleaf-editor-core')
  2. const assert = require('../assert')
  3. const knex = require('../knex')
  4. /**
  5. * Set up the initial data structures for a project
  6. */
  7. async function initialize(projectId) {
  8. // Nothing to do for Postgres
  9. }
  10. /**
  11. * Return blob metadata for the given project and hash
  12. */
  13. async function findBlob(projectId, hash) {
  14. projectId = parseInt(projectId, 10)
  15. assert.integer(projectId, 'bad projectId')
  16. assert.blobHash(hash, 'bad hash')
  17. const binaryHash = hashToBuffer(hash)
  18. const record = await knex('project_blobs')
  19. .select('hash_bytes', 'byte_length', 'string_length')
  20. .where({
  21. project_id: projectId,
  22. hash_bytes: binaryHash,
  23. })
  24. .first()
  25. return recordToBlob(record)
  26. }
  27. /**
  28. * Read multiple blob metadata records by hexadecimal hashes.
  29. *
  30. * @param {Array.<string>} hashes hexadecimal SHA-1 hashes
  31. * @return {Promise.<Array.<Blob?>>} no guarantee on order
  32. */
  33. async function findBlobs(projectId, hashes) {
  34. projectId = parseInt(projectId, 10)
  35. assert.integer(projectId, 'bad projectId')
  36. assert.array(hashes, 'bad hashes: not array')
  37. hashes.forEach(function (hash) {
  38. assert.blobHash(hash, 'bad hash')
  39. })
  40. const binaryHashes = hashes.map(hashToBuffer)
  41. const records = await knex('project_blobs')
  42. .select('hash_bytes', 'byte_length', 'string_length')
  43. .where('project_id', projectId)
  44. .whereIn('hash_bytes', binaryHashes)
  45. const blobs = records.map(recordToBlob)
  46. return blobs
  47. }
  48. /**
  49. * Return metadata for all blobs in the given project
  50. */
  51. async function getProjectBlobs(projectId) {
  52. projectId = parseInt(projectId, 10)
  53. assert.integer(projectId, 'bad projectId')
  54. const records = await knex('project_blobs')
  55. .select('hash_bytes', 'byte_length', 'string_length')
  56. .where({
  57. project_id: projectId,
  58. })
  59. const blobs = records.map(recordToBlob)
  60. return blobs
  61. }
  62. /**
  63. * Return metadata for all blobs in the given project
  64. * @param {Array<number>} projectIds
  65. * @return {Promise<{ nBlobs: number, blobs: Map<number, Array<Blob>> }>}
  66. */
  67. async function getProjectBlobsBatch(projectIds) {
  68. for (const projectId of projectIds) {
  69. assert.integer(projectId, 'bad projectId')
  70. }
  71. let nBlobs = 0
  72. const blobs = new Map()
  73. if (projectIds.length === 0) return { nBlobs, blobs }
  74. const cursor = knex('project_blobs')
  75. .select('project_id', 'hash_bytes', 'byte_length', 'string_length')
  76. .whereIn('project_id', projectIds)
  77. .stream()
  78. for await (const record of cursor) {
  79. const found = blobs.get(record.project_id)
  80. if (found) {
  81. found.push(recordToBlob(record))
  82. } else {
  83. blobs.set(record.project_id, [recordToBlob(record)])
  84. }
  85. nBlobs++
  86. }
  87. return { nBlobs, blobs }
  88. }
  89. /**
  90. * Add a blob's metadata to the blobs table after it has been uploaded.
  91. */
  92. async function insertBlob(projectId, blob) {
  93. projectId = parseInt(projectId, 10)
  94. assert.integer(projectId, 'bad projectId')
  95. await knex('project_blobs')
  96. .insert(blobToRecord(projectId, blob))
  97. .onConflict(['project_id', 'hash_bytes'])
  98. .ignore()
  99. }
  100. /**
  101. * Deletes all blobs for a given project
  102. */
  103. async function deleteBlobs(projectId) {
  104. projectId = parseInt(projectId, 10)
  105. assert.integer(projectId, 'bad projectId')
  106. await knex('project_blobs').where('project_id', projectId).delete()
  107. }
  108. function blobToRecord(projectId, blob) {
  109. return {
  110. project_id: projectId,
  111. hash_bytes: hashToBuffer(blob.hash),
  112. byte_length: blob.getByteLength(),
  113. string_length: blob.getStringLength(),
  114. }
  115. }
  116. function recordToBlob(record) {
  117. if (!record) return
  118. return new Blob(
  119. hashFromBuffer(record.hash_bytes),
  120. record.byte_length,
  121. record.string_length
  122. )
  123. }
  124. function hashToBuffer(hash) {
  125. if (!hash) return
  126. return Buffer.from(hash, 'hex')
  127. }
  128. function hashFromBuffer(buffer) {
  129. if (!buffer) return
  130. return buffer.toString('hex')
  131. }
  132. module.exports = {
  133. initialize,
  134. findBlob,
  135. findBlobs,
  136. getProjectBlobs,
  137. getProjectBlobsBatch,
  138. insertBlob,
  139. deleteBlobs,
  140. }