Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ CRUD webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Jul 24, 2019
1 parent 82f160a commit a5851c6
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 7 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "staart-manager",
"version": "1.0.100",
"version": "1.0.101",
"main": "index.js",
"repository": "[email protected]:AnandChowdhary/staart.git",
"author": "Anand Chowdhary <[email protected]>",
Expand Down Expand Up @@ -135,5 +135,5 @@
"setup"
],
"snyk": true,
"staart-version": "1.0.100"
"staart-version": "1.0.101"
}
137 changes: 136 additions & 1 deletion src/controllers/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ import {
getOrganizationDomainForUser,
updateDomainForUser,
deleteDomainForUser,
verifyDomainForUser
verifyDomainForUser,
getOrganizationWebhooksForUser,
createWebhookForUser,
getOrganizationWebhookForUser,
updateWebhookForUser,
deleteWebhookForUser
} from "../rest/organization";
import {
Get,
Expand Down Expand Up @@ -781,4 +786,134 @@ export class OrganizationController {
)
);
}

@Get(":id/webhooks")
async getUserWebhooks(req: Request, res: Response) {
const id = await organizationUsernameToId(req.params.id);
joiValidate(
{ id: [Joi.string().required(), Joi.number().required()] },
{ id }
);
const webhookParams = { ...req.query };
joiValidate(
{
start: Joi.string(),
itemsPerPage: Joi.number()
},
webhookParams
);
res.json(
await getOrganizationWebhooksForUser(
localsToTokenOrKey(res),
id,
webhookParams
)
);
}

@Put(":id/webhooks")
@Middleware(
validator(
{
event: Joi.string().required(),
url: Joi.string().required(),
contentType: Joi.string(),
secret: Joi.string().allow(""),
isActive: Joi.boolean()
},
"body"
)
)
async putUserWebhooks(req: Request, res: Response) {
const id = await organizationUsernameToId(req.params.id);
joiValidate(
{ id: [Joi.string().required(), Joi.number().required()] },
{ id }
);
res
.status(CREATED)
.json(
await createWebhookForUser(
localsToTokenOrKey(res),
id,
req.body,
res.locals
)
);
}

@Get(":id/webhooks/:webhookId")
async getUserWebhook(req: Request, res: Response) {
const id = await organizationUsernameToId(req.params.id);
const webhookId = req.params.webhookId;
joiValidate(
{
id: [Joi.string().required(), Joi.number().required()],
webhookId: Joi.number().required()
},
{ id, webhookId }
);
res.json(
await getOrganizationWebhookForUser(
localsToTokenOrKey(res),
id,
webhookId
)
);
}

@Patch(":id/webhooks/:webhookId")
@Middleware(
validator(
{
event: Joi.string(),
url: Joi.string(),
contentType: Joi.string(),
secret: Joi.string(),
isActive: Joi.boolean()
},
"body"
)
)
async patchUserWebhook(req: Request, res: Response) {
const id = await organizationUsernameToId(req.params.id);
const webhookId = req.params.webhookId;
joiValidate(
{
id: [Joi.string().required(), Joi.number().required()],
webhookId: Joi.number().required()
},
{ id, webhookId }
);
res.json(
await updateWebhookForUser(
localsToTokenOrKey(res),
id,
webhookId,
req.body,
res.locals
)
);
}

@Delete(":id/webhooks/:webhookId")
async deleteUserWebhook(req: Request, res: Response) {
const id = await organizationUsernameToId(req.params.id);
const webhookId = req.params.webhookId;
joiValidate(
{
id: [Joi.string().required(), Joi.number().required()],
webhookId: Joi.number().required()
},
{ id, webhookId }
);
res.json(
await deleteWebhookForUser(
localsToTokenOrKey(res),
id,
webhookId,
res.locals
)
);
}
}
87 changes: 86 additions & 1 deletion src/crud/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
removeReadOnlyValues,
tableName
} from "../helpers/mysql";
import { Organization, Domain } from "../interfaces/tables/organization";
import {
Organization,
Domain,
Webhook
} from "../interfaces/tables/organization";
import { capitalizeFirstAndLastLetter, createSlug } from "../helpers/utils";
import { KeyValue } from "../interfaces/general";
import { cachedQuery, deleteItemFromCache } from "../helpers/cache";
Expand Down Expand Up @@ -303,3 +307,84 @@ export const checkDomainAvailability = async (username: string) => {
} catch (error) {}
return true;
};

/**
* Get a list of webhooks for an organization
*/
export const getOrganizationWebhooks = async (
organizationId: number,
query: KeyValue
) => {
return await getPaginatedData({
table: "webhooks",
conditions: {
organizationId
},
...query
});
};

