Refactor API endpoints to include error handling middleware

- Updated multiple API routes to use NextFunction for error handling.
- Wrapped asynchronous route handlers in try-catch blocks to catch and pass errors to the next middleware.
- Ensured consistent error responses across various endpoints, improving maintainability and readability.
- Enhanced the structure of the code by reducing nested try-catch blocks and improving flow control.
This commit is contained in:
Nawaz Dhandala
2025-10-07 21:46:25 +01:00
parent 12f05937af
commit 2fbc44d5c3
18 changed files with 1130 additions and 842 deletions

View File

@@ -6,6 +6,7 @@ import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
OneUptimeRequest,
} from "Common/Server/Utils/Express";
import Response from "Common/Server/Utils/Response";
@@ -191,7 +192,11 @@ const formatTeamForSCIM: (
router.get(
"/scim/v2/:projectScimId/ServiceProviderConfig",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Project SCIM ServiceProviderConfig - scimId: ${req.params["projectScimId"]!}`,
@@ -210,7 +215,7 @@ router.get(
return Response.sendJsonObjectResponse(req, res, serviceProviderConfig);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -219,7 +224,11 @@ router.get(
router.get(
"/scim/v2/:projectScimId/Users",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Project SCIM Users list - scimId: ${req.params["projectScimId"]!}`,
@@ -392,7 +401,7 @@ router.get(
);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -401,7 +410,11 @@ router.get(
router.get(
"/scim/v2/:projectScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Get individual user request for userId: ${req.params["userId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -460,7 +473,7 @@ router.get(
return Response.sendJsonObjectResponse(req, res, user);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -469,7 +482,11 @@ router.get(
router.put(
"/scim/v2/:projectScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Update user request for userId: ${req.params["userId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -626,7 +643,7 @@ router.put(
return Response.sendJsonObjectResponse(req, res, user);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -635,7 +652,11 @@ router.put(
router.get(
"/scim/v2/:projectScimId/Groups",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Groups list request for projectScimId: ${req.params["projectScimId"]}`,
@@ -715,7 +736,7 @@ router.get(
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -724,7 +745,11 @@ router.get(
router.get(
"/scim/v2/:projectScimId/Groups/:groupId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Get individual group request for groupId: ${req.params["groupId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -779,7 +804,7 @@ router.get(
return Response.sendJsonObjectResponse(req, res, group);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -788,7 +813,11 @@ router.get(
router.post(
"/scim/v2/:projectScimId/Groups",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Create group request for projectScimId: ${req.params["projectScimId"]}`,
@@ -911,7 +940,7 @@ router.post(
return Response.sendJsonObjectResponse(req, res, createdGroup);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -920,7 +949,11 @@ router.post(
router.put(
"/scim/v2/:projectScimId/Groups/:groupId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Update group request for groupId: ${req.params["groupId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -1074,7 +1107,7 @@ router.put(
throw new NotFoundException("Failed to retrieve updated group");
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -1083,7 +1116,11 @@ router.put(
router.delete(
"/scim/v2/:projectScimId/Groups/:groupId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Delete group request for groupId: ${req.params["groupId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -1161,7 +1198,7 @@ router.delete(
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -1170,7 +1207,11 @@ router.delete(
router.patch(
"/scim/v2/:projectScimId/Groups/:groupId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Patch group request for groupId: ${req.params["groupId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -1405,7 +1446,7 @@ router.patch(
throw new NotFoundException("Failed to retrieve updated group");
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -1414,7 +1455,11 @@ router.patch(
router.post(
"/scim/v2/:projectScimId/Users",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Create user request for projectScimId: ${req.params["projectScimId"]}`,
@@ -1501,7 +1546,7 @@ router.post(
return Response.sendJsonObjectResponse(req, res, createdUser);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -1510,7 +1555,11 @@ router.post(
router.delete(
"/scim/v2/:projectScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`SCIM Delete user request for userId: ${req.params["userId"]}, projectScimId: ${req.params["projectScimId"]}`,
@@ -1562,7 +1611,7 @@ router.delete(
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);

View File

@@ -47,7 +47,11 @@ const router: ExpressRouter = Express.getRouter();
router.get(
"/service-provider-login",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
if (!req.query["email"]) {
return Response.sendErrorResponse(
@@ -152,7 +156,11 @@ router.get(
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, err as Exception);
if (err instanceof Exception) {
return next(err);
}
return next(new ServerException());
}
},
);
@@ -162,7 +170,7 @@ router.get(
async (
req: ExpressRequest,
res: ExpressResponse,
_next: NextFunction,
next: NextFunction,
): Promise<void> => {
try {
if (!req.params["projectId"]) {
@@ -238,22 +246,42 @@ router.get(
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, err as Exception);
if (err instanceof Exception) {
return next(err);
}
return next(new ServerException());
}
},
);
router.get(
"/idp-login/:projectId/:projectSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
await loginUserWithSso(req, res);
} catch (err) {
return next(err);
}
},
);
router.post(
"/idp-login/:projectId/:projectSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
await loginUserWithSso(req, res);
} catch (err) {
return next(err);
}
},
);

View File

@@ -4,6 +4,7 @@ import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
OneUptimeRequest,
} from "Common/Server/Utils/Express";
import Response from "Common/Server/Utils/Response";
@@ -29,7 +30,11 @@ const router: ExpressRouter = Express.getRouter();
router.get(
"/status-page-scim/v2/:statusPageScimId/ServiceProviderConfig",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM ServiceProviderConfig - scimId: ${req.params["statusPageScimId"]!}`,
@@ -44,7 +49,7 @@ router.get(
return Response.sendJsonObjectResponse(req, res, serviceProviderConfig);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -53,7 +58,11 @@ router.get(
router.get(
"/status-page-scim/v2/:statusPageScimId/Users",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM Users list request for statusPageScimId: ${req.params["statusPageScimId"]}`,
@@ -164,7 +173,7 @@ router.get(
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -173,7 +182,11 @@ router.get(
router.get(
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM Get individual user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
@@ -231,7 +244,7 @@ router.get(
return Response.sendJsonObjectResponse(req, res, user);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -240,7 +253,11 @@ router.get(
router.post(
"/status-page-scim/v2/:statusPageScimId/Users",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM Create user request for statusPageScimId: ${req.params["statusPageScimId"]}`,
@@ -333,7 +350,7 @@ router.post(
return Response.sendJsonObjectResponse(req, res, createdUser);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -342,7 +359,11 @@ router.post(
router.put(
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM Update user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
@@ -489,7 +510,7 @@ router.put(
return Response.sendJsonObjectResponse(req, res, user);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);
@@ -498,7 +519,11 @@ router.put(
router.delete(
"/status-page-scim/v2/:statusPageScimId/Users/:userId",
SCIMMiddleware.isAuthorizedSCIMRequest,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
logger.debug(
`Status Page SCIM Delete user request for userId: ${req.params["userId"]}, statusPageScimId: ${req.params["statusPageScimId"]}`,
@@ -562,7 +587,7 @@ router.delete(
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(req, res, err as BadRequestException);
return next(err);
}
},
);

View File

@@ -115,7 +115,11 @@ router.get(
router.post(
"/status-page-idp-login/:statusPageId/:statusPageSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
@@ -312,7 +316,11 @@ router.post(
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
if (err instanceof Exception) {
return next(err);
}
return next(new ServerException());
}
},
);

View File

@@ -62,108 +62,115 @@ router.post(
},
);
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
router.post(
"/test",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
const toPhone: Phone = new Phone(body["toPhone"] as string);
const toPhone: Phone = new Phone(body["toPhone"] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
// if any of the twilio config is missing, we will not send make the call
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
}
const testCallRequest: CallRequest = {
data: [
{
sayMessage: "This is a test call from OneUptime.",
},
],
to: toPhone,
};
await CallService.makeCall(testCallRequest, {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
throw new BadDataException(
"Error making test call. Please check the twilio logs for more details",
);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
const testCallRequest: CallRequest = {
data: [
{
sayMessage: "This is a test call from OneUptime.",
},
],
to: toPhone,
};
await CallService.makeCall(testCallRequest, {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Error making test call. Please check the twilio logs for more details",
),
);
}
return Response.sendEmptySuccessResponse(req, res);
});
},
);
export default router;

View File

@@ -66,99 +66,110 @@ router.post(
},
);
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
router.post(
"/test",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
const toPhone: Phone = new Phone(body["toPhone"] as string);
const toPhone: Phone = new Phone(body["toPhone"] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
// if any of the twilio config is missing, we will not send make the call
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
}
await SmsService.sendSms(
toPhone,
"This is a test SMS from OneUptime.",
{
projectId: config.projectId,
customTwilioConfig: twilioConfig,
},
);
} catch (err) {
logger.error(err);
throw new BadDataException(
"Failed to send test SMS. Please check the twilio logs for more details.",
);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
await SmsService.sendSms(toPhone, "This is a test SMS from OneUptime.", {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Failed to send test SMS. Please check the twilio logs for more details.",
),
);
}
return Response.sendEmptySuccessResponse(req, res);
});
},
);
export default router;

View File

@@ -11,6 +11,7 @@ import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "Common/Server/Utils/Express";
import logger from "Common/Server/Utils/Logger";
import Response from "Common/Server/Utils/Response";
@@ -18,87 +19,96 @@ import ProjectSmtpConfig from "Common/Models/DatabaseModels/ProjectSmtpConfig";
const router: ExpressRouter = Express.getRouter();
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
router.post(
"/test",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const body: JSONObject = req.body;
const smtpConfigId: ObjectID = new ObjectID(body["smtpConfigId"] as string);
const smtpConfigId: ObjectID = new ObjectID(
body["smtpConfigId"] as string,
);
const config: ProjectSmtpConfig | null =
await ProjectSMTPConfigService.findOneById({
id: smtpConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
hostname: true,
port: true,
username: true,
password: true,
fromEmail: true,
fromName: true,
secure: true,
projectId: true,
},
});
const config: ProjectSmtpConfig | null =
await ProjectSMTPConfigService.findOneById({
id: smtpConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
hostname: true,
port: true,
username: true,
password: true,
fromEmail: true,
fromName: true,
secure: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"smtp-config not found for id" + smtpConfigId.toString(),
),
);
}
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"smtp-config not found for id" + smtpConfigId.toString(),
),
);
}
const toEmail: Email = new Email(body["toEmail"] as string);
const toEmail: Email = new Email(body["toEmail"] as string);
if (!toEmail) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toEmail is required"),
);
}
if (!toEmail) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toEmail is required"),
);
}
const mail: EmailMessage = {
templateType: EmailTemplateType.SMTPTest,
toEmail: new Email(body["toEmail"] as string),
subject: "Test Email from OneUptime",
vars: {},
body: "",
};
const mail: EmailMessage = {
templateType: EmailTemplateType.SMTPTest,
toEmail: new Email(body["toEmail"] as string),
subject: "Test Email from OneUptime",
vars: {},
body: "",
};
const mailServer: EmailServer = {
id: config.id!,
host: config.hostname!,
port: config.port!,
username: config.username!,
password: config.password!,
fromEmail: config.fromEmail!,
fromName: config.fromName!,
secure: Boolean(config.secure),
};
const mailServer: EmailServer = {
id: config.id!,
host: config.hostname!,
port: config.port!,
username: config.username!,
password: config.password!,
fromEmail: config.fromEmail!,
fromName: config.fromName!,
secure: Boolean(config.secure),
};
try {
await MailService.send(mail, {
emailServer: mailServer,
projectId: config.projectId!,
timeout: 4000,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
),
);
}
try {
await MailService.send(mail, {
emailServer: mailServer,
projectId: config.projectId!,
timeout: 4000,
});
} catch (err) {
logger.error(err);
throw new BadDataException(
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
);
}
return Response.sendEmptySuccessResponse(req, res);
});
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
},
);
export default router;

View File

@@ -120,45 +120,57 @@ router.post(
}
);
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body as JSONObject;
router.post(
"/test",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const body: JSONObject = req.body as JSONObject;
if (!body["toPhone"]) {
throw new BadDataException("toPhone is required");
}
if (!body["toPhone"]) {
throw new BadDataException("toPhone is required");
}
const toPhone: Phone = new Phone(body["toPhone"] as string);
const toPhone: Phone = new Phone(body["toPhone"] as string);
const templateKey: WhatsAppTemplateId = WhatsAppTemplateIds.TestNotification;
const templateKey: WhatsAppTemplateId =
WhatsAppTemplateIds.TestNotification;
const templateLanguageCode: string =
WhatsAppTemplateLanguage[templateKey] || "en";
const templateLanguageCode: string =
WhatsAppTemplateLanguage[templateKey] || "en";
const message: WhatsAppMessage = {
to: toPhone,
body: "",
templateKey,
templateVariables: undefined,
templateLanguageCode,
};
const message: WhatsAppMessage = {
to: toPhone,
body: "",
templateKey,
templateVariables: undefined,
templateLanguageCode,
};
try {
await WhatsAppService.sendWhatsApp(message, {
projectId: body["projectId"]
? new ObjectID(body["projectId"] as string)
: undefined,
isSensitive: false,
});
} catch (err) {
const errorMsg: string =
err instanceof Error && err.message
? err.message
: "Failed to send test WhatsApp message.";
try {
await WhatsAppService.sendWhatsApp(message, {
projectId: body["projectId"]
? new ObjectID(body["projectId"] as string)
: undefined,
isSensitive: false,
});
} catch (err) {
const errorMsg: string =
err instanceof Error && err.message
? err.message
: "Failed to send test WhatsApp message.";
return Response.sendErrorResponse(req, res, new BadDataException(errorMsg));
}
throw new BadDataException(errorMsg);
}
return Response.sendEmptySuccessResponse(req, res);
});
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
},
);
export default router;

View File

@@ -1,7 +1,11 @@
import ProjectSsoService, {
Service as ProjectSsoServiceType,
} from "../Services/ProjectSsoService";
import { ExpressRequest, ExpressResponse } from "../Utils/Express";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
@@ -22,43 +26,51 @@ export default class ProjectSsoAPI extends BaseAPI<
`${new this.entityType()
.getCrudApiPath()
?.toString()}/:projectId/sso-list`,
async (req: ExpressRequest, res: ExpressResponse) => {
const projectId: ObjectID = new ObjectID(
req.params["projectId"] as string,
);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const projectId: ObjectID = new ObjectID(
req.params["projectId"] as string,
);
if (!projectId) {
return Response.sendErrorResponse(
if (!projectId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid project id."),
);
}
const sso: Array<ProjectSSO> = await this.service.findBy({
query: {
projectId: projectId,
isEnabled: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
description: true,
_id: true,
},
props: {
isRoot: true,
},
});
return Response.sendEntityArrayResponse(
req,
res,
new BadDataException("Invalid project id."),
sso,
new PositiveNumber(sso.length),
ProjectSSO,
);
} catch (err) {
return next(err);
}
const sso: Array<ProjectSSO> = await this.service.findBy({
query: {
projectId: projectId,
isEnabled: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
description: true,
_id: true,
},
props: {
isRoot: true,
},
});
return Response.sendEntityArrayResponse(
req,
res,
sso,
new PositiveNumber(sso.length),
ProjectSSO,
);
},
);
}

View File

@@ -1,7 +1,11 @@
import ShortLinkService, {
Service as ShortLinkServiceType,
} from "../Services/ShortLinkService";
import { ExpressRequest, ExpressResponse } from "../Utils/Express";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../Utils/Express";
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
@@ -18,34 +22,41 @@ export default class ShortLinkAPI extends BaseAPI<
`${new this.entityType()
.getCrudApiPath()
?.toString()}/redirect-to-shortlink/:id`,
async (req: ExpressRequest, res: ExpressResponse) => {
if (!req.params["id"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("id is required"),
);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
if (!req.params["id"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("id is required"),
);
}
if (req.params["id"] === "status") {
return Response.sendJsonObjectResponse(req, res, {
status: "ok",
});
}
const link: ShortLink | null =
await ShortLinkService.getShortLinkFor(req.params["id"]);
if (!link || !link.link) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("This URL is invalid or expired"),
);
}
return Response.redirect(req, res, link.link);
} catch (err) {
return next(err);
}
if (req.params["id"] === "status") {
return Response.sendJsonObjectResponse(req, res, {
status: "ok",
});
}
const link: ShortLink | null = await ShortLinkService.getShortLinkFor(
req.params["id"],
);
if (!link || !link.link) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("This URL is invalid or expired"),
);
}
return Response.redirect(req, res, link.link);
},
);
}

