diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 09c97e1fc..363e699d9 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -121,7 +121,7 @@ import { IMaintenanceWindowsRepository, } from "@/repositories/index.js"; import { ILogger } from "@/utils/logger.js"; -import { AppError } from "@/utils/AppError.js"; +// import { AppError } from "@/utils/AppError.js"; export type InitializedServices = { settingsService: ISettingsService; @@ -173,55 +173,76 @@ export const initializeServices = async ({ }): Promise => { // Create DB - const dbType = envSettings.dbType; - let db: IDb | null = null; - - if (dbType === "mongodb") { - db = new MongoDB(logger, envSettings); - } - - if (!db) { - throw new AppError({ message: "Unsupported database type", status: 500 }); - } - + db = new MongoDB(logger, envSettings); await db.connect(); - let monitorsRepository: IMonitorsRepository; - let checksRepository: IChecksRepository; - let geoChecksRepository: IGeoChecksRepository; - let monitorStatsRepository: IMonitorStatsRepository; - let statusPagesRepository: IStatusPagesRepository; - let usersRepository: IUsersRepository; - let invitesRepository: IInvitesRepository; - let recoveryTokensRepository: IRecoveryTokensRepository; - let settingsRepository: ISettingsRepository; - let notificationsRepository: INotificationsRepository; - let tagsRepository: ITagsRepository; - let incidentsRepository: IIncidentsRepository; - let teamsRepository: ITeamsRepository; - let maintenanceWindowsRepository: IMaintenanceWindowsRepository; + // ** NOTE ** + // const dbType = envSettings.dbType; + // DB type has been fixed to MongoDB for now + + // if (dbType === "mongodb") { + // db = new MongoDB(logger, envSettings); + // } + + // if (!db) { + // throw new AppError({ message: "Unsupported database type", status: 500 }); + // } + + // await db.connect(); // Repositories - if (dbType === "mongodb") { - monitorsRepository = new MongoMonitorsRepository(); - checksRepository = new MongoChecksRepository(logger); - geoChecksRepository = new MongoGeoChecksRepository(logger); - monitorStatsRepository = new MongoMonitorStatsRepository(); - statusPagesRepository = new MongoStatusPagesRepository(); - usersRepository = new MongoUsersRepository(); - invitesRepository = new MongoInvitesRepository(); - recoveryTokensRepository = new MongoRecoveryTokensRepository(); - settingsRepository = new MongoSettingsRepository(); - notificationsRepository = new MongoNotificationsRepository(); - tagsRepository = new MongoTagsRepository(); - incidentsRepository = new MongoIncidentsRepository(); - teamsRepository = new MongoTeamsRepository(); - maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository(); - } else { - throw new AppError({ message: "Unsupported database type", status: 500 }); - } + // ** NOTE ** + // DB Type fixed to MongoDB for now + // let monitorsRepository: IMonitorsRepository; + // let checksRepository: IChecksRepository; + // let geoChecksRepository: IGeoChecksRepository; + // let monitorStatsRepository: IMonitorStatsRepository; + // let statusPagesRepository: IStatusPagesRepository; + // let usersRepository: IUsersRepository; + // let invitesRepository: IInvitesRepository; + // let recoveryTokensRepository: IRecoveryTokensRepository; + // let settingsRepository: ISettingsRepository; + // let notificationsRepository: INotificationsRepository; + // let tagsRepository: ITagsRepository; + // let incidentsRepository: IIncidentsRepository; + // let teamsRepository: ITeamsRepository; + // let maintenanceWindowsRepository: IMaintenanceWindowsRepository; + + // if (dbType === "mongodb") { + // monitorsRepository = new MongoMonitorsRepository(); + // checksRepository = new MongoChecksRepository(logger); + // geoChecksRepository = new MongoGeoChecksRepository(logger); + // monitorStatsRepository = new MongoMonitorStatsRepository(); + // statusPagesRepository = new MongoStatusPagesRepository(); + // usersRepository = new MongoUsersRepository(); + // invitesRepository = new MongoInvitesRepository(); + // recoveryTokensRepository = new MongoRecoveryTokensRepository(); + // settingsRepository = new MongoSettingsRepository(); + // notificationsRepository = new MongoNotificationsRepository(); + // tagsRepository = new MongoTagsRepository(); + // incidentsRepository = new MongoIncidentsRepository(); + // teamsRepository = new MongoTeamsRepository(); + // maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository(); + // } else { + // throw new AppError({ message: "Unsupported database type", status: 500 }); + // } + + const monitorsRepository = new MongoMonitorsRepository(); + const checksRepository = new MongoChecksRepository(logger); + const geoChecksRepository = new MongoGeoChecksRepository(logger); + const monitorStatsRepository = new MongoMonitorStatsRepository(); + const statusPagesRepository = new MongoStatusPagesRepository(); + const usersRepository = new MongoUsersRepository(); + const invitesRepository = new MongoInvitesRepository(); + const recoveryTokensRepository = new MongoRecoveryTokensRepository(); + const settingsRepository = new MongoSettingsRepository(); + const notificationsRepository = new MongoNotificationsRepository(); + const tagsRepository = new MongoTagsRepository(); + const incidentsRepository = new MongoIncidentsRepository(); + const teamsRepository = new MongoTeamsRepository(); + const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository(); // Inject settings repository into settings service (now that DB is connected) (settingsService as SettingsService).setRepository(settingsRepository); @@ -340,7 +361,7 @@ export const initializeServices = async ({ teamsRepository, }); - const diagnosticService = new DiagnosticService(); + const diagnosticService = new DiagnosticService(db); const inviteService = new InviteService({ invitesRepository, settingsService, diff --git a/server/src/db/IDb.ts b/server/src/db/IDb.ts index 4c719999f..837ad4cf9 100644 --- a/server/src/db/IDb.ts +++ b/server/src/db/IDb.ts @@ -1,4 +1,5 @@ -export interface IDb { +export interface IDb { connect(): Promise; disconnect(): Promise; + getConnection(): Promise; } diff --git a/server/src/db/MongoDB.ts b/server/src/db/MongoDB.ts index 1284aef4d..4531ce5a9 100755 --- a/server/src/db/MongoDB.ts +++ b/server/src/db/MongoDB.ts @@ -1,3 +1,4 @@ +import { Mongoose } from "mongoose"; import mongoose from "mongoose"; import AppSettings from "@/db/models/AppSettings.js"; import { runMigrations } from "@/db/migration/index.js"; @@ -5,16 +6,19 @@ import { ILogger } from "@/utils/logger.js"; import { EnvConfig } from "@/service/system/settingsService.js"; const SERVICE_NAME = "MongoDB"; import { IDb } from "@/db/IDb.js"; +import { AppError } from "@/utils/AppError.js"; -class MongoDB implements IDb { +class MongoDB implements IDb { static SERVICE_NAME = SERVICE_NAME; private logger: ILogger; private envSettings: EnvConfig; + private db: Mongoose | null; constructor(logger: ILogger, envSettings: EnvConfig) { this.logger = logger; this.envSettings = envSettings; + this.db = null; } get serviceName() { @@ -24,7 +28,7 @@ class MongoDB implements IDb { connect = async () => { try { const connectionString = this.envSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db"; - await mongoose.connect(connectionString); + this.db = await mongoose.connect(connectionString); // If there are no AppSettings, create one // TODO why is this here? await AppSettings.findOneAndUpdate( {}, // empty filter to match any document @@ -63,6 +67,13 @@ class MongoDB implements IDb { } }; + getConnection = async (): Promise => { + if (this.db === null) { + throw new AppError({ message: "Database not connected", service: SERVICE_NAME, method: "getConnection" }); + } + return this.db; + }; + disconnect = async () => { try { this.logger.info({ message: "Disconnecting from MongoDB", service: SERVICE_NAME, method: "disconnect" }); diff --git a/server/src/service/business/diagnosticService.ts b/server/src/service/business/diagnosticService.ts index 45a5592f5..49c691175 100644 --- a/server/src/service/business/diagnosticService.ts +++ b/server/src/service/business/diagnosticService.ts @@ -1,5 +1,8 @@ import v8 from "v8"; import os from "os"; +import { IDb } from "@/db/IDb.js"; +import { Mongoose } from "mongoose"; +import { AppError } from "@/utils/AppError.js"; const SERVICE_NAME = "diagnosticService"; @@ -18,7 +21,7 @@ export interface IDiagnosticService { export class DiagnosticService implements IDiagnosticService { static SERVICE_NAME = SERVICE_NAME; - constructor() { + constructor(private db: IDb) { /** * Performance Observer for monitoring system performance metrics. * Clears performance marks after each measurement to prevent memory leaks. @@ -47,6 +50,44 @@ export class DiagnosticService implements IDiagnosticService { return cpuUsage; }; + private getMongoDBStats = async () => { + const mongo = await this.db.getConnection(); + const db = mongo.connection.db; + if (!db) { + throw new AppError({ message: "Database connection is not available", service: SERVICE_NAME, method: "getMongoDBStats" }); + } + + const readyState = mongo.connection.readyState; + const host = mongo.connection.host; + const port = mongo.connection.port; + const dbName = mongo.connection.name; + + const serverStatus = await db.admin().serverStatus(); + const serverInfo = await db.admin().serverInfo(); + + const stats = await db.stats(); + const collections = await db.collections(); + + const collectionStats = await Promise.all( + (collections || []).map(async (collection) => { + const stats = await db.command({ collStats: collection.collectionName }); + const documentCount = await db.collection(collection.collectionName).countDocuments({}); + return { name: collection.collectionName, documentCount, ...stats }; + }) + ); + + return { + readyState, + host, + port, + dbName, + serverStatus, + serverInfo, + stats, + collectionStats, + }; + }; + getSystemStats = async () => { // Memory Usage const totalMemory = os.totalmem(); @@ -93,6 +134,9 @@ export class DiagnosticService implements IDiagnosticService { // Uptime const uptimeMs = process.uptime() * 1000; // ms + // Mongo + const mongoStats = await this.getMongoDBStats(); + // Combine Metrics const diagnostics = { osStats, @@ -101,6 +145,7 @@ export class DiagnosticService implements IDiagnosticService { v8HeapStats: v8Metrics, eventLoopDelayMs: eventLoopDelay, uptimeMs, + mongoStats, }; return diagnostics;