All files / src/databases/mongo mongoCreateDao.ts

5.43% Statements 15/276
100% Branches 0/0
0% Functions 0/1
5.43% Lines 15/276

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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 2761x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
 
 
 
import mongoose from 'mongoose'
import { mongoBeforeRequest } from './mongoBeforeRequest'
import { mongoAfterRequest, catchMongoDbDuplicateError } from './mongoAfterRequest'
 
import { DaoMethodsBaseMongo, DaoMethodsMongo, RequestConfigRead } from './types/mongoDbTypes'
import { LocalConfigParsed } from './types/mongoDbTypes'
import { DaoGenericMethods, ModelReadWrite } from '../../types/core.types'
 
import { asArray, getId, deepClone, isset } from 'topkat-utils'
import type { AllDbIds, DbIds, ModelNames } from '../../cache/dbs/index.generated'
 
export async function mongoCreateDao<ModelTypes extends ModelReadWrite>(
    MongooseModel: mongoose.Model<any>,
    dbId: AllDbIds,
    dbName: keyof DbIds,
    modelName: ModelNames
) {

    type ModelRead = ModelTypes['Read']
    type Config = RequestConfigRead<ModelTypes['Read']>


    const dao: DaoMethodsBaseMongo<ModelTypes> = {
        //----------------------------------------
        // READ
        //----------------------------------------
        async getById(ctx, id, config?) {
            return await dao.getOne(ctx, { _id: getId(id) } as any, config) // * for any see mongoDbBaseTypes.ts
        },

        async getOne(ctx, filter?, config?) {
            const localConfig = getLocalConfigForRead('getOne', config, filter)
            await mongoBeforeRequest(ctx, localConfig)
            const promise = MongooseModel.findOne(localConfig.filter, null, { session: ctx.transactionSession })
            const result = await mongoAfterRequest<ModelRead, 'getOne', typeof localConfig>(ctx, promise, localConfig)
            if (config?.triggerErrorIfNotSet === true && !result) throw ctx.error.ressourceDoesNotExists({
                triggerErrorIfNotSetOption: true,
                filter,
                additionalMsg: 'RESSOURCE DO NOT EXIST',
                modelName: localConfig.modelName || localConfig.dbName,
                dbId: localConfig.dbId,
            })
            return result
        },

        async getAll(ctx, filter?, config?) {
            const localConfig = getLocalConfigForRead('getAll', config, filter)
            await mongoBeforeRequest(ctx, localConfig)
            const promise = MongooseModel.find(localConfig.filter, null, { session: ctx.transactionSession })
            const result = await mongoAfterRequest<ModelRead, 'getAll', typeof localConfig>(ctx, promise, localConfig, MongooseModel)
            return result as any
        },

        async getLastN(ctx, limit = 1, config) {
            return await getLastOrFirst(ctx, limit, config, false) as any
        },

        async getFirstN(ctx, limit = 1, config?) {
            return await getLastOrFirst(ctx, limit, config, true) as any
        },

        async count(ctx, filter = {}) {
            const localConfig = getLocalConfigForRead('getAll', {}, filter)
            await mongoBeforeRequest(ctx, localConfig)
            return await MongooseModel.countDocuments(localConfig.filter, { session: ctx.transactionSession })
        },

        //----------------------------------------
        // CREATE
        //----------------------------------------
        async create(ctx, fieldsOrArr?, config?) {
            const arrOfFields = asArray(deepClone(fieldsOrArr || {}))
            const results: string[] | ModelRead[] = []
            for (const fields of arrOfFields) {
                const localConfig = getLocalConfigForWrite('create', 'getOne', config, {}, fields) // we shall recreate each time for ref
                await mongoBeforeRequest(ctx, localConfig)
                if (!ctx.simulateRequest) {
                    let item
                    try {
                        item = await (new MongooseModel(localConfig.inputFields)).save({ session: ctx.transactionSession })
                    } catch (err) {
                        const { errmsg, code } = err
                        if (code === 11000) {
                            const { dbId, dbName, modelName, method, ressourceId } = localConfig
                            const extraInfs = { dbId, dbName, modelName, method, ressourceId }
                            catchMongoDbDuplicateError(ctx, errmsg, err, extraInfs)
                        } else throw err
                    }
                    const result = await getRessourceAfterUpdateIfReturnDocIsTrue(ctx, { _id: item._id }, localConfig, item._id.toString())
                    await mongoAfterRequest<ModelRead, 'create'>(ctx, result, localConfig)
                    results.push(result)
                }
            }
            return Array.isArray(fieldsOrArr) ? results : results[0] as any
        },

        //----------------------------------------
        // UPDATE
        //----------------------------------------
        async update(ctx, id, fields, config) {
            (fields as any)._id = id
            const localConfig = getLocalConfigForWrite('update', 'getOne', config, { _id: getId(id) }, fields)
            const results = await dao.updateMany(ctx, [fields as any], localConfig)
            return results[0] as any
        },

        async updateMany(ctx, fieldsArr, config) {
            const results = [] as ModelRead[]
            for (const fields of fieldsArr) {
                if (!isset(getId(fields))) throw ctx.error.serverError('_id field must be set when updating field', { fieldsOrArr: fieldsArr })
                const originalId = getId(fields)
                const localConfig = getLocalConfigForWrite('update', 'getAll', config, { _id: originalId }, fields)
                delete fields._id
                await mongoBeforeRequest(ctx, localConfig)
                if (localConfig.filter?._id !== originalId) throw ctx.error[403]({ originalId, allowedId: localConfig.filter?._id })
                if (!ctx.simulateRequest) {
                    const promise = MongooseModel.updateOne(localConfig.filter, localConfig.inputFields, { session: ctx.transactionSession })
                    await mongoAfterRequest<ModelRead, 'update'>(ctx, promise, localConfig)
                    if (localConfig.returnDoc) {
                        const updatedRessource = await getRessourceAfterUpdateIfReturnDocIsTrue(ctx, localConfig.filter, localConfig)
                        results.push(updatedRessource)
                    }
                }
            }
            return results as any
        },

        async upsert(ctx, fields, config?) {
            const ressourceId = getId(fields)
            const isUpdate = isset(ressourceId) && await dao.count(ctx.GM, { _id: ressourceId } as any) > 0
            const method = isUpdate ? 'update' : 'create'
            const localConfig = getLocalConfigForWrite(method, 'getOne', config, {}, fields)
            const result = isUpdate ? await dao.update(ctx, ressourceId, fields, localConfig) : await dao.create(ctx, fields, localConfig)
            return result || ressourceId as any
        },

        async updateWithFilter(ctx, filter, fields, config?) {
            const localConfig = getLocalConfigForWrite('update', 'getAll', config, filter, fields)
            await mongoBeforeRequest(ctx, localConfig)
            if (!ctx.isSystem && localConfig?.filter?._id) { // forcing _id filter since updateWithFilter is too powerful
                throw ctx.error[403]({ message: 'updateWithFilterNotAllowed', allowedId: localConfig.filter?._id })
            }
            if (!ctx.simulateRequest) {
                const returnVal = await MongooseModel.updateMany(filter, localConfig.inputFields, { session: ctx.transactionSession })
                await mongoAfterRequest<ModelRead, 'update'>(ctx, undefined, localConfig)
                return await getRessourceAfterUpdateIfReturnDocIsTrue(ctx, filter, localConfig, returnVal) as any // TODO
            }
        },

        //----------------------------------------
        // DELETE
        //----------------------------------------
        async delete(ctx, id) {
            await dao.deleteWithFilter(ctx, { _id: getId(id) } as any)
        },

        async deleteWithFilter(ctx, filter) {
            if (!ctx.isSystem && !filter._id) throw ctx.error[403]({ errorCode: '29667' })
            if (Object.keys(filter).length === 0) throw ctx.error.wrongValueForParam({ message: 'deleteWithFilterForbiddenWithEmptyFilter', filter })
            const localConfig = getLocalConfigForWrite('delete', 'getAll', undefined, filter)

            // if (daoConf.modelConfig?.hardDelete === false) {
            //     const fields = { isDeleted: true }
            //     localConfig.withDeleted = true
            //     const resp: any = await this.updateWithFilter(ctx, filter, fields, localConfig) || {}
            //     resp.hardDeleted = false
            //     return resp
            // } else {
            await mongoBeforeRequest(ctx, localConfig)
            if (!ctx.simulateRequest) {
                const resp = await MongooseModel.deleteMany(localConfig.filter, { session: ctx.transactionSession })
                return { success: true, deletedCount: resp.deletedCount, hardDeleted: true }
            }
            // }
        },
    }

    return {
        ...dao,
        simulateRequest: new Proxy(dao, {
            get(target, prop, receiver) {
                const value = target[prop]
                if (value instanceof Function) {
                    return function (this: typeof dao, ...args) {
                        const [ctx, ...otherArgs] = args
                        return value.apply(this === receiver ? target : this, [
                            ctx.clone({ simulateRequest: true }),
                            ...otherArgs
                        ])
                    }
                }
                return value
            },
        }),
    } as DaoMethodsMongo<ModelTypes>



    //----------------------------------------
    // HELPERS
    //----------------------------------------
    /** get extended and cleaned configuration for mongo request */
    function getLocalConfigForWrite<T extends Record<string, any>>(
        method: DaoGenericMethods,
        methodForRead: DaoGenericMethods,
        config: T | T & LocalConfigParsed = {} as T,
        filter = {},
        fields = {},
    ): T & LocalConfigParsed {
        let localConfig = {} as T & LocalConfigParsed
        if ('isLocalConfig' in config === false) { // CLONE CONFIG
            localConfig = (config ? deepClone(config) : {}) as T & LocalConfigParsed
            localConfig.isLocalConfig = true
            localConfig.method = method
            localConfig.methodForRead = methodForRead
            localConfig.dbName = dbName
            localConfig.dbId = dbId
            localConfig.modelName = modelName
        } else { // IS ALREADY LOCAL
            localConfig = { ...config, method } as T & LocalConfigParsed
        }
        localConfig.filter = filter || {}
        localConfig.inputFields = fields ? { ...fields } : {} // avoid modifying ref
        return localConfig
    }

    function getLocalConfigForRead<T extends Record<string, any>>(
        method: DaoGenericMethods,
        config: T | T & LocalConfigParsed = {} as T,
        filter = {},
    ): T & LocalConfigParsed {
        let localConfig = {} as T & LocalConfigParsed
        if ('isLocalConfig' in config === false) { // perf avoid repeating a clone
            localConfig = (config ? deepClone(config) : {}) as T & LocalConfigParsed
            localConfig.isLocalConfig = true
            localConfig.method = method
            localConfig.methodForRead = method
            localConfig.dbName = dbName
            localConfig.dbId = dbId
            localConfig.modelName = modelName
        } else localConfig = { ...config, method } as T & LocalConfigParsed
        localConfig.filter = filter || {}
        return localConfig
    }

    async function getRessourceAfterUpdateIfReturnDocIsTrue(
        ctx: Ctx,
        filter: any,
        localConfig: any,
        returnValueElse?: any
    ) {
        if (localConfig.returnDoc) {
            const readLocalConfig = { ...localConfig }
            readLocalConfig.method = readLocalConfig.methodForRead
            if (readLocalConfig.methodForRead === 'getOne') {
                return await dao.getOne(ctx, filter, readLocalConfig)
            } else {
                return await dao.getAll(ctx, filter, readLocalConfig)
            }
        } else return returnValueElse
    }

    async function getLastOrFirst(ctx: Ctx, limit, config: Config, getFirst: boolean) {
        const localConfig = getLocalConfigForRead('getAll', config)
        await mongoBeforeRequest(ctx, localConfig)
        const promise = MongooseModel.find(localConfig.filter || {}, null, { session: ctx.transactionSession })
            .sort({ $natural: getFirst ? 1 : -1 })
            .skip((localConfig.page || 0) * limit)
            .limit(limit)
        const result = await mongoAfterRequest<ModelRead, 'getAll', typeof localConfig>(ctx, promise, localConfig, MongooseModel)
        return result
    }
}