View File

@@ -6,6 +6,7 @@ import UserOnCallLogTimelineService, {
import {
ExpressRequest,
ExpressResponse,
NextFunction,
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
@@ -34,62 +35,70 @@ export default class UserNotificationLogTimelineAPI extends BaseAPI<
.getCrudApiPath()
?.toString()}/call/gather-input/:itemId`,
NotificationMiddleware.isValidCallNotificationRequest,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
req = req as OneUptimeRequest;
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item ID"),
);
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item ID"),
);
}
const token: JSONObject = (req as any).callTokenData;
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByAlertId: true,
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
}
// check digits.
if (req.body["Digits"] === "1") {
// then ack incident
await this.service.updateOneById({
id: itemId,
data: {
acknowledgedAt: OneUptimeDate.getCurrentDate(),
isAcknowledged: true,
status: UserNotificationStatus.Acknowledged,
statusMessage: "Notification Acknowledged",
},
props: {
isRoot: true,
},
});
}
return NotificationMiddleware.sendResponse(req, res, token as any);
} catch (error) {
return next(error);
}
const token: JSONObject = (req as any).callTokenData;
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByAlertId: true,
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
}
// check digits.
if (req.body["Digits"] === "1") {
// then ack incident
await this.service.updateOneById({
id: itemId,
data: {
acknowledgedAt: OneUptimeDate.getCurrentDate(),
isAcknowledged: true,
status: UserNotificationStatus.Acknowledged,
statusMessage: "Notification Acknowledged",
},
props: {
isRoot: true,
},
});
}
return NotificationMiddleware.sendResponse(req, res, token as any);
},
);
@@ -102,73 +111,81 @@ export default class UserNotificationLogTimelineAPI extends BaseAPI<
`${new this.entityType()
.getCrudApiPath()
?.toString()}/acknowledge-page/:itemId`,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
req = req as OneUptimeRequest;
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Item ID is required"),
);
}
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByIncident: {
title: true,
description: true,
},
triggeredByAlertId: true,
triggeredByAlert: {
title: true,
description: true,
},
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
}
const notificationType: string = timelineItem.triggeredByIncidentId
? "Incident"
: "Alert";
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
return Response.render(
req,
res,
new BadDataException("Item ID is required"),
);
}
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByIncident: {
title: true,
description: true,
},
triggeredByAlertId: true,
triggeredByAlert: {
title: true,
description: true,
},
"/usr/src/Common/Server/Views/AcknowledgeUserOnCallNotification.ejs",
{
title: `Acknowledge ${notificationType} - ${timelineItem.triggeredByIncident?.title || timelineItem.triggeredByAlert?.title}`,
message: `Do you want to acknowledge this ${notificationType}?`,
acknowledgeText: `Acknowledge ${notificationType}`,
acknowledgeUrl: new URL(
httpProtocol,
host,
new Route(AppApiRoute.toString())
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
.addRoute("/acknowledge/" + itemId.toString()),
).toString(),
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
} catch (error) {
return next(error);
}
const notificationType: string = timelineItem.triggeredByIncidentId
? "Incident"
: "Alert";
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
return Response.render(
req,
res,
"/usr/src/Common/Server/Views/AcknowledgeUserOnCallNotification.ejs",
{
title: `Acknowledge ${notificationType} - ${timelineItem.triggeredByIncident?.title || timelineItem.triggeredByAlert?.title}`,
message: `Do you want to acknowledge this ${notificationType}?`,
acknowledgeText: `Acknowledge ${notificationType}`,
acknowledgeUrl: new URL(
httpProtocol,
host,
new Route(AppApiRoute.toString())
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
.addRoute("/acknowledge/" + itemId.toString()),
).toString(),
},
);
},
);
@@ -177,124 +194,132 @@ export default class UserNotificationLogTimelineAPI extends BaseAPI<
`${new this.entityType()
.getCrudApiPath()
?.toString()}/acknowledge/:itemId`,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
req = req as OneUptimeRequest;
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Item ID is required"),
);
}
if (!req.params["itemId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Item ID is required"),
);
}
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const itemId: ObjectID = new ObjectID(req.params["itemId"]);
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
const timelineItem: UserOnCallLogTimeline | null =
await this.service.findOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByAlertId: true,
triggeredByAlert: {
title: true,
},
triggeredByIncident: {
title: true,
},
acknowledgedAt: true,
isAcknowledged: true,
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
}
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
if (timelineItem.isAcknowledged) {
// already acknowledged. Then show already acknowledged page with view details button.
const viewDetailsUrl: URL = new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/${timelineItem.triggeredByIncidentId ? "incidents" : "alerts"}/${timelineItem.triggeredByIncidentId ? timelineItem.triggeredByIncidentId!.toString() : timelineItem.triggeredByAlertId!.toString()}`,
),
);
return Response.render(
req,
res,
"/usr/src/Common/Server/Views/ViewMessage.ejs",
{
title: `Notification Already Acknowledged - ${timelineItem.triggeredByIncident?.title || timelineItem.triggeredByAlert?.title}`,
message: `This notification has already been acknowledged.`,
viewDetailsText: `View ${timelineItem.triggeredByIncidentId ? "Incident" : "Alert"}`,
viewDetailsUrl: viewDetailsUrl.toString(),
},
);
}
await this.service.updateOneById({
id: itemId,
select: {
_id: true,
projectId: true,
triggeredByIncidentId: true,
triggeredByAlertId: true,
triggeredByAlert: {
title: true,
},
triggeredByIncident: {
title: true,
},
acknowledgedAt: true,
data: {
acknowledgedAt: OneUptimeDate.getCurrentDate(),
isAcknowledged: true,
status: UserNotificationStatus.Acknowledged,
statusMessage: "Notification Acknowledged",
},
props: {
isRoot: true,
},
});
if (!timelineItem) {
// redirect to dashboard to incidents page.
if (timelineItem.triggeredByIncidentId) {
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/incidents/${timelineItem.triggeredByIncidentId!.toString()}`,
),
),
);
}
if (timelineItem.triggeredByAlertId) {
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/alerts/${timelineItem.triggeredByAlertId!.toString()}`,
),
),
);
}
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
} catch (error) {
return next(error);
}
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
if (timelineItem.isAcknowledged) {
// already acknowledged. Then show already acknowledged page with view details button.
const viewDetailsUrl: URL = new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/${timelineItem.triggeredByIncidentId ? "incidents" : "alerts"}/${timelineItem.triggeredByIncidentId ? timelineItem.triggeredByIncidentId!.toString() : timelineItem.triggeredByAlertId!.toString()}`,
),
);
return Response.render(
req,
res,
"/usr/src/Common/Server/Views/ViewMessage.ejs",
{
title: `Notification Already Acknowledged - ${timelineItem.triggeredByIncident?.title || timelineItem.triggeredByAlert?.title}`,
message: `This notification has already been acknowledged.`,
viewDetailsText: `View ${timelineItem.triggeredByIncidentId ? "Incident" : "Alert"}`,
viewDetailsUrl: viewDetailsUrl.toString(),
},
);
}
await this.service.updateOneById({
id: itemId,
data: {
acknowledgedAt: OneUptimeDate.getCurrentDate(),
isAcknowledged: true,
status: UserNotificationStatus.Acknowledged,
statusMessage: "Notification Acknowledged",
},
props: {
isRoot: true,
},
});
// redirect to dashboard to incidents page.
if (timelineItem.triggeredByIncidentId) {
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/incidents/${timelineItem.triggeredByIncidentId!.toString()}`,
),
),
);
}
if (timelineItem.triggeredByAlertId) {
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
DashboardRoute.addRoute(
`/${timelineItem.projectId?.toString()}/alerts/${timelineItem.triggeredByAlertId!.toString()}`,
),
),
);
}
return Response.sendErrorResponse(
req,
res,
new BadDataException("Invalid item Id"),
);
},
);
}

