-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #172 from makeopensource/webhooks
Webhooks
- Loading branch information
Showing
16 changed files
with
410 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { NextFunction, Request, Response } from 'express' | ||
|
||
import { Updated } from '../../utils/apiResponse.utils' | ||
import { serialize } from './webhooks.serializer' | ||
import WebhooksService from './webhooks.service' | ||
|
||
export async function get(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
const hooksModel = await WebhooksService.list() | ||
const hooks = hooksModel.map(serialize) | ||
res.status(200).json(hooks) | ||
} catch (err) { | ||
next(err) | ||
} | ||
} | ||
|
||
export async function getById(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
const hooksModel = await WebhooksService.retrieveByUserId(req.currentUser!.userId!) | ||
if (!hooksModel) { | ||
return res.status(404).json({ 'error': 'Webhook for user not found not found' }) | ||
} | ||
const hooks = hooksModel.map(serialize) | ||
|
||
res.status(200).json(hooks) | ||
} catch (err) { | ||
next(err) | ||
} | ||
} | ||
|
||
|
||
export async function post(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
req.body['userId'] = req.currentUser!.userId! | ||
|
||
const created = await WebhooksService.create(req.body) | ||
res.status(201).json(serialize(created)) | ||
} catch (err: any) { | ||
next(err) | ||
} | ||
} | ||
|
||
export async function put(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
const webhookId = parseInt(req.params.id) | ||
await WebhooksService.update(webhookId, req.body) | ||
|
||
res.status(201).json(Updated) | ||
} catch (err: any) { | ||
next(err) | ||
} | ||
} | ||
|
||
export async function _delete(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
const webhookId = parseInt(req.params.id) | ||
await WebhooksService._delete(webhookId) | ||
|
||
res.status(204).json('Deleted') | ||
} catch (err: any) { | ||
next(err) | ||
} | ||
} | ||
|
||
export default { | ||
get, | ||
put, | ||
post, | ||
_delete, | ||
getById, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { NextFunction, Request, Response } from 'express' | ||
import WebhooksService from './webhooks.service' | ||
import fetch from 'node-fetch' | ||
|
||
|
||
export async function processWebhook(statusCode: number, userId: number, path: string, body: any) { | ||
return new Promise(async (resolve, reject) => { | ||
try { | ||
// process path and check for match | ||
const hooks = await WebhooksService.retrieveByUserId(userId) | ||
if (hooks.length !== 0) { | ||
for (let hook of hooks) { | ||
// todo make matcher more generic that checks url patterns | ||
if (hook.matcherUrl == path) { | ||
try { | ||
// todo add multiple ways to format a webhook message currently only formated for discord | ||
const re = await fetch(hook.destinationUrl, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ 'content': body }), | ||
}) | ||
|
||
if (re.status >= 300) { | ||
reject(`Failed to send webhook ${re.statusText}`) | ||
} | ||
} catch (e) { | ||
reject(e) | ||
} | ||
} | ||
} | ||
// prepare request and send | ||
} else { | ||
reject('Url could not be matched') | ||
} | ||
|
||
resolve('Goodbye and thanks for all the fish') | ||
} catch (e) { | ||
reject(e) | ||
} | ||
}) | ||
} | ||
|
||
// adds a | ||
export function responseInterceptor(req: Request, res: Response, next: NextFunction) { | ||
const originalSend = res.send | ||
|
||
// Override function | ||
// @ts-ignore | ||
res.send = function(body) { | ||
// send response to client | ||
originalSend.call(this, body) | ||
|
||
// send body for processing in webhook | ||
if (req.currentUser?.userId) { | ||
processWebhook(res.statusCode, req.currentUser?.userId, req.originalUrl, body).then( | ||
value => { | ||
console.log('Sent webhook successfully') | ||
}, | ||
).catch(err => { | ||
console.error('Error sending webhook', err) | ||
}) | ||
} | ||
} | ||
next() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { | ||
JoinColumn, | ||
ManyToOne, | ||
Entity, | ||
Column, | ||
PrimaryGeneratedColumn, | ||
CreateDateColumn, | ||
UpdateDateColumn, | ||
DeleteDateColumn, | ||
} from 'typeorm' | ||
|
||
import UserModel from '../user/user.model' | ||
|
||
@Entity('Webhooks') | ||
export default class WebhooksModel { | ||
@PrimaryGeneratedColumn() | ||
id: number | ||
|
||
@Column({name: 'destination_url'}) | ||
destinationUrl: string | ||
|
||
@Column({name: 'matcher_url'}) | ||
matcherUrl: string | ||
|
||
@Column({ name: 'user_id' }) | ||
@JoinColumn({ name: 'user_id' }) | ||
@ManyToOne(() => UserModel) | ||
userId: number | ||
|
||
@CreateDateColumn({ name: 'created_at' }) | ||
createdAt: Date | ||
|
||
@UpdateDateColumn({ name: 'updated_at' }) | ||
updatedAt: Date | ||
|
||
@DeleteDateColumn({ name: 'deleted_at' }) | ||
deletedAt?: Date | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import express from 'express' | ||
|
||
import validator from './webhooks.validator' | ||
|
||
import WebhooksController from './webhooks.controller' | ||
import { isAuthorized } from '../../authorization/authorization.middleware' | ||
|
||
const Router = express.Router({ mergeParams: true }) | ||
|
||
|
||
/** | ||
* @swagger | ||
* /webhooks: | ||
* get: | ||
* summary: get all webhooks created by a user | ||
*/ | ||
Router.get('/', isAuthorized('courseViewAll'), WebhooksController.getById) | ||
|
||
/** | ||
* @swagger | ||
* /webhooks: | ||
* get: | ||
* summary: get all webhooks, only for admins | ||
*/ | ||
Router.get('/all', isAuthorized('admin'), WebhooksController.get) | ||
|
||
|
||
/** | ||
* @swagger | ||
* /webhooks: | ||
* get: | ||
* summary: create a webhook | ||
*/ | ||
Router.post('/', isAuthorized('courseViewAll'), validator, WebhooksController.post) | ||
|
||
|
||
/** | ||
* @swagger | ||
* /webhooks: | ||
* put: | ||
* summary: Edit webhook urls | ||
*/ | ||
Router.put('/:id', isAuthorized('courseViewAll'), validator, WebhooksController.put) | ||
|
||
|
||
/** | ||
* @swagger | ||
* /webhooks: | ||
* delete: | ||
* summary: delete a webhook | ||
*/ | ||
Router.delete('/:id', isAuthorized('courseViewAll'), WebhooksController._delete) | ||
|
||
export default Router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import WebhooksModel from './webhooks.model' | ||
import { Webhooks } from 'devu-shared-modules' | ||
|
||
export function serialize(webhooks: WebhooksModel): Webhooks { | ||
return { | ||
id: webhooks.id, | ||
userId: webhooks.userId, | ||
destinationUrl: webhooks.destinationUrl, | ||
matcherUrl: webhooks.matcherUrl, | ||
updatedAt: webhooks.updatedAt.toISOString(), | ||
createdAt: webhooks.createdAt.toISOString(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { dataSource } from '../../database' | ||
import WebhooksModel from './webhooks.model' | ||
import { Webhooks } from 'devu-shared-modules' | ||
import { IsNull } from 'typeorm' | ||
|
||
const connect = () => dataSource.getRepository(WebhooksModel) | ||
|
||
async function create(input: Webhooks) { | ||
return await connect().save(input) | ||
} | ||
|
||
|
||
|
||
async function retrieveByUserId(userId: number) { | ||
return await connect().findBy({ userId: userId, deletedAt: IsNull() }) | ||
} | ||
|
||
async function update(id: number, input: Webhooks) { | ||
return await connect().update(id, { matcherUrl: input.matcherUrl, destinationUrl: input.destinationUrl }) | ||
} | ||
|
||
async function list() { | ||
return await connect().findBy({ deletedAt: IsNull() }) | ||
} | ||
|
||
async function _delete(id: number) { | ||
return await connect().softDelete({ id, deletedAt: IsNull() }) | ||
} | ||
|
||
export default { | ||
create, | ||
list, | ||
update, | ||
retrieveByUserId, | ||
_delete, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { check } from 'express-validator' | ||
|
||
import validate from '../../middleware/validator/generic.validator' | ||
|
||
|
||
const destinationUrl = check('destinationUrl').isString().trim() | ||
const matcherUrl = check('matcherUrl').isString().trim() | ||
|
||
|
||
const validator = [ | ||
destinationUrl, | ||
matcherUrl, | ||
validate, | ||
] | ||
|
||
export default validator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { MigrationInterface, QueryRunner } from "typeorm"; | ||
|
||
export class Webhooks1730698767895 implements MigrationInterface { | ||
name = 'Webhooks1730698767895' | ||
|
||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`CREATE TABLE "Webhooks" ("id" SERIAL NOT NULL, "destination_url" character varying NOT NULL, "matcher_url" character varying NOT NULL, "user_id" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_ef540eaf209b4e5cb871ea34910" PRIMARY KEY ("id"))`); | ||
await queryRunner.query(`ALTER TABLE "Webhooks" ADD CONSTRAINT "FK_0831572f37f912eed2756aef1af" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`ALTER TABLE "Webhooks" DROP CONSTRAINT "FK_0831572f37f912eed2756aef1af"`); | ||
await queryRunner.query(`DROP TABLE "Webhooks"`); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@import 'variables'; | ||
|
||
.textField { | ||
align-items: center; | ||
} |
Oops, something went wrong.