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