Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): add recycle bin #1396

Merged
merged 12 commits into from
Jul 14, 2023
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