import { Response , Router } from 'express' ; import { Document } from 'mongoose' ; import { debug } from './debug' ; import { RestError } from './decorators/rest' ; import { fetchAll , fetchOne , getModel , postFetch , postFetchAll , preSend } from './hooks' ; import { parseQuery } from './parse-query' ; import { all , allWithin , create , createWithin , one , oneWithin , remove , removeAll , update } from './rest-controller' ; import { RestModelEntry , RestRegistry } from './rest-registry' ; import { RestgooseModel } from './restgoose-model' ; import { isPrimitive } from './type-checks' ; import { Constructor , RestRequest } from './types' ; export class Restgoose { private static ROUTES = { all : { httpMethod : 'get' , path : '/' , fn : all } , one : { httpMethod : 'get' , path : '/:id' , fn : one } , create : { httpMethod : 'post' , path : '/' , fn : create } , update : { httpMethod : 'patch' , path : '/:id' , fn : update } , remove : { httpMethod : 'delete' , path : '/:id' , fn : remove } , removeAll : { httpMethod : 'delete' , path : '/' , fn : removeAll } , custom : { httpMethod : 'delete' , path : '/:id' , fn : remove } , } ; private static ROUTES_EMBED = { all : { httpMethod : 'get' , path : '/' , fn : allWithin } , one : { httpMethod : 'get' , path : '/' , fn : oneWithin } , create : { httpMethod : 'post' , path : '/' , fn : createWithin } , } ; public static onError : ( ( req : RestRequest , error : any ) => Promise < RestError > ) = null ; public static initialize ( modelTypes ? ) { const models = RestRegistry . listModels ( ) ; const router = Router ( ) ; for ( const model of models ) { if ( ! modelTypes || ! ! modelTypes . find ( m => m . name === model . type . name ) ) { router . use ( model . restConfig . route , this . createRestRoot ( model ) ) ; } } return router as any ; } /**
     * Simulates a REST call on the method one() and passes the result through the
     * postFetch middlewares.
     * NOTE : preFetch middlewares are NOT called
     */ public static async getOne < T extends RestgooseModel > ( modelType , req ) { const model = RestRegistry . getModel ( modelType ) ; const methods = model . restConfig . methods || [ ] ; const method = methods . find ( m => m . method === 'one' ) ; if ( ! method ) { throw new Error ( `On model ${ modelType . name }: primivite one() is not specified. Cannot use getOne()` ) ; } const result = await fetchOne ( await getModel ( model , req ) , method , req , true ) ; return postFetch ( method , req , result ) ; } /**
     * Passes the entity through the preSend of its one() primivite
     */ public static async sendOne < T extends RestgooseModel > ( modelType , entity , req , res , status = 200 ) { const model = RestRegistry . getModel ( modelType ) ; const methods = model . restConfig . methods || [ ] ; const method = methods . find ( m => m . method === 'one' ) ; if ( ! method ) { throw new Error ( `On model ${ modelType . name }: primivite one() is not specified. Cannot use getOne()` ) ; } const preSendResult = await preSend ( method , req , null , entity ) ; res . status ( status ) . json ( preSendResult . toJSON ( ) ) ; } /**
     * Simulates a REST call on the method all() and passes the result through the
     * postFetch middlewares.
     * NOTE : preFetch middlewares are NOT called
     */ public static async getAll < T extends RestgooseModel > ( modelType , req ) { if ( ! req . restgoose ) { req = parseQuery ( req ) ; } const model = RestRegistry . getModel ( modelType ) ; const methods = model . restConfig . methods || [ ] ; const method = methods . find ( m => m . method === 'all' ) ; if ( ! method ) { throw new Error ( `On model ${ modelType . name }: method all() is not specified. Cannot use getAll()` ) ; } const result = await fetchAll ( await getModel ( model , req ) , method , req ) ; return postFetchAll ( method , req , result ) ; } private static createRestRoot < T extends RestgooseModel > ( model ) { const router = Router ( ) ; debug ( `Building routes for model ${ model . type . name }` ) ; const methods = model . restConfig . methods || [ ] ; methods . forEach ( method => { const route = this . ROUTES [ method . method ] ; const routerFn = router [ route . httpMethod ] . bind ( router ) ; const controllerFn = route . fn ( model , method ) ; debug ( `  ${ route . httpMethod . toUpperCase ( ) } ${ route . path }` ) ; routerFn ( route . path , controllerFn ) ; } ) ; const methodOne = methods . find ( m => m . method . toLowerCase ( ) === 'one' ) ; const submodels = RestRegistry . listSubrestsOf ( model . type ) ; const schema = model . type . prototype . buildSchema ( model . restConfig . schemaOptions ) ; for ( let submodel of submodels ) { if ( ! methodOne ) { throw new Error ( `In model '${ model . type . name }' : a nested REST route cannot be defined without a root 'one' route` ) ; } if ( ! isPrimitive ( submodel . type [ 0 ] ) ) { submodel = Object . assign ( { } , submodel ) ; const subtype = schema . tree [ submodel . name ] [ 0 ] ; if ( subtype && subtype . ref ) { const descriptor = Object . getOwnPropertyDescriptor ( subtype , 'ref' ) ; if ( descriptor . value . prototype ) { submodel . type = schema . tree [ submodel . name ] [ 0 ] . ref ; } } } const submethods = submodel . restConfig . methods || [ ] ; submethods . forEach ( method => { const route = this . ROUTES_EMBED [ method . method ] ; const routerFn = router [ route . httpMethod ] . bind ( router ) ; const routePath = `/:id${ submodel . restConfig . route }${ route . path }` ; const controllerFn = route . fn ( model , methodOne , submodel , method ) ; debug ( `  ${ route . httpMethod . toUpperCase ( ) } ${ routePath }` ) ; routerFn ( routePath , controllerFn ) ; } ) ; } return router ; } }