checks route, checks table
This commit is contained in:
1 parent
651fbd7cbb
commit
e7ba3cb5f9
15 files changed
+420
-19
No files matched your search
@@ -2,6 +2,7 @@ import MonitorController from "../controllers/monitorController.js";
|
||||
import AuthController from "../controllers/authController.js";
|
||||
import SettingsController from "../controllers/settingsController.js";
|
||||
import CheckController from "../controllers/checkController.js";
|
||||
import GeoCheckController from "../controllers/geoCheckController.js";
|
||||
import InviteController from "../controllers/inviteController.js";
|
||||
import MaintenanceWindowController from "../controllers/maintenanceWindowController.js";
|
||||
import QueueController from "../controllers/queueController.js";
|
||||
@@ -17,6 +18,7 @@ export interface InitializedControllers {
|
||||
monitorController: MonitorController;
|
||||
settingsController: SettingsController;
|
||||
checkController: CheckController;
|
||||
geoCheckController: GeoCheckController;
|
||||
inviteController: InviteController;
|
||||
maintenanceWindowController: MaintenanceWindowController;
|
||||
queueController: QueueController;
|
||||
@@ -32,6 +34,7 @@ export const initializeControllers = (services: InitializedServices): Initialize
|
||||
monitorController: new MonitorController(services.monitorService),
|
||||
settingsController: new SettingsController(services.settingsService, services.emailService),
|
||||
checkController: new CheckController(services.checkService),
|
||||
geoCheckController: new GeoCheckController(services.geoChecksService),
|
||||
inviteController: new InviteController(services.inviteService),
|
||||
maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService),
|
||||
queueController: new QueueController(services.jobQueue),
|
||||
|
||||
@@ -6,6 +6,7 @@ import AuthRoutes from "../routes/authRoute.js";
|
||||
import InviteRoutes from "../routes/inviteRoute.js";
|
||||
import MonitorRoutes from "../routes/monitorRoute.js";
|
||||
import CheckRoutes from "../routes/checkRoute.js";
|
||||
import GeoCheckRoutes from "../routes/geoCheckRoutes.js";
|
||||
import SettingsRoutes from "../routes/settingsRoute.js";
|
||||
import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js";
|
||||
import StatusPageRoutes from "../routes/statusPageRoute.js";
|
||||
@@ -22,6 +23,7 @@ export const setupRoutes = (app: any, controllers: Record<string, any>, services
|
||||
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
const checkRoutes = new CheckRoutes(controllers.checkController);
|
||||
const geoCheckRoutes = new GeoCheckRoutes(controllers.geoCheckController);
|
||||
const inviteRoutes = new InviteRoutes(controllers.inviteController, verifyJWT);
|
||||
const maintenanceWindowRoutes = new MaintenanceWindowRoutes(controllers.maintenanceWindowController);
|
||||
const queueRoutes = new QueueRoutes(controllers.queueController);
|
||||
@@ -36,6 +38,7 @@ export const setupRoutes = (app: any, controllers: Record<string, any>, services
|
||||
app.use("/api/v1/monitors", verifyJWT, monitorRoutes.getRouter());
|
||||
app.use("/api/v1/settings", verifyJWT, settingsRoutes.getRouter());
|
||||
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
|
||||
app.use("/api/v1/geo-checks", verifyJWT, geoCheckRoutes.getRouter());
|
||||
app.use("/api/v1/invite", inviteRoutes.getRouter());
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
|
||||
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
|
||||
|
||||
@@ -154,7 +154,12 @@ export const initializeServices = async ({
|
||||
|
||||
const globalPingService = new GlobalPingService(logger);
|
||||
|
||||
const geoChecksService = new GeoChecksService(logger, geoChecksRepository, globalPingService);
|
||||
const geoChecksService = new GeoChecksService({
|
||||
logger,
|
||||
geoChecksRepository,
|
||||
globalPingService,
|
||||
monitorsRepository,
|
||||
});
|
||||
|
||||
const bufferService = new BufferService(logger, checkService, geoChecksService, settingsService);
|
||||
|
||||
@@ -252,12 +257,12 @@ export const initializeServices = async ({
|
||||
jobQueue: superSimpleQueue,
|
||||
userService,
|
||||
checkService,
|
||||
geoChecksService,
|
||||
diagnosticService,
|
||||
inviteService,
|
||||
maintenanceWindowService,
|
||||
monitorService,
|
||||
incidentService,
|
||||
geoChecksService,
|
||||
logger,
|
||||
notificationsService,
|
||||
statusPageService,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/joi.js";
|
||||
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
||||
|
||||
const SERVICE_NAME = "geoCheckController";
|
||||
|
||||
class GeoCheckController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private geoChecksService: IGeoChecksService;
|
||||
constructor(geoChecksService: IGeoChecksService) {
|
||||
this.geoChecksService = geoChecksService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return GeoCheckController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await getChecksParamValidation.validateAsync(req.params);
|
||||
await getChecksQueryValidation.validateAsync(req.query);
|
||||
|
||||
const result = await this.geoChecksService.getGeoChecksByMonitor({
|
||||
monitorId: req?.params?.monitorId as string,
|
||||
query: req?.query,
|
||||
teamId: req?.user?.teamId as string,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Geo checks retrieved successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default GeoCheckController;
|
||||
@@ -1,8 +1,21 @@
|
||||
import type { GeoCheck, GroupedGeoCheck } from "@/types/geoCheck.js";
|
||||
import type { GeoContinent } from "@/types/geoCheck.js";
|
||||
|
||||
export interface GeoChecksQueryResult {
|
||||
geoChecksCount: number;
|
||||
geoChecks: GeoCheck[];
|
||||
}
|
||||
|
||||
export interface IGeoChecksRepository {
|
||||
createGeoChecks(geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]>;
|
||||
findByMonitorId(
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
continent?: GeoContinent
|
||||
): Promise<GeoChecksQueryResult>;
|
||||
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
|
||||
findGroupedByMonitorIdAndDateRange(
|
||||
monitorId: string,
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { IGeoChecksRepository } from "./IGeoChecksRepository.js";
|
||||
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck } from "@/types/geoCheck.js";
|
||||
import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent } from "@/types/geoCheck.js";
|
||||
import type { GeoChecksQueryResult } from "./IGeoChecksRepository.js";
|
||||
import { GeoCheckModel, type GeoCheckDocument } from "@/db/models/index.js";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const SERVICE_NAME = "GeoChecksRepository";
|
||||
|
||||
const dateRangeLookup: Record<string, Date> = {
|
||||
recent: new Date(Date.now() - 60 * 60 * 1000),
|
||||
day: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
||||
week: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
|
||||
month: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||
};
|
||||
|
||||
class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
@@ -97,6 +105,93 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
}
|
||||
};
|
||||
|
||||
findByMonitorId = async (
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
continent?: GeoContinent
|
||||
): Promise<GeoChecksQueryResult> => {
|
||||
try {
|
||||
const matchStage: Record<string, any> = {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
...(dateRangeLookup[dateRange] && {
|
||||
createdAt: {
|
||||
$gte: dateRangeLookup[dateRange],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
if (continent) {
|
||||
const pipeline: any[] = [
|
||||
{ $match: matchStage },
|
||||
{ $unwind: "$results" },
|
||||
{
|
||||
$match: {
|
||||
"results.location.continent": continent,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: "$_id",
|
||||
doc: { $first: "$$ROOT" },
|
||||
results: { $push: "$results" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$replaceRoot: {
|
||||
newRoot: {
|
||||
$mergeObjects: ["$doc", { results: "$results" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { createdAt: convertedSortOrder } },
|
||||
{ $skip: skip },
|
||||
{ $limit: rowsPerPage },
|
||||
];
|
||||
|
||||
const [countPipeline, dataResults] = await Promise.all([
|
||||
GeoCheckModel.aggregate([
|
||||
{ $match: matchStage },
|
||||
{ $unwind: "$results" },
|
||||
{ $match: { "results.location.continent": continent } },
|
||||
{ $group: { _id: "$_id" } },
|
||||
{ $count: "count" },
|
||||
]),
|
||||
GeoCheckModel.aggregate(pipeline),
|
||||
]);
|
||||
|
||||
const geoChecksCount = countPipeline[0]?.count || 0;
|
||||
const geoChecks = dataResults.map(this.toEntity);
|
||||
|
||||
return { geoChecksCount, geoChecks };
|
||||
} else {
|
||||
const [geoChecksCount, docs] = await Promise.all([
|
||||
GeoCheckModel.countDocuments(matchStage),
|
||||
GeoCheckModel.find(matchStage).sort({ createdAt: convertedSortOrder }).skip(skip).limit(rowsPerPage).lean() as Promise<GeoCheckDocument[]>,
|
||||
]);
|
||||
|
||||
return { geoChecksCount, geoChecks: docs.map(this.toEntity) };
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: `Error finding geo checks by monitor ID: ${error.message}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "findByMonitorId",
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
findByMonitorIdAndDateRange = async (monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]> => {
|
||||
try {
|
||||
const docs = await GeoCheckModel.find({
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Router } from "express";
|
||||
|
||||
class GeoCheckRoutes {
|
||||
private router: Router;
|
||||
private geoCheckController: any;
|
||||
|
||||
constructor(geoCheckController: any) {
|
||||
this.router = Router();
|
||||
this.geoCheckController = geoCheckController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes() {
|
||||
this.router.get("/:monitorId", this.geoCheckController.getGeoChecksByMonitor);
|
||||
}
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default GeoCheckRoutes;
|
||||
@@ -2,9 +2,10 @@ import type { Monitor, GeoCheck } from "@/types/index.js";
|
||||
import type { GeoCheckResult } from "@/types/geoCheck.js";
|
||||
import { Types } from "mongoose";
|
||||
import type { IGeoChecksRepository } from "@/repositories/index.js";
|
||||
import type { IMonitorsRepository } from "@/repositories/index.js";
|
||||
import type { IGlobalPingService } from "@/service/infrastructure/globalPingService.js";
|
||||
import type { ILogger } from "@/utils/logger.js";
|
||||
import type { ISettingsService } from "@/service/system/settingsService.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
|
||||
const SERVICE_NAME = "GeoChecksService";
|
||||
|
||||
@@ -12,6 +13,7 @@ export interface IGeoChecksService {
|
||||
readonly serviceName: string;
|
||||
buildGeoCheck(monitor: Monitor): Promise<GeoCheck | null>;
|
||||
createGeoChecks(geoChecks: GeoCheck[]): Promise<GeoCheck[]>;
|
||||
getGeoChecksByMonitor(args: { monitorId: string; query: any; teamId: string }): Promise<any>;
|
||||
}
|
||||
|
||||
class GeoChecksService implements IGeoChecksService {
|
||||
@@ -20,11 +22,23 @@ class GeoChecksService implements IGeoChecksService {
|
||||
private logger: ILogger;
|
||||
private geoChecksRepository: IGeoChecksRepository;
|
||||
private globalPingService: IGlobalPingService;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
|
||||
constructor(logger: ILogger, geoChecksRepository: IGeoChecksRepository, globalPingService: IGlobalPingService) {
|
||||
constructor({
|
||||
logger,
|
||||
geoChecksRepository,
|
||||
globalPingService,
|
||||
monitorsRepository,
|
||||
}: {
|
||||
logger: ILogger;
|
||||
geoChecksRepository: IGeoChecksRepository;
|
||||
globalPingService: IGlobalPingService;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
}) {
|
||||
this.logger = logger;
|
||||
this.geoChecksRepository = geoChecksRepository;
|
||||
this.globalPingService = globalPingService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -133,6 +147,43 @@ class GeoChecksService implements IGeoChecksService {
|
||||
createGeoChecks = async (geoChecks: GeoCheck[]) => {
|
||||
return this.geoChecksRepository.createGeoChecks(geoChecks);
|
||||
};
|
||||
|
||||
getGeoChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: any; teamId: string }) => {
|
||||
if (!monitorId) {
|
||||
throw new AppError({
|
||||
message: "No monitor ID in request",
|
||||
service: SERVICE_NAME,
|
||||
method: "getGeoChecksByMonitor",
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
if (!teamId) {
|
||||
throw new AppError({
|
||||
message: "No team ID in request",
|
||||
service: SERVICE_NAME,
|
||||
method: "getGeoChecksByMonitor",
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({
|
||||
message: `Monitor with ID ${monitorId} not found.`,
|
||||
service: SERVICE_NAME,
|
||||
method: "getGeoChecksByMonitor",
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
let { sortOrder, dateRange, page, rowsPerPage, continent } = query;
|
||||
const parsedPage = page ? parseInt(page) : page;
|
||||
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
|
||||
|
||||
const result = await this.geoChecksRepository.findByMonitorId(monitorId, sortOrder, dateRange, parsedPage, parsedRowsPerPage, continent);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export default GeoChecksService;
|
||||
@@ -479,6 +479,14 @@ export class MonitorService implements IMonitorService {
|
||||
});
|
||||
});
|
||||
|
||||
await this.geoChecksRepository.deleteByMonitorId(monitor.id).catch((err: any) => {
|
||||
this.logger.warn({
|
||||
message: `Error deleting geo checks for monitor ${monitor.id} with name ${monitor.name}`,
|
||||
service: SERVICE_NAME,
|
||||
stack: err.stack,
|
||||
});
|
||||
});
|
||||
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
return monitor;
|
||||
};
|
||||
@@ -490,6 +498,7 @@ export class MonitorService implements IMonitorService {
|
||||
try {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
await this.checksRepository.deleteByMonitorId(monitor.id);
|
||||
await this.geoChecksRepository.deleteByMonitorId(monitor.id);
|
||||
await this.statusPagesRepository.removeMonitorFromStatusPages(monitor.id);
|
||||
await this.monitorStatsRepository.deleteByMonitorId(monitor.id);
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -324,6 +324,7 @@ const getChecksQueryValidation = joi.object({
|
||||
page: joi.number(),
|
||||
rowsPerPage: joi.number(),
|
||||
status: joi.boolean(),
|
||||
continent: joi.string().valid(...GeoContinents),
|
||||
});
|
||||
|
||||
const getTeamChecksQueryValidation = joi.object({
|
||||
|
||||
Reference in new issue
Block a user