All files / src/databases/mongo mongoBeforeRequest.ts

15.38% Statements 14/91
100% Branches 0/0
0% Functions 0/1
15.38% Lines 14/91

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                                                          
 
import { LocalConfigParsed } from './types/mongoDbTypes'
import { hookInterpreterExpose } from '../0_hooks/hookInterpreterExpose'
import { mongoFilterHookInterpreter } from './hooks/mongoFilterHookInterpreter'
import { mongoSanitizeFilter } from './services/mongoSanitizeFilter'
 
import { unPopulate } from './services/populateService'
import { applyMaskIncludingOnPopulatedFieldsRecursive } from './services/maskService'
import event from '../../event'
 
import { getId } from 'topkat-utils'
import { getProjectDatabaseModels } from '../../helpers/getProjectModelsAndDaos'
 
export async function mongoBeforeRequest(
    ctx: Ctx,
    localConfig: LocalConfigParsed,
): Promise<void> {
    localConfig.ressourceId = getId(localConfig.inputFields) || getId(localConfig?.filter)

    const { modelName, method, dbName, dbId, ressourceId } = localConfig

    const hasFields = localConfig.inputFields && Object.keys(localConfig.inputFields).length

    const errExtraInfos = { modelName, dbName, dbId, method }

    await hookInterpreterExpose(ctx, dbId, dbName, method, modelName) // may throw

    await mongoSanitizeFilter(ctx, localConfig)

    // APPLY SECURITY FILTERS
    await mongoFilterHookInterpreter(ctx, localConfig)

    // EMIT "BEFORE" EVENTS
    // they are applied after security so that every changes that are made by an event is made as a system eventhough the ctx used is a normal one
    if (!ctx.simulateRequest && !localConfig.disableEmittingEvents) {
        const eventName = `${modelName}.${method}.before` // user.create.before

        // all that mess to keep type safe on ctx, ctx has different type depending on the method (getOne, update...)
        if (method === 'create') {
            await event.emit(
                `${modelName}.create.before`,
                ctx.clone({ ...localConfig, method, inputFields: localConfig.inputFields, createdId: localConfig.inputFields._id })
            )
        } else if (method === 'update') {
            if (!localConfig.ressourceId && event.registeredEvents[eventName] && event.registeredEvents[eventName].length) {
                throw ctx.error.serverError(`An event is registered on this request. When updating all, please use 'disableEmittingEvents' in request config, so that you make sure event emitting is bypassed. Actually updating all is not compatible with event emitting, because you wont get the id of the updated field`)
            }
            await event.emit(
                `${modelName}.update.before`,
                ctx.clone({ ...localConfig, method, updatedId: ressourceId, inputFields: localConfig.inputFields })
            )
        } else if (method === 'getOne') {
            await event.emit(
                `${modelName}.getOne.before`,
                ctx.clone({ ...localConfig, method })
            )
        } else if (method === 'getAll') {
            await event.emit(
                `${modelName}.getAll.before`,
                ctx.clone({ ...localConfig, method })
            )
        } else if (method === 'delete') {
            await event.emit(
                `${modelName}.delete.before`,
                ctx.clone({ ...localConfig, method, deletedId: ressourceId })
            )
        } else throw ctx.error.serverError('notExistingMethod', { method })
    }

    if (hasFields) {

        await unPopulate(dbName, modelName, localConfig.inputFields)

        // MASK UNAUTHORIZED DATA IN BODY
        localConfig.inputFields = await applyMaskIncludingOnPopulatedFieldsRecursive(ctx, method, dbName, modelName, localConfig.inputFields, false)

        // CHECK TYPES AND FORMAT DATA
        const dbs = await getProjectDatabaseModels()
        const validator = dbs[dbName][modelName]
        localConfig.inputFields = await validator.formatAndValidate(localConfig.inputFields, {
            user: ctx.getUserMinimal(),
            method,
            dbName,
            dbId,
            modelName,
            errorExtraInfos: errExtraInfos
        })
    }

    if (method === 'update' && hasFields) delete localConfig.inputFields._id // this is here so in an event we can still rely on fields._id if needed, the best way is to use ctx.ressourceId
}