feat(serverless): add res object on func
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user