View File

@@ -108,104 +108,108 @@ export default class UserPushAPI extends BaseAPI<
this.router.post(
`/user-push/:deviceId/test-notification`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
}
// Get the device
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
select: {
userId: true,
deviceName: true,
deviceToken: true,
deviceType: true,
isVerified: true,
projectId: true,
},
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
if (!device.isVerified) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device is not verified"),
);
}
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
// Send test notification
const testMessage: PushNotificationMessage =
PushNotificationUtil.createGenericNotification({
title: "Test Notification from OneUptime",
body: "This is a test notification to verify your device is working correctly.",
clickAction: "/dashboard",
tag: "test-notification",
requireInteraction: false,
});
req = req as OneUptimeRequest;
await PushNotificationService.sendPushNotification(
{
devices: [
{
token: device.deviceToken!,
...(device.deviceName && {
name: device.deviceName,
}),
},
],
message: testMessage,
deviceType: device.deviceType!,
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
}
// Get the device
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
{
isSensitive: false,
projectId: device.projectId!,
userId: device.userId!,
select: {
userId: true,
deviceName: true,
deviceToken: true,
deviceType: true,
isVerified: true,
projectId: true,
},
);
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
if (!device.isVerified) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device is not verified"),
);
}
try {
// Send test notification
const testMessage: PushNotificationMessage =
PushNotificationUtil.createGenericNotification({
title: "Test Notification from OneUptime",
body: "This is a test notification to verify your device is working correctly.",
clickAction: "/dashboard",
tag: "test-notification",
requireInteraction: false,
});
await PushNotificationService.sendPushNotification(
{
devices: [
{
token: device.deviceToken!,
...(device.deviceName && {
name: device.deviceName,
}),
},
],
message: testMessage,
deviceType: device.deviceType!,
},
{
isSensitive: false,
projectId: device.projectId!,
userId: device.userId!,
},
);
} catch (error: any) {
throw new BadDataException(
`Failed to send test notification: ${error.message}`,
);
}
return Response.sendJsonObjectResponse(req, res, {
success: true,
message: "Test notification sent successfully",
});
} catch (error: any) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
`Failed to send test notification: ${error.message}`,
),
);
} catch (error) {
return next(error);
}
},
);
@@ -213,100 +217,116 @@ export default class UserPushAPI extends BaseAPI<
this.router.post(
`/user-push/:deviceId/verify`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
req = req as OneUptimeRequest;
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
}
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
select: {
userId: true,
},
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
await this.service.verifyDevice(device._id!.toString());
return Response.sendEmptySuccessResponse(req, res);
} catch (error) {
return next(error);
}
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
select: {
userId: true,
},
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
await this.service.verifyDevice(device._id!.toString());
return Response.sendEmptySuccessResponse(req, res);
},
);
this.router.post(
`/user-push/:deviceId/unverify`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
req = req as OneUptimeRequest;
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device ID is required"),
);
}
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
select: {
userId: true,
},
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
await this.service.unverifyDevice(device._id!.toString());
return Response.sendEmptySuccessResponse(req, res);
} catch (error) {
return next(error);
}
const device: UserPush | null = await this.service.findOneById({
id: new ObjectID(req.params["deviceId"]),
props: {
isRoot: true,
},
select: {
userId: true,
},
});
if (!device) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Device not found"),
);
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("Unauthorized access to device"),
);
}
await this.service.unverifyDevice(device._id!.toString());
return Response.sendEmptySuccessResponse(req, res);
},
);
}

