Merge pull request #3430 from bluewave-labs/fix/services

fix/services
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-03-20 11:33:48 -07:00
commit 95bda35ec6
6 files changed
+134 -35

No files matched your search

+23 -5
View File
@@ -1,6 +1,8 @@
import { Request, Response, NextFunction } from "express";
import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js";
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
import { requireTeamId } from "./controllerUtils.js";
import { AppError } from "@/utils/AppError.js";
const SERVICE_NAME = "geoCheckController";
@@ -18,13 +20,29 @@ class GeoCheckController {
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
try {
getChecksParamValidation.parse(req.params);
getChecksQueryValidation.parse(req.query);
const validatedParams = getChecksParamValidation.parse(req.params);
const validatedQuery = getChecksQueryValidation.parse(req.query);
if (!validatedQuery.continent || validatedQuery.continent.length === 0) {
throw new AppError({
message: "At least one continent must be specified",
service: SERVICE_NAME,
method: "getGeoChecksByMonitor",
details: { monitorId: validatedParams.monitorId },
status: 400,
});
}
const teamId = requireTeamId(req.user?.teamId);
const result = await this.geoChecksService.getGeoChecksByMonitor({
monitorId: req?.params?.monitorId as string,
query: req?.query,
teamId: req?.user?.teamId as string,
monitorId: validatedParams.monitorId,
teamId: teamId,
sortOrder: validatedQuery.sortOrder,
dateRange: validatedQuery.dateRange,
page: validatedQuery.page,
rowsPerPage: validatedQuery.rowsPerPage,
continent: validatedQuery.continent,
});
return res.status(200).json({
@@ -21,7 +21,17 @@ class MaintenanceWindowController {
try {
const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
const teamId = requireTeamId(req?.user?.teamId);
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: validatedBody });
const monitorIDs = validatedBody.monitors;
const name = validatedBody.name;
const active = validatedBody.active ? validatedBody.active : true;
const duration = validatedBody.duration;
const durationUnit = validatedBody.durationUnit;
const repeat = validatedBody.repeat;
const start = validatedBody.start;
const end = validatedBody.end;
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
return res.status(200).json({
success: true,
@@ -24,8 +24,7 @@ export class DiagnosticService implements IDiagnosticService {
* Clears performance marks after each measurement to prevent memory leaks.
*/
const obs = new PerformanceObserver((items) => {
// Get the first entry but we don't need to store it
items.getEntries()[0];
items.getEntries();
performance.clearMarks();
});
obs.observe({ entryTypes: ["measure"] });
@@ -1,7 +1,7 @@
import type { Monitor, GeoCheck } from "@/types/index.js";
import type { GeoCheckResult } from "@/types/geoCheck.js";
import type { GeoCheckResult, GeoContinent } from "@/types/geoCheck.js";
import { Types } from "mongoose";
import type { IGeoChecksRepository } from "@/repositories/index.js";
import type { FlatGeoChecksQueryResult, 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";
@@ -13,7 +13,15 @@ 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>;
getGeoChecksByMonitor(args: {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
page?: number;
rowsPerPage?: number;
continent: GeoContinent | GeoContinent[];
}): Promise<FlatGeoChecksQueryResult>;
}
export class GeoChecksService implements IGeoChecksService {
@@ -139,7 +147,23 @@ export class GeoChecksService implements IGeoChecksService {
return this.geoChecksRepository.createGeoChecks(geoChecks);
};
getGeoChecksByMonitor = async ({ monitorId, query, teamId }: { monitorId: string; query: any; teamId: string }) => {
getGeoChecksByMonitor = async ({
monitorId,
teamId,
sortOrder,
dateRange,
page,
rowsPerPage,
continent,
}: {
monitorId: string;
teamId: string;
sortOrder: string;
dateRange: string;
page?: number;
rowsPerPage?: number;
continent: GeoContinent | GeoContinent[];
}) => {
if (!monitorId) {
throw new AppError({
message: "No monitor ID in request",
@@ -167,10 +191,9 @@ export class GeoChecksService implements IGeoChecksService {
});
}
const { sortOrder, dateRange, page, rowsPerPage, continent } = query;
const continents = continent ? (Array.isArray(continent) ? continent : [continent]) : undefined;
const parsedPage = page ? parseInt(page) : page;
const parsedRowsPerPage = rowsPerPage ? parseInt(rowsPerPage) : rowsPerPage;
const parsedPage = page || 0;
const parsedRowsPerPage = rowsPerPage || 5;
const result = await this.geoChecksRepository.findByMonitorId(monitorId, sortOrder, dateRange, parsedPage, parsedRowsPerPage, continents);
return result;
};
+26 -6
View File
@@ -2,6 +2,8 @@ import type { Invite, UserRole } from "@/types/index.js";
import { canManageRole } from "@/types/user.js";
import type { IInvitesRepository } from "@/repositories/index.js";
import { AppError } from "@/utils/AppError.js";
import { ISettingsService } from "../system/settingsService.js";
import { IEmailService } from "../infrastructure/emailService.js";
const SERVICE_NAME = "inviteService";
@@ -14,8 +16,8 @@ export interface IInviteService {
export class InviteService implements IInviteService {
static SERVICE_NAME = SERVICE_NAME;
private settingsService: any;
private emailService: any;
private settingsService: ISettingsService;
private emailService: IEmailService;
private invitesRepository: IInvitesRepository;
constructor({
@@ -24,8 +26,8 @@ export class InviteService implements IInviteService {
emailService,
}: {
invitesRepository: IInvitesRepository;
settingsService: any;
emailService: any;
settingsService: ISettingsService;
emailService: IEmailService;
}) {
this.invitesRepository = invitesRepository;
this.settingsService = settingsService;
@@ -57,8 +59,16 @@ export class InviteService implements IInviteService {
return inviteToken;
};
sendInviteEmail = async ({ invite, firstName, userRoles }: { invite: Partial<Invite>; firstName: any; userRoles: UserRole[] }) => {
sendInviteEmail = async ({ invite, firstName, userRoles }: { invite: Partial<Invite>; firstName: string; userRoles: UserRole[] }) => {
const inviteRoles = invite.role ?? [];
if (!invite.email) {
throw new AppError({
message: "Invite email is required to send an invite",
service: SERVICE_NAME,
method: "sendInviteEmail",
status: 400,
});
}
for (const targetRole of inviteRoles) {
const canManage = userRoles.some((actorRole) => canManageRole(actorRole, targetRole));
@@ -66,7 +76,7 @@ export class InviteService implements IInviteService {
throw new AppError({
message: "You do not have permission to create this invite",
service: SERVICE_NAME,
method: "getInviteToken",
method: "sendInviteEmail",
status: 403,
});
}
@@ -79,6 +89,16 @@ export class InviteService implements IInviteService {
name: firstName,
link: `${clientHost}/register/${inviteToken.token}`,
});
if (!html) {
throw new AppError({
message: "Failed to build invite e-mail... Please verify your settings.",
service: SERVICE_NAME,
method: "sendInviteEmail",
status: 500,
});
}
const result = await this.emailService.sendEmail(invite.email, "Welcome to Uptime Monitor", html);
if (!result) {
throw new AppError({
@@ -1,11 +1,21 @@
import { IMaintenanceWindowsRepository, IMonitorsRepository } from "@/repositories/index.js";
import type { MaintenanceWindow } from "@/types/index.js";
import type { DurationUnit, MaintenanceWindow } from "@/types/index.js";
import { AppError } from "@/utils/AppError.js";
const SERVICE_NAME = "maintenanceWindowService";
export interface IMaintenanceWindowService {
createMaintenanceWindow(params: { teamId: string; body: Partial<MaintenanceWindow> & { monitors: string[] } }): Promise<void>;
createMaintenanceWindow(params: {
teamId: string;
monitorIDs: string[];
name: string;
active: boolean;
duration: number;
durationUnit: DurationUnit;
repeat: number;
start: string;
end: string;
}): Promise<void>;
getMaintenanceWindowById(params: { id: string; teamId: string }): Promise<MaintenanceWindow>;
getMaintenanceWindowsByTeamId(params: {
teamId: string;
@@ -40,9 +50,28 @@ export class MaintenanceWindowService implements IMaintenanceWindowService {
return MaintenanceWindowService.SERVICE_NAME;
}
createMaintenanceWindow = async ({ teamId, body }: { teamId: string; body: any }) => {
const monitorIds = body.monitors;
const monitors = await this.monitorsRepository.findByIds(monitorIds);
createMaintenanceWindow = async ({
teamId,
monitorIDs,
name,
active,
duration,
durationUnit,
repeat,
start,
end,
}: {
teamId: string;
monitorIDs: string[];
name: string;
active: boolean;
duration: number;
durationUnit: DurationUnit;
repeat: number;
start: string;
end: string;
}) => {
const monitors = await this.monitorsRepository.findByIds(monitorIDs);
const unauthorizedMonitors = monitors.filter((monitor) => monitor.teamId !== teamId);
@@ -55,17 +84,17 @@ export class MaintenanceWindowService implements IMaintenanceWindowService {
});
}
const dbTransactions = monitorIds.map((monitorId: string) => {
const dbTransactions = monitorIDs.map((monitorId: string) => {
return this.maintenanceWindowsRepository.create({
teamId,
monitorId,
name: body.name,
active: body.active ? body.active : true,
duration: body.duration,
durationUnit: body.durationUnit,
repeat: body.repeat,
start: body.start,
end: body.end,
name: name,
active: active,
duration: duration,
durationUnit: durationUnit,
repeat: repeat,
start: start,
end: end,
});
});
await Promise.all(dbTransactions);
@@ -106,7 +135,7 @@ export class MaintenanceWindowService implements IMaintenanceWindowService {
return await this.maintenanceWindowsRepository.deleteById(id, teamId);
};
editMaintenanceWindow = async ({ id, teamId, body }: { id: string; teamId: string; body: any }) => {
editMaintenanceWindow = async ({ id, teamId, body }: { id: string; teamId: string; body: Partial<MaintenanceWindow> }) => {
return await this.maintenanceWindowsRepository.updateById(id, teamId, body);
};
}