move methods to checks, wire up service
This commit is contained in:
1 parent
6f9642c298
commit
3fc7020a90
6 files changed
+74
-43
No files matched your search
@@ -2,6 +2,7 @@ import type {
|
||||
Check,
|
||||
ChecksQueryResult,
|
||||
ChecksSummary,
|
||||
DailyCheckBucket,
|
||||
HardwareChecksResult,
|
||||
PageSpeedChecksResult,
|
||||
UptimeChecksResult,
|
||||
@@ -40,6 +41,8 @@ export interface IChecksRepository {
|
||||
): Promise<UptimeChecksResult | HardwareChecksResult | PageSpeedChecksResult>;
|
||||
findSummaryByTeamId(teamId: string, dateRange: DateRange): Promise<ChecksSummary>;
|
||||
findUnevaluatedByMonitorId(monitorId: string, since: number): Promise<Check[]>;
|
||||
getDailyStatusBuckets(monitorIds: string[], days: number, timezone: string | undefined): Promise<DailyCheckBucket[]>;
|
||||
|
||||
// update
|
||||
//delete
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
|
||||
@@ -329,6 +329,44 @@ class MongoChecksRepository implements IChecksRepository {
|
||||
return docs.map(this.toEntity);
|
||||
};
|
||||
|
||||
getDailyStatusBuckets = async (monitorIds: string[], days: number, timezone: string | undefined) => {
|
||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||
// One extra day so the oldest rendered day is complete; the client enumerates exactly `days` days and ignores the partial extra bucket
|
||||
const windowStart = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
||||
const results = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: objectIds },
|
||||
createdAt: { $gte: windowStart },
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
monitorId: "$metadata.monitorId",
|
||||
day: { $dateTrunc: { date: "$createdAt", unit: "day", timezone } },
|
||||
},
|
||||
totalChecks: { $sum: 1 },
|
||||
upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
},
|
||||
},
|
||||
{ $sort: { "_id.day": 1 } },
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
monitorId: { $toString: "$_id.monitorId" },
|
||||
date: { $dateToString: { date: "$_id.day", format: "%Y-%m-%d", timezone } },
|
||||
totalChecks: 1,
|
||||
upChecks: 1,
|
||||
downChecks: { $subtract: ["$totalChecks", "$upChecks"] },
|
||||
avgResponseTime: { $round: ["$avgResponseTime", 0] },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return results;
|
||||
};
|
||||
|
||||
deleteByMonitorId = async (monitorId: string): Promise<number> => {
|
||||
const result = await CheckModel.deleteMany({ "metadata.monitorId": new mongoose.Types.ObjectId(monitorId) });
|
||||
return result.deletedCount;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DailyCheckBucket } from "@/domain/checks/check.type.js";
|
||||
import type { StatusPage } from "@/domain/status-pages/status-page.type.js";
|
||||
|
||||
export interface IStatusPagesRepository {
|
||||
@@ -9,7 +8,6 @@ export interface IStatusPagesRepository {
|
||||
findByCustomDomain(customDomain: string): Promise<StatusPage>;
|
||||
findByTeamId(teamId: string): Promise<StatusPage[]>;
|
||||
// collection fetch
|
||||
getDailyStatusBuckets(monitorIds: string[], days: number, timezone: string | undefined): Promise<DailyCheckBucket[]>;
|
||||
// update
|
||||
updateById(id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage>;
|
||||
// delete
|
||||
|
||||
@@ -114,44 +114,6 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
|
||||
return this.mapDocuments(statusPages);
|
||||
};
|
||||
|
||||
getDailyStatusBuckets = async (monitorIds: string[], days: number, timezone: string | undefined) => {
|
||||
const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id));
|
||||
// One extra day so the oldest rendered day is complete; the client enumerates exactly `days` days and ignores the partial extra bucket
|
||||
const windowStart = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
||||
const results = await CheckModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
"metadata.monitorId": { $in: objectIds },
|
||||
createdAt: { $gte: windowStart },
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
monitorId: "$metadata.monitorId",
|
||||
day: { $dateTrunc: { date: "$createdAt", unit: "day", timezone } },
|
||||
},
|
||||
totalChecks: { $sum: 1 },
|
||||
upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
},
|
||||
},
|
||||
{ $sort: { "_id.day": 1 } },
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
monitorId: { $toString: "$_id.monitorId" },
|
||||
date: { $dateToString: { date: "$_id.day", format: "%Y-%m-%d", timezone } },
|
||||
totalChecks: 1,
|
||||
upChecks: 1,
|
||||
downChecks: { $subtract: ["$totalChecks", "$upChecks"] },
|
||||
avgResponseTime: { $round: ["$avgResponseTime", 0] },
|
||||
},
|
||||
},
|
||||
]);
|
||||
return results;
|
||||
};
|
||||
|
||||
updateById = async (
|
||||
id: string,
|
||||
teamId: string,
|
||||
|
||||
@@ -5,11 +5,14 @@ import {
|
||||
DEFAULT_STATUS_PAGE_THEME,
|
||||
DEFAULT_STATUS_PAGE_THEME_MODE,
|
||||
PublicStatusPagePayload,
|
||||
STATUS_PAGE_RANGE_DAYS,
|
||||
StatusPage,
|
||||
StatusPageRange,
|
||||
} from "@/domain/status-pages/status-page.type.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js";
|
||||
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||
import { IChecksRepository } from "@/domain/checks/check.repository.interface.js";
|
||||
|
||||
export interface IStatusPageService {
|
||||
createStatusPage(userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage>;
|
||||
@@ -26,7 +29,8 @@ export class StatusPageService implements IStatusPageService {
|
||||
constructor(
|
||||
private statusPagesRepository: IStatusPagesRepository,
|
||||
private settingsService: ISettingsService,
|
||||
private monitorsRepository: IMonitorsRepository
|
||||
private monitorsRepository: IMonitorsRepository,
|
||||
private checksRepository: IChecksRepository
|
||||
) {}
|
||||
|
||||
private assertCustomDomainAllowed = (customDomain: string | null | undefined) => {
|
||||
@@ -116,7 +120,11 @@ export class StatusPageService implements IStatusPageService {
|
||||
return statusPages.map((sp) => this.normalizeTheme(sp));
|
||||
};
|
||||
|
||||
getPublicStatusPagePayload = async (statusPage: StatusPage, requesterTeamId: string | undefined): Promise<PublicStatusPagePayload> => {
|
||||
getPublicStatusPagePayload = async (
|
||||
statusPage: StatusPage,
|
||||
requesterTeamId: string | undefined,
|
||||
range: StatusPageRange = "latest"
|
||||
): Promise<PublicStatusPagePayload> => {
|
||||
if (!statusPage.isPublished) {
|
||||
if (!requesterTeamId || statusPage.teamId !== requesterTeamId) {
|
||||
throw new AppError({ message: "Forbidden", status: 403 });
|
||||
@@ -128,7 +136,26 @@ export class StatusPageService implements IStatusPageService {
|
||||
const order = new Map(statusPage.monitors.map((id, i) => [id, i]));
|
||||
const sorted = [...monitors].sort((a, b) => (order.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.id) ?? Number.MAX_SAFE_INTEGER));
|
||||
|
||||
return { statusPage, monitors: sorted.map((monitor) => this.toPublicMonitor(monitor, showURL)) };
|
||||
if (range === "latest") {
|
||||
return { statusPage, monitors: sorted.map((monitor) => this.toPublicMonitor(monitor, showURL)) };
|
||||
}
|
||||
|
||||
const days = STATUS_PAGE_RANGE_DAYS[range];
|
||||
const bucketTimezone = statusPage.timezone ?? "Etc/UTC";
|
||||
const buckets = await this.checksRepository.getDailyStatusBuckets(statusPage.monitors, days, bucketTimezone);
|
||||
const bucketsByMonitor = Map.groupBy(buckets, (bucket) => bucket.monitorId);
|
||||
|
||||
return {
|
||||
statusPage,
|
||||
range,
|
||||
bucketTimezone,
|
||||
checkTTLDays: (await this.settingsService.getDBSettings()).checkTTL,
|
||||
monitors: sorted.map((monitor) => ({
|
||||
...this.toPublicMonitor(monitor, showURL),
|
||||
recentChecks: [],
|
||||
dailyChecks: bucketsByMonitor.get(monitor.id) ?? [],
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
updateStatusPage = async (id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage> => {
|
||||
|
||||
@@ -63,4 +63,7 @@ export type PublicStatusPageMonitor = Pick<Monitor, "id" | "name" | "type" | "st
|
||||
export interface PublicStatusPagePayload {
|
||||
statusPage: StatusPage;
|
||||
monitors: PublicStatusPageMonitor[];
|
||||
range?: StatusPageRange;
|
||||
bucketTimezone?: string;
|
||||
checkTTLDays?: number;
|
||||
}
|
||||
Reference in new issue
Block a user