diff --git a/server/src/config/services.ts b/server/src/config/services.ts index b31c8a5e9..5f66c470d 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -46,6 +46,7 @@ import { IIncidentService, INotificationMessageBuilder, ISettingsService, + SettingsService, EnvConfig, } from "@/service/index.js"; @@ -92,9 +93,23 @@ import { MongoInvitesRepository, MongoRecoveryTokensRepository, MongoNotificationsRepository, - MongoIncidentRepository, + MongoIncidentsRepository, MongoTeamsRepository, MongoMaintenanceWindowsRepository, + MongoSettingsRepository, + TimescaleMonitorsRepository, + TimescaleChecksRepository, + TimescaleGeoChecksRepository, + TimescaleMonitorStatsRepository, + TimescaleStatusPagesRepository, + TimescaleUsersRepository, + TimescaleInvitesRepository, + TimescaleRecoveryTokensRepository, + TimescaleNotificationsRepository, + TimescaleIncidentsRepository, + TimescaleTeamsRepository, + TimescaleMaintenanceWindowsRepository, + TimescaleSettingsRepository, IMonitorsRepository, IChecksRepository, IGeoChecksRepository, @@ -110,6 +125,7 @@ import { IMaintenanceWindowsRepository, } from "@/repositories/index.js"; import { ILogger } from "@/utils/logger.js"; +import TimescaleDB from "@/db/TimescaleDB.js"; export type InitializedServices = { settingsService: ISettingsService; @@ -152,32 +168,88 @@ export const initializeServices = async ({ logger, envSettings, settingsService, - settingsRepository, }: { logger: ILogger; envSettings: EnvConfig; settingsService: ISettingsService; - settingsRepository: ISettingsRepository; }): Promise => { // Create DB - const db = new MongoDB(logger, envSettings); + let dbType = "mongodb"; + const connectionString = envSettings.dbConnectionString; + + if (connectionString.startsWith("mongodb")) { + dbType = "mongodb"; + } else if (connectionString.startsWith("postgresql")) { + dbType = "postgresql"; + } + + let db: IDb | null = null; + + if (dbType === "mongodb") { + db = new MongoDB(logger, envSettings); + } else if (dbType === "postgresql") { + db = new TimescaleDB(logger, envSettings); + } + + if (!db) { + throw new Error("Unsupported database type"); + } 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 incidentsRepository: IIncidentsRepository; + let teamsRepository: ITeamsRepository; + let maintenanceWindowsRepository: IMaintenanceWindowsRepository; + // Repositories - 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 notificationsRepository = new MongoNotificationsRepository(); - const incidentsRepository = new MongoIncidentRepository(); - const teamsRepository = new MongoTeamsRepository(); - const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository(); + + 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(); + incidentsRepository = new MongoIncidentsRepository(); + teamsRepository = new MongoTeamsRepository(); + maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository(); + } else { + const pool = db.getPool(); + if (!pool) { + throw new Error("Failed to get database pool"); + } + monitorsRepository = new TimescaleMonitorsRepository(pool); + checksRepository = new TimescaleChecksRepository(pool); + geoChecksRepository = new TimescaleGeoChecksRepository(pool); + monitorStatsRepository = new TimescaleMonitorStatsRepository(pool); + statusPagesRepository = new TimescaleStatusPagesRepository(pool); + usersRepository = new TimescaleUsersRepository(pool); + invitesRepository = new TimescaleInvitesRepository(pool); + recoveryTokensRepository = new TimescaleRecoveryTokensRepository(pool); + settingsRepository = new TimescaleSettingsRepository(pool); + notificationsRepository = new TimescaleNotificationsRepository(pool); + incidentsRepository = new TimescaleIncidentsRepository(pool); + teamsRepository = new TimescaleTeamsRepository(pool); + maintenanceWindowsRepository = new TimescaleMaintenanceWindowsRepository(pool); + } + + // Inject settings repository into settings service (now that DB is connected) + (settingsService as SettingsService).setRepository(settingsRepository); // Network providers const pingProvider = new PingProvider(ping); diff --git a/server/src/index.ts b/server/src/index.ts index 2286d3887..7f8b4593e 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -6,11 +6,9 @@ import { validateEnv } from "./validation/envValidation.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; -import { runMigrations } from "./db/migration/index.js"; import Logger, { ILogger } from "@/utils/logger.js"; import { SettingsService } from "@/service/index.js"; -import { MongoSettingsRepository } from "./repositories/index.js"; const SERVICE_NAME = "Server"; let logger: ILogger; @@ -25,19 +23,15 @@ const startApp = async () => { const openApiSpec = JSON.parse(fs.readFileSync(path.join(__dirname, "../openapi.json"), "utf8")); const frontendPath = path.join(__dirname, "..", "public"); - // Create services - const settingsRepository = new MongoSettingsRepository(); - const settingsService = new SettingsService(settingsRepository, env); - + // Create settings service (env only — DB repository injected after connect) + const settingsService = new SettingsService(env); const envSettings = settingsService.loadSettings(); // Create logger logger = new Logger({ envSettings }); - // Initialize services - const services = await initializeServices({ logger, envSettings, settingsService, settingsRepository }); - - await runMigrations(logger); + // Initialize services (connects DB, creates repositories, injects settingsRepository) + const services = await initializeServices({ logger, envSettings, settingsService }); // Initialize controllers const controllers = initializeControllers(services); diff --git a/server/src/service/system/settingsService.ts b/server/src/service/system/settingsService.ts index 519d2f390..ee90accca 100755 --- a/server/src/service/system/settingsService.ts +++ b/server/src/service/system/settingsService.ts @@ -25,10 +25,9 @@ export interface ISettingsService { export class SettingsService implements ISettingsService { static SERVICE_NAME = SERVICE_NAME; private settings: EnvConfig; - private settingsRepository: ISettingsRepository; + private settingsRepository: ISettingsRepository | null = null; - constructor(settingsRepository: ISettingsRepository, env: ValidatedEnv) { - this.settingsRepository = settingsRepository; + constructor(env: ValidatedEnv) { this.settings = { jwtSecret: env.JWT_SECRET, jwtTTL: env.TOKEN_TTL as StringValue, @@ -39,6 +38,10 @@ export class SettingsService implements ISettingsService { }; } + setRepository(settingsRepository: ISettingsRepository) { + this.settingsRepository = settingsRepository; + } + get serviceName() { return SettingsService.SERVICE_NAME; } @@ -54,18 +57,30 @@ export class SettingsService implements ISettingsService { return this.settings; } + private getRepository(): ISettingsRepository { + if (!this.settingsRepository) { + throw new AppError({ + message: "Settings repository not initialized. Call setRepository() after DB connect.", + status: 500, + service: SERVICE_NAME, + }); + } + return this.settingsRepository; + } + updateDbSettings = async (newSettings: SettingsUpdate) => { - return await this.settingsRepository.update(newSettings); + return await this.getRepository().update(newSettings); }; getDBSettings = async () => { + const repo = this.getRepository(); // Remove any old settings - await this.settingsRepository.deleteLegacy(); + await repo.deleteLegacy(); - let settings = await this.settingsRepository.findSingleton(); + let settings = await repo.findSingleton(); if (settings === null) { - await this.settingsRepository.create({}); - settings = await this.settingsRepository.findSingleton(); + await repo.create({}); + settings = await repo.findSingleton(); } if (!settings) {