mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
6 changed files with 280 additions and 1 deletions
@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'; |
||||
import { HooksController } from './hooks.controller'; |
||||
import { HooksService } from './hooks.service'; |
||||
|
||||
describe('HooksController', () => { |
||||
let controller: HooksController; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
controllers: [HooksController], |
||||
providers: [HooksService], |
||||
}).compile(); |
||||
|
||||
controller = module.get<HooksController>(HooksController); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(controller).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,112 @@
|
||||
import { |
||||
Body, |
||||
Controller, |
||||
Request, |
||||
Delete, |
||||
Get, |
||||
Param, |
||||
Patch, |
||||
Post, |
||||
UseGuards, |
||||
} from '@nestjs/common'; |
||||
import { HookReqType, HookTestReqType, HookType } from 'nocodb-sdk'; |
||||
import { PagedResponseImpl } from '../../helpers/PagedResponse'; |
||||
import { |
||||
Acl, |
||||
ExtractProjectIdMiddleware, |
||||
} from '../../middlewares/extract-project-id/extract-project-id.middleware'; |
||||
import { HooksService } from './hooks.service'; |
||||
import { AuthGuard } from '@nestjs/passport'; |
||||
|
||||
@Controller('hooks') |
||||
@UseGuards(ExtractProjectIdMiddleware, AuthGuard('jwt')) |
||||
export class HooksController { |
||||
constructor(private readonly hooksService: HooksService) {} |
||||
|
||||
@Get('/api/v1/db/meta/tables/:tableId/hooks') |
||||
@Acl('hookList') |
||||
async hookList(@Param('tableId') tableId: string) { |
||||
return new PagedResponseImpl(await this.hooksService.hookList({ tableId })); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/hooks') |
||||
@Acl('hookCreate') |
||||
async hookCreate( |
||||
@Param('tableId') tableId: string, |
||||
@Body() body: HookReqType, |
||||
) { |
||||
const hook = await this.hooksService.hookCreate({ |
||||
hook: body, |
||||
tableId, |
||||
}); |
||||
return hook; |
||||
} |
||||
|
||||
@Delete('/api/v1/db/meta/hooks/:hookId') |
||||
@Acl('hookDelete') |
||||
async hookDelete(@Param('hookId') hookId: string) { |
||||
return await this.hooksService.hookDelete({ hookId }); |
||||
} |
||||
|
||||
@Patch('/api/v1/db/meta/hooks/:hookId') |
||||
@Acl('hookUpdate') |
||||
async hookUpdate(@Param('hookId') hookId: string, @Body() body: HookReqType) { |
||||
return; |
||||
await this.hooksService.hookUpdate({ hookId, hook: body }); |
||||
} |
||||
|
||||
@Post('/api/v1/db/meta/tables/:tableId/hooks/test') |
||||
@Acl('hookTest') |
||||
async hookTest(@Body() body: HookTestReqType, @Request() req: any) { |
||||
try { |
||||
await this.hooksService.hookTest({ |
||||
hookTest: { |
||||
...body, |
||||
payload: { |
||||
...body.payload, |
||||
user: (req as any)?.user, |
||||
}, |
||||
}, |
||||
tableId: req.params.tableId, |
||||
}); |
||||
return { msg: 'The hook has been tested successfully' }; |
||||
} catch (e) { |
||||
console.error(e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
@Get( |
||||
'/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation/:version', |
||||
) |
||||
@Acl('tableSampleData') |
||||
async tableSampleData( |
||||
@Param('tableId') tableId: string, |
||||
@Param('operation') operation: HookType['operation'], |
||||
@Param('version') version: HookType['version'], |
||||
) { |
||||
return; |
||||
await this.hooksService.tableSampleData({ |
||||
tableId, |
||||
operation, |
||||
version, |
||||
}); |
||||
} |
||||
|
||||
@Get('/api/v1/db/meta/hooks/:hookId/logs') |
||||
@Acl('hookLogList') |
||||
async hookLogList(@Param('hookId') hookId: string, @Request() req: any) { |
||||
return new PagedResponseImpl( |
||||
await this.hooksService.hookLogList({ |
||||
query: req.query, |
||||
hookId, |
||||
}), |
||||
{ |
||||
...req.query, |
||||
count: await this.hooksService.hookLogCount({ |
||||
hookId, |
||||
}), |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common'; |
||||
import { HooksService } from './hooks.service'; |
||||
import { HooksController } from './hooks.controller'; |
||||
|
||||
@Module({ |
||||
controllers: [HooksController], |
||||
providers: [HooksService] |
||||
}) |
||||
export class HooksModule {} |
@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'; |
||||
import { HooksService } from './hooks.service'; |
||||
|
||||
describe('HooksService', () => { |
||||
let service: HooksService; |
||||
|
||||
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [HooksService], |
||||
}).compile(); |
||||
|
||||
service = module.get<HooksService>(HooksService); |
||||
}); |
||||
|
||||
it('should be defined', () => { |
||||
expect(service).toBeDefined(); |
||||
}); |
||||
}); |
@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@nestjs/common'; |
||||
import { HookReqType, HookTestReqType, HookType } from 'nocodb-sdk'; |
||||
import { validatePayload } from '../../helpers'; |
||||
import { NcError } from '../../helpers/catchError'; |
||||
import { |
||||
populateSamplePayload, |
||||
populateSamplePayloadV2, |
||||
} from '../../helpers/populateSamplePayload'; |
||||
import { invokeWebhook } from '../../helpers/webhookHelpers'; |
||||
import { Hook, HookLog, Model } from '../../models'; |
||||
import { T } from 'nc-help'; |
||||
|
||||
@Injectable() |
||||
export class HooksService { |
||||
validateHookPayload(notificationJsonOrObject: string | Record<string, any>) { |
||||
let notification: { type?: string } = {}; |
||||
try { |
||||
notification = |
||||
typeof notificationJsonOrObject === 'string' |
||||
? JSON.parse(notificationJsonOrObject) |
||||
: notificationJsonOrObject; |
||||
} catch {} |
||||
|
||||
if (notification.type !== 'URL' && process.env.NC_CLOUD === 'true') { |
||||
NcError.badRequest('Only URL notification is supported'); |
||||
} |
||||
} |
||||
|
||||
async hookList(param: { tableId: string }) { |
||||
return await Hook.list({ fk_model_id: param.tableId }); |
||||
} |
||||
|
||||
async hookLogList(param: { query: any; hookId: string }) { |
||||
return await HookLog.list({ fk_hook_id: param.hookId }, param.query); |
||||
} |
||||
|
||||
async hookCreate(param: { tableId: string; hook: HookReqType }) { |
||||
validatePayload('swagger.json#/components/schemas/HookReq', param.hook); |
||||
|
||||
this.validateHookPayload(param.hook.notification); |
||||
|
||||
const hook = await Hook.insert({ |
||||
...param.hook, |
||||
fk_model_id: param.tableId, |
||||
} as any); |
||||
|
||||
T.emit('evt', { evt_type: 'webhooks:created' }); |
||||
|
||||
return hook; |
||||
} |
||||
|
||||
async hookDelete(param: { hookId: string }) { |
||||
T.emit('evt', { evt_type: 'webhooks:deleted' }); |
||||
await Hook.delete(param.hookId); |
||||
return true; |
||||
} |
||||
|
||||
async hookUpdate(param: { hookId: string; hook: HookReqType }) { |
||||
validatePayload('swagger.json#/components/schemas/HookReq', param.hook); |
||||
|
||||
T.emit('evt', { evt_type: 'webhooks:updated' }); |
||||
|
||||
this.validateHookPayload(param.hook.notification); |
||||
|
||||
return await Hook.update(param.hookId, param.hook); |
||||
} |
||||
|
||||
async hookTest(param: { tableId: string; hookTest: HookTestReqType }) { |
||||
validatePayload( |
||||
'swagger.json#/components/schemas/HookTestReq', |
||||
param.hookTest, |
||||
); |
||||
|
||||
this.validateHookPayload(param.hookTest.hook?.notification); |
||||
|
||||
const model = await Model.getByIdOrName({ id: param.tableId }); |
||||
|
||||
T.emit('evt', { evt_type: 'webhooks:tested' }); |
||||
|
||||
const { |
||||
hook, |
||||
payload: { data, user }, |
||||
} = param.hookTest; |
||||
try { |
||||
await invokeWebhook( |
||||
new Hook(hook), |
||||
model, |
||||
null, |
||||
null, |
||||
data, |
||||
user, |
||||
(hook as any)?.filters, |
||||
true, |
||||
true, |
||||
); |
||||
} catch (e) { |
||||
throw e; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
async tableSampleData(param: { |
||||
tableId: string; |
||||
operation: HookType['operation']; |
||||
version: HookType['version']; |
||||
}) { |
||||
const model = await Model.getByIdOrName({ id: param.tableId }); |
||||
|
||||
if (param.version === 'v1') { |
||||
return await populateSamplePayload(model, false, param.operation); |
||||
} |
||||
return await populateSamplePayloadV2(model, false, param.operation); |
||||
} |
||||
|
||||
async hookLogCount(param: { hookId: string }) { |
||||
return await HookLog.count({ hookId: param.hookId }); |
||||
} |
||||
} |
Loading…
Reference in new issue