/**
* Get a webhook
*/
export const getWebhook = async (organizationId: number, webhookId: number) => {
return (<Webhook[]>(
await query(
`SELECT * FROM ${tableName(
"webhooks"
)} WHERE id = ? AND organizationId = ? LIMIT 1`,
[webhookId, organizationId]
)
))[0];
};

/**
* Create a webhook
*/
export const createWebhook = async (
webhook: Webhook
): Promise<InsertResult> => {
webhook.contentType = webhook.contentType || "application/json";
webhook.isActive = webhook.isActive !== false;
webhook.createdAt = new Date();
webhook.updatedAt = webhook.createdAt;
return await query(
`INSERT INTO ${tableName("webhooks")} ${tableValues(webhook)}`,
Object.values(webhook)
);
};

/**
* Update a webhook
*/
export const updateWebhook = async (
organizationId: number,
webhookId: number,
data: KeyValue
) => {
data.updatedAt = new Date();
data = removeReadOnlyValues(data);
const webhook = await getWebhook(organizationId, webhookId);
return await query(
`UPDATE ${tableName("webhooks")} SET ${setValues(
data
)} WHERE id = ? AND organizationId = ?`,
[...Object.values(data), webhookId, organizationId]
);
};

/**
* Delete a webhook
*/
export const deleteWebhook = async (
organizationId: number,
webhookId: number
) => {
const currentWebhook = await getWebhook(organizationId, webhookId);
return await query(
`DELETE FROM ${tableName(
"webhooks"
)} WHERE id = ? AND organizationId = ? LIMIT 1`,
[webhookId, organizationId]
);
};
3 changes: 2 additions & 1 deletion src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ export const boolValues = [
"isVerified",
"forceTwoFactor",
"autoJoinDomain",
"onlyAllowDomain"
"onlyAllowDomain",
"isActive"
];

/**
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,7 @@ export enum UserScopes {
DELETE_USER_EMAILS = "user:emails:delete",
RESEND_USER_EMAIL_VERIFICATION = "user:emails:resend-verification"
}

export enum Webhooks {
EXAMPLE = 1
}
10 changes: 10 additions & 0 deletions src/interfaces/tables/organization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IdRow } from "../general";
import { Webhooks } from "../enum";

export interface Organization extends IdRow {
name?: string;
Expand Down Expand Up @@ -27,3 +28,12 @@ export interface Domain extends IdRow {
verificationCode?: string;
isVerified: boolean;
}

export interface Webhook extends IdRow {
organizationId: number;
url: string;
event: Webhooks;
contentType: "application/json" | "application/x-www-form-urlencoded";
secret?: string;
isActive: boolean;
}
78 changes: 76 additions & 2 deletions src/rest/organization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Organization } from "../interfaces/tables/organization";
import { Organization, Webhook } from "../interfaces/tables/organization";
import {
createOrganization,
updateOrganization,
Expand All @@ -14,7 +14,12 @@ import {
updateDomain,
createDomain,
deleteDomain,
checkDomainAvailability
checkDomainAvailability,
getOrganizationWebhooks,
getWebhook,
updateWebhook,
createWebhook,
deleteWebhook
} from "../crud/organization";
import { InsertResult } from "../interfaces/mysql";
import {
Expand Down Expand Up @@ -621,3 +626,72 @@ export const verifyDomainForUser = async (
}
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

export const getOrganizationWebhooksForUser = async (
userId: number | ApiKeyResponse,
organizationId: number,
query: KeyValue
) => {
if (await can(userId, Authorizations.READ, "organization", organizationId))
return await getOrganizationWebhooks(organizationId, query);
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

export const getOrganizationWebhookForUser = async (
userId: number | ApiKeyResponse,
organizationId: number,
webhookId: number
) => {
if (await can(userId, Authorizations.READ, "organization", organizationId))
return await getWebhook(organizationId, webhookId);
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

export const updateWebhookForUser = async (
userId: number | ApiKeyResponse,
organizationId: number,
webhookId: number,
data: KeyValue,
locals: Locals
) => {
if (
await can(userId, Authorizations.UPDATE, "organization", organizationId)
) {
await updateWebhook(organizationId, webhookId, data);
return;
}
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

export const createWebhookForUser = async (
userId: number | ApiKeyResponse,
organizationId: number,
webhook: KeyValue,
locals: Locals
) => {
if (
await can(userId, Authorizations.CREATE, "organization", organizationId)
) {
const key = await createWebhook({
organizationId,
...webhook
} as Webhook);
return;
}
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

export const deleteWebhookForUser = async (
userId: number | ApiKeyResponse,
organizationId: number,
webhookId: number,
locals: Locals
) => {
if (
await can(userId, Authorizations.DELETE, "organization", organizationId)
) {
await deleteWebhook(organizationId, webhookId);
return;
}
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
};

0 comments on commit a5851c6

Please sign in to comment.