monitor import/export
This commit is contained in:
1 parent
af6fede1b3
commit
6881be7555
12 files changed
+381
-154
No files matched your search
@@ -19,6 +19,8 @@ export interface IMonitorController {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void>;
|
||||
export(req: Request, res: Response, next: NextFunction): Promise<void>;
|
||||
import(req: Request, res: Response, next: NextFunction): Promise<void>;
|
||||
}
|
||||
class MonitorController {
|
||||
private monitorService: MonitorService;
|
||||
@@ -315,6 +317,62 @@ class MonitorController {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const userContext = req.user;
|
||||
if (!userContext) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const teamId = userContext.currentTeamId;
|
||||
if (!teamId) {
|
||||
throw new ApiError("No team ID", 400);
|
||||
}
|
||||
|
||||
const monitors = await this.monitorService.export(teamId);
|
||||
res.status(200).json({
|
||||
message: "Monitors exported successfully",
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
import = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const userContext = req.user;
|
||||
if (!userContext) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const teamId = userContext.currentTeamId;
|
||||
if (!teamId) {
|
||||
throw new ApiError("No team ID", 400);
|
||||
}
|
||||
|
||||
const orgId = userContext.orgId;
|
||||
if (!orgId) {
|
||||
throw new ApiError("No organization ID", 400);
|
||||
}
|
||||
|
||||
const result = await this.monitorService.import(
|
||||
orgId,
|
||||
teamId,
|
||||
userContext.sub,
|
||||
req.body
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
message: `Import request received. ${result.imported} successfully inserted, ${result.errors} failed`,
|
||||
data: result,
|
||||
});
|
||||
|
||||
try {
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorController;
|
||||
@@ -6,7 +6,7 @@ export type {
|
||||
ILighthouseResult,
|
||||
} from "./checks/Check.js";
|
||||
|
||||
export { Monitor, MonitorStatuses } from "./monitors/Monitor.js";
|
||||
export { Monitor, MonitorStatuses, MonitorTypes } from "./monitors/Monitor.js";
|
||||
export type { IMonitor } from "./monitors/Monitor.js";
|
||||
|
||||
export { MonitorStats } from "./monitors/MonitorStats.js";
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
monitorIdChecksQuerySchema,
|
||||
monitorPatchSchema,
|
||||
monitorAllEmbedChecksQuerySchema,
|
||||
monitorImportSchema,
|
||||
} from "@/validation/index.js";
|
||||
import { verify } from "node:crypto";
|
||||
|
||||
class MonitorRoutes {
|
||||
private router;
|
||||
@@ -41,6 +41,22 @@ class MonitorRoutes {
|
||||
this.controller.getAll
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/export",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
verifyTeamPermission([PERMISSIONS.monitors.read]),
|
||||
this.controller.export
|
||||
);
|
||||
this.router.post(
|
||||
"/import",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
validateBody(monitorImportSchema),
|
||||
verifyTeamPermission([PERMISSIONS.monitors.write]),
|
||||
this.controller.import
|
||||
);
|
||||
|
||||
this.router.get(
|
||||
"/:id/checks",
|
||||
verifyToken,
|
||||
|
||||
@@ -6,6 +6,14 @@ import { IJobQueue } from "@/services/infrastructure/JobQueue.js";
|
||||
import { MonitorWithChecksResponse } from "@/types/index.js";
|
||||
import { MonitorStatus, MonitorType } from "@/db/models/monitors/Monitor.js";
|
||||
|
||||
export interface IImportedMonitor {
|
||||
name: string;
|
||||
type: MonitorType;
|
||||
interval: number;
|
||||
url: string;
|
||||
n: number;
|
||||
}
|
||||
|
||||
const SERVICE_NAME = "MonitorService";
|
||||
|
||||
export interface IMonitorService {
|
||||
@@ -50,6 +58,13 @@ export interface IMonitorService {
|
||||
updateData: Partial<IMonitor>
|
||||
) => Promise<IMonitor>;
|
||||
delete: (teamId: string, monitorId: string) => Promise<boolean>;
|
||||
export: (teamId: string) => Promise<Array<Record<string, unknown>>>;
|
||||
import: (
|
||||
orgId: string,
|
||||
teamId: string,
|
||||
userId: string,
|
||||
data: { monitors: Array<IImportedMonitor> }
|
||||
) => Promise<{ imported: number; errors: number }>;
|
||||
}
|
||||
|
||||
class MonitorService implements IMonitorService {
|
||||
@@ -639,6 +654,43 @@ class MonitorService implements IMonitorService {
|
||||
await monitor.deleteOne();
|
||||
return true;
|
||||
}
|
||||
|
||||
export = async (teamId: string) => {
|
||||
return Monitor.find({ teamId })
|
||||
.select("name url type interval n secret -_id")
|
||||
.lean();
|
||||
};
|
||||
|
||||
import = async (
|
||||
orgId: string,
|
||||
teamId: string,
|
||||
userId: string,
|
||||
data: { monitors: Array<IImportedMonitor> }
|
||||
) => {
|
||||
let result = [];
|
||||
try {
|
||||
result = await Monitor.insertMany(
|
||||
data?.monitors.map((monitor) => ({
|
||||
...monitor,
|
||||
orgId: new mongoose.Types.ObjectId(orgId),
|
||||
teamId: new mongoose.Types.ObjectId(teamId),
|
||||
createdBy: new mongoose.Types.ObjectId(userId),
|
||||
updatedBy: new mongoose.Types.ObjectId(userId),
|
||||
}))
|
||||
);
|
||||
|
||||
for (const monitor of result) {
|
||||
await this.jobQueue.addJob(monitor);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Import error:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
imported: result.length,
|
||||
errors: data?.monitors?.length - result.length,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorService;
|
||||
@@ -96,9 +96,12 @@ class StatusService implements IStatusService {
|
||||
statusResponse: StatusResponse,
|
||||
statusChanged: boolean
|
||||
) => {
|
||||
const stats = await MonitorStats.findOne({ monitorId: monitor._id });
|
||||
let stats = await MonitorStats.findOne({ monitorId: monitor._id });
|
||||
if (!stats) {
|
||||
throw new ApiError("MonitorStats not found", 500);
|
||||
stats = await MonitorStats.create({
|
||||
monitorId: monitor._id,
|
||||
currentStreakStartedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Update check counts
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ChannelTypes,
|
||||
MonitorStatuses,
|
||||
MonitorTypes,
|
||||
ResolutionTypes,
|
||||
} from "@/db/models/index.js";
|
||||
import { z } from "zod";
|
||||
@@ -82,6 +83,25 @@ export const monitorSchema = z
|
||||
}
|
||||
});
|
||||
|
||||
export const monitorImportSchema = z.object({
|
||||
monitors: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
name: z.string().trim().min(1, "Monitor name is required"),
|
||||
url: z.string().trim().regex(urlRegex, "Invalid URL"),
|
||||
secret: z.string().trim().optional(),
|
||||
type: z.enum(MonitorTypes),
|
||||
interval: z.number().min(1, "Interval must be greater than 0"),
|
||||
n: z.number().min(1, "n must be at least 1"),
|
||||
})
|
||||
.strict()
|
||||
)
|
||||
.min(1, { message: "Provide at least one monitor" }),
|
||||
});
|
||||
|
||||
export type MonitorImportPayload = z.infer<typeof monitorImportSchema>;
|
||||
|
||||
export const monitorPatchSchema = monitorSchema
|
||||
.omit({
|
||||
url: true,
|
||||
|
||||
Reference in new issue
Block a user