From e6c268f14eca53d81fddc3637d1d74e6e87cfcd6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 22 Jan 2026 20:57:20 +0000 Subject: [PATCH] status page service/repo refactor --- .../v1/Charts/StatusPageBarChart/index.jsx | 2 +- client/src/Components/v1/Icon/index.jsx | 2 + .../Create/Hooks/useCreateStatusPage.jsx | 7 +- client/src/Pages/StatusPage/Create/index.jsx | 5 +- .../StatusPages/Hooks/useStatusPagesFetch.jsx | 2 +- client/src/Utils/NetworkService.js | 4 +- server/src/config/controllers.ts | 2 +- server/src/config/services.ts | 5 + server/src/controllers/controllerUtils.ts | 8 + .../src/controllers/statusPageController.ts | 81 +++++---- .../monitors/IMonitorsRepository.ts | 3 +- .../monitors/MongoMonitorsRepository.ts | 160 +++++++++++++++++- .../status-pages/IStatusPagesRepository.ts | 5 + .../MongoStatusPagesRepository.ts | 69 ++++++++ server/src/routes/statusPageRoute.ts | 3 +- .../src/service/business/statusPageService.ts | 42 +++++ server/src/service/index.ts | 1 + server/src/types/monitor.ts | 3 +- 18 files changed, 357 insertions(+), 47 deletions(-) create mode 100644 server/src/service/business/statusPageService.ts diff --git a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx index 7da4d58ba..309201b84 100644 --- a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx +++ b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx @@ -148,7 +148,7 @@ const StatusPageBarChart = ({ checks = [] }) => { } placement="top" - key={`check-${check?._id}`} + key={`check-${check?.id}`} slotProps={{ popper: { className: "bar-tooltip", diff --git a/client/src/Components/v1/Icon/index.jsx b/client/src/Components/v1/Icon/index.jsx index 5828601fd..9f7c61585 100644 --- a/client/src/Components/v1/Icon/index.jsx +++ b/client/src/Components/v1/Icon/index.jsx @@ -12,6 +12,7 @@ import { Calendar, Check, CheckCircle, + CheckCircle2, CheckSquare, ChevronDown, ChevronLeft, @@ -82,6 +83,7 @@ const iconComponents = { Calendar, Check, CheckCircle, + CheckCircle2, CheckSquare, ChevronDown, ChevronLeft, diff --git a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx index dac5997f9..256fa4be9 100644 --- a/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx +++ b/client/src/Pages/StatusPage/Create/Hooks/useCreateStatusPage.jsx @@ -2,13 +2,14 @@ import { useState } from "react"; import { networkService } from "../../../../main.jsx"; import { createToast } from "../../../../Utils/toastUtils.jsx"; -const useCreateStatusPage = (isCreate, url) => { +const useCreateStatusPage = (isCreate) => { const [isLoading, setIsLoading] = useState(false); const [networkError, setNetworkError] = useState(false); - const createStatusPage = async ({ form }) => { + const createStatusPage = async ({ form, id }) => { + console.log(id); setIsLoading(true); try { - await networkService.createStatusPage({ form, isCreate, url }); + await networkService.createStatusPage({ form, isCreate, id }); return true; } catch (error) { setNetworkError(true); diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx index f8fcf0e91..4eb171232 100644 --- a/client/src/Pages/StatusPage/Create/index.jsx +++ b/client/src/Pages/StatusPage/Create/index.jsx @@ -54,10 +54,10 @@ const CreateStatusPage = () => { //Utils const theme = useTheme(); const [monitors, isLoading, networkError] = useMonitorsFetch(); - const [createStatusPage] = useCreateStatusPage(isCreate); const navigate = useNavigate(); const { t } = useTranslation(); + const [createStatusPage] = useCreateStatusPage(isCreate); const [statusPage, statusPageMonitors, statusPageIsLoading, , fetchStatusPage] = useStatusPageFetch(isCreate, url); const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url); @@ -144,13 +144,12 @@ const CreateStatusPage = () => { logo: { type: form.logo?.type ?? null, size: form.logo?.size ?? null }, }; - console.log(toSubmit); const { error } = statusPageValidation.validate(toSubmit, { abortEarly: false, }); if (typeof error === "undefined") { - const success = await createStatusPage({ form }); + const success = await createStatusPage({ form, id: statusPage?.id }); if (success) { createToast({ body: isCreate ? t("statusPage.createSuccess") : t("statusPage.updateSuccess"), diff --git a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx index 839ff4cdf..9336e5d31 100644 --- a/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx +++ b/client/src/Pages/StatusPage/StatusPages/Hooks/useStatusPagesFetch.jsx @@ -14,7 +14,7 @@ const useStatusPagesFetch = () => { const fetchStatusPages = async () => { try { const res = await networkService.getStatusPagesByTeamId(); - setStatusPages(res?.data?.data); + setStatusPages(res?.data?.data || []); } catch (error) { setNetworkError(true); createToast({ diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js index 5b28281fe..4a096badd 100644 --- a/client/src/Utils/NetworkService.js +++ b/client/src/Utils/NetworkService.js @@ -909,7 +909,7 @@ class NetworkService { } async createStatusPage(config) { - const { form, isCreate } = config; + const { form, isCreate, id } = config; const fd = new FormData(); fd.append("type", form.type); @@ -955,7 +955,7 @@ class NetworkService { return this.axiosInstance.post(`/status-page`, fd, {}); } - return this.axiosInstance.put(`/status-page`, fd, {}); + return this.axiosInstance.put(`/status-page/${id}`, fd, {}); } async deleteStatusPage(config) { diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts index 3b81d27db..7a20ed93c 100644 --- a/server/src/config/controllers.ts +++ b/server/src/config/controllers.ts @@ -36,7 +36,7 @@ export const initializeControllers = (services: InitializedServices): Initialize maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService), queueController: new QueueController(services.jobQueue), logController: new LogController(services.logger), - statusPageController: new StatusPageController(services.db), + statusPageController: new StatusPageController(services.statusPageService, services.monitorsRepository, services.settingsService), notificationController: new NotificationController(services.notificationsService, services.monitorsRepository), diagnosticController: new DiagnosticController(services.diagnosticService), incidentController: new IncidentController(services.incidentService), diff --git a/server/src/config/services.ts b/server/src/config/services.ts index fff56f9fb..6fb27feb7 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -22,6 +22,7 @@ import DiagnosticService from "../service/business/diagnosticService.js"; import InviteService from "../service/business/inviteService.js"; import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; import { MonitorService } from "@/service/index.js"; +import { StatusPageService, IStatusPageService } from "../service/business/statusPageService.js"; import IncidentService from "../service/business/incidentService.js"; import axios from "axios"; import got from "got"; @@ -109,6 +110,7 @@ export type InitializedServices = { errorService: any; logger: any; notificationsService: INotificationsService; + statusPageService: IStatusPageService; // Repositories monitorsRepository: IMonitorsRepository; @@ -273,6 +275,8 @@ export const initializeServices = async ({ statusPagesRepository, }); + const statusPageService = new StatusPageService(statusPagesRepository); + const services = { //v1 settingsService, @@ -292,6 +296,7 @@ export const initializeServices = async ({ errorService, logger, notificationsService, + statusPageService, // Repositories monitorsRepository, diff --git a/server/src/controllers/controllerUtils.ts b/server/src/controllers/controllerUtils.ts index af3eddd44..e6c8767fa 100755 --- a/server/src/controllers/controllerUtils.ts +++ b/server/src/controllers/controllerUtils.ts @@ -99,6 +99,13 @@ const requireTeamId = (teamId?: string): string => { return teamId; }; +const requireUserId = (userId?: string): string => { + if (!userId) { + throw new AppError({ message: "User ID is required", status: 400 }); + } + return userId; +}; + export { fetchMonitorCertificate, requireString, @@ -108,4 +115,5 @@ export { parseMonitorTypeFilter, parseSortOrder, requireTeamId, + requireUserId, }; diff --git a/server/src/controllers/statusPageController.ts b/server/src/controllers/statusPageController.ts index b63e925c0..3501a6592 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/controllers/statusPageController.ts @@ -2,14 +2,25 @@ import { Request, Response, NextFunction } from "express"; import { createStatusPageBodyValidation, getStatusPageParamValidation, getStatusPageQueryValidation, imageValidation } from "@/validation/joi.js"; import { AppError } from "@/utils/AppError.js"; +import { requireTeamId, requireUserId } from "@/controllers/controllerUtils.js"; +import { IStatusPageService } from "@/service/business/statusPageService.js"; +import { IMonitorsRepository } from "@/repositories/index.js"; +import { ISettingsService } from "@/service/system/settingsService.js"; +import { ParseBoolean } from "@/utils/utils.js"; +import { MonitorWithChecks } from "@/types/index.js"; +import { NormalizeData } from "@/utils/dataUtils.js"; const SERVICE_NAME = "statusPageController"; class StatusPageController { static SERVICE_NAME = SERVICE_NAME; - private db: any; - constructor(db: any) { - this.db = db; + private statusPageService: IStatusPageService; + private monitorsRepository: IMonitorsRepository; + private settingsService: ISettingsService; + constructor(statusPageService: IStatusPageService, monitorsRepository: IMonitorsRepository, settingsService: ISettingsService) { + this.statusPageService = statusPageService; + this.monitorsRepository = monitorsRepository; + this.settingsService = settingsService; } get serviceName() { @@ -21,13 +32,10 @@ class StatusPageController { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - const { id, teamId } = req.user ?? {}; - const statusPage = await this.db.statusPageModule.createStatusPage({ - statusPageData: req.body, - image: req.file, - userId: id, - teamId, - }); + const teamId = requireTeamId(req?.user?.teamId); + const userId = requireUserId(req?.user?.id); + const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body); + return res.status(200).json({ success: true, msg: "Status page created successfully", @@ -42,8 +50,12 @@ class StatusPageController { try { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - - const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); + const teamId = requireTeamId(req?.user?.teamId); + const statusPageId = req.params.id; + if (!statusPageId) { + throw new AppError({ message: "Status page ID is required", status: 400 }); + } + const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body); if (statusPage === null) { throw new AppError({ message: "Status page not found", status: 404 }); } @@ -57,29 +69,32 @@ class StatusPageController { } }; - getStatusPage = async (req: Request, res: Response, next: NextFunction) => { - try { - const statusPage = await this.db.statusPageModule.getStatusPage(); - return res.status(200).json({ - success: true, - msg: "Status page retrieved successfully", - data: statusPage, - }); - } catch (error) { - next(error); - } - }; - getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => { try { await getStatusPageParamValidation.validateAsync(req.params); await getStatusPageQueryValidation.validateAsync(req.query); - const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); + if (!req.params.url) { + throw new AppError({ message: "Status page URL is required", status: 400 }); + } + + const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url); + const settings = await this.settingsService.getDBSettings(); + const showURL = settings.showURL; + + const monitors = await this.monitorsRepository.findByIdsWithChecks(statusPage.monitors); + const normalizedMonitors = monitors.map((monitor) => { + const normalizedChecks = NormalizeData(monitor.checks, 10, 100); + if (!showURL) { + const { url, port, secret, notifications, ...rest } = monitor; + return { ...rest, checks: normalizedChecks }; + } + return { ...monitor, checks: normalizedChecks }; + }); return res.status(200).json({ success: true, msg: "Status page retrieved successfully", - data: statusPage, + data: { statusPage, monitors: normalizedMonitors }, }); } catch (error) { next(error); @@ -87,8 +102,8 @@ class StatusPageController { }; getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => { try { - const teamId = req.user?.teamId; - const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); + const teamId = requireTeamId(req.user?.teamId); + const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId); return res.status(200).json({ success: true, @@ -99,9 +114,15 @@ class StatusPageController { next(error); } }; + deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => { try { - await this.db.statusPageModule.deleteStatusPage(req.params.url); + const statusPageId = req.params.id; + if (!statusPageId) { + throw new AppError({ message: "Status page ID is required", status: 400 }); + } + const teamId = requireTeamId(req.user?.teamId); + await this.statusPageService.deleteStatusPage(statusPageId, teamId); return res.status(200).json({ success: true, msg: "Status page deleted successfully", diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/repositories/monitors/IMonitorsRepository.ts index 8ead5a688..072772e9e 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/repositories/monitors/IMonitorsRepository.ts @@ -1,4 +1,4 @@ -import { type MonitorType, type Monitor, type MonitorsSummary } from "@/types/index.js"; +import { type MonitorType, type Monitor, type MonitorsSummary, type MonitorWithChecks } from "@/types/index.js"; export interface TeamQueryConfig { limit?: number; @@ -25,6 +25,7 @@ export interface IMonitorsRepository { findAll(): Promise; findByTeamId(teamId: string, config: TeamQueryConfig): Promise; findByIds(monitorIds: string[]): Promise; + findByIdsWithChecks(monitorIds: string[], checksCount?: number): Promise; // update updateById(monitorId: string, teamId: string, updates: Partial): Promise; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index ad7e8577d..00d921e18 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -1,7 +1,7 @@ import { MonitorModel } from "@/db/models/index.js"; import type { MonitorDocument } from "@/db/models/index.js"; -import type { Monitor, MonitorsSummary } from "@/types/index.js"; -import mongoose, { type FilterQuery } from "mongoose"; +import type { Monitor, MonitorsSummary, MonitorWithChecks, Check } from "@/types/index.js"; +import mongoose, { type FilterQuery, type PipelineStage } from "mongoose"; import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; @@ -79,6 +79,78 @@ class MongoMonitorsRepository implements IMonitorsRepository { return this.mapDocuments(monitors); }; + findByIdsWithChecks = async (monitorIds: string[], checksCount: number = 25): Promise => { + if (!monitorIds.length) { + return []; + } + + const objectIds = monitorIds.map((id) => new mongoose.Types.ObjectId(id)); + + const pipeline: PipelineStage[] = [ + { $match: { _id: { $in: objectIds } } }, + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { $match: { $expr: { $eq: ["$metadata.monitorId", "$$monitorId"] } } }, + { $sort: { createdAt: -1 } }, + { $limit: checksCount }, + ], + as: "checks", + }, + }, + { + $lookup: { + from: "maintenancewindows", + let: { monitorId: "$_id" }, + pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], + as: "maintenanceWindows", + }, + }, + { + $lookup: { + from: "monitorstats", + localField: "_id", + foreignField: "monitorId", + as: "stats", + }, + }, + { + $addFields: { + isMaintenance: { + $reduce: { + input: "$maintenanceWindows", + initialValue: false, + in: { + $or: [ + "$$value", + { + $and: [ + { $eq: ["$$this.active", true] }, + { $lte: ["$$this.start", "$$NOW"] }, + { $gte: ["$$this.end", "$$NOW"] }, + ], + }, + ], + }, + }, + }, + uptimePercentage: { $arrayElemAt: ["$stats.uptimePercentage", 0] }, + }, + }, + { + $project: { + maintenanceWindows: 0, + stats: 0, + }, + }, + ]; + + const documents = await MonitorModel.aggregate(pipeline); + return documents.map((doc) => this.toEntityWithChecks(doc)); + }; + findMonitorCountByTeamIdAndType = async (teamId: string, config?: TeamQueryConfig): Promise => { const { type } = config ?? {}; @@ -249,6 +321,90 @@ class MongoMonitorsRepository implements IMonitorsRepository { updatedAt: toDateString(doc.updatedAt), }; }; + + private toEntityWithChecks = (doc: any): MonitorWithChecks => { + const toStringId = (value: unknown): string => { + if (value instanceof mongoose.Types.ObjectId) { + return value.toString(); + } + return value?.toString() ?? ""; + }; + + const toDateString = (value: Date | string): string => { + if (!value) return ""; + return value instanceof Date ? value.toISOString() : value; + }; + + const notificationIds = (doc.notifications ?? []).map((notification: unknown) => toStringId(notification)); + + const checks: Check[] = (doc.checks ?? []).map((check: any) => ({ + id: toStringId(check._id), + metadata: { + monitorId: toStringId(check.metadata?.monitorId), + teamId: toStringId(check.metadata?.teamId), + type: check.metadata?.type, + }, + status: check.status, + responseTime: check.responseTime, + timings: check.timings, + statusCode: check.statusCode, + message: check.message, + ack: check.ack, + ackAt: check.ackAt ?? null, + expiry: toDateString(check.expiry), + cpu: check.cpu, + memory: check.memory, + disk: check.disk, + host: check.host, + errors: check.errors, + capture: check.capture, + net: check.net, + accessibility: check.accessibility, + bestPractices: check.bestPractices, + seo: check.seo, + performance: check.performance, + audits: check.audits, + __v: check.__v, + createdAt: toDateString(check.createdAt), + updatedAt: toDateString(check.updatedAt), + })); + + return { + id: toStringId(doc._id), + userId: toStringId(doc.userId), + teamId: toStringId(doc.teamId), + name: doc.name, + description: doc.description ?? undefined, + status: doc.status ?? undefined, + statusWindow: doc.statusWindow ?? [], + statusWindowSize: doc.statusWindowSize, + statusWindowThreshold: doc.statusWindowThreshold, + type: doc.type, + ignoreTlsErrors: doc.ignoreTlsErrors, + jsonPath: doc.jsonPath ?? undefined, + expectedValue: doc.expectedValue ?? undefined, + matchMethod: doc.matchMethod ?? undefined, + url: doc.url, + port: doc.port ?? undefined, + isActive: doc.isActive, + interval: doc.interval, + uptimePercentage: doc.uptimePercentage ?? undefined, + notifications: notificationIds, + secret: doc.secret ?? undefined, + thresholds: doc.thresholds ?? undefined, + alertThreshold: doc.alertThreshold, + cpuAlertThreshold: doc.cpuAlertThreshold, + memoryAlertThreshold: doc.memoryAlertThreshold, + diskAlertThreshold: doc.diskAlertThreshold, + tempAlertThreshold: doc.tempAlertThreshold, + selectedDisks: doc.selectedDisks ?? [], + gameId: doc.gameId ?? undefined, + group: doc.group ?? null, + createdAt: toDateString(doc.createdAt), + updatedAt: toDateString(doc.updatedAt), + checks, + }; + }; } export default MongoMonitorsRepository; diff --git a/server/src/repositories/status-pages/IStatusPagesRepository.ts b/server/src/repositories/status-pages/IStatusPagesRepository.ts index 1b9f9303b..c901b3c6d 100644 --- a/server/src/repositories/status-pages/IStatusPagesRepository.ts +++ b/server/src/repositories/status-pages/IStatusPagesRepository.ts @@ -2,10 +2,15 @@ import type { StatusPage } from "@/types/statusPage.js"; export interface IStatusPagesRepository { // create + create(userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise; // single fetch + findByUrl(url: string): Promise; + findByTeamId(teamId: string): Promise; // collection fetch // update + updateById(id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise; // delete + deleteById(id: string, teamId: string): Promise; // other removeMonitorFromStatusPages(monitorId: string): Promise; } diff --git a/server/src/repositories/status-pages/MongoStatusPagesRepository.ts b/server/src/repositories/status-pages/MongoStatusPagesRepository.ts index e1eb4715b..d5461bef3 100644 --- a/server/src/repositories/status-pages/MongoStatusPagesRepository.ts +++ b/server/src/repositories/status-pages/MongoStatusPagesRepository.ts @@ -2,6 +2,7 @@ import { IStatusPagesRepository } from "@/repositories/index.js"; import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js"; import type { StatusPage, StatusPageLogo } from "@/types/statusPage.js"; import mongoose from "mongoose"; +import { AppError } from "@/utils/AppError.js"; class MongoStatusPagesRepository implements IStatusPagesRepository { private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => { @@ -56,6 +57,74 @@ class MongoStatusPagesRepository implements IStatusPagesRepository { }; }; + private mapDocuments = (documents: StatusPageDocument[]): StatusPage[] => { + if (!documents?.length) { + return []; + } + return documents.map((doc) => this.toEntity(doc)); + }; + + create = async (userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise => { + const statusPage = new StatusPageModel({ + ...data, + userId, + teamId, + }); + if (image) { + statusPage.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } + await statusPage.save(); + return this.toEntity(statusPage); + }; + + findByUrl = async (url: string): Promise => { + const statusPage = await StatusPageModel.findOne({ + url, + }); + if (!statusPage) { + throw new AppError({ message: "Status page not found", status: 404 }); + } + return this.toEntity(statusPage); + // Get status page + }; + + findByTeamId = async (teamId: string): Promise => { + const statusPages = await StatusPageModel.find({ teamId }); + return this.mapDocuments(statusPages); + }; + + updateById = async (id: string, teamId: string, image: Express.Multer.File | undefined, patch: Partial): Promise => { + if (image) { + patch.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } else { + patch.logo = null; + } + + const statusPage = await StatusPageModel.findOneAndUpdate({ teamId, _id: id }, patch, { + new: true, + }); + + if (!statusPage) { + throw new AppError({ message: "Status page not found", status: 404 }); + } + + return this.toEntity(statusPage); + }; + + deleteById = async (id: string, teamId: string): Promise => { + const statusPage = await StatusPageModel.findOneAndDelete({ _id: id, teamId }); + if (!statusPage) { + throw new AppError({ message: "Status page not found", status: 404 }); + } + return this.toEntity(statusPage); + }; + removeMonitorFromStatusPages = async (monitorId: string): Promise => { const res = await StatusPageModel.updateMany({ monitors: monitorId }, { $pull: { monitors: monitorId } }); return res.modifiedCount; diff --git a/server/src/routes/statusPageRoute.ts b/server/src/routes/statusPageRoute.ts index 9d6902aac..f70802f73 100755 --- a/server/src/routes/statusPageRoute.ts +++ b/server/src/routes/statusPageRoute.ts @@ -13,11 +13,10 @@ class StatusPageRoutes { } initRoutes(verifyJWT: RequestHandler) { - this.router.get("/", this.statusPageController.getStatusPage); this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId); this.router.post("/", upload.single("logo"), verifyJWT, this.statusPageController.createStatusPage); - this.router.put("/", upload.single("logo"), verifyJWT, this.statusPageController.updateStatusPage); + this.router.put("/:id", upload.single("logo"), verifyJWT, this.statusPageController.updateStatusPage); this.router.get("/:url", this.statusPageController.getStatusPageByUrl); this.router.delete("/:url(*)", verifyJWT, this.statusPageController.deleteStatusPage); diff --git a/server/src/service/business/statusPageService.ts b/server/src/service/business/statusPageService.ts new file mode 100644 index 000000000..971f6897f --- /dev/null +++ b/server/src/service/business/statusPageService.ts @@ -0,0 +1,42 @@ +import { type IStatusPagesRepository } from "@/repositories/index.js"; +import { StatusPage } from "@/types/index.js"; +export interface IStatusPageService { + createStatusPage(userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise; + getStatusPageByUrl(url: string): Promise; + getStatusPagesByTeamId(teamId: string): Promise; + updateStatusPage(id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise; + + deleteStatusPage(statusPageId: string, teamId: string): Promise; +} + +export class StatusPageService implements IStatusPageService { + private statusPagesRepository: IStatusPagesRepository; + constructor(statusPagesRepository: IStatusPagesRepository) { + this.statusPagesRepository = statusPagesRepository; + } + + createStatusPage = async ( + userId: string, + teamId: string, + image: Express.Multer.File | undefined, + data: Partial + ): Promise => { + return await this.statusPagesRepository.create(userId, teamId, image, data); + }; + + getStatusPageByUrl = async (url: string): Promise => { + return await this.statusPagesRepository.findByUrl(url); + }; + + getStatusPagesByTeamId = async (teamId: string): Promise => { + return await this.statusPagesRepository.findByTeamId(teamId); + }; + + updateStatusPage = async (id: string, teamId: string, image: Express.Multer.File | undefined, data: Partial): Promise => { + return await this.statusPagesRepository.updateById(id, teamId, image, data); + }; + + deleteStatusPage = async (statusPageId: string, teamId: string): Promise => { + return await this.statusPagesRepository.deleteById(statusPageId, teamId); + }; +} diff --git a/server/src/service/index.ts b/server/src/service/index.ts index 7f7d0f14a..7ec49288f 100644 --- a/server/src/service/index.ts +++ b/server/src/service/index.ts @@ -11,3 +11,4 @@ export * from "@/service/infrastructure/notificationsService.js"; export * from "@/service/infrastructure/statusService.js"; export * from "@/service/infrastructure/notificationProviders/webhook.js"; export * from "@/service/infrastructure/notificationProviders/matrix.js"; +export * from "@/service/business/statusPageService.js"; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index 1d41a7333..c7d99a88a 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -1,3 +1,4 @@ +import type { Check } from "@/types/check.js"; export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "unknown"] as const; export type MonitorType = (typeof MonitorTypes)[number]; @@ -53,7 +54,7 @@ export interface MonitorsSummary { } export interface MonitorWithChecks extends Monitor { - checks: import("./check.js").Check[]; + checks: Check[]; } export interface MonitorsWithChecksByTeamIdResult {