feat(serverless): add res object on func

This commit is contained in:
Innei
2022-03-11 22:44:50 +08:00
parent 19ee9a6f3c
commit bc5de61602
7 changed files with 91 additions and 32 deletions

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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
- [x] Cron to clean require cache

View File

@@ -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)

View File

@@ -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
}