Merge pull request #3238 from bluewave-labs/feat/v2-create-config-status-page

feat: v2 create config status page
This commit is contained in:
Alexander Holliday
2026-02-03 15:31:39 -08:00
committed by GitHub
38 changed files with 1117 additions and 1142 deletions
@@ -82,7 +82,15 @@ class StatusPageController {
const showURL = settings.showURL;
const monitors = await this.monitorsRepository.findByIds(statusPage.monitors);
const normalizedMonitors = monitors.map((monitor) => {
// Sort monitors according to the order in statusPage.monitors
const monitorOrder = new Map(statusPage.monitors.map((id, index) => [id, index]));
const sortedMonitors = [...monitors].sort((a, b) => {
const orderA = monitorOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER;
const orderB = monitorOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER;
return orderA - orderB;
});
const normalizedMonitors = sortedMonitors.map((monitor) => {
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
if (!showURL) {
const { url, port, secret, notifications, ...rest } = monitor;
+4 -4
View File
@@ -1,15 +1,15 @@
import { Schema, model, type Types } from "mongoose";
import type { StatusPage, StatusPageLogo } from "@/types/statusPage.js";
import type { StatusPage, StatusPageLogoDocument } from "@/types/statusPage.js";
import { StatusPageTypes } from "@/types/statusPage.js";
type StatusPageDocumentBase = Omit<
StatusPage,
"id" | "userId" | "teamId" | "monitors" | "subMonitors" | "originalMonitors" | "createdAt" | "updatedAt"
"id" | "userId" | "teamId" | "monitors" | "subMonitors" | "originalMonitors" | "logo" | "createdAt" | "updatedAt"
> & {
monitors: Types.ObjectId[];
subMonitors: Types.ObjectId[];
originalMonitors?: Types.ObjectId[];
logo?: StatusPageLogo | null;
logo?: StatusPageLogoDocument | null;
};
interface StatusPageDocument extends StatusPageDocumentBase {
@@ -20,7 +20,7 @@ interface StatusPageDocument extends StatusPageDocumentBase {
updatedAt: Date;
}
const logoSchema = new Schema<StatusPageLogo & { data: Buffer }>(
const logoSchema = new Schema<StatusPageLogoDocument>(
{
data: { type: Buffer },
contentType: { type: String },
@@ -1,9 +1,13 @@
import { IStatusPagesRepository } from "@/repositories/index.js";
import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js";
import type { StatusPage, StatusPageLogo } from "@/types/statusPage.js";
import type { StatusPage, StatusPageLogo, StatusPageLogoDocument } from "@/types/statusPage.js";
import mongoose from "mongoose";
import { AppError } from "@/utils/AppError.js";
// Type for update data that can include document-level fields (Buffer for logo)
type StatusPageUpdateData = Partial<Omit<StatusPage, "id" | "userId" | "teamId" | "logo" | "createdAt" | "updatedAt">> & {
logo?: StatusPageLogoDocument | null;
};
class MongoStatusPagesRepository implements IStatusPagesRepository {
private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => {
if (!value) {
@@ -23,12 +27,14 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
return values?.map((value) => this.toStringId(value)) ?? [];
};
private mapLogo = (logo?: StatusPageLogo | null): StatusPageLogo | undefined => {
private mapLogo = (logo?: StatusPageLogoDocument | null): StatusPageLogo | undefined => {
if (!logo) {
return undefined;
}
// Convert Buffer to base64 string for JSON serialization
const base64Data = Buffer.isBuffer(logo.data) ? logo.data.toString("base64") : logo.data;
return {
data: logo.data,
data: base64Data,
contentType: logo.contentType,
};
};
@@ -65,14 +71,15 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
};
create = async (userId: string, teamId: string, image: Express.Multer.File | undefined, data: Partial<StatusPage>): Promise<StatusPage> => {
const { logo: _logo, ...restData } = data;
const statusPage = new StatusPageModel({
...data,
...restData,
userId,
teamId,
});
if (image) {
statusPage.logo = {
data: image.buffer,
data: image.buffer as Buffer,
contentType: image.mimetype,
};
}
@@ -96,17 +103,24 @@ class MongoStatusPagesRepository implements IStatusPagesRepository {
return this.mapDocuments(statusPages);
};
updateById = async (id: string, teamId: string, image: Express.Multer.File | undefined, patch: Partial<StatusPage>): Promise<StatusPage> => {
updateById = async (
id: string,
teamId: string,
image: Express.Multer.File | undefined,
patch: Partial<StatusPage> & { removeLogo?: string }
): Promise<StatusPage> => {
const { logo: _logo, removeLogo, ...restPatch } = patch;
const updateData: StatusPageUpdateData = { ...restPatch };
if (image) {
patch.logo = {
data: image.buffer,
updateData.logo = {
data: image.buffer as Buffer,
contentType: image.mimetype,
};
} else {
patch.logo = null;
} else if (removeLogo === "true") {
updateData.logo = null;
}
const statusPage = await StatusPageModel.findOneAndUpdate({ teamId, _id: id }, patch, {
const statusPage = await StatusPageModel.findOneAndUpdate({ teamId, _id: id }, updateData, {
new: true,
});
+1 -1
View File
@@ -19,7 +19,7 @@ class StatusPageRoutes {
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);
this.router.delete("/:id", verifyJWT, this.statusPageController.deleteStatusPage);
}
getRouter() {
+5
View File
@@ -2,6 +2,11 @@ export const StatusPageTypes = ["uptime"] as const;
export type StatusPageType = (typeof StatusPageTypes)[number];
export interface StatusPageLogo {
data: string;
contentType: string;
}
export interface StatusPageLogoDocument {
data: Buffer;
contentType: string;
}
+1
View File
@@ -480,6 +480,7 @@ const createStatusPageBodyValidation = joi.object({
showCharts: joi.boolean().optional(),
showUptimePercentage: joi.boolean(),
showAdminLoginLink: joi.boolean().optional(),
removeLogo: joi.string().valid("true", "false").optional(),
});
const imageValidation = joi