| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- const {
- db,
- READ_PREFERENCE_PRIMARY,
- READ_PREFERENCE_SECONDARY,
- } = require('../app/src/infrastructure/mongodb')
- const { promiseMapWithLimit } = require('../app/src/util/promises')
- async function getDeletedProject(projectId, readPreference) {
- return await db.deletedProjects.findOne(
- { 'deleterData.deletedProjectId': projectId },
- {
- // There is no index on .project. Pull down something small.
- projection: { 'project._id': 1 },
- readPreference,
- }
- )
- }
- async function getProject(projectId, readPreference) {
- return await db.projects.findOne(
- { _id: projectId },
- {
- // Pulling down an empty object is fine for differentiating with null.
- projection: { _id: 0 },
- readPreference,
- }
- )
- }
- async function checkProjectExistsWithReadPreference(projectId, readPreference) {
- // NOTE: Possible race conditions!
- // There are two processes which are racing with our queries:
- // 1. project deletion
- // 2. project restoring
- // For 1. we check the projects collection before deletedProjects.
- // If a project were to be delete in this very moment, we should see the
- // soft-deleted entry which is created before deleting the projects entry.
- // For 2. we check the projects collection after deletedProjects again.
- // If a project were to be restored in this very moment, it is very likely
- // to see the projects entry again.
- // Unlikely edge case: Restore+Deletion in rapid succession.
- // We could add locking to the ProjectDeleter for ruling ^ out.
- if (await getProject(projectId, readPreference)) {
- // The project is live.
- return true
- }
- const deletedProject = await getDeletedProject(projectId, readPreference)
- if (deletedProject && deletedProject.project) {
- // The project is registered for hard-deletion.
- return true
- }
- if (await getProject(projectId, readPreference)) {
- // The project was just restored.
- return true
- }
- // The project does not exist.
- return false
- }
- async function checkProjectExistsOnPrimary(projectId) {
- return await checkProjectExistsWithReadPreference(
- projectId,
- READ_PREFERENCE_PRIMARY
- )
- }
- async function checkProjectExistsOnSecondary(projectId) {
- return await checkProjectExistsWithReadPreference(
- projectId,
- READ_PREFERENCE_SECONDARY
- )
- }
- async function getHardDeletedProjectIds({
- projectIds,
- READ_CONCURRENCY_PRIMARY,
- READ_CONCURRENCY_SECONDARY,
- }) {
- const doubleCheckProjectIdsOnPrimary = []
- async function checkProjectOnSecondary(projectId) {
- if (await checkProjectExistsOnSecondary(projectId)) {
- // Finding a project with secondary confidence is sufficient.
- return
- }
- // At this point, the secondaries deem this project as having orphaned docs.
- doubleCheckProjectIdsOnPrimary.push(projectId)
- }
- const hardDeletedProjectIds = []
- async function checkProjectOnPrimary(projectId) {
- if (await checkProjectExistsOnPrimary(projectId)) {
- // The project is actually live.
- return
- }
- hardDeletedProjectIds.push(projectId)
- }
- await promiseMapWithLimit(
- READ_CONCURRENCY_SECONDARY,
- projectIds,
- checkProjectOnSecondary
- )
- await promiseMapWithLimit(
- READ_CONCURRENCY_PRIMARY,
- doubleCheckProjectIdsOnPrimary,
- checkProjectOnPrimary
- )
- return hardDeletedProjectIds
- }
- module.exports = {
- getHardDeletedProjectIds,
- }
|