import { Response } from 'express' ; import { CastError , Document , Types } from 'mongoose' ; import { debug } from './debug' ; import { ArrayPropConfiguration } from './decorators/array-prop' ; import { RestConfigurationMethod , RestError } from './decorators/rest' ; import { fetchAll , fetchCreate , fetchOne , getModel , persistDeleteAll , persistDeleteOne , persistSave , postFetch , postFetchAll , preFetch , preSave , preSaveAll , preSend , preSendAll , } from './hooks' ; import { parseQuery } from './parse-query' ; import { buildPayload } from './request-util' ; import { RestModelEntry , RestPropEntry } from './rest-registry' ; import { Restgoose } from './restgoose' ; import { RestgooseModel } from './restgoose-model' ; import { Constructor , Dic , RestRequest } from './types' ; export const ERROR_FORBIDDEN_CODE = 'FORBIDDEN' ; export const ERROR_NOT_FOUND_CODE = 'NOT_FOUND' ; export const ERROR_READONLY_CODE = 'READ_ONLY' ; export const ERROR_VALIDATION_CODE = 'BAD_DATA' ; export const ERROR_VALIDATION_NAME = 'ValidationError' ; export const ERROR_BAD_FORMAT_CODE = 'BAD_FORMAT' ; export function all < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { req = parseQuery ( req ) ; const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchAll ( modelType , methodConfig , req ) ; const postFetchResult = await postFetchAll ( methodConfig , req , fetchResult ) ; const preSendResult = await preSendAll ( methodConfig , req , postFetchResult ) ; return res . status ( 200 ) . json ( preSendResult ) ; } ) ; } export function allWithin < T extends RestgooseModel , S extends RestgooseModel > ( modelEntry , methodConfig , propEntry , submethodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { req = parseQuery ( req ) ; const modelType = await getModel ( modelEntry , req ) ; await preFetch ( submethodConfig , req ) ; const fetchParentResult = await fetchOne ( modelType , methodConfig , req , false ) ; const postFetchParentResult = await postFetch ( methodConfig , req , fetchParentResult ) ; if ( ! postFetchParentResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const isReferenced = ( propEntry . config as ArrayPropConfiguration < T , S > ) . ref ; let fetchSubResult ; if ( isReferenced ) { const refs = postFetchParentResult [ propEntry . name ] ; req = Object . assign ( { } , req ) ; req . restgoose . query = Object . assign ( { } , req . restgoose . query || { } , { _id : { $in : refs } , } ) ; const submodelEntry = { type : propEntry . type [ 0 ] as Constructor < S > , restConfig : propEntry . restConfig , } ; const submodelType = await getModel ( submodelEntry , req ) ; fetchSubResult = await fetchAll ( submodelType , submethodConfig , req ) ; } else { fetchSubResult = postFetchParentResult [ propEntry . name ] ; } const postFetchSubResult = await postFetchAll ( submethodConfig , req , fetchSubResult ) ; const preSendSubResult = await preSendAll ( submethodConfig , req , postFetchSubResult ) ; return res . status ( 200 ) . json ( preSendSubResult ) ; } } ) ; } export function create < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchCreate ( modelType , methodConfig , req ) ; const postFetchResult = await postFetch ( methodConfig , req , fetchResult ) ; if ( ! postFetchResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const payload = buildPayload ( req , modelType ) ; const prev = postFetchResult . toObject ( ) ; updateDocument ( postFetchResult , payload ) ; const preSaveResult = await preSave ( methodConfig , req , prev , postFetchResult ) ; const saveResult = await persistSave ( methodConfig , req , prev , preSaveResult ) ; const preSendResult = await preSend ( methodConfig , req , prev , saveResult ) ; res . status ( 201 ) . json ( preSendResult ) ; } } ) ; } export function createWithin < T extends RestgooseModel , S extends RestgooseModel > ( modelEntry , methodConfig , propEntry , submethodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { const modelType = await getModel ( modelEntry , req ) ; await preFetch ( submethodConfig , req ) ; const fetchParentResult = await fetchOne ( modelType , methodConfig , req , false ) ; const postFetchParentResult = await postFetch ( methodConfig , req , fetchParentResult ) ; if ( ! postFetchParentResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const isReferenced = ( propEntry . config as ArrayPropConfiguration < T , S > ) . ref ; let saveSubResult ; postFetchParentResult [ propEntry . name ] = postFetchParentResult [ propEntry . name ] || [ ] ; let prev = null ; if ( isReferenced ) { const submodelEntry = { type : propEntry . type [ 0 ] as Constructor < S > , restConfig : propEntry . restConfig , } ; const submodelType = await getModel ( submodelEntry , req ) ; const fetchSubResult = await fetchCreate ( submodelType , submethodConfig , req ) ; const postFetchSubResult = await postFetch ( submethodConfig , req , fetchSubResult ) ; const payload = buildPayload ( req , submodelType ) ; prev = postFetchSubResult . toObject ( ) ; updateDocument ( postFetchSubResult , payload ) ; const preSaveSubResult = await preSave ( submethodConfig , req , prev , postFetchSubResult ) ; saveSubResult = await persistSave ( submethodConfig , req , prev , preSaveSubResult ) ; postFetchParentResult [ propEntry . name ] . push ( saveSubResult . _id ) ; await persistSave ( methodConfig , req , prev , postFetchParentResult ) ; } else { postFetchParentResult [ propEntry . name ] . push ( req . body ) ; const parentSaveResult = await persistSave ( methodConfig , req , postFetchParentResult , postFetchParentResult ) ; saveSubResult = parentSaveResult [ propEntry . name ] [ parentSaveResult [ propEntry . name ] . length - 1 ] ; } const preSendSubResult = await preSend ( submethodConfig , req , prev , saveSubResult ) ; return res . status ( 201 ) . json ( preSendSubResult ) ; } } ) ; } export function one < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchOne ( modelType , methodConfig , req , true ) ; const postFetchResult = await postFetch ( methodConfig , req , fetchResult ) ; if ( ! postFetchResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const preSendResult = await preSend ( methodConfig , req , null , postFetchResult ) ; return res . status ( 200 ) . json ( preSendResult ) ; } } ) ; } export function oneWithin < T extends RestgooseModel , S extends RestgooseModel > ( modelEntry , methodConfig , propEntry , submethodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { req = parseQuery ( req ) ; const modelType = await getModel ( modelEntry , req ) ; await preFetch ( submethodConfig , req ) ; const fetchParentResult = await fetchOne ( modelType , methodConfig , req , false ) ; const postFetchParentResult = await postFetch ( methodConfig , req , fetchParentResult ) ; if ( ! postFetchParentResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const isReferenced = ( propEntry . config as ArrayPropConfiguration < T , S > ) . ref ; if ( isReferenced ) { const refs = postFetchParentResult [ propEntry . name ] ; const entityExists = refs . contains ( req . params . id ) ; if ( entityExists ) { return one ( { type : propEntry . type as Constructor < S > , restConfig : propEntry . restConfig , } , submethodConfig ) ; } } else { const fetchSubResult = postFetchParentResult [ propEntry . name ] . find ( item => item . _id === req . params . id ) ; const postFetchSubResult = await postFetch ( submethodConfig , req , fetchSubResult ) ; const preSendSubResult = await preSend ( submethodConfig , req , null , postFetchSubResult ) ; return res . status ( 200 ) . json ( preSendSubResult ) ; } } } ) ; } export function remove < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchOne ( modelType , methodConfig , req , true ) ; const postFetchResult = await postFetch ( methodConfig , req , fetchResult ) ; if ( ! postFetchResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { await preSave ( methodConfig , req , postFetchResult , null ) ; await persistDeleteOne ( modelType , methodConfig , req , postFetchResult ) ; return res . status ( 204 ) . end ( ) ; } } ) ; } export function removeAll < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { req = parseQuery ( req ) ; const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchAll ( modelType , methodConfig , req ) ; const postFetchResult = await postFetchAll ( methodConfig , req , fetchResult ) ; await preSaveAll ( methodConfig , req , postFetchResult , new Array ( postFetchResult . length ) . fill ( null ) ) ; await persistDeleteAll ( modelType , methodConfig , req , postFetchResult ) ; return res . status ( 204 ) . end ( ) ; } ) ; } export function update < T extends RestgooseModel > ( modelEntry , methodConfig ) { return wrapException ( methodConfig , async ( req , res ) => { const modelType = await getModel ( modelEntry , req ) ; await preFetch ( methodConfig , req ) ; const fetchResult = await fetchOne ( modelType , methodConfig , req , true ) ; const postFetchResult = await postFetch ( methodConfig , req , fetchResult ) ; if ( ! postFetchResult ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { const payload = buildPayload ( req , modelType ) ; const prev = postFetchResult . toObject ( ) ; updateDocument ( postFetchResult , payload ) ; const preSaveResult = await preSave ( methodConfig , req , prev , postFetchResult ) ; const saveResult = await persistSave ( methodConfig , req , prev , preSaveResult ) ; const preSendResult = await preSend ( methodConfig , req , prev , saveResult ) ; return res . status ( 200 ) . json ( preSendResult ) ; } } ) ; } /**
 * Deeply updates a mongoose document with a JSON object
 */ function updateDocument < T extends RestgooseModel > ( entity , payload ) { for ( const key in payload ) { if ( entity [ key ] instanceof Types . Subdocument ) { updateDocument ( entity [ key ] , payload [ key ] ) ; } else { entity [ key ] = payload [ key ] ; } } } function wrapException < T extends RestgooseModel > ( methodConfig , fn ) { return async ( req , res ) => { try { return await fn . bind ( this ) ( req , res ) ; } catch ( error ) { return handleException ( methodConfig , req , res , error ) ; } } ; } async function handleException < T extends RestgooseModel > ( methodConfig , req , res , error ) { if ( error instanceof RestError ) { const restError = error as RestError ; return res . status ( restError . httpCode ) . send ( restError . errorData ) ; } else if ( error instanceof CastError ) { error = error as CastError ; if ( error . path === '_id' ) { return res . status ( 404 ) . json ( { code : ERROR_NOT_FOUND_CODE , } ) ; } else { debug ( error ) ; return res . status ( 400 ) . json ( { code : ERROR_BAD_FORMAT_CODE , field : error . path , } ) ; } } else if ( error . name === ERROR_VALIDATION_NAME ) { debug ( error ) ; return res . status ( 400 ) . json ( { code : ERROR_VALIDATION_CODE , errors : error . errors } ) ; } else { debug ( error ) ; let customError = null ; if ( methodConfig . onError ) { customError = await methodConfig . onError ( req , error ) ; } if ( ! customError && Restgoose . onError ) { customError = await Restgoose . onError ( req , error ) ; } if ( customError ) { const restError = customError as RestError ; return res . status ( restError . httpCode ) . send ( restError . errorData ) ; } else { return res . status ( 500 ) . end ( ) ; } } }