diff --git a/src/modules/serverless/serverless.service.ts b/src/modules/serverless/serverless.service.ts index fec36531..700e1200 100644 --- a/src/modules/serverless/serverless.service.ts +++ b/src/modules/serverless/serverless.service.ts @@ -1,10 +1,12 @@ import { isURL } from 'class-validator' import fs, { mkdir, stat } from 'fs/promises' +import { isPlainObject } from 'lodash' import LRUCache from 'lru-cache' import { createRequire } from 'module' import { mongo } from 'mongoose' import path, { resolve } from 'path' import { nextTick } from 'process' +import qs from 'qs' import { TransformOptions, parseAsync, transformAsync } from '@babel/core' import BabelPluginTransformCommonJS from '@babel/plugin-transform-modules-commonjs' @@ -235,6 +237,14 @@ export class ServerlessService { const { raw: functionString } = model const logger = new Logger(`fx:${model.reference}/${model.name}`) const document = await this.model.findById(model.id) + const secretObj = model.secret ? qs.parse(model.secret) : {} + + if (!isPlainObject(secretObj)) { + throw new InternalServerErrorException( + `secret parsing error, must be object, got ${typeof secretObj}`, + ) + } + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this const globalContext = { @@ -257,6 +267,8 @@ export class ServerlessService { }, }, + secret: secretObj, + model, document, name: model.name, diff --git a/src/modules/snippet/snippet.model.ts b/src/modules/snippet/snippet.model.ts index 36023941..a8fecb83 100644 --- a/src/modules/snippet/snippet.model.ts +++ b/src/modules/snippet/snippet.model.ts @@ -8,7 +8,9 @@ import { Matches, MaxLength, } from 'class-validator' +import { isNil } from 'lodash' import aggregatePaginate from 'mongoose-aggregate-paginate-v2' +import { stringify } from 'qs' import { index, modelOptions, plugin, prop } from '@typegoose/typegoose' @@ -84,12 +86,20 @@ export class SnippetModel extends BaseModel { @IsOptional() schema?: string - // for function + // for function start @prop() @IsEnum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) @IsOptional() method?: string + @prop() + @IsString() + @IsOptional() + @Transform(({ value }) => (isNil(value) ? value : stringify(value))) + // username=123&password=123 + secret?: string + // for function end + @prop() @IsBoolean() @IsOptional() diff --git a/src/modules/snippet/snippet.service.ts b/src/modules/snippet/snippet.service.ts index d3405173..979a1139 100644 --- a/src/modules/snippet/snippet.service.ts +++ b/src/modules/snippet/snippet.service.ts @@ -1,6 +1,7 @@ import { load } from 'js-yaml' import JSON5 from 'json5' import { AggregatePaginateModel, Document } from 'mongoose' +import qs from 'qs' import { BadRequestException, @@ -52,9 +53,9 @@ export class SnippetService { return await this.model.create({ ...model, created: new Date() }) } - async update(id: string, model: SnippetModel) { - await this.validateTypeAndCleanup(model) - delete model.created + async update(id: string, newModel: SnippetModel) { + await this.validateTypeAndCleanup(newModel) + delete newModel.created const old = await this.model.findById(id).lean() if (!old) { @@ -63,19 +64,48 @@ export class SnippetService { if ( old.type === SnippetType.Function && - model.type !== SnippetType.Function + newModel.type !== SnippetType.Function ) { throw new BadRequestException( '`type` is not allowed to change if this snippet set to Function type.', ) } + // merge secret + if (old.secret && newModel.secret) { + const oldSecret = qs.parse(old.secret) + const newSecret = qs.parse(newModel.secret) + + // first delete key if newer secret not provide + for (const key in oldSecret) { + if (!(key in newSecret)) { + delete oldSecret[key] + } + } + + for (const key in newSecret) { + // if newSecret has same key, but value is empty, remove it + + if (newSecret[key] === '' && oldSecret[key] !== '') { + delete newSecret[key] + } + } + + newModel.secret = qs.stringify({ ...oldSecret, ...newSecret }) + } + await this.deleteCachedSnippet(old.reference, old.name) - return await this.model.findByIdAndUpdate( + const newerDoc = await this.model.findByIdAndUpdate( id, - { ...model, modified: new Date() }, + { ...newModel, modified: new Date() }, { new: true }, ) + if (newerDoc) { + const nextSnippet = this.transformLeanSnippetModel(newerDoc.toObject()) + + return nextSnippet + } + return newerDoc } async delete(id: string) { @@ -134,7 +164,7 @@ export class SnippetService { // TODO refactor // cleanup if (model.type !== SnippetType.Function) { - const deleteKeys: (keyof SnippetModel)[] = ['enable', 'method'] + const deleteKeys: (keyof SnippetModel)[] = ['enable', 'method', 'secret'] deleteKeys.forEach((key) => { Reflect.deleteProperty(model, key) }) @@ -146,7 +176,29 @@ export class SnippetService { if (!doc) { throw new NotFoundException() } - return doc + + // transform sth. + const nextSnippet = this.transformLeanSnippetModel(doc) + + return nextSnippet + } + + private transformLeanSnippetModel(snippet: SnippetModel) { + const nextSnippet = { ...snippet } + // transform sth. + if (snippet.type === SnippetType.Function) { + if (snippet.secret) { + const secretObj = qs.parse(snippet.secret) + + for (const key in secretObj) { + // remove secret value, only keep key + secretObj[key] = '' + } + nextSnippet.secret = secretObj as any + } + } + + return nextSnippet } /**