From adbc4785deba2142a26cbff22c0bc711aef2f2ef Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Mon, 10 Jul 2023 06:55:00 +0000 Subject: [PATCH 1/8] feat(server): function templates add user avatar support --- .../dto/function-template-usedBy.dto.ts | 7 ++ .../function-templates.dto.ts} | 49 +++------- .../function-template.controller.ts | 90 +++++++------------ .../function-template.service.ts | 15 +--- 4 files changed, 51 insertions(+), 110 deletions(-) create mode 100644 server/src/function-template/dto/function-template-usedBy.dto.ts rename server/src/function-template/{entities/swagger-help.ts => dto/function-templates.dto.ts} (64%) diff --git a/server/src/function-template/dto/function-template-usedBy.dto.ts b/server/src/function-template/dto/function-template-usedBy.dto.ts new file mode 100644 index 0000000000..5db6fb7766 --- /dev/null +++ b/server/src/function-template/dto/function-template-usedBy.dto.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger' +import { ObjectId } from 'mongodb' + +export class GetFunctionTemplateUsedByDto { + @ApiProperty({ type: String }) + uid: ObjectId +} diff --git a/server/src/function-template/entities/swagger-help.ts b/server/src/function-template/dto/function-templates.dto.ts similarity index 64% rename from server/src/function-template/entities/swagger-help.ts rename to server/src/function-template/dto/function-templates.dto.ts index 27fbbd63bd..a55b206dcc 100644 --- a/server/src/function-template/entities/swagger-help.ts +++ b/server/src/function-template/dto/function-templates.dto.ts @@ -2,15 +2,14 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { HttpMethod } from 'src/function/entities/cloud-function' import { EnvironmentVariable } from 'src/application/entities/application-configuration' import { ObjectId } from 'mongodb' -import { User } from 'src/user/entities/user' -import { FunctionTemplate } from './function-template' +import { FunctionTemplate } from '../entities/function-template' -class FunctionTemplateItemSourceSwagger { +class FunctionTemplateItemSource { @ApiProperty({ description: 'The source code of the function' }) code: string } -class FunctionTemplateItemSwagger { +class FunctionTemplateItems { @ApiProperty({ type: String }) _id?: ObjectId @@ -24,7 +23,7 @@ class FunctionTemplateItemSwagger { desc: string @ApiProperty() - source: FunctionTemplateItemSourceSwagger + source: FunctionTemplateItemSource @ApiProperty({ type: [String], enum: HttpMethod }) methods: HttpMethod[] @@ -35,7 +34,7 @@ class FunctionTemplateItemSwagger { @ApiProperty() updatedAt: Date } -export class FunctionTemplateSwagger { +export class FunctionTemplatesDto { @ApiProperty({ type: String }) _id?: ObjectId @@ -69,33 +68,11 @@ export class FunctionTemplateSwagger { @ApiProperty() updatedAt: Date - @ApiPropertyOptional({ type: [FunctionTemplateItemSwagger] }) - items: FunctionTemplateItemSwagger[] + @ApiPropertyOptional({ type: [FunctionTemplateItems] }) + items: FunctionTemplateItems[] } -export class GetFunctionTemplateUsedBySwagger { - @ApiProperty({ type: String }) - _id: ObjectId - - @ApiProperty({ type: String }) - uid: ObjectId - - @ApiProperty({ type: String }) - templateId: ObjectId - - @ApiProperty() - createdAt: Date - - @ApiProperty() - updatedAt: Date - - @ApiProperty({ - type: [User], - }) - users: User[] -} - -export class GetMyStaredFunctionTemplateSwagger { +export class GetMyStaredFunctionTemplatesDto { @ApiProperty({ type: String }) _id: ObjectId @@ -114,11 +91,11 @@ export class GetMyStaredFunctionTemplateSwagger { @ApiProperty({ type: [FunctionTemplate] }) functionTemplate: FunctionTemplate[] - @ApiProperty({ type: [FunctionTemplateItemSwagger] }) - items: FunctionTemplateItemSwagger[] + @ApiProperty({ type: [FunctionTemplateItems] }) + items: FunctionTemplateItems[] } -export class GetMyRecentUseFunctionTemplateSwagger { +export class GetMyRecentUseFunctionTemplatesDto { @ApiProperty({ type: String }) _id: ObjectId @@ -137,6 +114,6 @@ export class GetMyRecentUseFunctionTemplateSwagger { @ApiProperty({ type: [FunctionTemplate] }) functionTemplate: FunctionTemplate[] - @ApiProperty({ type: [FunctionTemplateItemSwagger] }) - items: FunctionTemplateItemSwagger[] + @ApiProperty({ type: [FunctionTemplateItems] }) + items: FunctionTemplateItems[] } diff --git a/server/src/function-template/function-template.controller.ts b/server/src/function-template/function-template.controller.ts index 312bae6736..1546f7dbb9 100644 --- a/server/src/function-template/function-template.controller.ts +++ b/server/src/function-template/function-template.controller.ts @@ -27,10 +27,8 @@ import { ObjectId } from 'mongodb' import { FunctionService } from 'src/function/function.service' import { BundleService } from 'src/application/bundle.service' import { DependencyService } from 'src/dependency/dependency.service' -import { - FunctionTemplateSwagger, - GetFunctionTemplateUsedBySwagger, -} from './entities/swagger-help' +import { GetFunctionTemplateUsedByDto } from './dto/function-template-usedBy.dto' +import { FunctionTemplatesDto } from './dto/function-templates.dto' @ApiTags('FunctionTemplate') @ApiBearerAuth('Authorization') @@ -49,7 +47,7 @@ export class FunctionTemplateController { * @returns */ @ApiOperation({ summary: 'create a function template' }) - @ApiResponseArray(FunctionTemplateSwagger) + @ApiResponseArray(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Post() async createFunctionTemplate( @@ -81,7 +79,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'use a function template' }) - @ApiResponseArray(FunctionTemplateSwagger) + @ApiResponseArray(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Post(':templateId/:appid') async useFunctionTemplate( @@ -137,7 +135,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'update a function template' }) - @ApiResponseArray(FunctionTemplateSwagger) + @ApiResponseArray(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Patch('update/:id') async updateFunctionTemplate( @@ -169,7 +167,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'delete a function template' }) - @ApiResponseArray(FunctionTemplateSwagger) + @ApiResponseArray(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Delete(':id') async deleteFunctionTemplate( @@ -246,7 +244,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'get people who use this function template' }) - @ApiResponsePagination(GetFunctionTemplateUsedBySwagger) + @ApiResponsePagination(GetFunctionTemplateUsedByDto) @UseGuards(JwtAuthGuard) @Get(':id/used-by') async getFunctionTemplateUsedBy( @@ -262,6 +260,9 @@ export class FunctionTemplateController { asc = asc === 0 ? Number(asc) : 1 page = page ? Number(page) : 1 pageSize = pageSize ? Number(pageSize) : 10 + if (pageSize > 100) { + pageSize = 100 + } const found = await this.functionTemplateService.findOneFunctionTemplate( new ObjectId(templateId), ) @@ -283,7 +284,7 @@ export class FunctionTemplateController { * For example, if the value of sort is hot, then asc's sort is the star field */ @ApiOperation({ summary: 'get my function template' }) - @ApiResponsePagination(FunctionTemplateSwagger) + @ApiResponsePagination(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Get('my') async getMyFunctionTemplate( @@ -295,11 +296,14 @@ export class FunctionTemplateController { @Query('type') type: string, @Req() req: IRequest, ) { - if (type === 'default' && keyword) { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 + asc = asc === 0 ? Number(asc) : 1 + page = page ? Number(page) : 1 + pageSize = pageSize ? Number(pageSize) : 10 + if (pageSize > 100) { + pageSize = 100 + } + if (type === 'default' && keyword) { const res = await this.functionTemplateService.findMyFunctionTemplatesByName( asc, @@ -312,11 +316,7 @@ export class FunctionTemplateController { } if (type === 'default' && sort === 'hot') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 const hot = true - const res = await this.functionTemplateService.findMyFunctionTemplates( asc, page, @@ -328,10 +328,6 @@ export class FunctionTemplateController { } if (type === 'default') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const res = await this.functionTemplateService.findMyFunctionTemplates( asc, page, @@ -345,10 +341,6 @@ export class FunctionTemplateController { * stared function template */ if (type === 'stared' && keyword) { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const condition = { asc, page, @@ -364,9 +356,6 @@ export class FunctionTemplateController { } if (type === 'stared' && sort === 'hot') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 const condition = { page, pageSize, @@ -382,10 +371,6 @@ export class FunctionTemplateController { } if (type === 'stared') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const condition = { asc, page, @@ -403,10 +388,6 @@ export class FunctionTemplateController { * recent used function template */ if (type === 'recentUsed' && keyword) { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const condition = { asc, page, @@ -422,10 +403,6 @@ export class FunctionTemplateController { } if (type === 'recentUsed' && sort === 'hot') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const condition = { asc, page, @@ -441,10 +418,6 @@ export class FunctionTemplateController { } if (type === 'recentUsed') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const condition = { asc, page, @@ -460,7 +433,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'get all recommend function template' }) - @ApiResponsePagination(FunctionTemplateSwagger) + @ApiResponsePagination(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Get('recommend') async getRecommendFunctionTemplate( @@ -473,6 +446,9 @@ export class FunctionTemplateController { asc = asc === 0 ? Number(asc) : 1 page = page ? Number(page) : 1 pageSize = pageSize ? Number(pageSize) : 10 + if (pageSize > 100) { + pageSize = 100 + } const condition = { page, @@ -491,7 +467,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'get one function template' }) - @ApiResponseArray(FunctionTemplateSwagger) + @ApiResponseArray(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Get(':id') async getOneFunctionTemplate( @@ -523,7 +499,7 @@ export class FunctionTemplateController { } @ApiOperation({ summary: 'get all function template' }) - @ApiResponsePagination(FunctionTemplateSwagger) + @ApiResponsePagination(FunctionTemplatesDto) @UseGuards(JwtAuthGuard) @Get() async getAllFunctionTemplate( @@ -533,11 +509,13 @@ export class FunctionTemplateController { @Query('keyword') keyword: string, @Query('sort') sort: string, ) { + asc = asc === 0 ? Number(asc) : 1 + page = page ? Number(page) : 1 + pageSize = pageSize ? Number(pageSize) : 10 + if (pageSize > 100) { + pageSize = 100 + } if (keyword) { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const res = await this.functionTemplateService.findFunctionTemplatesByName( asc, @@ -549,10 +527,6 @@ export class FunctionTemplateController { } if (sort === 'hot') { - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const hot = true const res = await this.functionTemplateService.findFunctionTemplates( asc, @@ -563,10 +537,6 @@ export class FunctionTemplateController { return ResponseUtil.ok(res) } - asc = asc === 0 ? Number(asc) : 1 - page = page ? Number(page) : 1 - pageSize = pageSize ? Number(pageSize) : 10 - const res = await this.functionTemplateService.findFunctionTemplates( asc, page, diff --git a/server/src/function-template/function-template.service.ts b/server/src/function-template/function-template.service.ts index 7493cfd416..a63ddeb4bb 100644 --- a/server/src/function-template/function-template.service.ts +++ b/server/src/function-template/function-template.service.ts @@ -575,14 +575,7 @@ export class FunctionTemplateService { ) { const pipe = [ { $match: { templateId: templateId } }, - { - $lookup: { - from: 'User', - localField: 'uid', - foreignField: '_id', - as: 'users', - }, - }, + { $project: { uid: 1, _id: 0 } }, { $sort: { updatedAt: asc === 0 ? 1 : -1 } }, { $skip: (page - 1) * pageSize }, { $limit: pageSize }, @@ -596,12 +589,6 @@ export class FunctionTemplateService { .collection('FunctionTemplateUseRelation') .countDocuments({ templateId }) - usedBy.forEach((item) => { - item.users[0].username = item.users[0].username.slice(0, 3) - item.users[0].email = null - item.users[0].phone = null - }) - const res = { list: usedBy, total: total, From e95fe24a5230a7581e60e390ce2f3aea279c754c Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Tue, 11 Jul 2023 09:33:40 +0000 Subject: [PATCH 2/8] feat(server): add function recycle bin --- .../entities/cloud-function-recycle-bin.ts | 16 +++ server/src/function/function.controller.ts | 39 +++++- server/src/function/function.service.ts | 119 ++++++++++++++++++ .../cloud-function-query.interface.ts | 8 ++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 server/src/function/entities/cloud-function-recycle-bin.ts create mode 100644 server/src/function/interface/cloud-function-query.interface.ts diff --git a/server/src/function/entities/cloud-function-recycle-bin.ts b/server/src/function/entities/cloud-function-recycle-bin.ts new file mode 100644 index 0000000000..655f566ebd --- /dev/null +++ b/server/src/function/entities/cloud-function-recycle-bin.ts @@ -0,0 +1,16 @@ +import { ObjectId } from 'mongodb' +import { CloudFunctionSource, HttpMethod } from './cloud-function' + +export class CloudFunctionRecycleBin { + _id?: ObjectId + appid: string + name: string + source: CloudFunctionSource + desc: string + tags: string[] + methods: HttpMethod[] + params?: any + createdAt: Date + updatedAt: Date + createdBy: ObjectId +} diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index 02967170b3..8969cc354b 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -10,12 +10,14 @@ import { HttpException, HttpStatus, Req, + Query, } from '@nestjs/common' import { CreateFunctionDto } from './dto/create-function.dto' import { UpdateFunctionDto } from './dto/update-function.dto' import { ApiResponseArray, ApiResponseObject, + ApiResponsePagination, ResponseUtil, } from '../utils/response' import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger' @@ -30,6 +32,8 @@ import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard' import { CloudFunctionHistory } from './entities/cloud-function-history' import { CloudFunction } from './entities/cloud-function' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' +import { CloudFunctionRecycleBin } from './entities/cloud-function-recycle-bin' +import { CloudFunctionRecycleBinQuery } from './interface/cloud-function-query.interface' @ApiTags('Function') @ApiBearerAuth('Authorization') @@ -214,7 +218,12 @@ export class FunctionController { HttpStatus.NOT_FOUND, ) } - + const recycleBinStorage = await this.functionsService.getRecycleBinStorage( + appid, + ) + if (recycleBinStorage >= 1000) { + return ResponseUtil.error('Recycle bin is full, please free up space') + } const res = await this.functionsService.removeOne(func) if (!res) { return ResponseUtil.error(i18n.t('function.delete.error')) @@ -277,4 +286,32 @@ export class FunctionController { const res = await this.functionsService.getHistory(func) return ResponseUtil.ok(res) } + + /** + * Get function history + */ + @ApiOperation({ summary: 'Get cloud function recycle bin' }) + @ApiResponsePagination(CloudFunctionRecycleBin) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Get('recycle-bin') + async getRecycleBin( + @Param('appid') appid: string, + @Query('keyword') keyword?: string, + @Query('page') page?: number, + @Query('pageSize') pageSize?: number, + @Query('startTime') startTime?: number, + @Query('endTime') endTime?: number, + ) { + const query: CloudFunctionRecycleBinQuery = { + page: page || 1, + pageSize: pageSize || 12, + } + if (query.pageSize > 100) { + query.pageSize = 100 + } + if (keyword) { + query.name = keyword + } + // return ResponseUtil.ok(res) + } } diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index d9dc36332f..9c370c1408 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -22,6 +22,8 @@ import { CloudFunctionHistory } from './entities/cloud-function-history' import { TriggerService } from 'src/trigger/trigger.service' import { TriggerPhase } from 'src/trigger/entities/cron-trigger' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' +import { CloudFunctionRecycleBin } from './entities/cloud-function-recycle-bin' +import { CloudFunctionRecycleBinQuery } from './interface/cloud-function-query.interface' @Injectable() export class FunctionService { @@ -196,6 +198,9 @@ export class FunctionService { await this.deleteHistory(res.value) await this.unpublish(appid, name) + + // add this function to rcycle bin + await this.addToRecycleBin(res.value) return res.value } @@ -227,6 +232,24 @@ export class FunctionService { } } + async publishMany(funcs: CloudFunction[]) { + const { db, client } = await this.databaseService.findAndConnect( + funcs[0].appid, + ) + const session = client.startSession() + try { + await session.withTransaction(async () => { + const coll = db.collection(CN_PUBLISHED_FUNCTIONS) + const funcNames = funcs.map((func) => func.name) + await coll.deleteMany({ name: { $in: funcNames } }, { session }) + await coll.insertMany(funcs, { session }) + }) + } finally { + await session.endSession() + await client.close() + } + } + async publishFunctionTemplateItems(funcs: CloudFunction[]) { const { db, client } = await this.databaseService.findAndConnect( funcs[0].appid, @@ -397,4 +420,100 @@ export class FunctionService { }) return res } + + async addToRecycleBin(func: CloudFunction) { + const res = await this.db + .collection('CloudFunctionRecycleBin') + .insertOne(func) + return res + } + + async getRecycleBinStorage(appid: string) { + const res = await this.db + .collection('CloudFunctionRecycleBin') + .countDocuments({ appid }) + return res + } + + async getRecycleBin(appid: string, condition: CloudFunctionRecycleBinQuery) { + const query = { + appid, + } + if (condition.name) { + query['name'] = { + $regex: condition.name, + $options: 'i', + } + } + + if (condition.startTime) { + query['createdAt'] = { $gte: condition.startTime } + } + + if (condition.endTime) { + if (condition.startTime) { + query['createdAt']['$lte'] = condition.endTime + } else { + query['createdAt'] = { $lte: condition.endTime } + } + } + + const total = await this.db + .collection('CloudFunctionRecycleBin') + .countDocuments(query) + + const functions = await this.db + .collection('CloudFunctionRecycleBin') + .find(query) + .skip((condition.page - 1) * condition.pageSize) + .limit(condition.pageSize) + .toArray() + + const res = { + total, + list: functions, + page: condition.page, + pageSize: condition.pageSize, + } + return res + } + + async emptyRecycleBin(appid: string) { + const res = await this.db + .collection('CloudFunctionRecycleBin') + .deleteMany({ appid }) + return res + } + + async deleteFromRecycleBin(ids: ObjectId[]) { + const res = await this.db + .collection('CloudFunctionRecycleBin') + .deleteMany({ _id: { $in: ids } }) + return res + } + + async restoreDeletedCloudFunctions(ids: ObjectId[]) { + const client = SystemDatabase.client + const session = client.startSession() + try { + session.startTransaction() + + const res = await this.db + .collection('CloudFunctionRecycleBin') + .find({ _id: { $in: ids } }) + .toArray() + + await this.db + .collection('CloudFunction') + .insertMany(res, { session }) + + await this.publishMany(res) + } catch (err) { + await session.abortTransaction() + this.logger.error(err) + throw err + } finally { + await session.endSession() + } + } } diff --git a/server/src/function/interface/cloud-function-query.interface.ts b/server/src/function/interface/cloud-function-query.interface.ts new file mode 100644 index 0000000000..6853f885d0 --- /dev/null +++ b/server/src/function/interface/cloud-function-query.interface.ts @@ -0,0 +1,8 @@ +export interface CloudFunctionRecycleBinQuery { + name?: string + appid?: string + startTime?: Date + endTime?: Date + page?: number + pageSize?: number +} From 682e3a1576a53b90596df928226db102e8206ddc Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Tue, 11 Jul 2023 10:32:40 +0000 Subject: [PATCH 3/8] add function recycle bin control --- server/src/function/function.controller.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index 8969cc354b..c2d56f6699 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -288,7 +288,7 @@ export class FunctionController { } /** - * Get function history + * Get function Recycle bin */ @ApiOperation({ summary: 'Get cloud function recycle bin' }) @ApiResponsePagination(CloudFunctionRecycleBin) @@ -309,9 +309,20 @@ export class FunctionController { if (query.pageSize > 100) { query.pageSize = 100 } + if (startTime) { + query.startTime = new Date(startTime) + } + if (endTime) { + query.endTime = new Date(endTime) + } if (keyword) { query.name = keyword } - // return ResponseUtil.ok(res) + const res = await this.functionsService.getRecycleBin(appid, query) + return ResponseUtil.ok(res) } + + // delete all + // delete + // restore } From 900ed11bf0588f90df6a05dc854e7d83cc5d0210 Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Thu, 13 Jul 2023 03:50:45 +0000 Subject: [PATCH 4/8] refactor recycle-bin --- server/src/app.module.ts | 2 + .../entities/cloud-function-recycle-bin.ts | 16 --- server/src/function/function.controller.ts | 54 +------- server/src/function/function.module.ts | 3 +- server/src/function/function.service.ts | 102 +------------- .../dto/delete-recycle-bin-functions.dto.ts | 13 ++ .../dto/get-recycle-bin-functions.dto.ts | 76 +++++++++++ .../dto/restore-recycle-bin-functions.dto.ts | 13 ++ .../function-recycle-bin.controller.ts | 122 +++++++++++++++++ .../function-recycle-bin.service.ts | 127 ++++++++++++++++++ .../function-recycle-bin-query.interface.ts} | 0 .../src/recycle-bin/entities/recycle-bin.ts | 8 ++ server/src/recycle-bin/recycle-bin.module.ts | 12 ++ 13 files changed, 385 insertions(+), 163 deletions(-) delete mode 100644 server/src/function/entities/cloud-function-recycle-bin.ts create mode 100644 server/src/recycle-bin/cloud-function/dto/delete-recycle-bin-functions.dto.ts create mode 100644 server/src/recycle-bin/cloud-function/dto/get-recycle-bin-functions.dto.ts create mode 100644 server/src/recycle-bin/cloud-function/dto/restore-recycle-bin-functions.dto.ts create mode 100644 server/src/recycle-bin/cloud-function/function-recycle-bin.controller.ts create mode 100644 server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts rename server/src/{function/interface/cloud-function-query.interface.ts => recycle-bin/cloud-function/interface/function-recycle-bin-query.interface.ts} (100%) create mode 100644 server/src/recycle-bin/entities/recycle-bin.ts create mode 100644 server/src/recycle-bin/recycle-bin.module.ts diff --git a/server/src/app.module.ts b/server/src/app.module.ts index db74f48f1c..0d86da61de 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -24,6 +24,7 @@ import { BillingModule } from './billing/billing.module' import { AuthenticationModule } from './authentication/authentication.module' import { FunctionTemplateModule } from './function-template/function-template.module' import { MulterModule } from '@nestjs/platform-express' +import { RecycleBinModule } from './recycle-bin/recycle-bin.module' @Module({ imports: [ @@ -66,6 +67,7 @@ import { MulterModule } from '@nestjs/platform-express' BillingModule, FunctionTemplateModule, MulterModule.register(), + RecycleBinModule, ], controllers: [AppController], providers: [AppService], diff --git a/server/src/function/entities/cloud-function-recycle-bin.ts b/server/src/function/entities/cloud-function-recycle-bin.ts deleted file mode 100644 index 655f566ebd..0000000000 --- a/server/src/function/entities/cloud-function-recycle-bin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ObjectId } from 'mongodb' -import { CloudFunctionSource, HttpMethod } from './cloud-function' - -export class CloudFunctionRecycleBin { - _id?: ObjectId - appid: string - name: string - source: CloudFunctionSource - desc: string - tags: string[] - methods: HttpMethod[] - params?: any - createdAt: Date - updatedAt: Date - createdBy: ObjectId -} diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index c2d56f6699..519a5ea3a7 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -10,14 +10,12 @@ import { HttpException, HttpStatus, Req, - Query, } from '@nestjs/common' import { CreateFunctionDto } from './dto/create-function.dto' import { UpdateFunctionDto } from './dto/update-function.dto' import { ApiResponseArray, ApiResponseObject, - ApiResponsePagination, ResponseUtil, } from '../utils/response' import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger' @@ -32,8 +30,7 @@ import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard' import { CloudFunctionHistory } from './entities/cloud-function-history' import { CloudFunction } from './entities/cloud-function' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' -import { CloudFunctionRecycleBin } from './entities/cloud-function-recycle-bin' -import { CloudFunctionRecycleBinQuery } from './interface/cloud-function-query.interface' +import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' @ApiTags('Function') @ApiBearerAuth('Authorization') @@ -42,6 +39,7 @@ export class FunctionController { constructor( private readonly functionsService: FunctionService, private readonly bundleService: BundleService, + private readonly functionRecycleBinService: FunctionRecycleBinService, private readonly i18n: I18nService, ) {} @@ -218,12 +216,13 @@ export class FunctionController { HttpStatus.NOT_FOUND, ) } - const recycleBinStorage = await this.functionsService.getRecycleBinStorage( - appid, - ) - if (recycleBinStorage >= 1000) { + const recycleBinStorage = + await this.functionRecycleBinService.getRecycleBinStorage(appid) + + if (recycleBinStorage >= 3) { return ResponseUtil.error('Recycle bin is full, please free up space') } + const res = await this.functionsService.removeOne(func) if (!res) { return ResponseUtil.error(i18n.t('function.delete.error')) @@ -286,43 +285,4 @@ export class FunctionController { const res = await this.functionsService.getHistory(func) return ResponseUtil.ok(res) } - - /** - * Get function Recycle bin - */ - @ApiOperation({ summary: 'Get cloud function recycle bin' }) - @ApiResponsePagination(CloudFunctionRecycleBin) - @UseGuards(JwtAuthGuard, ApplicationAuthGuard) - @Get('recycle-bin') - async getRecycleBin( - @Param('appid') appid: string, - @Query('keyword') keyword?: string, - @Query('page') page?: number, - @Query('pageSize') pageSize?: number, - @Query('startTime') startTime?: number, - @Query('endTime') endTime?: number, - ) { - const query: CloudFunctionRecycleBinQuery = { - page: page || 1, - pageSize: pageSize || 12, - } - if (query.pageSize > 100) { - query.pageSize = 100 - } - if (startTime) { - query.startTime = new Date(startTime) - } - if (endTime) { - query.endTime = new Date(endTime) - } - if (keyword) { - query.name = keyword - } - const res = await this.functionsService.getRecycleBin(appid, query) - return ResponseUtil.ok(res) - } - - // delete all - // delete - // restore } diff --git a/server/src/function/function.module.ts b/server/src/function/function.module.ts index fb6e9bafd8..77e47603a4 100644 --- a/server/src/function/function.module.ts +++ b/server/src/function/function.module.ts @@ -5,9 +5,10 @@ import { FunctionController } from './function.controller' import { FunctionService } from './function.service' import { DatabaseModule } from 'src/database/database.module' import { TriggerService } from 'src/trigger/trigger.service' +import { RecycleBinModule } from 'src/recycle-bin/recycle-bin.module' @Module({ - imports: [ApplicationModule, DatabaseModule], + imports: [ApplicationModule, DatabaseModule, RecycleBinModule], controllers: [FunctionController], providers: [FunctionService, JwtService, TriggerService], exports: [FunctionService], diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index 9c370c1408..5bc9c8afb2 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -22,8 +22,7 @@ import { CloudFunctionHistory } from './entities/cloud-function-history' import { TriggerService } from 'src/trigger/trigger.service' import { TriggerPhase } from 'src/trigger/entities/cron-trigger' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' -import { CloudFunctionRecycleBin } from './entities/cloud-function-recycle-bin' -import { CloudFunctionRecycleBinQuery } from './interface/cloud-function-query.interface' +import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' @Injectable() export class FunctionService { @@ -34,6 +33,7 @@ export class FunctionService { private readonly databaseService: DatabaseService, private readonly jwtService: JwtService, private readonly triggerService: TriggerService, + private readonly functionRecycleBinService: FunctionRecycleBinService, ) {} async create(appid: string, userid: ObjectId, dto: CreateFunctionDto) { await this.db.collection('CloudFunction').insertOne({ @@ -200,7 +200,7 @@ export class FunctionService { await this.unpublish(appid, name) // add this function to rcycle bin - await this.addToRecycleBin(res.value) + await this.functionRecycleBinService.addToRecycleBin(res.value) return res.value } @@ -420,100 +420,4 @@ export class FunctionService { }) return res } - - async addToRecycleBin(func: CloudFunction) { - const res = await this.db - .collection('CloudFunctionRecycleBin') - .insertOne(func) - return res - } - - async getRecycleBinStorage(appid: string) { - const res = await this.db - .collection('CloudFunctionRecycleBin') - .countDocuments({ appid }) - return res - } - - async getRecycleBin(appid: string, condition: CloudFunctionRecycleBinQuery) { - const query = { - appid, - } - if (condition.name) { - query['name'] = { - $regex: condition.name, - $options: 'i', - } - } - - if (condition.startTime) { - query['createdAt'] = { $gte: condition.startTime } - } - - if (condition.endTime) { - if (condition.startTime) { - query['createdAt']['$lte'] = condition.endTime - } else { - query['createdAt'] = { $lte: condition.endTime } - } - } - - const total = await this.db - .collection('CloudFunctionRecycleBin') - .countDocuments(query) - - const functions = await this.db - .collection('CloudFunctionRecycleBin') - .find(query) - .skip((condition.page - 1) * condition.pageSize) - .limit(condition.pageSize) - .toArray() - - const res = { - total, - list: functions, - page: condition.page, - pageSize: condition.pageSize, - } - return res - } - - async emptyRecycleBin(appid: string) { - const res = await this.db - .collection('CloudFunctionRecycleBin') - .deleteMany({ appid }) - return res - } - - async deleteFromRecycleBin(ids: ObjectId[]) { - const res = await this.db - .collection('CloudFunctionRecycleBin') - .deleteMany({ _id: { $in: ids } }) - return res - } - - async restoreDeletedCloudFunctions(ids: ObjectId[]) { - const client = SystemDatabase.client - const session = client.startSession() - try { - session.startTransaction() - - const res = await this.db - .collection('CloudFunctionRecycleBin') - .find({ _id: { $in: ids } }) - .toArray() - - await this.db - .collection('CloudFunction') - .insertMany(res, { session }) - - await this.publishMany(res) - } catch (err) { - await session.abortTransaction() - this.logger.error(err) - throw err - } finally { - await session.endSession() - } - } } diff --git a/server/src/recycle-bin/cloud-function/dto/delete-recycle-bin-functions.dto.ts b/server/src/recycle-bin/cloud-function/dto/delete-recycle-bin-functions.dto.ts new file mode 100644 index 0000000000..30b846c663 --- /dev/null +++ b/server/src/recycle-bin/cloud-function/dto/delete-recycle-bin-functions.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsArray, IsNotEmpty, IsString } from 'class-validator' + +export class DeleteRecycleBinItemsDto { + @ApiProperty({ + description: 'The list of item ids', + type: [String], + }) + @IsNotEmpty() + @IsArray() + @IsString({ each: true }) + ids: string[] +} diff --git a/server/src/recycle-bin/cloud-function/dto/get-recycle-bin-functions.dto.ts b/server/src/recycle-bin/cloud-function/dto/get-recycle-bin-functions.dto.ts new file mode 100644 index 0000000000..ac3bfeb1f2 --- /dev/null +++ b/server/src/recycle-bin/cloud-function/dto/get-recycle-bin-functions.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' +import { + IsArray, + IsIn, + IsNotEmpty, + IsString, + Matches, + MaxLength, +} from 'class-validator' +import { HTTP_METHODS } from 'src/constants' +import { HttpMethod } from 'src/function/entities/cloud-function' + +class CloudFunctionSourceDto { + @ApiProperty() + code: string + + @ApiProperty() + compiled: string + + @ApiPropertyOptional() + uri?: string + + @ApiProperty() + version: number + + @ApiPropertyOptional() + hash?: string + + @ApiPropertyOptional() + lang?: string +} + +export class FunctionRecycleBinItemsDto { + @ApiProperty() + _id: string + + @ApiProperty() + appid: string + + @ApiProperty({ + description: 'Function name is unique in the application', + }) + @IsNotEmpty() + @Matches(/^[a-zA-Z0-9_.\-\/]{1,256}$/) + name: string + + @ApiProperty({ type: CloudFunctionSourceDto }) + source: CloudFunctionSourceDto + + @ApiPropertyOptional() + @MaxLength(256) + description: string + + @ApiPropertyOptional({ type: [String] }) + @IsString({ each: true }) + @IsArray() + @MaxLength(16, { each: true }) + @IsNotEmpty({ each: true }) + tags: string[] + + @ApiProperty({ type: [String], enum: HttpMethod }) + @IsIn(HTTP_METHODS, { each: true }) + methods: HttpMethod[] = [] + + @ApiPropertyOptional() + params?: any + + @ApiProperty({ type: 'date' }) + createdAt: Date + + @ApiProperty({ type: 'date' }) + updatedAt: Date + + @ApiProperty() + createdBy: string +} diff --git a/server/src/recycle-bin/cloud-function/dto/restore-recycle-bin-functions.dto.ts b/server/src/recycle-bin/cloud-function/dto/restore-recycle-bin-functions.dto.ts new file mode 100644 index 0000000000..011ab73da7 --- /dev/null +++ b/server/src/recycle-bin/cloud-function/dto/restore-recycle-bin-functions.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsArray, IsNotEmpty, IsString } from 'class-validator' + +export class RestoreRecycleBinItemsDto { + @ApiProperty({ + description: 'The list of item ids', + type: [String], + }) + @IsNotEmpty() + @IsArray() + @IsString({ each: true }) + ids: string[] +} diff --git a/server/src/recycle-bin/cloud-function/function-recycle-bin.controller.ts b/server/src/recycle-bin/cloud-function/function-recycle-bin.controller.ts new file mode 100644 index 0000000000..d5e668156f --- /dev/null +++ b/server/src/recycle-bin/cloud-function/function-recycle-bin.controller.ts @@ -0,0 +1,122 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Delete, + UseGuards, + Query, +} from '@nestjs/common' +import { + ApiResponseObject, + ApiResponsePagination, + ResponseUtil, +} from 'src/utils/response' +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger' +import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard' +import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard' +import { ObjectId } from 'mongodb' +import { FunctionRecycleBinService } from './function-recycle-bin.service' +import { DeleteRecycleBinItemsDto } from './dto/delete-recycle-bin-functions.dto' +import { RestoreRecycleBinItemsDto } from './dto/restore-recycle-bin-functions.dto' +import { FunctionRecycleBinItemsDto } from './dto/get-recycle-bin-functions.dto' +import { CloudFunctionRecycleBinQuery } from './interface/function-recycle-bin-query.interface' + +@ApiTags('RecycleBin') +@ApiBearerAuth('Authorization') +@Controller('recycle-bin/:appid/functions') +export class FunctionRecycleBinController { + constructor( + private readonly functionRecycleBinService: FunctionRecycleBinService, + ) {} + + /** + * Delete function Recycle bin items + */ + @ApiOperation({ summary: 'Delete function Recycle bin items' }) + @ApiResponseObject(Number) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Post('delete') + async deleteRecycleBinItems( + @Param('appid') appid: string, + @Body() dto: DeleteRecycleBinItemsDto, + ) { + const ids = dto.ids.map((id) => new ObjectId(id)) + const res = await this.functionRecycleBinService.deleteFromRecycleBin( + appid, + ids, + ) + if (!res.acknowledged) { + return ResponseUtil.error('empty recycle bin items failed') + } + return ResponseUtil.ok(res.deletedCount) + } + + @ApiOperation({ summary: 'Empty function Recycle bin items' }) + @ApiResponseObject(Number) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Delete() + async emptyRecycleBin(@Param('appid') appid: string) { + const res = await this.functionRecycleBinService.emptyRecycleBin(appid) + if (!res.acknowledged) { + return ResponseUtil.error('Empty function Recycle bin failed') + } + return ResponseUtil.ok(res.deletedCount) + } + + /** + * Restore function Recycle bin items + */ + @ApiOperation({ summary: 'restore function Recycle bin items' }) + @ApiResponseObject(Number) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Post('restore') + async restoreRecycleBinItems( + @Param('appid') appid: string, + @Body() dto: RestoreRecycleBinItemsDto, + ) { + const ids = dto.ids.map((id) => new ObjectId(id)) + const res = + await this.functionRecycleBinService.restoreDeletedCloudFunctions( + appid, + ids, + ) + if (!res.acknowledged) { + return ResponseUtil.error('restore function recycle items failed') + } + return ResponseUtil.ok(res.insertedCount) + } + + @ApiOperation({ summary: 'Get cloud function recycle bin' }) + @ApiResponsePagination(FunctionRecycleBinItemsDto) + @UseGuards(JwtAuthGuard, ApplicationAuthGuard) + @Get() + async getRecycleBin( + @Param('appid') appid: string, + @Query('keyword') keyword?: string, + @Query('page') page?: number, + @Query('pageSize') pageSize?: number, + @Query('startTime') startTime?: number, + @Query('endTime') endTime?: number, + ) { + const query: CloudFunctionRecycleBinQuery = { + page: page || 1, + pageSize: pageSize || 12, + } + if (query.pageSize > 100) { + query.pageSize = 100 + } + if (startTime) { + query.startTime = new Date(startTime) + } + if (endTime) { + query.endTime = new Date(endTime) + } + if (keyword) { + query.name = keyword + } + const res = await this.functionRecycleBinService.getRecycleBin(appid, query) + return ResponseUtil.ok(res) + } +} diff --git a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts new file mode 100644 index 0000000000..a32118fc65 --- /dev/null +++ b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts @@ -0,0 +1,127 @@ +import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common' +import { SystemDatabase } from 'src/system-database' +import { ObjectId } from 'mongodb' +import { CloudFunction } from 'src/function/entities/cloud-function' +import { DataType, RecycleBin } from '../entities/recycle-bin' +import { CloudFunctionRecycleBinQuery } from './interface/function-recycle-bin-query.interface' +import { FunctionService } from 'src/function/function.service' + +@Injectable() +export class FunctionRecycleBinService { + private readonly logger = new Logger(FunctionRecycleBinService.name) + private readonly db = SystemDatabase.db + + constructor( + @Inject(forwardRef(() => FunctionService)) + private readonly functionService: FunctionService, + ) {} + + async addToRecycleBin(func: CloudFunction) { + const res = await this.db + .collection('RecycleBin') + .insertOne({ type: DataType.FUNCTION, data: func }) + return res + } + + async getRecycleBinStorage(appid: string) { + const res = await this.db + .collection('RecycleBin') + .countDocuments({ type: DataType.FUNCTION, 'data.appid': appid }) + return res + } + + async getRecycleBin(appid: string, condition: CloudFunctionRecycleBinQuery) { + const query = { + type: DataType.FUNCTION, + 'data.appid': appid, + } + if (condition.name) { + query['data.name'] = { + $regex: condition.name, + $options: 'i', + } + } + + if (condition.startTime) { + query['data.createdAt'] = { $gte: condition.startTime } + } + + if (condition.endTime) { + if (condition.startTime) { + query['data.createdAt']['$lte'] = condition.endTime + } else { + query['data.createdAt'] = { $lte: condition.endTime } + } + } + + const total = await this.db + .collection('RecycleBin') + .countDocuments(query) + + const functions = await this.db + .collection('RecycleBin') + .find(query) + .skip((condition.page - 1) * condition.pageSize) + .limit(condition.pageSize) + .toArray() + + const res = { + total, + list: functions, + page: condition.page, + pageSize: condition.pageSize, + } + return res + } + + async emptyRecycleBin(appid: string) { + const res = await this.db + .collection('RecycleBin') + .deleteMany({ type: DataType.FUNCTION, 'data.appid': appid }) + return res + } + + async deleteFromRecycleBin(appid: string, ids: ObjectId[]) { + const res = await this.db.collection('RecycleBin').deleteMany({ + type: DataType.FUNCTION, + 'data._id': { $in: ids }, + 'data.appid': appid, + }) + return res + } + + async restoreDeletedCloudFunctions(appid: string, ids: ObjectId[]) { + const client = SystemDatabase.client + const session = client.startSession() + try { + session.startTransaction() + + const recycleBinItems = await this.db + .collection('RecycleBin') + .find({ + type: DataType.FUNCTION, + 'data._id': { $in: ids }, + 'data.appid': appid, + }) + .toArray() + + const functions = recycleBinItems.map((item) => item.data) + + const insertResults = await this.db + .collection('CloudFunction') + .insertMany(functions, { session }) + + await this.functionService.publishMany(functions) + + await session.commitTransaction() + + return insertResults + } catch (err) { + await session.abortTransaction() + this.logger.error(err) + throw err + } finally { + await session.endSession() + } + } +} diff --git a/server/src/function/interface/cloud-function-query.interface.ts b/server/src/recycle-bin/cloud-function/interface/function-recycle-bin-query.interface.ts similarity index 100% rename from server/src/function/interface/cloud-function-query.interface.ts rename to server/src/recycle-bin/cloud-function/interface/function-recycle-bin-query.interface.ts diff --git a/server/src/recycle-bin/entities/recycle-bin.ts b/server/src/recycle-bin/entities/recycle-bin.ts new file mode 100644 index 0000000000..f8eb2adb45 --- /dev/null +++ b/server/src/recycle-bin/entities/recycle-bin.ts @@ -0,0 +1,8 @@ +export enum DataType { + FUNCTION = 'function', +} + +export class RecycleBin { + type: DataType + data: any +} diff --git a/server/src/recycle-bin/recycle-bin.module.ts b/server/src/recycle-bin/recycle-bin.module.ts new file mode 100644 index 0000000000..b1d4a9c18b --- /dev/null +++ b/server/src/recycle-bin/recycle-bin.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' +import { FunctionRecycleBinController } from './cloud-function/function-recycle-bin.controller' +import { FunctionRecycleBinService } from './cloud-function/function-recycle-bin.service' +import { FunctionModule } from 'src/function/function.module' + +@Module({ + imports: [FunctionModule], + controllers: [FunctionRecycleBinController], + providers: [FunctionRecycleBinService], + exports: [FunctionRecycleBinService], +}) +export class RecycleBinModule {} From 3967760d56fd71ee1bfcad169682968cdd5c8c23 Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Thu, 13 Jul 2023 07:02:46 +0000 Subject: [PATCH 5/8] fix dependency and refactor recycle-bin entity --- server/src/application/application.module.ts | 2 ++ server/src/constants.ts | 3 +++ server/src/function/function.controller.ts | 3 ++- server/src/function/function.module.ts | 11 ++++++--- server/src/function/function.service.ts | 2 +- .../function-recycle-bin.service.ts | 23 +++++++++++++++---- .../src/recycle-bin/entities/recycle-bin.ts | 1 + server/src/recycle-bin/recycle-bin.module.ts | 20 +++++++++++++--- server/src/trigger/trigger.module.ts | 2 ++ 9 files changed, 55 insertions(+), 12 deletions(-) diff --git a/server/src/application/application.module.ts b/server/src/application/application.module.ts index 8d9dd8d301..482488fede 100644 --- a/server/src/application/application.module.ts +++ b/server/src/application/application.module.ts @@ -16,6 +16,7 @@ import { WebsiteService } from 'src/website/website.service' import { AccountModule } from 'src/account/account.module' import { BundleService } from './bundle.service' import { ResourceService } from 'src/billing/resource.service' +import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' @Module({ imports: [StorageModule, DatabaseModule, GatewayModule, AccountModule], @@ -24,6 +25,7 @@ import { ResourceService } from 'src/billing/resource.service' ApplicationService, ApplicationTaskService, InstanceService, + FunctionRecycleBinService, JwtService, FunctionService, EnvironmentVariableService, diff --git a/server/src/constants.ts b/server/src/constants.ts index 1c883c0a5c..41fe07767f 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -194,3 +194,6 @@ export const ALISMS_KEY = 'alisms' export const LIMIT_CODE_FREQUENCY = 60 * 1000 // 60 seconds (in milliseconds) export const LIMIT_CODE_PER_IP_PER_DAY = 30 // 30 times export const CODE_VALIDITY = 10 * 60 * 1000 // 10 minutes (in milliseconds) + +// Recycle bin constants +export const Storage_Limit = 1000 // 1000 items diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index 519a5ea3a7..d6190e0937 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -31,6 +31,7 @@ import { CloudFunctionHistory } from './entities/cloud-function-history' import { CloudFunction } from './entities/cloud-function' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' +import { Storage_Limit } from 'src/constants' @ApiTags('Function') @ApiBearerAuth('Authorization') @@ -219,7 +220,7 @@ export class FunctionController { const recycleBinStorage = await this.functionRecycleBinService.getRecycleBinStorage(appid) - if (recycleBinStorage >= 3) { + if (recycleBinStorage >= Storage_Limit) { return ResponseUtil.error('Recycle bin is full, please free up space') } diff --git a/server/src/function/function.module.ts b/server/src/function/function.module.ts index 77e47603a4..cb2ec03493 100644 --- a/server/src/function/function.module.ts +++ b/server/src/function/function.module.ts @@ -5,12 +5,17 @@ import { FunctionController } from './function.controller' import { FunctionService } from './function.service' import { DatabaseModule } from 'src/database/database.module' import { TriggerService } from 'src/trigger/trigger.service' -import { RecycleBinModule } from 'src/recycle-bin/recycle-bin.module' +import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' @Module({ - imports: [ApplicationModule, DatabaseModule, RecycleBinModule], + imports: [ApplicationModule, DatabaseModule], controllers: [FunctionController], - providers: [FunctionService, JwtService, TriggerService], + providers: [ + FunctionService, + JwtService, + TriggerService, + FunctionRecycleBinService, + ], exports: [FunctionService], }) export class FunctionModule {} diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index 5bc9c8afb2..f7f797342e 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -199,7 +199,7 @@ export class FunctionService { await this.deleteHistory(res.value) await this.unpublish(appid, name) - // add this function to rcycle bin + // add this function to recycle bin await this.functionRecycleBinService.addToRecycleBin(res.value) return res.value } diff --git a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts index a32118fc65..cc658667d7 100644 --- a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts +++ b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts @@ -19,7 +19,7 @@ export class FunctionRecycleBinService { async addToRecycleBin(func: CloudFunction) { const res = await this.db .collection('RecycleBin') - .insertOne({ type: DataType.FUNCTION, data: func }) + .insertOne({ type: DataType.FUNCTION, data: func, createdAt: new Date() }) return res } @@ -60,9 +60,15 @@ export class FunctionRecycleBinService { const functions = await this.db .collection('RecycleBin') - .find(query) - .skip((condition.page - 1) * condition.pageSize) - .limit(condition.pageSize) + .aggregate([ + { $match: query }, + { + $replaceRoot: { newRoot: '$data' }, + }, + { $sort: { createdAt: -1 } }, + { $skip: (condition.page - 1) * condition.pageSize }, + { $limit: condition.pageSize }, + ]) .toArray() const res = { @@ -111,6 +117,15 @@ export class FunctionRecycleBinService { .collection('CloudFunction') .insertMany(functions, { session }) + await this.db.collection('RecycleBin').deleteMany( + { + type: DataType.FUNCTION, + 'data._id': { $in: ids }, + 'data.appid': appid, + }, + { session }, + ) + await this.functionService.publishMany(functions) await session.commitTransaction() diff --git a/server/src/recycle-bin/entities/recycle-bin.ts b/server/src/recycle-bin/entities/recycle-bin.ts index f8eb2adb45..ea4b2c6d79 100644 --- a/server/src/recycle-bin/entities/recycle-bin.ts +++ b/server/src/recycle-bin/entities/recycle-bin.ts @@ -5,4 +5,5 @@ export enum DataType { export class RecycleBin { type: DataType data: any + createdAt: Date } diff --git a/server/src/recycle-bin/recycle-bin.module.ts b/server/src/recycle-bin/recycle-bin.module.ts index b1d4a9c18b..2f96a835dc 100644 --- a/server/src/recycle-bin/recycle-bin.module.ts +++ b/server/src/recycle-bin/recycle-bin.module.ts @@ -1,12 +1,26 @@ import { Module } from '@nestjs/common' import { FunctionRecycleBinController } from './cloud-function/function-recycle-bin.controller' import { FunctionRecycleBinService } from './cloud-function/function-recycle-bin.service' -import { FunctionModule } from 'src/function/function.module' +import { FunctionService } from 'src/function/function.service' +import { DatabaseService } from 'src/database/database.service' +import { JwtService } from '@nestjs/jwt' +import { TriggerService } from 'src/trigger/trigger.service' +import { MongoService } from 'src/database/mongo.service' +import { RegionService } from 'src/region/region.service' +import { ApplicationService } from 'src/application/application.service' @Module({ - imports: [FunctionModule], controllers: [FunctionRecycleBinController], - providers: [FunctionRecycleBinService], + providers: [ + ApplicationService, + DatabaseService, + JwtService, + TriggerService, + FunctionRecycleBinService, + FunctionService, + MongoService, + RegionService, + ], exports: [FunctionRecycleBinService], }) export class RecycleBinModule {} diff --git a/server/src/trigger/trigger.module.ts b/server/src/trigger/trigger.module.ts index 9d024eb77f..795d8b49ca 100644 --- a/server/src/trigger/trigger.module.ts +++ b/server/src/trigger/trigger.module.ts @@ -11,6 +11,7 @@ import { FunctionService } from 'src/function/function.service' import { DatabaseService } from 'src/database/database.service' import { MongoService } from 'src/database/mongo.service' import { BundleService } from 'src/application/bundle.service' +import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' @Module({ imports: [StorageModule, HttpModule], @@ -19,6 +20,7 @@ import { BundleService } from 'src/application/bundle.service' TriggerService, JwtService, ApplicationService, + FunctionRecycleBinService, CronJobService, TriggerTaskService, FunctionService, From 6e09956f8cb96f369a7938643fbecb50224c0fab Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Fri, 14 Jul 2023 10:00:58 +0000 Subject: [PATCH 6/8] add transaction --- server/src/constants.ts | 2 +- server/src/function/function.controller.ts | 4 +- server/src/function/function.service.ts | 47 +++++++++++++------ .../function-recycle-bin.service.ts | 16 +++---- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index 41fe07767f..1171d2d6ba 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -196,4 +196,4 @@ export const LIMIT_CODE_PER_IP_PER_DAY = 30 // 30 times export const CODE_VALIDITY = 10 * 60 * 1000 // 10 minutes (in milliseconds) // Recycle bin constants -export const Storage_Limit = 1000 // 1000 items +export const StORAGE_LIMIT = 1000 // 1000 items diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index d6190e0937..fce73784ed 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -31,7 +31,7 @@ import { CloudFunctionHistory } from './entities/cloud-function-history' import { CloudFunction } from './entities/cloud-function' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' -import { Storage_Limit } from 'src/constants' +import { StORAGE_LIMIT } from 'src/constants' @ApiTags('Function') @ApiBearerAuth('Authorization') @@ -220,7 +220,7 @@ export class FunctionController { const recycleBinStorage = await this.functionRecycleBinService.getRecycleBinStorage(appid) - if (recycleBinStorage >= Storage_Limit) { + if (recycleBinStorage >= StORAGE_LIMIT) { return ResponseUtil.error('Recycle bin is full, please free up space') } diff --git a/server/src/function/function.service.ts b/server/src/function/function.service.ts index f7f797342e..cf29c6fb09 100644 --- a/server/src/function/function.service.ts +++ b/server/src/function/function.service.ts @@ -14,7 +14,7 @@ import { CompileFunctionDto } from './dto/compile-function.dto' import { DatabaseService } from 'src/database/database.service' import { GetApplicationNamespaceByAppId } from 'src/utils/getter' import { SystemDatabase } from 'src/system-database' -import { ObjectId } from 'mongodb' +import { ClientSession, ObjectId } from 'mongodb' import { CloudFunction } from './entities/cloud-function' import { ApplicationConfiguration } from 'src/application/entities/application-configuration' import { FunctionLog } from 'src/log/entities/function-log' @@ -191,17 +191,33 @@ export class FunctionService { } async removeOne(func: CloudFunction) { - const { appid, name } = func - const res = await this.db - .collection('CloudFunction') - .findOneAndDelete({ appid, name }) + const client = SystemDatabase.client + const session = client.startSession() + try { + session.startTransaction() + + const { appid, name } = func - await this.deleteHistory(res.value) - await this.unpublish(appid, name) + const res = await this.db + .collection('CloudFunction') + .findOneAndDelete({ appid, name }, { session }) - // add this function to recycle bin - await this.functionRecycleBinService.addToRecycleBin(res.value) - return res.value + await this.deleteHistory(res.value, session) + + // add this function to recycle bin + await this.functionRecycleBinService.addToRecycleBin(res.value, session) + + await this.unpublish(appid, name) + + await session.commitTransaction() + return res.value + } catch (err) { + await session.abortTransaction() + this.logger.error(err) + throw err + } finally { + await session.endSession() + } } async removeAll(appid: string) { @@ -403,12 +419,15 @@ export class FunctionService { }) } - async deleteHistory(func: CloudFunction) { + async deleteHistory(func: CloudFunction, session?: ClientSession) { const res = await this.db .collection('CloudFunctionHistory') - .deleteMany({ - functionId: func._id, - }) + .deleteMany( + { + functionId: func._id, + }, + { session }, + ) return res } diff --git a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts index cc658667d7..eeae4e10ce 100644 --- a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts +++ b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts @@ -1,6 +1,6 @@ -import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common' +import { Injectable, Logger } from '@nestjs/common' import { SystemDatabase } from 'src/system-database' -import { ObjectId } from 'mongodb' +import { ClientSession, ObjectId } from 'mongodb' import { CloudFunction } from 'src/function/entities/cloud-function' import { DataType, RecycleBin } from '../entities/recycle-bin' import { CloudFunctionRecycleBinQuery } from './interface/function-recycle-bin-query.interface' @@ -11,15 +11,15 @@ export class FunctionRecycleBinService { private readonly logger = new Logger(FunctionRecycleBinService.name) private readonly db = SystemDatabase.db - constructor( - @Inject(forwardRef(() => FunctionService)) - private readonly functionService: FunctionService, - ) {} + constructor(private readonly functionService: FunctionService) {} - async addToRecycleBin(func: CloudFunction) { + async addToRecycleBin(func: CloudFunction, session?: ClientSession) { const res = await this.db .collection('RecycleBin') - .insertOne({ type: DataType.FUNCTION, data: func, createdAt: new Date() }) + .insertOne( + { type: DataType.FUNCTION, data: func, createdAt: new Date() }, + { session }, + ) return res } From d4d3bf1dad0a5fcb48e500a49510566bca0b7ed5 Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Fri, 14 Jul 2023 10:16:55 +0000 Subject: [PATCH 7/8] chore --- server/src/constants.ts | 2 +- server/src/function/function.controller.ts | 4 ++-- server/src/function/function.module.ts | 2 +- .../cloud-function/function-recycle-bin.service.ts | 7 +++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index 1171d2d6ba..dfb4b9fa76 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -196,4 +196,4 @@ export const LIMIT_CODE_PER_IP_PER_DAY = 30 // 30 times export const CODE_VALIDITY = 10 * 60 * 1000 // 10 minutes (in milliseconds) // Recycle bin constants -export const StORAGE_LIMIT = 1000 // 1000 items +export const STORAGE_LIMIT = 1000 // 1000 items diff --git a/server/src/function/function.controller.ts b/server/src/function/function.controller.ts index fce73784ed..05d0dec83c 100644 --- a/server/src/function/function.controller.ts +++ b/server/src/function/function.controller.ts @@ -31,7 +31,7 @@ import { CloudFunctionHistory } from './entities/cloud-function-history' import { CloudFunction } from './entities/cloud-function' import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto' import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' -import { StORAGE_LIMIT } from 'src/constants' +import { STORAGE_LIMIT } from 'src/constants' @ApiTags('Function') @ApiBearerAuth('Authorization') @@ -220,7 +220,7 @@ export class FunctionController { const recycleBinStorage = await this.functionRecycleBinService.getRecycleBinStorage(appid) - if (recycleBinStorage >= StORAGE_LIMIT) { + if (recycleBinStorage >= STORAGE_LIMIT) { return ResponseUtil.error('Recycle bin is full, please free up space') } diff --git a/server/src/function/function.module.ts b/server/src/function/function.module.ts index cb2ec03493..e811d29460 100644 --- a/server/src/function/function.module.ts +++ b/server/src/function/function.module.ts @@ -12,9 +12,9 @@ import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/functi controllers: [FunctionController], providers: [ FunctionService, + FunctionRecycleBinService, JwtService, TriggerService, - FunctionRecycleBinService, ], exports: [FunctionService], }) diff --git a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts index eeae4e10ce..7bf5e2b9a6 100644 --- a/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts +++ b/server/src/recycle-bin/cloud-function/function-recycle-bin.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common' +import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common' import { SystemDatabase } from 'src/system-database' import { ClientSession, ObjectId } from 'mongodb' import { CloudFunction } from 'src/function/entities/cloud-function' @@ -11,7 +11,10 @@ export class FunctionRecycleBinService { private readonly logger = new Logger(FunctionRecycleBinService.name) private readonly db = SystemDatabase.db - constructor(private readonly functionService: FunctionService) {} + constructor( + @Inject(forwardRef(() => FunctionService)) + private readonly functionService: FunctionService, + ) {} async addToRecycleBin(func: CloudFunction, session?: ClientSession) { const res = await this.db From d6acde7830525c4b03a0d6a7143bccf6ce1d20a5 Mon Sep 17 00:00:00 2001 From: HUAHUAI23 Date: Fri, 14 Jul 2023 10:54:16 +0000 Subject: [PATCH 8/8] fix dependecy --- server/src/instance/instance.module.ts | 3 ++- server/src/recycle-bin/recycle-bin.module.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/instance/instance.module.ts b/server/src/instance/instance.module.ts index ac61be1768..65497ed3c1 100644 --- a/server/src/instance/instance.module.ts +++ b/server/src/instance/instance.module.ts @@ -5,9 +5,10 @@ import { StorageModule } from '../storage/storage.module' import { DatabaseModule } from '../database/database.module' import { TriggerModule } from 'src/trigger/trigger.module' import { ApplicationModule } from 'src/application/application.module' +import { JwtService } from '@nestjs/jwt' @Module({ imports: [StorageModule, DatabaseModule, TriggerModule, ApplicationModule], - providers: [InstanceService, InstanceTaskService], + providers: [InstanceService, InstanceTaskService, JwtService], }) export class InstanceModule {} diff --git a/server/src/recycle-bin/recycle-bin.module.ts b/server/src/recycle-bin/recycle-bin.module.ts index 2f96a835dc..236632bcfb 100644 --- a/server/src/recycle-bin/recycle-bin.module.ts +++ b/server/src/recycle-bin/recycle-bin.module.ts @@ -8,8 +8,10 @@ import { TriggerService } from 'src/trigger/trigger.service' import { MongoService } from 'src/database/mongo.service' import { RegionService } from 'src/region/region.service' import { ApplicationService } from 'src/application/application.service' +import { HttpModule } from '@nestjs/axios' @Module({ + imports: [HttpModule], controllers: [FunctionRecycleBinController], providers: [ ApplicationService,