View File

@@ -1,7 +1,11 @@
import ClusterKeyAuthorization from "../../../../Middleware/ClusterKeyAuthorization";
import DatabaseService from "../../../../Services/DatabaseService";
import WorkflowService from "../../../../Services/WorkflowService";
import { ExpressRequest, ExpressResponse } from "../../../../Utils/Express";
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from "../../../../Utils/Express";
import logger from "../../../../Utils/Logger";
import Response from "../../../../Utils/Response";
import Select from "../../../Database/Select";
@@ -60,16 +64,32 @@ export default class OnTriggerBaseModel<
props.router.get(
`/model/:projectId/${this.modelId}/${this.type}`,
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
await this.initTrigger(req, res, props);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
await this.initTrigger(req, res, props);
} catch (err) {
return next(err);
}
},
);
props.router.post(
`/model/:projectId/${this.modelId}/${this.type}`,
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
await this.initTrigger(req, res, props);
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
await this.initTrigger(req, res, props);
} catch (err) {
return next(err);
}
},
);
}

View File

@@ -59,7 +59,7 @@ export default class WebhookTrigger extends TriggerCode {
try {
await this.initTrigger(req, res, props);
} catch (e) {
next(e);
return next(e);
}
},
);
@@ -70,7 +70,7 @@ export default class WebhookTrigger extends TriggerCode {
try {
await this.initTrigger(req, res, props);
} catch (e) {
next(e);
return next(e);
}
},
);

