-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Discord webhook notification support #1612
Changes from 26 commits
28f5d35
172597e
d3810db
d83773a
84fe58b
92d9573
cc48f1d
8667f74
0bb7eae
d3fb41a
8f94062
1d9f94c
e44cbf3
aa977ce
964a923
e9bcf66
64c5aa9
01e73fe
d8379f7
f46b076
ccd6c58
ef41712
fa50706
67da574
023d943
012483d
8007daa
70ffe1b
12d9f15
07882e0
8409571
55c1a1c
ff2b3b4
6152679
e4e3f9c
f22b67a
d710ec0
c4168a5
afc9d46
03a938d
5be17af
4ef2b29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import logger from '../utils/logger.js'; | ||
|
||
class NotificationController { | ||
constructor(notificationService) { | ||
this.notificationService = notificationService; | ||
} | ||
|
||
async triggerNotification(req, res) { | ||
try { | ||
const { monitorId, type, config } = req.body; | ||
|
||
const networkResponse = { | ||
monitor: { _id: monitorId, name: "Test Monitor", url: "http://www.google.com" }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the name and url in networkResponse object are hardcoded. Was this just for testing? |
||
status: false, | ||
statusChanged: true, | ||
prevStatus: true, | ||
}; | ||
if (type === "webhook") { | ||
await this.notificationService.sendWebhookNotification( | ||
networkResponse, | ||
config | ||
); | ||
} | ||
res.json({ success: true, msg: "Notification sent successfully" }); | ||
} catch (error) { | ||
logger.error({ | ||
message: error.message, | ||
service: "NotificationController", | ||
method: "triggerNotification", | ||
stack: error.stack, | ||
}); | ||
res.status(500).json({ success: false, msg: "Failed to send notification" }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for errors, please use the error handling middleware There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
} | ||
} | ||
|
||
export default NotificationController; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,14 @@ const NotificationSchema = mongoose.Schema( | |
}, | ||
type: { | ||
type: String, | ||
enum: ["email", "sms"], | ||
enum: ["email", "sms", "webhook"], | ||
}, | ||
config: { | ||
type: String, | ||
webhookUrl: String, | ||
botToken: String, | ||
chatId: String | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aplogoies, I looked at this too quickly on my phone last time I reviewed. This is not the correct way to define an object schema with Mongoose. You need to define a separate schema for Config like
config: {
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All done! |
||
address: { | ||
type: String, | ||
}, | ||
|
@@ -76,4 +82,5 @@ NotificationSchema.pre("findOneAndUpdate", function (next) { | |
} | ||
next(); | ||
}); | ||
|
||
export default mongoose.model("Notification", NotificationSchema); |
Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -38,6 +38,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import DistributedUptimeController from "./controllers/distributedUptimeController.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import NotificationRoutes from "./routes/notificationRoute.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import NotificationController from "./controllers/notificationController.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//JobQueue service and dependencies | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import JobQueue from "./service/jobQueue.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Queue, Worker } from "bullmq"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -183,7 +187,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const statusService = new StatusService(db, logger); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationService = new NotificationService(emailService, db, logger); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationService = new NotificationService(emailService, db, logger, networkService); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const jobQueue = new JobQueue( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -269,6 +273,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(StringService.SERVICE_NAME) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationController = new NotificationController( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(NotificationService.SERVICE_NAME) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const distributedUptimeController = new DistributedUptimeController( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ServiceRegistry.get(MongoDB.SERVICE_NAME), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
http, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -289,6 +297,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const distributedUptimeRoutes = new DistributedUptimeRoutes( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
distributedUptimeController | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const notificationRoutes = new NotificationRoutes(notificationController); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Init job queue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await jobQueue.initJobQueue(); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Middleware | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -311,6 +322,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/status-page", statusPageRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter()); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Missing rate limiting High
This route handler performs
authorization Error loading related location Loading
Copilot Autofix AI 1 day ago To fix the problem, we need to introduce rate limiting to the Express application. The best way to do this is by using the
Suggested changeset
2
Server/index.js
Server/package.json
Outside changed files
This fix introduces these dependencies
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add rate limiting to protect against abuse. The notification route is properly secured with JWT verification, but it lacks rate limiting which could make it vulnerable to abuse. Apply this diff to add rate limiting: -app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
+app.use("/api/v1/notifications", verifyJWT, rateLimiter, notificationRoutes.getRouter()); And add the rate limiter middleware at the top of the file: import rateLimit from 'express-rate-limit';
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
}); 🧰 Tools🪛 GitHub Check: CodeQL[failure] 308-308: Missing rate limiting |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
app.use(handleErrors); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import express from 'express'; | ||
import { verifyJWT } from '../middleware/verifyJWT.js'; | ||
import { triggerNotificationBodyValidation } from '../validation/joi.js'; | ||
|
||
class NotificationRoutes { | ||
constructor(notificationController) { | ||
this.notificationController = notificationController; | ||
this.router = express.Router(); | ||
this.initializeRoutes(); | ||
} | ||
|
||
validateRequest(schema) { | ||
return (req, res, next) => { | ||
const { error } = schema.validate(req.body, { | ||
abortEarly: false, | ||
stripUnknown: true | ||
}); | ||
|
||
if (error) { | ||
const errorMessage = error.details | ||
.map(detail => detail.message) | ||
.join(', '); | ||
|
||
return res.status(400).json({ | ||
success: false, | ||
msg: errorMessage | ||
}); | ||
} | ||
|
||
next(); | ||
}; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets move the validation to the Controller, all validation in this application is done at the controller level. Please do follow the I don't think there's any reason to use middleware for this validation either. If we are going to use middleware for validation then all validation should be refactored to match. I don't think we want to do that right now, but perhaps in the future. We should always follow convention as much as possible so we know where to look for things in the application. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Complete. |
||
|
||
initializeRoutes() { | ||
this.router.post( | ||
'/trigger', | ||
verifyJWT, | ||
|
||
this.validateRequest(triggerNotificationBodyValidation), | ||
this.notificationController.triggerNotification.bind(this.notificationController) | ||
); | ||
} | ||
|
||
getRouter() { | ||
return this.router; | ||
} | ||
} | ||
|
||
export default NotificationRoutes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -418,6 +418,52 @@ class NetworkService { | |
throw err; | ||
} | ||
|
||
async requestWebhook(platform, url, message) { | ||
try { | ||
const { response, responseTime, error } = await this.timeRequest(() => | ||
this.axios.post(url, message, { | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we timing the webook request? I don't think we're interested in how long it took to carry out the webhook request are we? We can just make the axios request directly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. No need. Got rid of it. |
||
|
||
const webhookResponse = { | ||
type: 'webhook', | ||
responseTime, | ||
payload: response?.data | ||
}; | ||
|
||
if (error) { | ||
webhookResponse.status = false; | ||
webhookResponse.code = error.response?.status || this.NETWORK_ERROR; | ||
webhookResponse.message = `Failed to send ${platform} notification`; | ||
this.logger.warn({ | ||
message: error.message, | ||
service: this.SERVICE_NAME, | ||
method: 'requestWebhook', | ||
url, | ||
platform, | ||
error: error.message, | ||
statusCode: error.response?.status, | ||
responseData: error.response?.data, | ||
requestPayload: message | ||
}); | ||
return webhookResponse; | ||
} | ||
|
||
webhookResponse.status = true; | ||
webhookResponse.code = response.status; | ||
webhookResponse.message = `Successfully sent ${platform} notification`; | ||
return webhookResponse; | ||
} catch (error) { | ||
error.service = this.SERVICE_NAME; | ||
error.method = 'requestWebhook'; | ||
throw error; | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Gets the status of a job based on its type and returns the appropriate response. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
const SERVICE_NAME = "NotificationService"; | ||
const TELEGRAM_API_BASE_URL = "https://api.telegram.org/bot"; | ||
|
||
class NotificationService { | ||
static SERVICE_NAME = SERVICE_NAME; | ||
|
@@ -9,13 +10,70 @@ class NotificationService { | |
* @param {Object} db - The database instance for storing notification data. | ||
* @param {Object} logger - The logger instance for logging activities. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be sure to update the JSdoc since this now takes a |
||
*/ | ||
constructor(emailService, db, logger) { | ||
constructor(emailService, db, logger, networkService) { | ||
this.SERVICE_NAME = SERVICE_NAME; | ||
this.emailService = emailService; | ||
this.db = db; | ||
this.logger = logger; | ||
this.networkService = networkService; | ||
} | ||
|
||
formatNotificationMessage(monitor, status, platform, chatId) { | ||
const messageText = `Monitor ${monitor.name} is ${status ? "up" : "down"}. URL: ${monitor.url}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Localization service neeed here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
if (platform === 'telegram') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's refactor these types into either an Array of acceptable types. This gives us the benefit of being able to easily add more types or change this value easily. Nothing worse than looking for hardcoded strings all over the place.
Then you can just check if the value exists in the array or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All done. |
||
return { chat_id: chatId, text: messageText }; | ||
} | ||
if (platform === 'slack') { | ||
return { text: messageText }; | ||
} | ||
if (platform === 'discord') { | ||
return { content: messageText }; | ||
} | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. All done. |
||
} | ||
|
||
async sendWebhookNotification(networkResponse, config) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need JSdocs for these methods |
||
const { monitor, status } = networkResponse; | ||
const { type, webhookUrl, botToken, chatId } = config; | ||
let url = webhookUrl; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of first trying to build a message and then checking if the message is undefined or not, let's not even try to build a message if the webhook type is not supported. Just check if the type is in the acceptable type array described above, if not return early. We should always try to return as early as possible to do as little work as necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. |
||
const message = this.formatNotificationMessage(monitor, status, type, chatId); | ||
if (message === null) { | ||
this.logger.warn({ | ||
message: `Unsupported webhook type: ${type}`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure to use the localization service for user facing strings There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
service: this.SERVICE_NAME, | ||
method: 'sendWebhookNotification', | ||
type | ||
}); | ||
return false; | ||
} | ||
|
||
if (type === 'telegram') { | ||
if (!botToken || !chatId) { | ||
return false; | ||
} | ||
url = `${TELEGRAM_API_BASE_URL}${botToken}/sendMessage`; | ||
} | ||
|
||
try { | ||
const response = await this.networkService.requestWebhook(type, url, message); | ||
return response.status; | ||
} catch (error) { | ||
this.logger.error({ | ||
message: `Error sending ${type} notification`, | ||
service: this.SERVICE_NAME, | ||
method: 'sendWebhookNotification', | ||
error: error.message, | ||
stack: error.stack, | ||
url, | ||
type, | ||
requestPayload: message | ||
}); | ||
return false; | ||
ajhollid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/** | ||
* Sends an email notification for hardware infrastructure alerts | ||
* | ||
|
@@ -59,19 +117,20 @@ class NotificationService { | |
try { | ||
//If status hasn't changed, we're done | ||
if (networkResponse.statusChanged === false) return false; | ||
|
||
// if prevStatus is undefined, monitor is resuming, we're done | ||
ajhollid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (networkResponse.prevStatus === undefined) return false; | ||
const notifications = await this.db.getNotificationsByMonitorId( | ||
networkResponse.monitorId | ||
); | ||
|
||
|
||
const notifications = await this.db.getNotificationsByMonitorId(networkResponse.monitorId); | ||
|
||
for (const notification of notifications) { | ||
if (notification.type === "email") { | ||
this.sendEmail(networkResponse, notification.address); | ||
} | ||
} else if (notification.type === "webhook") { | ||
this.sendWebhookNotification(networkResponse, notification.address, notification.type); | ||
|
||
// Handle other types of notifications here | ||
} | ||
} | ||
return true; | ||
} catch (error) { | ||
this.logger.warn({ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -475,6 +475,59 @@ const imageValidation = joi | |
"any.required": "Image file is required", | ||
}); | ||
|
||
const telegramWebhookConfigValidation = joi.object({ | ||
type: joi.string().valid('telegram').required(), | ||
botToken: joi.string().required().messages({ | ||
'string.empty': 'Telegram bot token is required', | ||
'any.required': 'Telegram bot token is required' | ||
}), | ||
chatId: joi.string().required().messages({ | ||
'string.empty': 'Telegram chat ID is required', | ||
'any.required': 'Telegram chat ID is required' | ||
}) | ||
}); | ||
|
||
const discordWebhookConfigValidation = joi.object({ | ||
type: joi.string().valid('discord').required(), | ||
webhookUrl: joi.string().uri().required().messages({ | ||
'string.empty': 'Discord webhook URL is required', | ||
'string.uri': 'Discord webhook URL must be a valid URL', | ||
'any.required': 'Discord webhook URL is required' | ||
}) | ||
}); | ||
|
||
const slackWebhookConfigValidation = joi.object({ | ||
type: joi.string().valid('slack').required(), | ||
webhookUrl: joi.string().uri().required().messages({ | ||
'string.empty': 'Slack webhook URL is required', | ||
'string.uri': 'Slack webhook URL must be a valid URL', | ||
'any.required': 'Slack webhook URL is required' | ||
}) | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need three different validation schemas, one is sufficient since they are all the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
const triggerNotificationBodyValidation = joi.object({ | ||
monitorId: joi.string().required().messages({ | ||
'string.empty': 'Monitor ID is required', | ||
'any.required': 'Monitor ID is required' | ||
}), | ||
type: joi.string().valid('webhook').required().messages({ | ||
'string.empty': 'Notification type is required', | ||
'any.required': 'Notification type is required', | ||
'any.only': 'Notification type must be webhook' | ||
}), | ||
config: joi.alternatives() | ||
.conditional('type', { | ||
is: 'webhook', | ||
then: joi.alternatives().try( | ||
telegramWebhookConfigValidation, | ||
discordWebhookConfigValidation, | ||
slackWebhookConfigValidation | ||
).required().messages({ | ||
'any.required': 'Webhook configuration is required' | ||
}) | ||
}) | ||
}); | ||
|
||
export { | ||
roleValidatior, | ||
loginValidation, | ||
|
@@ -533,5 +586,9 @@ export { | |
createStatusPageBodyValidation, | ||
getStatusPageParamValidation, | ||
getStatusPageQueryValidation, | ||
imageValidation, | ||
imageValidation, | ||
triggerNotificationBodyValidation, | ||
telegramWebhookConfigValidation, | ||
discordWebhookConfigValidation, | ||
slackWebhookConfigValidation | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation should be done on this body at this level