jobqueue and stauts service
This commit is contained in:
1 parent
3bdfe5567d
commit
602597abe2
8 files changed
+270
-124
No files matched your search
@@ -36,6 +36,8 @@ import { MeService } from "@/services/index.js";
|
||||
import { MongoMonitorRepository } from "@/repositories/index.js";
|
||||
|
||||
export const initServices = async () => {
|
||||
const monitorRepository = new MongoMonitorRepository();
|
||||
|
||||
const checkService = new CheckService();
|
||||
const inviteService = new InviteService();
|
||||
const maintenanceService = new MaintenanceService();
|
||||
@@ -46,7 +48,7 @@ export const initServices = async () => {
|
||||
const slackService = new SlackService();
|
||||
const webhookService = new WebhookService();
|
||||
const networkService = new NetworkService(got);
|
||||
const statusService = new StatusService();
|
||||
const statusService = new StatusService(monitorRepository);
|
||||
const settingsService = new SettingsService();
|
||||
const stripeService = new StripeService();
|
||||
const billingService = new BillingService();
|
||||
@@ -68,11 +70,10 @@ export const initServices = async () => {
|
||||
maintenanceService,
|
||||
statsAggregationService
|
||||
);
|
||||
const jobQueue = await JobQueue.create(jobGenerator);
|
||||
const jobQueue = await JobQueue.create(jobGenerator, monitorRepository);
|
||||
const entitlementsProvider = EntitlementsFactory.create();
|
||||
const authService = new AuthService(jobQueue, entitlementsProvider);
|
||||
const meService = new MeService(entitlementsProvider);
|
||||
const monitorRepository = new MongoMonitorRepository();
|
||||
const monitorService = new MonitorService(jobQueue, monitorRepository);
|
||||
const queueService = new QueueService(jobQueue);
|
||||
const teamService = new TeamService(jobQueue);
|
||||
|
||||
@@ -4,6 +4,20 @@ import type {
|
||||
MonitorStatus,
|
||||
} from "@/types/domain/monitor.js";
|
||||
|
||||
export type UpdateMonitorPatch = Partial<
|
||||
Pick<
|
||||
Monitor,
|
||||
| "name"
|
||||
| "secret"
|
||||
| "interval"
|
||||
| "rejectUnauthorized"
|
||||
| "n"
|
||||
| "thresholds"
|
||||
| "notificationChannels"
|
||||
| "status"
|
||||
>
|
||||
>;
|
||||
|
||||
export interface TeamQueryConfig {
|
||||
search: string;
|
||||
sortField: string;
|
||||
@@ -15,7 +29,16 @@ export interface TeamQueryConfig {
|
||||
}
|
||||
|
||||
export interface IMonitorRepository {
|
||||
// Create
|
||||
create(
|
||||
orgId: string,
|
||||
userId: string,
|
||||
currentTeamId: string,
|
||||
monitorData: Monitor
|
||||
): Promise<Monitor>;
|
||||
|
||||
// Single fetch
|
||||
findAll(): Promise<Monitor[]>;
|
||||
findById(monitorId: string, teamId: string): Promise<Monitor | null>;
|
||||
|
||||
// Collection fetches
|
||||
@@ -34,8 +57,23 @@ export interface IMonitorRepository {
|
||||
findByOrgId(orgId: string): Promise<Monitor[]>;
|
||||
|
||||
// Deletions
|
||||
deleteById(id: string): Promise<boolean>;
|
||||
deleteByOrgId(orgId: string): Promise<number>;
|
||||
}
|
||||
deleteById(monitorId: string, teamId: string): Promise<boolean>;
|
||||
deleteByOrgId(orgId: string): Promise<boolean>;
|
||||
|
||||
export default IMonitorRepository;
|
||||
// Updates
|
||||
togglePauseById(
|
||||
monitorId: string,
|
||||
teamId: string,
|
||||
userId: string
|
||||
): Promise<Monitor | null>;
|
||||
|
||||
updateById(
|
||||
monitorId: string,
|
||||
teamId: string,
|
||||
userId: string,
|
||||
patch: UpdateMonitorPatch
|
||||
): Promise<Monitor | null>;
|
||||
|
||||
// Counts
|
||||
countByOrgId(orgId: string): Promise<number>;
|
||||
}
|
||||
@@ -30,6 +30,27 @@ class MongoMonitorRepository implements IMonitorRepository {
|
||||
};
|
||||
};
|
||||
|
||||
// Create
|
||||
create = async (
|
||||
orgId: string,
|
||||
userId: string,
|
||||
currentTeamId: string,
|
||||
monitorData: MonitorEntity
|
||||
) => {
|
||||
const monitorLiteral: Partial<IMonitor> = {
|
||||
...monitorData,
|
||||
orgId: new mongoose.Types.ObjectId(orgId),
|
||||
teamId: new mongoose.Types.ObjectId(currentTeamId),
|
||||
createdBy: new mongoose.Types.ObjectId(userId),
|
||||
updatedBy: new mongoose.Types.ObjectId(userId),
|
||||
notificationChannels: monitorData.notificationChannels?.map(
|
||||
(id) => new mongoose.Types.ObjectId(id)
|
||||
),
|
||||
};
|
||||
const monitor = await Monitor.create(monitorLiteral);
|
||||
return this.toEntity(monitor);
|
||||
};
|
||||
|
||||
// Single fetch
|
||||
findById = async (monitorId: string, teamId: string) => {
|
||||
const monitor = await Monitor.findOne({ _id: monitorId, teamId });
|
||||
@@ -38,6 +59,11 @@ class MongoMonitorRepository implements IMonitorRepository {
|
||||
};
|
||||
|
||||
// Collection fetches
|
||||
findAll = async () => {
|
||||
const monitors = await Monitor.find();
|
||||
return monitors.map(this.toEntity);
|
||||
};
|
||||
|
||||
findByTeamId = async (teamId: string) => {
|
||||
const monitors = await Monitor.find({ teamId });
|
||||
return monitors.map(this.toEntity);
|
||||
@@ -81,8 +107,10 @@ class MongoMonitorRepository implements IMonitorRepository {
|
||||
|
||||
return monitors.map(this.toEntity);
|
||||
};
|
||||
|
||||
findByOrgId = async (orgId: string) => {
|
||||
return [];
|
||||
const monitors = await Monitor.find({ orgId });
|
||||
return monitors.map(this.toEntity);
|
||||
};
|
||||
|
||||
findMonitorCountsByTeamId = async (teamId: string) => {
|
||||
@@ -115,12 +143,69 @@ class MongoMonitorRepository implements IMonitorRepository {
|
||||
};
|
||||
|
||||
// Deletions
|
||||
deleteById = async (id: string) => {
|
||||
return false;
|
||||
deleteById = async (monitorId: string, teamId: string) => {
|
||||
await Monitor.deleteOne({ _id: monitorId, teamId });
|
||||
return true;
|
||||
};
|
||||
deleteByOrgId = async (orgId: string) => {
|
||||
return 0;
|
||||
const deleted = await Monitor.deleteMany({ orgId });
|
||||
return deleted.acknowledged;
|
||||
};
|
||||
|
||||
togglePauseById = async (
|
||||
monitorId: string,
|
||||
teamId: string,
|
||||
userId: string
|
||||
) => {
|
||||
const monitor = await Monitor.findOneAndUpdate(
|
||||
{ _id: monitorId, teamId },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
status: {
|
||||
$cond: {
|
||||
if: { $eq: ["$status", "paused"] },
|
||||
then: "resuming",
|
||||
else: "paused",
|
||||
},
|
||||
},
|
||||
updatedBy: userId,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
);
|
||||
if (!monitor) return null;
|
||||
return this.toEntity(monitor);
|
||||
};
|
||||
|
||||
// Update
|
||||
updateById = async (
|
||||
monitorId: string,
|
||||
teamId: string,
|
||||
userId: string,
|
||||
patch: Partial<MonitorEntity>
|
||||
) => {
|
||||
const updatedMonitor = await Monitor.findOneAndUpdate(
|
||||
{ _id: monitorId, teamId },
|
||||
{
|
||||
$set: {
|
||||
...patch,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: userId,
|
||||
},
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
if (!updatedMonitor) return null;
|
||||
return this.toEntity(updatedMonitor);
|
||||
};
|
||||
|
||||
// Counts
|
||||
countByOrgId(orgId: string): Promise<number> {
|
||||
return Monitor.countDocuments({ orgId });
|
||||
}
|
||||
}
|
||||
|
||||
export default MongoMonitorRepository;
|
||||
@@ -44,8 +44,8 @@ export interface IMonitorService {
|
||||
orgId: string,
|
||||
userId: string,
|
||||
currentTeamId: string,
|
||||
monitorData: IMonitor
|
||||
) => Promise<IMonitor>;
|
||||
monitorData: MonitorEntity
|
||||
) => Promise<MonitorEntity>;
|
||||
getAll: (teamId: string) => Promise<MonitorEntity[]>;
|
||||
getAllEmbedChecks: (
|
||||
teamId: string,
|
||||
@@ -73,13 +73,13 @@ export interface IMonitorService {
|
||||
userId: string,
|
||||
teamId: string,
|
||||
monitorId: string
|
||||
) => Promise<IMonitor>;
|
||||
) => Promise<MonitorEntity>;
|
||||
update: (
|
||||
userId: string,
|
||||
teamId: string,
|
||||
monitorId: string,
|
||||
updateData: Partial<IMonitor>
|
||||
) => Promise<IMonitor>;
|
||||
updateData: Partial<MonitorEntity>
|
||||
) => Promise<MonitorEntity>;
|
||||
delete: (teamId: string, monitorId: string) => Promise<boolean>;
|
||||
deleteAllInOrg: (orgId: string) => Promise<boolean>;
|
||||
export: (teamId: string) => Promise<Array<Record<string, unknown>>>;
|
||||
@@ -106,19 +106,16 @@ class MonitorService implements IMonitorService {
|
||||
orgId: string,
|
||||
userId: string,
|
||||
currentTeamId: string,
|
||||
monitorData: IMonitor
|
||||
monitorData: MonitorEntity
|
||||
) => {
|
||||
const monitorLiteral: Partial<IMonitor> = {
|
||||
...monitorData,
|
||||
orgId: new mongoose.Types.ObjectId(orgId),
|
||||
teamId: new mongoose.Types.ObjectId(currentTeamId),
|
||||
createdBy: new mongoose.Types.ObjectId(userId),
|
||||
updatedBy: new mongoose.Types.ObjectId(userId),
|
||||
};
|
||||
|
||||
const monitor = await Monitor.create(monitorLiteral);
|
||||
const monitor = await this.monitorRepository.create(
|
||||
orgId,
|
||||
userId,
|
||||
currentTeamId,
|
||||
monitorData
|
||||
);
|
||||
await MonitorStats.create({
|
||||
monitorId: monitor._id,
|
||||
monitorId: monitor.id,
|
||||
currentStreakStartedAt: Date.now(),
|
||||
});
|
||||
await this.jobQueue.addJob(monitor);
|
||||
@@ -736,24 +733,10 @@ class MonitorService implements IMonitorService {
|
||||
};
|
||||
|
||||
async togglePause(userId: string, teamId: string, id: string) {
|
||||
const updatedMonitor = await Monitor.findOneAndUpdate(
|
||||
{ _id: id, teamId },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
status: {
|
||||
$cond: {
|
||||
if: { $eq: ["$status", "paused"] },
|
||||
then: "resuming",
|
||||
else: "paused",
|
||||
},
|
||||
},
|
||||
updatedBy: userId,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
const updatedMonitor = await this.monitorRepository.togglePauseById(
|
||||
id,
|
||||
teamId,
|
||||
userId
|
||||
);
|
||||
|
||||
if (!updatedMonitor) {
|
||||
@@ -774,9 +757,9 @@ class MonitorService implements IMonitorService {
|
||||
userId: string,
|
||||
teamId: string,
|
||||
monitorId: string,
|
||||
updateData: Partial<IMonitor>
|
||||
updateData: Partial<MonitorEntity>
|
||||
) {
|
||||
const allowedFields: (keyof IMonitor)[] = [
|
||||
const allowedFields: (keyof MonitorEntity)[] = [
|
||||
"name",
|
||||
"secret",
|
||||
"interval",
|
||||
@@ -785,7 +768,7 @@ class MonitorService implements IMonitorService {
|
||||
"notificationChannels",
|
||||
"thresholds",
|
||||
];
|
||||
const safeUpdate: Partial<IMonitor> = {};
|
||||
const safeUpdate: Partial<MonitorEntity> = {};
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
@@ -793,16 +776,11 @@ class MonitorService implements IMonitorService {
|
||||
}
|
||||
}
|
||||
|
||||
const updatedMonitor = await Monitor.findOneAndUpdate(
|
||||
{ _id: monitorId, teamId },
|
||||
{
|
||||
$set: {
|
||||
...safeUpdate,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: userId,
|
||||
},
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
const updatedMonitor = await this.monitorRepository.updateById(
|
||||
monitorId,
|
||||
teamId,
|
||||
userId,
|
||||
safeUpdate
|
||||
);
|
||||
|
||||
if (!updatedMonitor) {
|
||||
@@ -813,7 +791,7 @@ class MonitorService implements IMonitorService {
|
||||
}
|
||||
|
||||
async delete(teamId: string, monitorId: string) {
|
||||
const monitor = await Monitor.findOne({ _id: monitorId, teamId });
|
||||
const monitor = await this.monitorRepository.findById(monitorId, teamId);
|
||||
if (!monitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
@@ -821,23 +799,29 @@ class MonitorService implements IMonitorService {
|
||||
if (!deleted) {
|
||||
throw new ApiError("Failed to delete monitor job from queue", 500);
|
||||
}
|
||||
await monitor.deleteOne();
|
||||
await this.monitorRepository.deleteById(monitorId, teamId);
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteAllInOrg(orgId: string) {
|
||||
const montiors = await Monitor.find({ orgId });
|
||||
const montiors = await this.monitorRepository.findByOrgId(orgId);
|
||||
for (const monitor of montiors) {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
}
|
||||
const deleted = await Monitor.deleteMany({ orgId });
|
||||
return deleted.acknowledged;
|
||||
return await this.monitorRepository.deleteByOrgId(orgId);
|
||||
}
|
||||
|
||||
export = async (teamId: string) => {
|
||||
return Monitor.find({ teamId })
|
||||
.select("name url port type interval n secret -_id")
|
||||
.lean();
|
||||
const monitor = await this.monitorRepository.findByTeamId(teamId);
|
||||
return monitor.map((m) => ({
|
||||
name: m.name,
|
||||
url: m.url,
|
||||
port: m.port,
|
||||
type: m.type,
|
||||
interval: m.interval,
|
||||
n: m.n,
|
||||
secret: m.secret,
|
||||
}));
|
||||
};
|
||||
|
||||
import = async (
|
||||
@@ -852,7 +836,7 @@ class MonitorService implements IMonitorService {
|
||||
let canImport = 0;
|
||||
try {
|
||||
// Get current count
|
||||
const monitorCount = await Monitor.countDocuments({ orgId });
|
||||
const monitorCount = await this.monitorRepository.countByOrgId(orgId);
|
||||
|
||||
// Check limits, return NO_CAPACITY if at limit
|
||||
if (monitorCount >= entitlements.monitorsMax) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IMonitor, ICheck } from "@/db/models/index.js";
|
||||
import { IMonitor } from "@/db/models/index.js";
|
||||
import { INetworkService } from "./NetworkService.js";
|
||||
import { ICheckService } from "../business/CheckService.js";
|
||||
import { IMonitorStatsService } from "../business/MonitorStatsService.js";
|
||||
@@ -6,17 +6,15 @@ import { IStatusService } from "./StatusService.js";
|
||||
import { INotificationService } from "./NotificationService.js";
|
||||
import { IMaintenanceService } from "../business/MaintenanceService.js";
|
||||
import { IIncidentService } from "../business/IncidentService.js";
|
||||
import StatsAggregationService, {
|
||||
IStatsAggregationService,
|
||||
} from "@/services/business/StatsAggregationService.js";
|
||||
import { StatsHourly } from "@/db/models/index.js";
|
||||
import { IStatsAggregationService } from "@/services/business/StatsAggregationService.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import { getChildLogger } from "@/logger/Logger.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/index.js";
|
||||
|
||||
const SERVICE_NAME = "JobGenerator";
|
||||
const logger = getChildLogger(SERVICE_NAME);
|
||||
export interface IJobGenerator {
|
||||
generateJob: () => (Monitor: IMonitor) => Promise<void>;
|
||||
generateJob: () => (monitor: MonitorEntity) => Promise<void>;
|
||||
generateCleanupJob: () => () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -53,9 +51,9 @@ class JobGenerator implements IJobGenerator {
|
||||
}
|
||||
|
||||
generateJob = () => {
|
||||
return async (monitor: IMonitor) => {
|
||||
return async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
const monitorId = monitor._id.toString();
|
||||
const monitorId = monitor.id.toString();
|
||||
if (!monitorId) {
|
||||
throw new ApiError("No monitorID for creating job", 400);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { IJob } from "super-simple-scheduler/dist/job/job.js";
|
||||
import { Monitor, IMonitor } from "@/db/models/index.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/index.js";
|
||||
import Scheduler from "super-simple-scheduler";
|
||||
import { IJobGenerator } from "./JobGenerator.js";
|
||||
import { getChildLogger } from "@/logger/Logger.js";
|
||||
import { config } from "@/config/index.js";
|
||||
import { IMonitorRepository } from "@/repositories/index.js";
|
||||
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
const logger = getChildLogger(SERVICE_NAME);
|
||||
@@ -29,11 +30,11 @@ export interface IJobData extends IJob {
|
||||
|
||||
export interface IJobQueue {
|
||||
init: () => Promise<boolean>;
|
||||
addJob: (monitor: IMonitor) => Promise<boolean>;
|
||||
pauseJob: (monitor: IMonitor) => Promise<boolean>;
|
||||
resumeJob: (monitor: IMonitor) => Promise<boolean>;
|
||||
updateJob: (monitor: IMonitor) => Promise<boolean>;
|
||||
deleteJob: (monitor: IMonitor) => Promise<boolean>;
|
||||
addJob: (monitor: MonitorEntity) => Promise<boolean>;
|
||||
pauseJob: (monitor: MonitorEntity) => Promise<boolean>;
|
||||
resumeJob: (monitor: MonitorEntity) => Promise<boolean>;
|
||||
updateJob: (monitor: MonitorEntity) => Promise<boolean>;
|
||||
deleteJob: (monitor: MonitorEntity) => Promise<boolean>;
|
||||
getMetrics: () => Promise<IJobMetrics | null>;
|
||||
getJobs: () => Promise<IJobData[] | null>;
|
||||
flush: () => Promise<boolean>;
|
||||
@@ -45,16 +46,21 @@ export default class JobQueue implements IJobQueue {
|
||||
private scheduler: Scheduler;
|
||||
private static instance: JobQueue | null = null;
|
||||
private jobGenerator: any;
|
||||
constructor() {
|
||||
private monitorRepository: IMonitorRepository;
|
||||
constructor(monitorRepository: IMonitorRepository) {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.scheduler = new Scheduler({
|
||||
logLevel: "debug",
|
||||
});
|
||||
this.monitorRepository = monitorRepository;
|
||||
}
|
||||
|
||||
static async create(jobGenerator: IJobGenerator) {
|
||||
static async create(
|
||||
jobGenerator: IJobGenerator,
|
||||
monitorRepository: IMonitorRepository
|
||||
) {
|
||||
if (!JobQueue.instance) {
|
||||
const instance = new JobQueue();
|
||||
const instance = new JobQueue(monitorRepository);
|
||||
instance.jobGenerator = jobGenerator;
|
||||
await instance.init();
|
||||
JobQueue.instance = instance;
|
||||
@@ -107,12 +113,12 @@ export default class JobQueue implements IJobQueue {
|
||||
logger.warn("Initial stats aggregation run failed", e as any);
|
||||
}
|
||||
|
||||
const monitors = await Monitor.find();
|
||||
const monitors = await this.monitorRepository.findAll();
|
||||
for (const monitor of monitors) {
|
||||
const randomOffset = 1000 + Math.random() * 1000;
|
||||
|
||||
setTimeout(async () => {
|
||||
const exists = await Monitor.exists({ _id: monitor._id });
|
||||
const exists = await Monitor.exists({ _id: monitor.id });
|
||||
if (exists) {
|
||||
this.addJob(monitor);
|
||||
}
|
||||
@@ -126,10 +132,10 @@ export default class JobQueue implements IJobQueue {
|
||||
}
|
||||
};
|
||||
|
||||
addJob = async (monitor: IMonitor) => {
|
||||
addJob = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
return await this.scheduler?.addJob({
|
||||
id: monitor._id.toString(),
|
||||
id: monitor.id,
|
||||
template: "monitor-job",
|
||||
repeat: monitor.interval,
|
||||
active: monitor.status !== "paused",
|
||||
@@ -141,27 +147,27 @@ export default class JobQueue implements IJobQueue {
|
||||
}
|
||||
};
|
||||
|
||||
pauseJob = async (monitor: IMonitor) => {
|
||||
pauseJob = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
return await this.scheduler?.pauseJob(monitor._id.toString());
|
||||
return await this.scheduler?.pauseJob(monitor.id);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
resumeJob = async (monitor: IMonitor) => {
|
||||
resumeJob = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
return await this.scheduler.resumeJob(monitor._id.toString());
|
||||
return await this.scheduler.resumeJob(monitor.id);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
updateJob = async (monitor: IMonitor) => {
|
||||
updateJob = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
return await this.scheduler.updateJob(monitor._id.toString(), {
|
||||
return await this.scheduler.updateJob(monitor.id, {
|
||||
repeat: monitor.interval,
|
||||
data: monitor,
|
||||
});
|
||||
@@ -171,9 +177,9 @@ export default class JobQueue implements IJobQueue {
|
||||
}
|
||||
};
|
||||
|
||||
deleteJob = async (monitor: IMonitor) => {
|
||||
deleteJob = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
this.scheduler?.removeJob(monitor._id.toString());
|
||||
this.scheduler?.removeJob(monitor.id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
|
||||
@@ -16,16 +16,17 @@ import { config } from "@/config/index.js";
|
||||
import CacheableLookup from "cacheable-lookup";
|
||||
import { getChildLogger } from "@/logger/Logger.js";
|
||||
import type { IDockerContainerSummary } from "@/db/models/index.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/monitor.js";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
const logger = getChildLogger(SERVICE_NAME);
|
||||
export interface INetworkService {
|
||||
requestHttp: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestInfrastructure: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestStatus: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestPagespeed: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestPing: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestPort: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestHttp: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestInfrastructure: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestStatus: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPagespeed: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPing: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
requestPort: (monitor: MonitorEntity) => Promise<StatusResponse>;
|
||||
}
|
||||
|
||||
export interface ICapturePayload {
|
||||
@@ -92,7 +93,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
|
||||
private buildStatusResponse = <T>(
|
||||
monitor: IMonitor,
|
||||
monitor: MonitorEntity,
|
||||
response: Response<T> | null,
|
||||
certificateExpiryOrError?: Date | null | any,
|
||||
maybeError?: any | null
|
||||
@@ -110,8 +111,8 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
if (error) {
|
||||
const statusResponse: StatusResponse<T> = {
|
||||
monitorId: monitor._id.toString(),
|
||||
teamId: monitor.teamId.toString(),
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: "down" as MonitorStatus,
|
||||
code: this.NETWORK_ERROR,
|
||||
@@ -130,8 +131,8 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
|
||||
const statusResponse: StatusResponse<T> = {
|
||||
monitorId: monitor._id.toString(),
|
||||
teamId: monitor.teamId.toString(),
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
code: response?.statusCode || this.NETWORK_ERROR,
|
||||
status: response?.ok === true ? "up" : "down",
|
||||
@@ -144,7 +145,7 @@ class NetworkService implements INetworkService {
|
||||
return statusResponse;
|
||||
};
|
||||
|
||||
requestHttp = async (monitor: IMonitor) => {
|
||||
requestHttp = async (monitor: MonitorEntity) => {
|
||||
try {
|
||||
const url = monitor.url;
|
||||
if (!url) {
|
||||
@@ -190,7 +191,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestInfrastructure = async (monitor: IMonitor) => {
|
||||
requestInfrastructure = async (monitor: MonitorEntity) => {
|
||||
const url = monitor.url;
|
||||
if (!url) {
|
||||
throw new Error("No URL provided");
|
||||
@@ -225,7 +226,7 @@ class NetworkService implements INetworkService {
|
||||
return statusResponse;
|
||||
};
|
||||
|
||||
requestPagespeed = async (monitor: IMonitor) => {
|
||||
requestPagespeed = async (monitor: MonitorEntity) => {
|
||||
const apiKey = config.PAGESPEED_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("No API key provided for pagespeed monitor");
|
||||
@@ -265,7 +266,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestPing = async (monitor: IMonitor) => {
|
||||
requestPing = async (monitor: MonitorEntity) => {
|
||||
const response = await ping.promise.probe(monitor.url);
|
||||
const status = response?.alive === true ? "up" : "down";
|
||||
|
||||
@@ -276,8 +277,8 @@ class NetworkService implements INetworkService {
|
||||
const responseTime = Number.isFinite(rawTime) ? rawTime : 0;
|
||||
|
||||
return {
|
||||
monitorId: monitor._id.toString(),
|
||||
teamId: monitor.teamId.toString(),
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: status as MonitorStatus,
|
||||
message: "Ping successful",
|
||||
@@ -286,10 +287,10 @@ class NetworkService implements INetworkService {
|
||||
};
|
||||
};
|
||||
|
||||
requestPort = async (monitor: IMonitor) => {
|
||||
requestPort = async (monitor: MonitorEntity) => {
|
||||
const response = {
|
||||
monitorId: monitor._id.toString(),
|
||||
teamId: monitor.teamId.toString(),
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
status: "down" as MonitorStatus,
|
||||
message: "Port check failed",
|
||||
@@ -358,7 +359,7 @@ class NetworkService implements INetworkService {
|
||||
}
|
||||
};
|
||||
|
||||
requestStatus = async (monitor: IMonitor) => {
|
||||
requestStatus = async (monitor: MonitorEntity) => {
|
||||
switch (monitor?.type) {
|
||||
case "http":
|
||||
return await this.requestHttp(monitor); // uses GOT
|
||||
|
||||
@@ -5,6 +5,9 @@ import {
|
||||
ICheck,
|
||||
} from "@/db/models/index.js";
|
||||
import { StatusResponse } from "./NetworkService.js";
|
||||
import { IMonitorRepository } from "@/repositories/index.js";
|
||||
import type { Monitor as MonitorEntity } from "@/types/domain/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
// Using compact latestStatuses on Monitor for transition checks
|
||||
|
||||
const emptyMetrics = (): ThresholdEvaluationResult["metrics"] => ({
|
||||
@@ -33,9 +36,10 @@ export interface ThresholdEvaluationResult {
|
||||
|
||||
const SERVICE_NAME = "StatusService";
|
||||
const MAX_LATEST_CHECKS = 25;
|
||||
|
||||
export interface IStatusService {
|
||||
updateMonitorStatus: (
|
||||
monitor: IMonitor,
|
||||
monitor: MonitorEntity,
|
||||
status: StatusResponse
|
||||
) => Promise<StatusChangeResult>;
|
||||
|
||||
@@ -56,18 +60,20 @@ export interface IStatusService {
|
||||
}
|
||||
|
||||
export type StatusChangeResult = [
|
||||
updatedMonitor: IMonitor,
|
||||
updatedMonitor: MonitorEntity,
|
||||
statusChanged: boolean
|
||||
];
|
||||
|
||||
class StatusService implements IStatusService {
|
||||
public SERVICE_NAME: string;
|
||||
constructor() {
|
||||
private monitorRepository: IMonitorRepository;
|
||||
constructor(monitorRepository: IMonitorRepository) {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.monitorRepository = monitorRepository;
|
||||
}
|
||||
|
||||
updateMonitorStatus = async (
|
||||
monitor: IMonitor,
|
||||
monitor: MonitorEntity,
|
||||
statusResponse: StatusResponse
|
||||
): Promise<StatusChangeResult> => {
|
||||
const newStatus = statusResponse.status;
|
||||
@@ -84,20 +90,47 @@ class StatusService implements IStatusService {
|
||||
monitor.status = newStatus;
|
||||
const lenAfter = monitor.latestStatuses.length;
|
||||
const thresholdReachedNow = lenAfter >= monitor.n && prevLen < monitor.n;
|
||||
return [await monitor.save(), thresholdReachedNow];
|
||||
const updated = await this.monitorRepository.updateById(
|
||||
monitor.id,
|
||||
monitor.teamId,
|
||||
monitor.updatedBy,
|
||||
monitor
|
||||
);
|
||||
if (!updated) {
|
||||
throw new ApiError("Failed to update monitor status", 500);
|
||||
}
|
||||
return [updated, thresholdReachedNow];
|
||||
}
|
||||
|
||||
const { n } = monitor;
|
||||
const latestN = monitor.latestStatuses.slice(-n);
|
||||
if (latestN.length < n) {
|
||||
return [await monitor.save(), false];
|
||||
const updated = await this.monitorRepository.updateById(
|
||||
monitor.id,
|
||||
monitor.teamId,
|
||||
monitor.updatedBy,
|
||||
monitor
|
||||
);
|
||||
if (!updated) {
|
||||
throw new ApiError("Failed to update monitor status", 500);
|
||||
}
|
||||
return [updated, false];
|
||||
}
|
||||
|
||||
const allDifferent = latestN.every((s) => s !== monitor.status);
|
||||
if (allDifferent && monitor.status !== newStatus) {
|
||||
monitor.status = newStatus;
|
||||
}
|
||||
return [await monitor.save(), allDifferent];
|
||||
const updated = await this.monitorRepository.updateById(
|
||||
monitor.id,
|
||||
monitor.teamId,
|
||||
monitor.updatedBy,
|
||||
monitor
|
||||
);
|
||||
if (!updated) {
|
||||
throw new ApiError("Failed to update monitor status", 500);
|
||||
}
|
||||
return [updated, allDifferent];
|
||||
};
|
||||
|
||||
calculateAvgResponseTime = (
|
||||
|
||||
Reference in new issue
Block a user