mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: add Slack webhook notifications for user creation, project management, and subscription updates
This commit is contained in:
@@ -279,8 +279,23 @@ export const AllowedSubscribersCountInFreePlan: number = process.env[
|
||||
? parseInt(process.env["ALLOWED_SUBSCRIBERS_COUNT_IN_FREE_PLAN"].toString())
|
||||
: 100;
|
||||
|
||||
export const NotificationWebhookOnCreateUser: string =
|
||||
process.env["NOTIFICATION_WEBHOOK_ON_CREATED_USER"] || "";
|
||||
export const NotificationSlackWebhookOnCreateUser: string =
|
||||
process.env["NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER"] || "";
|
||||
|
||||
export const NotificationSlackWebhookOnCreateProject: string = process.env[
|
||||
"NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT"
|
||||
] || "";
|
||||
|
||||
// notification delete project
|
||||
export const NotificationSlackWebhookOnDeleteProject: string = process.env[
|
||||
"NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT"
|
||||
] || "";
|
||||
|
||||
// notification subscripton update.
|
||||
export const NotificationSlackWebhookOnSubscriptionUpdate: string = process.env[
|
||||
"NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE"
|
||||
] || "";
|
||||
|
||||
|
||||
export const AdminDashboardClientURL: URL = new URL(
|
||||
HttpProtocol,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ResellerPlan from "Common/Models/DatabaseModels/ResellerPlan";
|
||||
import { IsBillingEnabled, getAllEnvVars } from "../EnvironmentConfig";
|
||||
import { IsBillingEnabled, NotificationSlackWebhookOnCreateProject, NotificationSlackWebhookOnDeleteProject, getAllEnvVars } from "../EnvironmentConfig";
|
||||
import AllMeteredPlans from "../Types/Billing/MeteredPlan/AllMeteredPlans";
|
||||
import CreateBy from "../Types/Database/CreateBy";
|
||||
import DeleteBy from "../Types/Database/DeleteBy";
|
||||
@@ -61,6 +61,8 @@ import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
|
||||
import AlertSeverityService from "./AlertSeverityService";
|
||||
import AlertState from "../../Models/DatabaseModels/AlertState";
|
||||
import AlertStateService from "./AlertStateService";
|
||||
import SlackUtil from "../Utils/Slack";
|
||||
import URL from "../../Types/API/URL";
|
||||
|
||||
export interface CurrentPlan {
|
||||
plan: PlanType | null;
|
||||
@@ -178,8 +180,8 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
if (promoCode.planType !== data.data.planName) {
|
||||
throw new BadDataException(
|
||||
"Promocode is not valid for this plan. Please select the " +
|
||||
promoCode.planType +
|
||||
" plan.",
|
||||
promoCode.planType +
|
||||
" plan.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -310,9 +312,9 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
|
||||
logger.debug(
|
||||
"Changing plan for project " +
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName(),
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName(),
|
||||
);
|
||||
|
||||
if (!project.paymentProviderSubscriptionSeats) {
|
||||
@@ -324,11 +326,11 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
|
||||
logger.debug(
|
||||
"Changing plan for project " +
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats,
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats,
|
||||
);
|
||||
|
||||
const subscription: {
|
||||
@@ -350,12 +352,12 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
|
||||
logger.debug(
|
||||
"Changing plan for project " +
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats +
|
||||
" completed.",
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats +
|
||||
" completed.",
|
||||
);
|
||||
|
||||
// refresh subscription status.
|
||||
@@ -391,12 +393,12 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
|
||||
logger.debug(
|
||||
"Changing plan for project " +
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats +
|
||||
" completed and project updated.",
|
||||
project.id?.toString() +
|
||||
" to " +
|
||||
plan.getName() +
|
||||
" with seats " +
|
||||
project.paymentProviderSubscriptionSeats +
|
||||
" completed and project updated.",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -561,6 +563,57 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
createdItem = await this.addDefaultScheduledMaintenanceState(createdItem);
|
||||
createdItem = await this.addDefaultAlertState(createdItem);
|
||||
|
||||
|
||||
if (NotificationSlackWebhookOnCreateProject) {
|
||||
|
||||
|
||||
// fetch project again.
|
||||
const project: Model | null = await this.findOneById({
|
||||
id: createdItem.id!,
|
||||
select: {
|
||||
name: true,
|
||||
id: true,
|
||||
createdOwnerName: true,
|
||||
createdOwnerEmail: true,
|
||||
planName: true,
|
||||
createdByUserId: true,
|
||||
paymentProviderSubscriptionStatus: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BadDataException("Project not found");
|
||||
}
|
||||
|
||||
let slackMessage: string = `*Project Created:*
|
||||
*Project Name:* ${project.name?.toString() || "N/A"}
|
||||
*Project ID:* ${project.id?.toString() || "N/A"}
|
||||
`;
|
||||
|
||||
if (project.createdOwnerName && project.createdOwnerEmail) {
|
||||
slackMessage += `*Created By:* ${project?.createdOwnerName?.toString() + "(" + project.createdOwnerEmail.toString() + ")" || "N/A"}
|
||||
`;
|
||||
|
||||
if (IsBillingEnabled) {
|
||||
// which plan?
|
||||
slackMessage += `*Plan:* ${project.planName?.toString() || "N/A"}
|
||||
*Subscription Status:* ${project.paymentProviderSubscriptionStatus?.toString() || "N/A"}
|
||||
`;
|
||||
}
|
||||
|
||||
SlackUtil.sendMessageToChannel({
|
||||
url: URL.fromString(NotificationSlackWebhookOnCreateProject),
|
||||
text: slackMessage,
|
||||
}).catch((error) => {
|
||||
logger.error("Error sending slack message: " + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return createdItem;
|
||||
}
|
||||
|
||||
@@ -1007,29 +1060,75 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
protected override async onBeforeDelete(
|
||||
deleteBy: DeleteBy<Model>,
|
||||
): Promise<OnDelete<Model>> {
|
||||
if (IsBillingEnabled) {
|
||||
const projects: Array<Model> = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
props: deleteBy.props,
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
paymentProviderSubscriptionId: true,
|
||||
paymentProviderMeteredSubscriptionId: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { deleteBy, carryForward: projects };
|
||||
}
|
||||
const projects: Array<Model> = await this.findBy({
|
||||
query: deleteBy.query,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
limit: LIMIT_MAX,
|
||||
skip: 0,
|
||||
select: {
|
||||
_id: true,
|
||||
paymentProviderSubscriptionId: true,
|
||||
paymentProviderMeteredSubscriptionId: true,
|
||||
name: true,
|
||||
createdByUser: {
|
||||
name: true,
|
||||
email: true,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { deleteBy, carryForward: projects };
|
||||
|
||||
return { deleteBy, carryForward: [] };
|
||||
}
|
||||
|
||||
protected override async onDeleteSuccess(
|
||||
onDelete: OnDelete<Model>,
|
||||
_itemIdsBeforeDelete: ObjectID[],
|
||||
): Promise<OnDelete<Model>> {
|
||||
|
||||
if (NotificationSlackWebhookOnDeleteProject) {
|
||||
|
||||
for (const project of onDelete.carryForward) {
|
||||
|
||||
|
||||
let subscriptionStatus: SubscriptionStatus | null = null;
|
||||
|
||||
if (IsBillingEnabled) {
|
||||
subscriptionStatus = await BillingService.getSubscriptionStatus(
|
||||
project.paymentProviderSubscriptionId!,
|
||||
);
|
||||
}
|
||||
|
||||
let slackMessage: string = `*Project Deleted:*
|
||||
*Project Name:* ${project.name?.toString() || "N/A"}
|
||||
*Project ID:* ${project._id?.toString() || "N/A"}
|
||||
`;
|
||||
|
||||
if (subscriptionStatus) {
|
||||
slackMessage += `*Project Subscription Status:* ${subscriptionStatus?.toString() || "N/A"}
|
||||
`;
|
||||
}
|
||||
|
||||
if (project.createdByUser && project.createdByUser.name && project.createdByUser.email) {
|
||||
slackMessage += `*Created By:* ${project?.createdByUser.name?.toString() + "(" + project.createdByUser.email.toString() + ")" || "N/A"}
|
||||
`;
|
||||
}
|
||||
|
||||
SlackUtil.sendMessageToChannel({
|
||||
url: URL.fromString(NotificationSlackWebhookOnDeleteProject),
|
||||
text: slackMessage,
|
||||
}).catch((err: Error) => {
|
||||
// log this error but do not throw it. Not important enough to stop the process.
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get project id
|
||||
if (IsBillingEnabled) {
|
||||
for (const project of onDelete.carryForward) {
|
||||
@@ -1047,6 +1146,8 @@ export class ProjectService extends DatabaseService<Model> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import DatabaseConfig from "../DatabaseConfig";
|
||||
import {
|
||||
IsBillingEnabled,
|
||||
NotificationWebhookOnCreateUser,
|
||||
NotificationSlackWebhookOnCreateUser,
|
||||
} from "../EnvironmentConfig";
|
||||
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
||||
import UpdateBy from "../Types/Database/UpdateBy";
|
||||
@@ -42,9 +42,9 @@ export class Service extends DatabaseService<Model> {
|
||||
_onCreate: OnCreate<Model>,
|
||||
createdItem: Model,
|
||||
): Promise<Model> {
|
||||
if (NotificationWebhookOnCreateUser) {
|
||||
if (NotificationSlackWebhookOnCreateUser) {
|
||||
SlackUtil.sendMessageToChannel({
|
||||
url: URL.fromString(NotificationWebhookOnCreateUser),
|
||||
url: URL.fromString(NotificationSlackWebhookOnCreateUser),
|
||||
text: `*New OneUptime User:*
|
||||
*Email:* ${createdItem.email?.toString() || "N/A"}
|
||||
*Name:* ${createdItem.name?.toString() || "N/A"}
|
||||
|
||||
@@ -152,10 +152,17 @@ Usage:
|
||||
- name: IS_SERVER
|
||||
value: {{ printf "true" | squote }}
|
||||
|
||||
- name: NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER
|
||||
value: {{ $.Values.notifications.webhooks.slack.onCreateUser }}
|
||||
|
||||
- name: NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT
|
||||
value: {{ $.Values.notifications.webhooks.slack.onCreateProject }}
|
||||
|
||||
- name: NOTIFICATION_WEBHOOK_ON_CREATED_USER
|
||||
value: {{ $.Values.notifications.webhooks.onCreateUser }}
|
||||
- name: NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT
|
||||
value: {{ $.Values.notifications.webhooks.slack.onDeleteProject }}
|
||||
|
||||
- name: NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE
|
||||
value: {{ $.Values.notifications.webhooks.slack.onSubscriptionUpdate }}
|
||||
|
||||
- name: LETS_ENCRYPT_NOTIFICATION_EMAIL
|
||||
value: {{ $.Values.letsEncrypt.email }}
|
||||
|
||||
@@ -392,8 +392,12 @@ externalClickhouse:
|
||||
# Notification webhooks when certain events happen in the system. (usually they are slack webhooks)
|
||||
notifications:
|
||||
webhooks:
|
||||
# This is the webhook that will be called when a user is created or signs up.
|
||||
onCreateUser:
|
||||
slack:
|
||||
# This is the webhook that will be called when a user is created or signs up.
|
||||
onCreateUser:
|
||||
onDeleteProject:
|
||||
onCreateProject:
|
||||
onSubscriptionUpdate:
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -250,7 +250,13 @@ ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN=10
|
||||
# Notifications Webhook (Slack)
|
||||
|
||||
# This webhook notifies slack when the new user signs up or is created.
|
||||
NOTIFICATION_WEBHOOK_ON_CREATED_USER=
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER=
|
||||
# This webhook notifies slack when the new project is created.
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT=
|
||||
# This webhook notifies slack when the project is deleted.
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT=
|
||||
# This webhook notifies slack when the subscription is updated.
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE=
|
||||
|
||||
# Copilot Environment Variables
|
||||
COPILOT_ONEUPTIME_URL=http://localhost
|
||||
|
||||
@@ -115,8 +115,10 @@ x-common-server-variables: &common-server-variables
|
||||
DISABLE_AUTOMATIC_ALERT_CREATION: ${DISABLE_AUTOMATIC_ALERT_CREATION}
|
||||
|
||||
# Notification Webhooks
|
||||
NOTIFICATION_WEBHOOK_ON_CREATED_USER: ${NOTIFICATION_WEBHOOK_ON_CREATED_USER}
|
||||
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER: ${NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_USER}
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT: ${NOTIFICATION_SLACK_WEBHOOK_ON_CREATED_PROJECT}
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT: ${NOTIFICATION_SLACK_WEBHOOK_ON_DELETED_PROJECT}
|
||||
NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE: ${NOTIFICATION_SLACK_WEBHOOK_ON_SUBSCRIPTION_UPDATE}
|
||||
|
||||
services:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user