Merge pull request #3799 from bluewave-labs/chore/domain-refactoring
chore: domain refactoring
This commit is contained in:
12 files changed
+112
-137
No files matched your search
@@ -15,6 +15,7 @@
|
||||
"worker:dev": "QUEUE_MODE=worker nodemon --exec tsx src/index.js",
|
||||
"clean": "rm -rf dist",
|
||||
"build": "npm run clean && npm run build:openapi && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && cp -r src/templates dist/templates",
|
||||
"typeCheck": "tsc --noEmit",
|
||||
"build:openapi": "tsc --noEmit && tsx scripts/buildOpenApi.ts",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint --fix .",
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from "@/domain/checks/check.type.js";
|
||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
||||
import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
export interface IChecksRepository {
|
||||
// create
|
||||
@@ -19,7 +20,7 @@ export interface IChecksRepository {
|
||||
findByMonitorId(
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
filter: string | undefined,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
@@ -27,7 +28,7 @@ export interface IChecksRepository {
|
||||
): Promise<ChecksQueryResult>;
|
||||
findByTeamId(
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
filter: string | undefined,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
@@ -36,12 +37,10 @@ export interface IChecksRepository {
|
||||
findLatestByMonitorIds(monitorIds: string[], options?: { limitPerMonitor?: number }): Promise<LatestChecksMap>;
|
||||
findByDateRangeAndMonitorId(
|
||||
monitorId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
dateString: string,
|
||||
dateRange: DateRange,
|
||||
options?: { type?: MonitorType }
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
findSummaryByTeamId(teamId: string, dateRange: string): Promise<ChecksSummary>;
|
||||
findSummaryByTeamId(teamId: string, dateRange: DateRange): Promise<ChecksSummary>;
|
||||
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise<Check[]>;
|
||||
// update
|
||||
//delete
|
||||
|
||||
@@ -15,11 +15,12 @@ import type {
|
||||
import type { MonitorType } from "@/domain/monitors/monitor.types.js";
|
||||
import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js";
|
||||
import mongoose from "mongoose";
|
||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
||||
import { getDateFormat, getDateForRange } from "@/utils/dataUtils.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||
|
||||
import { getHardwareUpChecks, getHardwareStats, getHardwareTotalChecks } from "@/domain/checks/check.hardware.aggregations.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
const SERVICE_NAME = "StatusService";
|
||||
|
||||
@@ -207,7 +208,7 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
findByMonitorId = async (
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
filter: string | undefined,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
@@ -217,11 +218,7 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
const matchStage: Record<string, unknown> = {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
...(typeof status !== "undefined" && { status }),
|
||||
...(getDateForRange(dateRange) && {
|
||||
createdAt: {
|
||||
$gte: getDateForRange(dateRange),
|
||||
},
|
||||
}),
|
||||
createdAt: { $gte: getDateForRange(dateRange) },
|
||||
};
|
||||
|
||||
if (filter !== undefined) {
|
||||
@@ -265,14 +262,10 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return { checksCount, checks: this.mapDocuments(checks) };
|
||||
};
|
||||
|
||||
findByTeamId = async (sortOrder: string, dateRange: string, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
||||
findByTeamId = async (sortOrder: string, dateRange: DateRange, filter: string, page: number, rowsPerPage: number, teamId: string) => {
|
||||
const matchStage: Record<string, unknown> = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
...(getDateForRange(dateRange) && {
|
||||
createdAt: {
|
||||
$gte: getDateForRange(dateRange),
|
||||
},
|
||||
}),
|
||||
createdAt: { $gte: getDateForRange(dateRange) },
|
||||
};
|
||||
// Add filter to match stage
|
||||
if (filter !== undefined) {
|
||||
@@ -341,25 +334,25 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return mapped;
|
||||
};
|
||||
|
||||
findByDateRangeAndMonitorId = async (monitorId: string, startDate: Date, endDate: Date, dateString: string, options?: { type?: MonitorType }) => {
|
||||
findByDateRangeAndMonitorId = async (monitorId: string, dateRange: DateRange, options?: { type?: MonitorType }) => {
|
||||
const monitorObjectId = new mongoose.Types.ObjectId(monitorId);
|
||||
const start = getDateForRange(dateRange);
|
||||
const dateString = getDateFormat(dateRange);
|
||||
|
||||
const end = new Date();
|
||||
if (options?.type === "hardware") {
|
||||
return this.findHardwareDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
||||
return this.findHardwareDateRangeChecks(monitorObjectId, start, end, dateString);
|
||||
}
|
||||
if (options?.type === "pagespeed") {
|
||||
return this.findPageSpeedDateRangeChecks(monitorObjectId, startDate, endDate, dateString);
|
||||
return this.findPageSpeedDateRangeChecks(monitorObjectId, start, end, dateString);
|
||||
}
|
||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, startDate, endDate, dateString);
|
||||
return this.findUptimeDateRangeChecks(options?.type ?? "http", monitorObjectId, start, end, dateString);
|
||||
};
|
||||
|
||||
findSummaryByTeamId = async (teamId: string, dateRange: string) => {
|
||||
findSummaryByTeamId = async (teamId: string, dateRange: DateRange) => {
|
||||
const baseMatch = {
|
||||
"metadata.teamId": new mongoose.Types.ObjectId(teamId),
|
||||
...(getDateForRange(dateRange) && {
|
||||
createdAt: {
|
||||
$gte: getDateForRange(dateRange),
|
||||
},
|
||||
}),
|
||||
createdAt: { $gte: getDateForRange(dateRange) },
|
||||
};
|
||||
|
||||
const [totalResult, downResult] = await Promise.all([
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.j
|
||||
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
const SERVICE_NAME = "checkService";
|
||||
|
||||
@@ -18,7 +19,7 @@ export interface ICheckService {
|
||||
monitorId: string;
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
filter?: string;
|
||||
@@ -27,12 +28,12 @@ export interface ICheckService {
|
||||
getChecksByTeam(params: {
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
filter?: string;
|
||||
}): Promise<ChecksQueryResult>;
|
||||
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
|
||||
getChecksSummaryByTeamId(params: { teamId: string; dateRange: DateRange }): Promise<ChecksSummary>;
|
||||
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
|
||||
deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
|
||||
deleteOlderThan(date: Date): Promise<number>;
|
||||
@@ -164,7 +165,7 @@ export class CheckService implements ICheckService {
|
||||
monitorId: string;
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
status?: boolean;
|
||||
@@ -198,7 +199,7 @@ export class CheckService implements ICheckService {
|
||||
}: {
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
filter?: string;
|
||||
@@ -210,7 +211,7 @@ export class CheckService implements ICheckService {
|
||||
return checkData;
|
||||
};
|
||||
|
||||
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: string }) => {
|
||||
getChecksSummaryByTeamId = async ({ teamId, dateRange }: { teamId: string; dateRange: DateRange }) => {
|
||||
const summary = await this.checksRepository.findSummaryByTeamId(teamId, dateRange);
|
||||
return summary;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||
import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
export interface GeoChecksQueryResult {
|
||||
geoChecksCount: number;
|
||||
@@ -16,19 +17,13 @@ export interface IGeoChecksRepository {
|
||||
findByMonitorId(
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
continents?: GeoContinent[]
|
||||
): Promise<FlatGeoChecksQueryResult>;
|
||||
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
|
||||
findGroupedByMonitorIdAndDateRange(
|
||||
monitorId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
dateFormat: string,
|
||||
continents?: GeoContinent[]
|
||||
): Promise<GroupedGeoCheck[]>;
|
||||
findGroupedByMonitorIdAndDateRange(monitorId: string, DateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]>;
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
deleteByTeamId(teamId: string): Promise<number>;
|
||||
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
|
||||
|
||||
@@ -3,9 +3,10 @@ import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoCo
|
||||
import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.js";
|
||||
import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js";
|
||||
import mongoose, { PipelineStage } from "mongoose";
|
||||
import { getDateForRange } from "@/utils/dataUtils.js";
|
||||
import { getDateForRange, getDateFormat } from "@/utils/dataUtils.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
const SERVICE_NAME = "GeoChecksRepository";
|
||||
|
||||
@@ -90,7 +91,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
findByMonitorId = async (
|
||||
monitorId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
continents?: GeoContinent[]
|
||||
@@ -98,11 +99,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
try {
|
||||
const matchStage: Record<string, unknown> = {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
...(getDateForRange(dateRange) && {
|
||||
createdAt: {
|
||||
$gte: getDateForRange(dateRange),
|
||||
},
|
||||
}),
|
||||
createdAt: { $gte: getDateForRange(dateRange) },
|
||||
};
|
||||
|
||||
const convertedSortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
@@ -214,13 +211,11 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
}
|
||||
};
|
||||
|
||||
findGroupedByMonitorIdAndDateRange = async (
|
||||
monitorId: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
dateFormat: string,
|
||||
continents?: GeoContinent[]
|
||||
): Promise<GroupedGeoCheck[]> => {
|
||||
findGroupedByMonitorIdAndDateRange = async (monitorId: string, dateRange: DateRange, continents?: GeoContinent[]): Promise<GroupedGeoCheck[]> => {
|
||||
const start = getDateForRange(dateRange);
|
||||
const dateString = getDateFormat(dateRange);
|
||||
|
||||
const end = new Date();
|
||||
try {
|
||||
const pipeline: PipelineStage[] = [
|
||||
// Match geo checks for this monitor in date range
|
||||
@@ -228,8 +223,8 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
$match: {
|
||||
"metadata.monitorId": new mongoose.Types.ObjectId(monitorId),
|
||||
createdAt: {
|
||||
$gte: startDate,
|
||||
$lte: endDate,
|
||||
$gte: start,
|
||||
$lte: end,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -253,7 +248,7 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
_id: {
|
||||
bucketDate: {
|
||||
$dateToString: {
|
||||
format: dateFormat,
|
||||
format: dateString,
|
||||
date: "$createdAt",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.i
|
||||
import type { IGlobalPingService } from "@/service/globalPingService.js";
|
||||
import type { ILogger } from "@/utils/logger.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
const SERVICE_NAME = "GeoChecksService";
|
||||
|
||||
@@ -17,7 +18,7 @@ export interface IGeoChecksService {
|
||||
monitorId: string;
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page?: number;
|
||||
rowsPerPage?: number;
|
||||
continent?: GeoContinent | GeoContinent[];
|
||||
@@ -155,7 +156,7 @@ export class GeoChecksService implements IGeoChecksService {
|
||||
monitorId: string;
|
||||
teamId: string;
|
||||
sortOrder: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
page?: number;
|
||||
rowsPerPage?: number;
|
||||
continent: GeoContinent | GeoContinent[];
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { User } from "@/domain/users/user.type.js";
|
||||
import type { MonitorActionDecision } from "@/worker/worker.helper.js";
|
||||
import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js";
|
||||
import type { ILogger } from "@/utils/logger.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
export interface IIncidentService {
|
||||
handleIncident(
|
||||
@@ -23,7 +24,7 @@ export interface IIncidentService {
|
||||
getIncidentsByTeam(
|
||||
teamId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
status: boolean | undefined,
|
||||
@@ -174,7 +175,7 @@ export class IncidentService implements IIncidentService {
|
||||
getIncidentsByTeam = async (
|
||||
teamId: string,
|
||||
sortOrder: string,
|
||||
dateRange: string,
|
||||
dateRange: DateRange,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
status: boolean | undefined,
|
||||
|
||||
@@ -91,27 +91,26 @@ class MongoMonitorsRepository implements IMonitorsRepository {
|
||||
{ $project: { stats: 0 } },
|
||||
];
|
||||
|
||||
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
||||
private pageOptions = (config: TeamQueryConfig) => {
|
||||
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
|
||||
return { sort: { [field]: order === "asc" ? 1 : -1 } as const, skip: Math.max(page, 0) * rowsPerPage, limit: rowsPerPage };
|
||||
};
|
||||
|
||||
findByTeamId = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
||||
const query = this.queryBuilder(config, teamId);
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
|
||||
const skip = Math.max(page, 0) * rowsPerPage;
|
||||
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(rowsPerPage);
|
||||
const { sort, skip, limit } = this.pageOptions(config);
|
||||
const documents = await MonitorModel.find(query).sort(sort).skip(skip).limit(limit);
|
||||
return this.mapDocuments(documents);
|
||||
};
|
||||
|
||||
findByTeamIdWithStats = async (teamId: string, config: TeamQueryConfig): Promise<Monitor[] | null> => {
|
||||
const { page = 0, rowsPerPage = 0, field = "createdAt", order = "desc" } = config ?? {};
|
||||
|
||||
const { sort, skip, limit } = this.pageOptions(config);
|
||||
const query = this.queryBuilder(config, teamId);
|
||||
|
||||
const sort = { [field]: order === "asc" ? 1 : -1 } as const;
|
||||
const skip = Math.max(page, 0) * rowsPerPage;
|
||||
|
||||
const pipeline: PipelineStage[] = [
|
||||
{ $match: query },
|
||||
{ $sort: sort },
|
||||
...(rowsPerPage ? [{ $skip: skip }, { $limit: rowsPerPage }] : []),
|
||||
...(limit ? [{ $skip: skip }, { $limit: limit }] : []),
|
||||
...this.uptimeStatsLookupStages,
|
||||
];
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import { AppError } from "@/utils/AppError.js";
|
||||
import type { ImportedMonitor } from "@/api/validation/monitorValidation.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { IJobScheduler } from "@/worker/worker.interface.js";
|
||||
import { DateRange } from "@/types/query.js";
|
||||
|
||||
const SERVICE_NAME = "MonitorService";
|
||||
type DateRangeKey = "recent" | "day" | "week" | "month" | "all";
|
||||
|
||||
const isUptimeChecksResult = (result: UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult): result is UptimeChecksResult =>
|
||||
supportsUptimeDetails(result.monitorType);
|
||||
@@ -37,13 +37,13 @@ export interface IMonitorService {
|
||||
addDemoMonitors(args: { userId: string; teamId: string }): Promise<Monitor[]>;
|
||||
|
||||
// read
|
||||
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<UptimeDetailsResult>;
|
||||
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<HardwareDetailsResult>;
|
||||
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: string }): Promise<PageSpeedDetailsResult>;
|
||||
getUptimeDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<UptimeDetailsResult>;
|
||||
getHardwareDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<HardwareDetailsResult>;
|
||||
getPageSpeedDetailsById(args: { teamId: string; monitorId: string; dateRange: DateRange }): Promise<PageSpeedDetailsResult>;
|
||||
getGeoChecksByMonitorId(args: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
continents?: GeoContinent[];
|
||||
}): Promise<GroupedGeoCheckResult>;
|
||||
getMonitorById(args: { teamId: string; monitorId: string }): Promise<Monitor>;
|
||||
@@ -133,31 +133,6 @@ export class MonitorService implements IMonitorService {
|
||||
this.incidentsRepository = incidentsRepository;
|
||||
}
|
||||
|
||||
private getDateRange = (dateRange: DateRangeKey) => {
|
||||
const startDates = {
|
||||
recent: new Date(new Date().setHours(new Date().getHours() - 2)),
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
week: new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
all: new Date(0),
|
||||
};
|
||||
return {
|
||||
start: startDates[dateRange],
|
||||
end: new Date(),
|
||||
};
|
||||
};
|
||||
|
||||
private getDateFormat = (dateRange: DateRangeKey): string => {
|
||||
const formatLookup = {
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: "%Y-%m-%dT%H:00:00Z",
|
||||
week: "%Y-%m-%dT00:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
all: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
return formatLookup[dateRange];
|
||||
};
|
||||
|
||||
createMonitor = async (teamId: string, userId: string, body: Monitor): Promise<void> => {
|
||||
const monitor = await this.monitorsRepository.create(body, teamId, userId);
|
||||
if (!monitor) {
|
||||
@@ -200,15 +175,13 @@ export class MonitorService implements IMonitorService {
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
}): Promise<UptimeDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 });
|
||||
}
|
||||
const rangeKey = dateRange as DateRangeKey;
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||
type: monitor.type,
|
||||
});
|
||||
const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id);
|
||||
@@ -237,7 +210,7 @@ export class MonitorService implements IMonitorService {
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
}): Promise<HardwareDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
@@ -247,9 +220,7 @@ export class MonitorService implements IMonitorService {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for hardware details`, status: 400 });
|
||||
}
|
||||
|
||||
const rangeKey = dateRange as DateRangeKey;
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||
type: monitor.type,
|
||||
});
|
||||
|
||||
@@ -279,7 +250,7 @@ export class MonitorService implements IMonitorService {
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
}): Promise<PageSpeedDetailsResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
@@ -289,9 +260,7 @@ export class MonitorService implements IMonitorService {
|
||||
throw new AppError({ message: `${monitor.type} monitors are not supported for pagespeed details`, status: 400 });
|
||||
}
|
||||
|
||||
const rangeKey = dateRange as DateRangeKey;
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, start, end, this.getDateFormat(rangeKey), {
|
||||
const checksData = await this.checksRepository.findByDateRangeAndMonitorId(monitor.id, dateRange, {
|
||||
type: monitor.type,
|
||||
});
|
||||
|
||||
@@ -318,7 +287,7 @@ export class MonitorService implements IMonitorService {
|
||||
}: {
|
||||
teamId: string;
|
||||
monitorId: string;
|
||||
dateRange: string;
|
||||
dateRange: DateRange;
|
||||
continents?: GeoContinent[];
|
||||
}): Promise<GroupedGeoCheckResult> => {
|
||||
const monitor = await this.monitorsRepository.findById(monitorId, teamId);
|
||||
@@ -330,15 +299,7 @@ export class MonitorService implements IMonitorService {
|
||||
return { groupedGeoChecks: [] };
|
||||
}
|
||||
|
||||
const rangeKey = dateRange as DateRangeKey;
|
||||
const { start, end } = this.getDateRange(rangeKey);
|
||||
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(
|
||||
monitor.id,
|
||||
start,
|
||||
end,
|
||||
this.getDateFormat(rangeKey),
|
||||
continents
|
||||
);
|
||||
const groupedGeoChecks = await this.geoChecksRepository.findGroupedByMonitorIdAndDateRange(monitor.id, dateRange, continents);
|
||||
|
||||
return { groupedGeoChecks };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { GroupedCheck, HasResponseTime, NormalizedCheck, NormalizedUptimeCheck } from "@/domain/checks/check.type.js";
|
||||
import { type DateRange } from "@/types/query.js";
|
||||
|
||||
export const getDateForRange = (dateRange: string): Date | undefined => {
|
||||
export const getDateForRange = (dateRange: DateRange): Date => {
|
||||
const now = Date.now();
|
||||
switch (dateRange) {
|
||||
case "recent":
|
||||
@@ -14,12 +15,24 @@ export const getDateForRange = (dateRange: string): Date | undefined => {
|
||||
case "month":
|
||||
return new Date(now - 30 * 24 * 60 * 60 * 1000); // 30 days
|
||||
case "all":
|
||||
return undefined;
|
||||
return new Date(0);
|
||||
default:
|
||||
return undefined;
|
||||
return new Date(0);
|
||||
}
|
||||
};
|
||||
|
||||
export const getDateFormat = (dateRange: DateRange): string => {
|
||||
const formatLookup = {
|
||||
hour: "%Y-%m-%dT%H:%M:00Z",
|
||||
recent: "%Y-%m-%dT%H:%M:00Z",
|
||||
day: "%Y-%m-%dT%H:00:00Z",
|
||||
week: "%Y-%m-%dT00:00:00Z",
|
||||
month: "%Y-%m-%dT00:00:00Z",
|
||||
all: "%Y-%m-%dT00:00:00Z",
|
||||
};
|
||||
return formatLookup[dateRange];
|
||||
};
|
||||
|
||||
const percentileBy = <T>(arr: T[], percentile: number, value: (item: T) => number): number => {
|
||||
const sorted = arr.slice().sort((a, b) => value(a) - value(b));
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "@jest/globals";
|
||||
import { getDateForRange, NormalizeData, NormalizeDataUptimeDetails } from "../../../src/utils/dataUtils.ts";
|
||||
import { getDateForRange, getDateFormat, NormalizeData, NormalizeDataUptimeDetails } from "../../../src/utils/dataUtils.ts";
|
||||
import type { GroupedCheck } from "../../../src/domain/checks/check.type.ts";
|
||||
import { DateRanges, type DateRange } from "../../../src/types/query.ts";
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -29,20 +30,35 @@ describe("getDateForRange", () => {
|
||||
["month", 30 * 24 * 60 * 60 * 1000],
|
||||
])("returns now minus the %s window", (range, offsetMs) => {
|
||||
const before = Date.now();
|
||||
const result = getDateForRange(range as string);
|
||||
const result = getDateForRange(range as DateRange);
|
||||
const after = Date.now();
|
||||
|
||||
expect(result).toBeInstanceOf(Date);
|
||||
expect(result!.getTime()).toBeGreaterThanOrEqual(before - offsetMs);
|
||||
expect(result!.getTime()).toBeLessThanOrEqual(after - offsetMs);
|
||||
expect(result.getTime()).toBeGreaterThanOrEqual(before - offsetMs);
|
||||
expect(result.getTime()).toBeLessThanOrEqual(after - offsetMs);
|
||||
});
|
||||
|
||||
it("returns undefined for 'all'", () => {
|
||||
expect(getDateForRange("all")).toBeUndefined();
|
||||
it("returns the epoch for 'all'", () => {
|
||||
expect(getDateForRange("all")).toEqual(new Date(0));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDateFormat", () => {
|
||||
it.each([
|
||||
["recent", "%Y-%m-%dT%H:%M:00Z"],
|
||||
["hour", "%Y-%m-%dT%H:%M:00Z"],
|
||||
["day", "%Y-%m-%dT%H:00:00Z"],
|
||||
["week", "%Y-%m-%dT00:00:00Z"],
|
||||
["month", "%Y-%m-%dT00:00:00Z"],
|
||||
["all", "%Y-%m-%dT00:00:00Z"],
|
||||
])("returns the %s bucket format", (range, format) => {
|
||||
expect(getDateFormat(range as DateRange)).toBe(format);
|
||||
});
|
||||
|
||||
it("returns undefined for unknown ranges", () => {
|
||||
expect(getDateForRange("fortnight")).toBeUndefined();
|
||||
it("has a format for every member of DateRanges", () => {
|
||||
for (const range of DateRanges) {
|
||||
expect(getDateFormat(range)).toMatch(/^%Y-%m-%d/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user