diff --git a/.github/workflows/generate-sponsors-readme.md b/.github/workflows/generate-sponsors-readme.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 63b0eeb..8069db2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@

+[![](https://img.shields.io/badge/site-tryintent.com-98d422?logo=data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AQMAAACyIsh+AAAACXBIWXMAAAdiAAAHYgE4epnbAAAABlBMVEWW1haU1hO0Wf1AAAAAAnRSTlP5Al3j9uAAAASYaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pgo8eDp4bXBtZXRhIHhtbG5zOng9J2Fkb2JlOm5zOm1ldGEvJz4KPHJkZjpSREYgeG1sbnM6cmRmPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjJz4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOkF0dHJpYj0naHR0cDovL25zLmF0dHJpYnV0aW9uLmNvbS9hZHMvMS4wLyc+CiAgPEF0dHJpYjpBZHM+CiAgIDxyZGY6U2VxPgogICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSdSZXNvdXJjZSc+CiAgICAgPEF0dHJpYjpDcmVhdGVkPjIwMjUtMDEtMDU8L0F0dHJpYjpDcmVhdGVkPgogICAgIDxBdHRyaWI6RXh0SWQ+Y2FiYjgwNjUtOTRlMi00MWIwLWI5Y2MtYWUzMzE0N2I3YTMzPC9BdHRyaWI6RXh0SWQ+CiAgICAgPEF0dHJpYjpGYklkPjUyNTI2NTkxNDE3OTU4MDwvQXR0cmliOkZiSWQ+CiAgICAgPEF0dHJpYjpUb3VjaFR5cGU+MjwvQXR0cmliOlRvdWNoVHlwZT4KICAgIDwvcmRmOmxpPgogICA8L3JkZjpTZXE+CiAgPC9BdHRyaWI6QWRzPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpkYz0naHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8nPgogIDxkYzp0aXRsZT4KICAgPHJkZjpBbHQ+CiAgICA8cmRmOmxpIHhtbDpsYW5nPSd4LWRlZmF1bHQnPlVudGl0bGVkIGRlc2lnbiAtIDE8L3JkZjpsaT4KICAgPC9yZGY6QWx0PgogIDwvZGM6dGl0bGU+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOnBkZj0naHR0cDovL25zLmFkb2JlLmNvbS9wZGYvMS4zLyc+CiAgPHBkZjpBdXRob3I+VmluYXlhayBTYXJhd2FnaTwvcGRmOkF1dGhvcj4KIDwvcmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PScnCiAgeG1sbnM6eG1wPSdodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvJz4KICA8eG1wOkNyZWF0b3JUb29sPkNhbnZhIGRvYz1EQUdPdkROY004OCB1c2VyPVVBQjhqa0NhSktzPC94bXA6Q3JlYXRvclRvb2w+CiA8L3JkZjpEZXNjcmlwdGlvbj4KPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0ncic/PhLgAeEAAAVZSURBVGje7ZlLsuwmDIZxXCky8xK8g2QJ3lnM0ryELIFxRgxdKWKn0QsJcHfljPsMbt22vxZCIJD+dvf7v8N9gS/wBT4Bf7lf3gLJOTe/A1z5W56BCMD0CFwO/9YnIBEwPwEHAW4fAzyCGUMDpwDzGJARnBsDoQL7CMj1vXJCAacC5hEQFTCNAOWCckIB+r3beiAbYOmBZIC5B6IBph6AOMYulhUoT33ovBQAlvLPPpYClEnMZz8NAcq7Rc3Et0Bxbzv6aQhQ3mG0/f2PmoYAr3fzxV9NdTUEKC5kdj/XeTJQvrydYrrOk4HynT3J9ILMk4GzDH6I5SjzZCCVBQzifJL1ZCAWm3WZswSCgZf19aquXWJLAZua3C2BYOA1PCyH2h0WKGMmtdMiGyPgKu/q3GAaqwZyeXeolDs5UgTA56DSIbO1CqzgqDpMDPAacrvM+cYOE/Dyb7/MyRKIJuDlXx1Wnhhgqo6zTQ2El/VkTrdEoRTAyyMJhAaK9WiATEFB4CrAoU+/AqwGWMFRc+wqAOwFDMO/NFmakwD7TWEI5ErAjwicCCy0/30HvGbIg0ZOngNHZGBiIHD6xRbAiV9yehigfMDQnXIIJpz1QeP58qoehui4BRI8OOSMa4CFTNYzLmsgwFrN9V5bKHgVWBHI9ai+NIBr5eutMwGwKWADP+qRTw87QN+9CoDhggE2dMwCq7q3VgPAlOFbMAE67GDMBrj4ux0wFWCHMMghGCtw4nbYSxhmXKe5AH4ELOj01ABzeVgPHkjtZAFwNEq+OQMkAKaa0gAm8IcAjwAnV0KHDHAiMHNqb0OAkgV3ODxBIAIw486SzM0aWBCQM2YAJAMUUx3geZe9BeS29LTvD/xnpVgU4G9y+xoCxcFlCEQY1eHCgMsD4MRFnN4DkdfBAFsN1iHrUIGggSDrAHPqLTgJcwXQwsqucRRHAMyet48FVLDGQDTAhbn2ZKF48jjEScCDheUT8P8shKGFsuffWlCp7P6fhSjARwvnOFAfLUh20aIPLZwEPKwmbVrIify05R6AasFJXk56V6vM2lVm27ygzbLhtX/2mUUbdsVi4xnwWJQ9AFA0Rk69DsCs+h3uoya7dV7ijSbR1MCkgPgAHHxpDgA+7R1dJs0x6OW+QOCQlVHXgeObeWbAnPZ8Z2GVFGR3WCBz8dABM53TfG87Dl291G4nd/eOdHPrIXBgGHpgotuG5IXM0bQAlSieij8F0JXopT06MWq2PDjIaUfVYVc/RIhsBjuRw61LlKhq3gOdsjWMLqsD7p7VlEmn6i4cbtCtBVbTXFig3ABS+8N/TakWMGF8retXW+wF/MasWyRTLsKUdRv7st4Avpbk9L+upq1tFnaCBoh0Hy7SvTZlc6KkmNnHaQQkJ30PVmqz6Q5ws6zkgm+K/5P3vGcVoGkfMm1lOvhog/umhYGk+BXTb2tamKvueT4hrrZLWrRMMrVdEtpLWmSxjVhNCpEvzrbXmxvJq2kGD91cgPHY9puuUdWahhSL2agEkqalTdzWiMTSNMW455X0d/V993Krk7ZrzOlzVRfb1p72fBYXWnHgpq49sGDWygvc94sEegyAjaSNTfetRiQBZ8IfY5EkWu20l1mSFT57oea04mov9eRWGW3FokvrgSO56TYK70CwAsnLCLb7SDS7+w9WdlMuTEPhTrngh9KfcmEZiof1EOvFw7sqhnkoP/J9phWGgQTKst5IAk0qp4YiKjXtvz3KsFbIXX8iBX8Ukz/K0XkkFT9J4tvPRPWPsvxHYb868eMfFz7+PKGEngcgdwZGP7JMb3+mOZr339+zvsAX+BHwHzCG3z9YaHJQAAAAAElFTkSuQmCC)](https://tryintent.com) [![](https://img.shields.io/badge/docs-98d422?)](https://tryintent.com/docs) discord community + ## Introduction Intent is a web application framework built on top of NestJS. It _intends_ to provide declarative APIs to develop and ship sophisticated solutions. While NestJS provides great syntax and structure to your application, it lacks many things that are needed to develop a good solution. @@ -22,6 +24,11 @@ Intent provides necessary feature-integrations out of the box. - [Localization](https://tryintent.com/docs/localization) - [Console Commands](https://tryintent.com/docs/console) +## Contributors + + + + ## Contact Us If you would like to support our work or just a few words of feedback, we are all ears at hi@tryintent.com. diff --git a/integrations/sample-app/app/boot/sp/app.ts b/integrations/sample-app/app/boot/sp/app.ts index 5587b26..925ef11 100644 --- a/integrations/sample-app/app/boot/sp/app.ts +++ b/integrations/sample-app/app/boot/sp/app.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { IntentApplicationContext, ServiceProvider } from '@intentjs/core'; +import { OrderPlacedListener } from 'app/events/listeners/sample-listener'; import { IntentController } from 'app/http/controllers/icon'; import { QueueJobs } from 'app/jobs/job'; import { UserDbRepository } from 'app/repositories/userDbRepository'; @@ -27,6 +28,8 @@ export class AppServiceProvider extends ServiceProvider { this.bindWithClass('USER_DB_REPO', UserDbRepository); this.bind(QueueJobs); + + this.bind(OrderPlacedListener); } /** diff --git a/integrations/sample-app/app/events/events/sample-event.ts b/integrations/sample-app/app/events/events/sample-event.ts new file mode 100644 index 0000000..f408655 --- /dev/null +++ b/integrations/sample-app/app/events/events/sample-event.ts @@ -0,0 +1,8 @@ +import { EmitsEvent, Event } from '@intentjs/core'; + +@Event('order_placed') +export class OrderPlacedEvent extends EmitsEvent { + constructor(public order: Record) { + super(); + } +} diff --git a/integrations/sample-app/app/events/listeners/sample-listener.ts b/integrations/sample-app/app/events/listeners/sample-listener.ts new file mode 100644 index 0000000..dd1be45 --- /dev/null +++ b/integrations/sample-app/app/events/listeners/sample-listener.ts @@ -0,0 +1,10 @@ +import { Injectable, ListensTo } from '@intentjs/core'; + +@Injectable() +export class OrderPlacedListener { + @ListensTo('order_placed') + async handle(data: Record): Promise { + console.log('data ==> ', data); + // write your code here... + } +} diff --git a/integrations/sample-app/app/http/controllers/app.ts b/integrations/sample-app/app/http/controllers/app.ts index aa7910a..f3f57b7 100644 --- a/integrations/sample-app/app/http/controllers/app.ts +++ b/integrations/sample-app/app/http/controllers/app.ts @@ -1,17 +1,21 @@ import { Controller, Get, Req } from '@intentjs/core'; +import { OrderPlacedEvent } from 'app/events/events/sample-event'; import { UserService } from 'app/services'; @Controller() export class UserController { constructor(private readonly service: UserService) {} - @Get() + @Get('/') async getHello(@Req() req: Request) { return { hello: 'Intent' }; } - @Get('hello') + @Get('hello/') async getHello2(@Req() req: Request) { - return { hello: 'Intent' }; + const order = { id: 123, product: 'A book' }; + const event = new OrderPlacedEvent(order); + event.emit(); + return { hello: 'Intent2' }; } } diff --git a/packages/core/lib/exceptions/base-exception-handler.ts b/packages/core/lib/exceptions/base-exception-handler.ts index 822699f..bd67c71 100644 --- a/packages/core/lib/exceptions/base-exception-handler.ts +++ b/packages/core/lib/exceptions/base-exception-handler.ts @@ -6,6 +6,7 @@ import { HttpException } from './http-exception'; import { ValidationFailed } from './validation-failed'; import { HttpStatus } from '../rest/http-server/status-codes'; import { ExecutionContext } from '../rest/http-server/contexts/execution-context'; +import { RouteNotFoundException } from './route-not-found-exception'; export abstract class IntentExceptionFilter { doNotReport(): Array> { @@ -28,9 +29,20 @@ export abstract class IntentExceptionFilter { async handleHttp(context: ExecutionContext, exception: any): Promise { const res = context.switchToHttp().getResponse(); + const req = context.switchToHttp().getRequest(); const debugMode = ConfigService.get('app.debug'); + if (exception instanceof RouteNotFoundException) { + if (req.expectsJson()) { + return res.status(HttpStatus.NOT_FOUND).json({ + message: exception.message, + status: exception.getStatus(), + // stack: debugMode && exception.stack, + }); + } + } + if (exception instanceof ValidationFailed) { return res.status(HttpStatus.UNPROCESSABLE_ENTITY).json({ message: 'validation failed', diff --git a/packages/core/lib/exceptions/index.ts b/packages/core/lib/exceptions/index.ts index b8c4fbc..5c14bef 100644 --- a/packages/core/lib/exceptions/index.ts +++ b/packages/core/lib/exceptions/index.ts @@ -8,3 +8,4 @@ export * from './invalid-value-type'; export * from './http-exception'; export * from './forbidden-exception'; export * from './file-not-found-exception'; +export * from './route-not-found-exception'; diff --git a/packages/core/lib/exceptions/route-not-found-exception.ts b/packages/core/lib/exceptions/route-not-found-exception.ts new file mode 100644 index 0000000..39bb533 --- /dev/null +++ b/packages/core/lib/exceptions/route-not-found-exception.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '../rest/http-server/status-codes'; +import { HttpException } from './http-exception'; + +export class RouteNotFoundException extends HttpException { + constructor(response: string | Record) { + super(response, HttpStatus.NOT_FOUND); + } +} diff --git a/packages/core/lib/rest/foundation/server.ts b/packages/core/lib/rest/foundation/server.ts index e4ccb3b..7393a46 100644 --- a/packages/core/lib/rest/foundation/server.ts +++ b/packages/core/lib/rest/foundation/server.ts @@ -22,6 +22,7 @@ import { HttpExecutionContext } from '../http-server/contexts/http-execution-con import { ExecutionContext } from '../http-server/contexts/execution-context'; import { Response } from '../http-server/response'; import { RouteExplorer } from '../http-server/route-explorer'; +import { RouteNotFoundException } from '../../exceptions/route-not-found-exception'; const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; @@ -98,12 +99,20 @@ export class IntentHttpServer { await this.container.boot(app); await this.kernel.boot(server); + + server.set_not_found_handler((hReq: any, hRes: HyperResponse) => { + const httpContext = new HttpExecutionContext(hReq, hRes); + const context = new ExecutionContext(httpContext, null, null); + const routeNotFoundError = new RouteNotFoundException( + `[${hReq.method}] ${hReq.url} is not a valid route.`, + ); + errorHandler.catch(context, routeNotFoundError); + }); + server.set_error_handler((hReq: any, hRes: HyperResponse, error: Error) => { - const res = new Response(); const httpContext = new HttpExecutionContext(hReq, hRes); const context = new ExecutionContext(httpContext, null, null); errorHandler.catch(context, error); - res.reply(hReq, hRes); }); this.configureErrorReporter(config.get('app.sentry')); diff --git a/packages/hyper-express/src/components/compatibility/ExpressRequest.js b/packages/hyper-express/src/components/compatibility/ExpressRequest.js index c1fe39f..6f5ea92 100644 --- a/packages/hyper-express/src/components/compatibility/ExpressRequest.js +++ b/packages/hyper-express/src/components/compatibility/ExpressRequest.js @@ -28,11 +28,6 @@ class ExpressRequest { return this.get(name); } - accepts() { - let instance = accepts(this); - return instance.types.apply(instance, arguments); - } - acceptsCharsets() { charsets = flattened(charsets, arguments); diff --git a/packages/hyper-express/src/components/http/Request.js b/packages/hyper-express/src/components/http/Request.js index 4e47e7b..00876d7 100644 --- a/packages/hyper-express/src/components/http/Request.js +++ b/packages/hyper-express/src/components/http/Request.js @@ -1115,7 +1115,7 @@ class Request { } expectsJson() { - return this.accepts().includes('application/json'); + return this.accepts().includes('application/json') || this.accepts().includes('*/*'); } setUser(user) { diff --git a/packages/hyper-express/types/components/http/Response.d.ts b/packages/hyper-express/types/components/http/Response.d.ts index 94c90d4..d40a2e1 100644 --- a/packages/hyper-express/types/components/http/Response.d.ts +++ b/packages/hyper-express/types/components/http/Response.d.ts @@ -88,7 +88,7 @@ export class Response extends Writable { value: string | null, expiry?: number, options?: CookieOptions, - sign_cookie?: boolean + sign_cookie?: boolean, ): Response; /** @@ -264,6 +264,7 @@ export class Response extends Writable { sendStatus(status_code: number): Response; set(field: string | object, value?: string | Array): Response | void; vary(name: string): Response; + notFound(): Response; /* ExpressJS Properties */ get headersSent(): boolean;