Skip to content

Commit fd8292d

Browse files
authored
feat(server): support function rename (#1336)
* feat(server): make function service support function rename * chore * chore(server): * chore(server): Add function function trigger handling measures * chore() * chore
1 parent ad4d8ab commit fd8292d

File tree

6 files changed

+150
-6
lines changed

6 files changed

+150
-6
lines changed

server/src/function/dto/update-function.dto.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
2-
import { IsArray, IsIn, IsNotEmpty, IsString, MaxLength } from 'class-validator'
2+
import {
3+
IsArray,
4+
IsIn,
5+
IsNotEmpty,
6+
IsOptional,
7+
IsString,
8+
Matches,
9+
MaxLength,
10+
} from 'class-validator'
311
import { HTTP_METHODS } from '../../constants'
412
import { HttpMethod } from '../entities/cloud-function'
513

614
export class UpdateFunctionDto {
15+
@ApiProperty({
16+
description: 'Function name is unique in the application',
17+
})
18+
@IsOptional()
19+
@Matches(/^[a-zA-Z0-9_.\-\/]{1,256}$/)
20+
newName?: string
21+
722
@ApiPropertyOptional()
823
@MaxLength(256)
924
description: string

server/src/function/function.controller.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,14 @@ export class FunctionController {
181181
HttpStatus.NOT_FOUND,
182182
)
183183
}
184-
185184
const res = await this.functionsService.updateOne(func, dto)
186185
if (!res) {
187186
return ResponseUtil.error(i18n.t('function.update.error'))
188187
}
188+
if (res instanceof Error) {
189+
return ResponseUtil.error(res.message)
190+
}
191+
189192
return ResponseUtil.ok(res)
190193
}
191194

server/src/function/function.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { ApplicationModule } from '../application/application.module'
44
import { FunctionController } from './function.controller'
55
import { FunctionService } from './function.service'
66
import { DatabaseModule } from 'src/database/database.module'
7+
import { TriggerService } from 'src/trigger/trigger.service'
78

89
@Module({
910
imports: [ApplicationModule, DatabaseModule],
1011
controllers: [FunctionController],
11-
providers: [FunctionService, JwtService],
12+
providers: [FunctionService, JwtService, TriggerService],
1213
exports: [FunctionService],
1314
})
1415
export class FunctionModule {}

server/src/function/function.service.ts

+76-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
APPLICATION_SECRET_KEY,
55
CN_FUNCTION_LOGS,
66
CN_PUBLISHED_FUNCTIONS,
7+
TASK_LOCK_INIT_TIME,
78
} from '../constants'
89
import { CreateFunctionDto } from './dto/create-function.dto'
910
import { UpdateFunctionDto } from './dto/update-function.dto'
@@ -18,6 +19,8 @@ import { CloudFunction } from './entities/cloud-function'
1819
import { ApplicationConfiguration } from 'src/application/entities/application-configuration'
1920
import { FunctionLog } from 'src/log/entities/function-log'
2021
import { CloudFunctionHistory } from './entities/cloud-function-history'
22+
import { TriggerService } from 'src/trigger/trigger.service'
23+
import { TriggerPhase } from 'src/trigger/entities/cron-trigger'
2124
import { UpdateFunctionDebugDto } from './dto/update-function-debug.dto'
2225

