From bc5de61602e4e6d54298b5ae86f54150c99fc92a Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 11 Mar 2022 22:44:50 +0800 Subject: [PATCH] feat(serverless): add res object on func --- src/modules/debug/debug.controller.ts | 23 +++++++---- src/modules/serverless/function.types.ts | 3 ++ src/modules/serverless/mock-response.util.ts | 22 ++++++++++- .../serverless/serverless.controller.ts | 39 ++++++++++++++++--- src/modules/serverless/serverless.readme.md | 8 ++-- src/modules/serverless/serverless.service.ts | 4 +- src/modules/snippet/snippet.controller.ts | 24 ++++++------ 7 files changed, 91 insertions(+), 32 deletions(-) diff --git a/src/modules/debug/debug.controller.ts b/src/modules/debug/debug.controller.ts index a7cdef67..21b9d0d1 100644 --- a/src/modules/debug/debug.controller.ts +++ b/src/modules/debug/debug.controller.ts @@ -2,10 +2,10 @@ import { Body, Controller, Get, - NotFoundException, Post, Query, Request, + Response, } from '@nestjs/common' import { HTTPDecorators } from '~/common/decorator/http.decorator' import { AdminEventsGateway } from '~/processors/gateway/admin/events.gateway' @@ -51,16 +51,25 @@ export class DebugController { @Post('/function') @HTTPDecorators.Bypass - async runFunction(@Body('function') functionString: string, @Request() req) { + async runFunction( + @Body('function') functionString: string, + @Request() req, + @Response() res, + ) { const model = new SnippetModel() model.name = 'debug' model.raw = functionString model.private = false model.type = SnippetType.Function - NotFoundException - return await this.serverlessService.injectContextIntoServerlessFunctionAndCall( - model, - { req, res: createMockedContextResponse() }, - ) + + const result = + await this.serverlessService.injectContextIntoServerlessFunctionAndCall( + model, + { req, res: createMockedContextResponse(res) }, + ) + + if (!res.sent) { + res.send(result) + } } } diff --git a/src/modules/serverless/function.types.ts b/src/modules/serverless/function.types.ts index a82c8515..e6c6aed4 100644 --- a/src/modules/serverless/function.types.ts +++ b/src/modules/serverless/function.types.ts @@ -4,4 +4,7 @@ export interface FunctionContextRequest extends FastifyRequest {} export interface FunctionContextResponse { throws(code: number, message: any): void + type(type: string): FunctionContextResponse + status(code: number, statusMessage?: string): FunctionContextResponse + send(data: any): void } diff --git a/src/modules/serverless/mock-response.util.ts b/src/modules/serverless/mock-response.util.ts index e480cc83..db5ee899 100644 --- a/src/modules/serverless/mock-response.util.ts +++ b/src/modules/serverless/mock-response.util.ts @@ -1,13 +1,31 @@ import { HttpException } from '@nestjs/common' +import type { FastifyReply } from 'fastify' import { FunctionContextResponse } from './function.types' -export const createMockedContextResponse = (): FunctionContextResponse => { - return { +export const createMockedContextResponse = ( + reply: FastifyReply, +): FunctionContextResponse => { + const response: FunctionContextResponse = { throws(code, message) { throw new HttpException( HttpException.createBody({ message }, message, code), code, ) }, + type(type: string) { + reply.type(type) + return response + }, + send(data: any) { + reply.send(data) + }, + status(code: number, message?: string) { + reply.raw.statusCode = code + if (message) { + reply.raw.statusMessage = message + } + return response + }, } + return response } diff --git a/src/modules/serverless/serverless.controller.ts b/src/modules/serverless/serverless.controller.ts index 210fc898..1a7a7115 100644 --- a/src/modules/serverless/serverless.controller.ts +++ b/src/modules/serverless/serverless.controller.ts @@ -1,12 +1,16 @@ import { + CacheTTL, Controller, ForbiddenException, Get, + InternalServerErrorException, NotFoundException, Param, Request, + Response, } from '@nestjs/common' -import type { FastifyRequest } from 'fastify' +import type { FastifyReply, FastifyRequest } from 'fastify' +import { Auth } from '~/common/decorator/auth.decorator' import { HTTPDecorators } from '~/common/decorator/http.decorator' import { ApiName } from '~/common/decorator/openapi.decorator' import { IsMaster } from '~/common/decorator/role.decorator' @@ -19,6 +23,25 @@ import { ServerlessService } from './serverless.service' export class ServerlessController { constructor(private readonly serverlessService: ServerlessService) {} + @Get('/types') + @Auth() + @HTTPDecorators.Bypass + @CacheTTL(60 * 60 * 24) + async getCodeDefined() { + try { + const text = await fs.readFile( + path.join(process.cwd(), 'assets', 'types', 'type.declare.ts'), + { + encoding: 'utf-8', + }, + ) + + return text + } catch (e) { + throw new InternalServerErrorException('code defined file not found') + } + } + @Get('/:reference/:name') @HTTPDecorators.Bypass async runServerlessFunction( @@ -26,6 +49,7 @@ export class ServerlessController { @IsMaster() isMaster: boolean, @Request() req: FastifyRequest, + @Response() reply: FastifyReply, ) { const { name, reference } = param const snippet = await this.serverlessService.model.findOne({ @@ -40,9 +64,14 @@ export class ServerlessController { if (snippet.private && !isMaster) { throw new ForbiddenException('no permission to run this function') } - return this.serverlessService.injectContextIntoServerlessFunctionAndCall( - snippet, - { req, res: createMockedContextResponse() }, - ) + const result = + await this.serverlessService.injectContextIntoServerlessFunctionAndCall( + snippet, + { req, res: createMockedContextResponse(reply) }, + ) + + if (!reply.sent) { + reply.send(result) + } } } diff --git a/src/modules/serverless/serverless.readme.md b/src/modules/serverless/serverless.readme.md index 7b46b4de..f2672d42 100644 --- a/src/modules/serverless/serverless.readme.md +++ b/src/modules/serverless/serverless.readme.md @@ -105,7 +105,7 @@ const remoteModule = `context.req` Request 对象 -~~`context.res`~~ 正在计划中 +`context.res` FunctionContextResponse 对象 `context.throws` 请求抛错,e.g. `context.throws(400, 'bad request')` @@ -147,9 +147,9 @@ And other global api is all banned. # TODO - [ ] HTTP Methods: POST, PUT, DELETE, PATCH -- [ ] ResponseType: buffer, stream +- [x] ResponseType: buffer, stream - [ ] handle safeEval throw - [ ] MongoDb inject (can access db) -- [ ] set Content-Type +- [x] set Content-Type - [ ] ESM AST Parser -- [ ] Cron to clean require cache \ No newline at end of file +- [x] Cron to clean require cache \ No newline at end of file diff --git a/src/modules/serverless/serverless.service.ts b/src/modules/serverless/serverless.service.ts index de25682e..9b42c97e 100644 --- a/src/modules/serverless/serverless.service.ts +++ b/src/modules/serverless/serverless.service.ts @@ -8,7 +8,7 @@ import { isURL } from 'class-validator' import fs, { mkdir, stat } from 'fs/promises' import { cloneDeep } from 'lodash' import { InjectModel } from 'nestjs-typegoose' -import { join } from 'path' +import path from 'path' import { nextTick } from 'process' import { DATA_DIR, NODE_REQUIRE_PATH } from '~/constants/path.constant' import { AssetService } from '~/processors/helper/helper.asset.service' @@ -37,7 +37,7 @@ export class ServerlessService { // `require('../hello.js')`. We can do that by adding /includes/plugin/a, // /includes/plugin/a/b, etc.. to the list mkdir(NODE_REQUIRE_PATH, { recursive: true }).then(async () => { - const pkgPath = join(NODE_REQUIRE_PATH, 'package.json') + const pkgPath = path.join(NODE_REQUIRE_PATH, 'package.json') const isPackageFileExist = await stat(pkgPath) .then(() => true) diff --git a/src/modules/snippet/snippet.controller.ts b/src/modules/snippet/snippet.controller.ts index 8b502963..4046b180 100644 --- a/src/modules/snippet/snippet.controller.ts +++ b/src/modules/snippet/snippet.controller.ts @@ -29,13 +29,18 @@ export class SnippetController { @Get('/') @Auth() async getList(@Query() query: PagerDto) { - const { page, size, select = '' } = query + const { page, size, select = '', db_query } = query return transformDataToPaginate( - await this.snippetService.model.paginate( - {}, - { page, limit: size, select }, - ), + await this.snippetService.model.paginate(db_query ?? {}, { + page, + limit: size, + select, + sort: { + reference: 1, + created: -1, + }, + }), ) } @@ -47,15 +52,10 @@ export class SnippetController { @Get('/:id') @Auth() - async getSnippetById( - @Param() param: MongoIdDto, - @IsMaster() isMaster: boolean, - ) { + async getSnippetById(@Param() param: MongoIdDto) { const { id } = param const snippet = await this.snippetService.getSnippetById(id) - if (snippet.private && !isMaster) { - throw new ForbiddenException('snippet is private') - } + return snippet }