Skip to content

Commit

Permalink
feat(server): support recycle bin (#1396)
Browse files Browse the repository at this point in the history
* feat(server): function templates add user avatar support

* feat(server): add function recycle bin

* add function recycle bin control

* refactor recycle-bin

* fix dependency and refactor recycle-bin entity

* add transaction

* chore

* fix dependecy
  • Loading branch information
HUAHUAI23 committed Jul 14, 2023
1 parent a79e25a commit 1ee6def
Show file tree
Hide file tree
Showing 16 changed files with 495 additions and 14 deletions.
2 changes: 2 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -66,6 +67,7 @@ import { MulterModule } from '@nestjs/platform-express'
BillingModule,
FunctionTemplateModule,
MulterModule.register(),
RecycleBinModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
2 changes: 2 additions & 0 deletions server/src/application/application.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
import { HttpModule } from '@nestjs/axios'

@Module({
Expand All @@ -31,6 +32,7 @@ import { HttpModule } from '@nestjs/axios'
ApplicationService,
ApplicationTaskService,
InstanceService,
FunctionRecycleBinService,
JwtService,
FunctionService,
EnvironmentVariableService,
Expand Down
3 changes: 3 additions & 0 deletions server/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,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
9 changes: 9 additions & 0 deletions server/src/function/function.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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 { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service'
import { STORAGE_LIMIT } from 'src/constants'

@ApiTags('Function')
@ApiBearerAuth('Authorization')
Expand All @@ -38,6 +40,7 @@ export class FunctionController {
constructor(
private readonly functionsService: FunctionService,
private readonly bundleService: BundleService,
private readonly functionRecycleBinService: FunctionRecycleBinService,
private readonly i18n: I18nService<I18nTranslations>,
) {}

Expand Down Expand Up @@ -214,6 +217,12 @@ export class FunctionController {
HttpStatus.NOT_FOUND,
)
}
const recycleBinStorage =
await this.functionRecycleBinService.getRecycleBinStorage(appid)

if (recycleBinStorage >= STORAGE_LIMIT) {
return ResponseUtil.error('Recycle bin is full, please free up space')
}

const res = await this.functionsService.removeOne(func)
if (!res) {
Expand Down
8 changes: 7 additions & 1 deletion server/src/function/function.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ 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 { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service'
import { HttpModule } from '@nestjs/axios'
import { ApplicationModule } from 'src/application/application.module'

@Module({
imports: [ApplicationModule, DatabaseModule, HttpModule],
controllers: [FunctionController],
providers: [FunctionService, JwtService, TriggerService],
providers: [
FunctionService,
FunctionRecycleBinService,
JwtService,
TriggerService,
],
exports: [FunctionService],
})
export class FunctionModule {}
66 changes: 54 additions & 12 deletions server/src/function/function.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ 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 { 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 { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service'
import { HttpService } from '@nestjs/axios'
import { RegionService } from 'src/region/region.service'

Expand All @@ -32,6 +33,7 @@ export class FunctionService {
private readonly databaseService: DatabaseService,
private readonly jwtService: JwtService,
private readonly triggerService: TriggerService,
private readonly functionRecycleBinService: FunctionRecycleBinService,
private readonly httpService: HttpService,
private readonly regionService: RegionService,
) {}
Expand Down Expand Up @@ -191,14 +193,33 @@ export class FunctionService {
}

async removeOne(func: CloudFunction) {
const { appid, name } = func
const res = await this.db
.collection<CloudFunction>('CloudFunction')
.findOneAndDelete({ appid, name })
const client = SystemDatabase.client
const session = client.startSession()
try {
session.startTransaction()

const { appid, name } = func

const res = await this.db
.collection<CloudFunction>('CloudFunction')
.findOneAndDelete({ appid, name }, { session })

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 this.deleteHistory(res.value)
await this.unpublish(appid, name)
return res.value
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) {
Expand Down Expand Up @@ -229,6 +250,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,
Expand Down Expand Up @@ -371,12 +410,15 @@ export class FunctionService {
})
}

async deleteHistory(func: CloudFunction) {
async deleteHistory(func: CloudFunction, session?: ClientSession) {
const res = await this.db
.collection<CloudFunctionHistory>('CloudFunctionHistory')
.deleteMany({
functionId: func._id,
})
.deleteMany(
{
functionId: func._id,
},
{ session },
)
return res
}

Expand Down
3 changes: 2 additions & 1 deletion server/src/instance/instance.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Original file line number Diff line number Diff line change
@@ -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[]
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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[]
}
Loading

0 comments on commit 1ee6def

Please sign in to comment.