2326
@Injectable()
@@ -28,6 +31,7 @@ export class FunctionService {
2831
constructor(
2932
private readonly databaseService: DatabaseService,
3033
private readonly jwtService: JwtService,
34+
private readonly triggerService: TriggerService,
3135
) {}
3236
async create(appid: string, userid: ObjectId, dto: CreateFunctionDto) {
3337
await this.db.collection<CloudFunction>('CloudFunction').insertOne({
@@ -79,6 +83,73 @@ export class FunctionService {
7983
}
8084

8185
async updateOne(func: CloudFunction, dto: UpdateFunctionDto) {
86+
// update function name
87+
if (dto.newName) {
88+
const client = SystemDatabase.client
89+
const session = client.startSession()
90+
91+
const found = await this.findOne(func.appid, dto.newName)
92+
if (found) {
93+
return new Error(`Function name ${found.name} already exists`)
94+
}
95+
96+
try {
97+
session.startTransaction()
98+
99+
const fn = await this.db
100+
.collection<CloudFunction>('CloudFunction')
101+
.findOneAndUpdate(
102+
{ appid: func.appid, name: func.name },
103+
{
104+
$set: {
105+
name: dto.newName,
106+
desc: dto.description,
107+
methods: dto.methods,
108+
tags: dto.tags || [],
109+
updatedAt: new Date(),
110+
},
111+
},
112+
{ session, returnDocument: 'after' },
113+
)
114+
115+
// publish
116+
await this.publish(fn.value, func.name)
117+
118+
// trigger
119+
const triggers = await this.triggerService.findAllByTarget(
120+
func.appid,
121+
func.name,
122+
)
123+
if (triggers.length !== 0) {
124+
const triggersToInsert = triggers.map((doc) => ({
125+
appid: doc.appid,
126+
desc: doc.desc,
127+
cron: doc.cron,
128+
target: dto.newName, // set to new function name
129+
state: doc.state,
130+
phase: TriggerPhase.Creating,
131+
lockedAt: TASK_LOCK_INIT_TIME,
132+
createdAt: new Date(doc.createdAt),
133+
updatedAt: new Date(),
134+
}))
135+
await this.triggerService.removeAllByTarget(
136+
func.appid,
137+
func.name,
138+
session,
139+
)
140+
await this.triggerService.createMany(triggersToInsert, session)
141+
}
142+
await session.commitTransaction()
143+
return fn.value
144+
} catch (error) {
145+
await session.abortTransaction()
146+
this.logger.error(error)
147+
throw error
148+
} finally {
149+
await session.endSession()
150+
}
151+
}
152+
82153
await this.db.collection<CloudFunction>('CloudFunction').updateOne(
83154
{ appid: func.appid, name: func.name },
84155
{
@@ -138,13 +209,16 @@ export class FunctionService {
138209
return res
139210
}
140211

141-
async publish(func: CloudFunction) {
212+
async publish(func: CloudFunction, oldFuncName?: string) {
142213
const { db, client } = await this.databaseService.findAndConnect(func.appid)
143214
const session = client.startSession()
144215
try {
145216
await session.withTransaction(async () => {
146217
const coll = db.collection(CN_PUBLISHED_FUNCTIONS)
147-
await coll.deleteOne({ name: func.name }, { session })
218+
await coll.deleteOne(
219+
{ name: oldFuncName ? oldFuncName : func.name },
220+
{ session },
221+
)
148222
await coll.insertOne(func, { session })
149223
})
150224
} finally {

server/src/trigger/trigger.controller.ts

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { BundleService } from 'src/application/bundle.service'
2121
import { ObjectId } from 'mongodb'
2222
import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard'
2323
import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard'
24+
import { FunctionService } from 'src/function/function.service'
2425

2526
@ApiTags('Trigger')
2627
@Controller('apps/:appid/triggers')
@@ -30,6 +31,7 @@ export class TriggerController {
3031
constructor(
3132
private readonly triggerService: TriggerService,
3233
private readonly bundleService: BundleService,
34+
private readonly funcService: FunctionService,
3335
) {}
3436

3537
/**
@@ -57,6 +59,12 @@ export class TriggerController {
5759
return ResponseUtil.error('Invalid cron expression')
5860
}
5961

62+
// Check if the target function exists
63+
const found = await this.funcService.findOne(appid, dto.target)
64+
if (!found) {
65+
return ResponseUtil.error("Target function doesn't exist")
66+
}
67+
6068
const res = await this.triggerService.create(appid, dto)
6169
return ResponseUtil.ok(res)
6270
}

server/src/trigger/trigger.service.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
TriggerPhase,
99
TriggerState,
1010
} from './entities/cron-trigger'
11-
import { ObjectId } from 'mongodb'
11+
import { ClientSession, ObjectId } from 'mongodb'
1212

1313
@Injectable()
1414
export class TriggerService {
@@ -73,6 +73,49 @@ export class TriggerService {
7373
return res
7474
}
7575

76+
async findAllByTarget(appid: string, target: string) {
77+
const docs = await this.db
78+
.collection<CronTrigger>('CronTrigger')
79+
.find({ appid, target })
80+
.toArray()
81+
return docs
82+
}
83+
84+
async createMany(docs: CronTrigger[], session?: ClientSession) {
85+
if (session) {
86+
const result = await this.db
87+
.collection<CronTrigger>('CronTrigger')
88+
.insertMany(docs, { session })
89+
return result
90+
}
91+
const result = await this.db
92+
.collection<CronTrigger>('CronTrigger')
93+
.insertMany(docs)
94+
return result
95+
}
96+
97+
async removeAllByTarget(
98+
appid: string,
99+
target: string,
100+
session?: ClientSession,
101+
) {
102+
if (session) {
103+
const res = await this.db
104+
.collection<CronTrigger>('CronTrigger')
105+
.updateMany(
106+
{ appid, target },
107+
{ $set: { state: TriggerState.Deleted } },
108+
{ session },
109+
)
110+
return res
111+
}
112+
const res = await this.db
113+
.collection<CronTrigger>('CronTrigger')
114+
.updateMany({ appid, target }, { $set: { state: TriggerState.Deleted } })
115+
116+
return res
117+
}
118+
76119
isValidCronExpression(cron: string) {
77120
const ret = CronValidate(cron)
78121
if (ret.isValid()) {

0 commit comments

Comments
 (0)