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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x |
import mongoose from 'mongoose'
import event from '../../event'
import { applyMaskToPopulateConfig, getMongoMaskForUser } from './services/maskService'
import { DaoGenericMethods } from '../../types/core.types'
import { PaginationData, MaybePaginated } from './types/mongoDaoTypes'
import { LocalConfigParsed } from './types/mongoDbTypes'
import { firstMatch } from 'topkat-utils'
import { getActiveAppConfig } from '../../helpers/getGreenDotConfigs'
import { getProjectDatabaseDaosForModel } from '../../helpers/getProjectModelsAndDaos'
type MongooseReqRead = mongoose.Query<any[], any, {}, any, 'find'> | mongoose.Query<any, any, {}, any, 'findOne'>
type MongooseReqDel = mongoose.Query<mongoose.mongo.DeleteResult, any, {}, any, 'deleteMany'>
export async function mongoAfterRequest<
ModelRead,
Method extends DaoGenericMethods,
Config extends LocalConfigParsed = any
>(
ctx,
promise: Method extends 'delete' ? MongooseReqDel : Method extends 'create' | 'update' ? string | ModelRead | void | mongoose.Query<any, any, {}, any, 'findOneAndUpdate'> : MongooseReqRead,
localConfig: Config,
...[model]: Method extends 'getAll' ? [mongoose.Model<any>] : [] // => if method === 'getAll' parameter is required. Unreadable but type safe https://stackoverflow.com/questions/52318011/optional-parameters-based-on-conditional-types
): Promise<Method extends 'create' ? void : Method extends 'getAll' ? MaybePaginated<ModelRead[], Config> : ModelRead> {
try {
const appConfig = await getActiveAppConfig()
const { method, modelName, dbName, populate = [], inputFields, ressourceId, populateAsAdmin = false } = localConfig
const isRead = method === 'getAll' || method === 'getOne'
const isMongooseQuery = promise instanceof mongoose.Query
let paginationData: PaginationData
const dao = await getProjectDatabaseDaosForModel(localConfig.dbName, localConfig.modelName)
if (isRead && isMongooseQuery) {
// MASK FIRST LEVEL FIELDS
const maskArr = await getMongoMaskForUser(ctx, method, dbName, modelName)
if (maskArr.length) promise.select(maskArr.join(' '))
// POPULATE
if (dao.populate) populate.unshift(...dao.populate)
if (populate?.length && !populateAsAdmin) {
await applyMaskToPopulateConfig(ctx, populate, dbName, modelName, method)
}
populate.forEach(p => promise.populate(p as any))
if (method === 'getAll') {
// SORTING
if (typeof (promise as any)?.options?.sort?.$natural !== 'number') { // if getFirst / Last is used, sorting is disabled and also leads to a bug
if (dao.sort) promise.sort(dao.sort)
if ('sort' in localConfig && Object.keys(localConfig.sort).length > 0) promise.sort(localConfig.sort)
}
// PAGINATION and LIMIT
if ('page' in localConfig && typeof localConfig.page === 'number') {
const limit = 'limit' in localConfig ? localConfig.limit : appConfig.defaultPaginationLimit || 25
promise.skip(localConfig.page * limit).limit(limit)
paginationData = {
page: localConfig.page,
limit,
total: await model.countDocuments(localConfig.filter).exec() || 0
}
} else if ('limit' in localConfig) promise.limit(localConfig.limit)
}
}
if (promise instanceof mongoose.Query) {
// avoid creation of full featured mongo prototype (that leads to many errors for example because _id is not an string)
promise.lean()
// this will return a real promise, thus improving stack traces
// promise.exec()
}
const result = isMongooseQuery ? await promise as any : promise
if (paginationData) result._paginationData = paginationData
// EVENTS
if (!ctx.simulateRequest) {
const eventName = `${modelName}.${method}.after` // user.create.after
if (method === 'create') {
await event.emit(
`${modelName}.create.after`,
ctx.clone({ ...localConfig, method, inputFields, createdId: ressourceId })
)
} 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.after`,
ctx.clone({ ...localConfig, method, updatedId: ressourceId, inputFields })
)
} else if (method === 'getOne' || method === 'getAll') {
await event.emit(
`${modelName}.${method}.after`,
ctx.clone({ ...localConfig, method, data: result })
)
} else if (method === 'delete') {
await event.emit(
`${modelName}.delete.after`,
ctx.clone({ ...localConfig, method, deletedId: localConfig.filter._id })
)
} else throw ctx.error.serverError('notExistingMethod', { method })
}
return result
} catch (err) {
// TODO use err instanceof mongoose.Error.ValidationError pattern here
// https://mongoosejs.com/docs/api/error.html
// "Plan executor error during findAndModify :: caused by :: E11000 duplicate key error collection: test.users index: phoneWithPrefix_1 dup key: { phoneWithPrefix: \"+33600112233\" }"
const { errmsg, name, code } = err
const { dbId, dbName, modelName, method, ressourceId } = localConfig
const extraInfs = { dbId, dbName, modelName, method, ressourceId }
if (code === 11000) {
catchMongoDbDuplicateError(ctx, errmsg, err, extraInfs)
} else if (name === 'CastError') {
throw ctx.error.applicationError('databaseWrongCast', { code: 422, err, ...extraInfs })
} else {
throw ctx.error.serverError('databaseError', { err, stack: err.stack, ...extraInfs })
}
}
}
export function catchMongoDbDuplicateError(ctx, errmsg, err, extraInf) {
const value = firstMatch(errmsg, /: "(.*?)"? }/)
const duplicateKey = firstMatch(errmsg, /index: (.*?)_?\d? dup key/)
throw ctx.error.duplicateRessource({ duplicateKey, value, err, ...extraInf })
} |