diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 847c73731..8bd131dfc 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -23,6 +23,7 @@ import { parseMonitorTypeFilter, parseSortOrder, requireTeamId, + requireUserId, } from "./controllerUtils.js"; import { AppError } from "@/utils/AppError.js"; import { IMonitorService } from "@/service/index.js"; @@ -172,26 +173,23 @@ class MonitorController { } }; - // TODO rename to Import, change to accept JSON - createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => { + importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => { try { - const userId = req.user?.id; // TODO use the correct helper function - if (!userId) { - throw new AppError({ message: "User ID is required", status: 400 }); + const teamId = requireTeamId(req?.user?.teamId); + const userId = requireUserId(req?.user?.id); + + const { monitors } = req.body; + + if (!monitors || !Array.isArray(monitors)) { + throw new AppError({ message: "Invalid request: monitors array is required", status: 400 }); } - const teamId = requireTeamId(req?.user?.teamId); - - const data = req.body; - - // TODO extract JSON, validate? - - const monitors = await this.monitorService.createBulkMonitors(data, userId, teamId); + const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors }); return res.status(200).json({ success: true, - msg: "Bulk monitors created successfully", - data: monitors, + msg: `Successfully imported ${result.imported} monitor(s)`, + data: result, }); } catch (error) { next(error); diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 1cbb1d3fc..63940f591 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -17,7 +17,6 @@ export interface SummaryConfig { export interface IMonitorsRepository { // create create(monitor: Monitor, teamId: string, userId: string): Promise; - // TODO rename createMonitors createBulkMonitors(monitors: Monitor[]): Promise; // single fetch findById(monitorId: string, teamId: string): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index a8d7d202d..728d2081f 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -12,7 +12,6 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.toEntity(saved); }; - // TODO rename to createMonitors, must accept JSON createBulkMonitors = async (monitors: Monitor[]): Promise => { if (!monitors.length) { return []; diff --git a/server/src/routes/monitorRoute.ts b/server/src/routes/monitorRoute.ts index 5458c3c31..afe8c7819 100755 --- a/server/src/routes/monitorRoute.ts +++ b/server/src/routes/monitorRoute.ts @@ -45,10 +45,7 @@ class MonitorRoutes { // Other static routes this.router.post("/demo", isAllowed(["admin", "superadmin"]), this.monitorController.addDemoMonitors); this.router.get("/export/json", isAllowed(["admin", "superadmin"]), this.monitorController.exportMonitorsToJSON); - - // TODO this needs to be modified to accept JSON, change to /import - this.router.post("/bulk", isAllowed(["admin", "superadmin"]), upload.single("csvFile"), this.monitorController.createBulkMonitors); - // END TODO + this.router.post("/import/json", isAllowed(["admin", "superadmin"]), this.monitorController.importMonitorsFromJSON); this.router.post("/test-email", isAllowed(["admin", "superadmin"]), this.monitorController.sendTestEmail); this.router.get("/games", this.monitorController.getAllGames); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 8c2f9e111..2d633ab46 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -29,8 +29,6 @@ export interface IMonitorService { // create createMonitor(teamId: string, userId: string, body: Monitor): Promise; - // TODO rename createMonitors - createBulkMonitors(monitors: Array, userId: string, teamId: string): Promise; addDemoMonitors(args: { userId: string; teamId: string }): Promise; // read @@ -73,6 +71,7 @@ export interface IMonitorService { // other sendTestEmail(args: { to: string }): Promise; exportMonitorsToJSON(args: { teamId: string }): Promise; + importMonitorsFromJSON(args: { teamId: string; userId: string; monitors: any[] }): Promise<{ imported: number; errors: string[] }>; } export class MonitorService implements IMonitorService { @@ -488,4 +487,43 @@ export class MonitorService implements IMonitorService { return monitors; }; + + importMonitorsFromJSON = async ({ + teamId, + userId, + monitors, + }: { + teamId: string; + userId: string; + monitors: any[]; + }): Promise<{ imported: number; errors: string[] }> => { + const errors: string[] = []; + + // Clean the data: remove id, _id, createdAt, updatedAt, recentChecks fields + const cleanedMonitors = monitors.map((monitor) => { + const cleanData = { ...monitor }; + delete cleanData.id; + delete cleanData._id; + delete cleanData.createdAt; + delete cleanData.updatedAt; + delete cleanData.recentChecks; + // Force the monitor to belong to the current team + cleanData.teamId = teamId; + return cleanData; + }); + + // Use createBulkMonitors for efficient bulk insert + const createdMonitors = await this.createBulkMonitors(cleanedMonitors, userId, teamId); + + if (!createdMonitors || createdMonitors.length === 0) { + throw new AppError({ + message: "Failed to import any monitors. Please check the file format and try again.", + service: SERVICE_NAME, + method: "importMonitorsFromJSON", + status: 400, + }); + } + + return { imported: createdMonitors.length, errors }; + }; }