View File

@@ -182,14 +182,19 @@ const init: InitFunction = async (
app.get(
[`/${appName}/env.js`, "/env.js"],
async (req: ExpressRequest, res: ExpressResponse) => {
// ping api server for database config.
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
// ping api server for database config.
const env: JSONObject = {
...process.env,
};
const env: JSONObject = {
...process.env,
};
const script: string = `
const script: string = `
if(!window.process){
window.process = {}
}
@@ -201,7 +206,10 @@ const init: InitFunction = async (
window.process.env = JSON.parse(envVars);
`;
Response.sendJavaScriptResponse(req, res, script);
Response.sendJavaScriptResponse(req, res, script);
} catch (err) {
return next(err);
}
},
);
@@ -216,32 +224,40 @@ const init: InitFunction = async (
app.get(
["/*", `/${appName}/*`],
async (_req: ExpressRequest, res: ExpressResponse) => {
logger.debug("Rendering index page");
async (
_req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
logger.debug("Rendering index page");
let variables: JSONObject = {};
let variables: JSONObject = {};
if (data.getVariablesToRenderIndexPage) {
logger.debug("Getting variables to render index page");
try {
const variablesToRenderIndexPage: JSONObject =
await data.getVariablesToRenderIndexPage(_req, res);
variables = {
...variables,
...variablesToRenderIndexPage,
};
} catch (error) {
logger.error(error);
if (data.getVariablesToRenderIndexPage) {
logger.debug("Getting variables to render index page");
try {
const variablesToRenderIndexPage: JSONObject =
await data.getVariablesToRenderIndexPage(_req, res);
variables = {
...variables,
...variablesToRenderIndexPage,
};
} catch (error) {
logger.error(error);
}
}
logger.debug("Rendering index page with variables: ");
logger.debug(variables);
return res.render("/usr/src/app/views/index.ejs", {
enableGoogleTagManager: IsBillingEnabled || false,
...variables,
});
} catch (err) {
return next(err);
}
logger.debug("Rendering index page with variables: ");
logger.debug(variables);
return res.render("/usr/src/app/views/index.ejs", {
enableGoogleTagManager: IsBillingEnabled || false,
...variables,
});
},
);
}

View File

@@ -7,6 +7,7 @@ import Express, {
ExpressRequest,
ExpressResponse,
ExpressStatic,
NextFunction,
} from "Common/Server/Utils/Express";
import Response from "Common/Server/Utils/Response";
import LocalFile from "Common/Server/Utils/LocalFile";
@@ -25,7 +26,11 @@ const DocsFeatureSet: FeatureSet = {
// Handle requests to specific documentation pages
app.get(
"/docs/as-markdown/:categorypath/:pagepath",
async (req: ExpressRequest, res: ExpressResponse) => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const fullPath: string =
`${req.params["categorypath"]}/${req.params["pagepath"]}`.toLowerCase();
@@ -38,15 +43,18 @@ const DocsFeatureSet: FeatureSet = {
return Response.sendMarkdownResponse(req, res, contentInMarkdown);
} catch (err) {
logger.error(err);
res.status(500);
return res.send("Internal Server Error");
return next(err);
}
},
);
app.get(
"/docs/:categorypath/:pagepath",
async (_req: ExpressRequest, res: ExpressResponse) => {
async (
_req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const fullPath: string =
`${_req.params["categorypath"]}/${_req.params["pagepath"]}`.toLowerCase();
@@ -114,12 +122,7 @@ const DocsFeatureSet: FeatureSet = {
});
} catch (err) {
logger.error(err);
res.status(500);
return res.render(`${ViewsPath}/ServerError`, {
nav: DocsNav,
enableGoogleTagManager: IsBillingEnabled,
link: "",
});
return next(err);
}
},
);

View File

@@ -1,12 +1,12 @@
import BlogPostUtil, { BlogPost, BlogPostHeader } from "../Utils/BlogPost";
import { BlogRootPath, ViewsPath } from "../Utils/Config";
import NotFoundUtil from "../Utils/NotFound";
import ServerErrorUtil from "../Utils/ServerError";
import Text from "Common/Types/Text";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
NextFunction,
} from "Common/Server/Utils/Express";
import logger from "Common/Server/Utils/Logger";
import Response from "Common/Server/Utils/Response";
@@ -19,7 +19,11 @@ const app: ExpressApplication = Express.getExpressApp();
app.get(
"/blog/post/:file",
async (req: ExpressRequest, res: ExpressResponse) => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const fileName: string = req.params["file"] as string;
@@ -30,14 +34,18 @@ app.get(
);
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
return next(e);
}
},
);
app.get(
"/blog/post/:file/view",
async (req: ExpressRequest, res: ExpressResponse) => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const fileName: string = req.params["file"] as string;
@@ -59,14 +67,18 @@ app.get(
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
return next(e);
}
},
);
app.get(
"/blog/post/:postName/:fileName",
async (req: ExpressRequest, res: ExpressResponse) => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
/*
* return static files for blog post images
* the static files are stored in the /usr/src/blog/post/:file/:imageName
@@ -83,7 +95,7 @@ app.get(
);
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
return next(e);
}
},
);
@@ -92,7 +104,11 @@ app.get(
app.get(
"/blog/tag/:tagName",
async (req: ExpressRequest, res: ExpressResponse) => {
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const tagName: string = req.params["tagName"] as string;
const tagSlug: string = tagName; // original slug
@@ -149,14 +165,20 @@ app.get(
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
return next(e);
}
},
);
// main blog page
app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => {
try {
app.get(
"/blog",
async (
_req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
) => {
try {
const req: ExpressRequest = _req; // alias for clarity
const pageParam: string | undefined = req.query["page"] as
| string
@@ -205,8 +227,9 @@ app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => {
allTags: allTags,
enableGoogleTagManager: IsBillingEnabled,
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
});
} catch (e) {
logger.error(e);
return next(e);
}
},
);

View File

@@ -36,16 +36,24 @@ const router: ExpressRouter = Express.getRouter();
router.post(
"/alive",
ProbeAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
// Update last alive in probe and return success response.
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
// Update last alive in probe and return success response.
const data: JSONObject = req.body;
const data: JSONObject = req.body;
const probeId: ObjectID = new ObjectID(data["probeId"] as string);
const probeId: ObjectID = new ObjectID(data["probeId"] as string);
await ProbeService.updateLastAlive(probeId);
await ProbeService.updateLastAlive(probeId);
return Response.sendEmptySuccessResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
},
);