system settings
This commit is contained in:
1 parent
986e8b926a
commit
f40667615e
14 files changed
+244
-1
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,41 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import SettingsService from "@/services/system/SettingsService.js";
|
||||
|
||||
export interface ISettingsController {
|
||||
get: (req: Request, res: Response, next: NextFunction) => void;
|
||||
update: (req: Request, res: Response, next: NextFunction) => void;
|
||||
}
|
||||
|
||||
class SettingsController implements ISettingsController {
|
||||
private settingsService: SettingsService;
|
||||
constructor(settingsService: SettingsService) {
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
get = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const settings = await this.settingsService.get();
|
||||
res.status(200).json({
|
||||
message: "Settings retrieved successfully",
|
||||
data: settings,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
update = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const updatedSettings = await this.settingsService.update(req.body);
|
||||
res.status(200).json({
|
||||
message: "Settings updated successfully",
|
||||
data: updatedSettings,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SettingsController;
|
||||
@@ -13,3 +13,4 @@ export { default as DiagnosticController } from "./DiagnosticController.js";
|
||||
export { default as RecoveryController } from "./RecoveryController.js";
|
||||
export { default as ProfileController } from "./ProfileController.js";
|
||||
export { default as IncidentsController } from "./IncidentsController.js";
|
||||
export { default as SettingsController } from "./SettingsController.js";
|
||||
@@ -56,3 +56,6 @@ export type { IRecoveryToken } from "./recovery/RecoveryToken.js";
|
||||
|
||||
export { Incident, ResolutionTypes } from "./incidents/Incident.js";
|
||||
export type { IIncident, ResolutionType } from "./incidents/Incident.js";
|
||||
|
||||
export { SystemSettings } from "./system/SystemSettings.js";
|
||||
export type { ISystemSettings } from "./system/SystemSettings.js";
|
||||
@@ -0,0 +1,45 @@
|
||||
import mongoose, { Schema, Document } from "mongoose";
|
||||
|
||||
export interface ISystemSettings extends Document {
|
||||
systemEmailHost?: string;
|
||||
systemEmailPort?: number;
|
||||
systemEmailAddress?: string;
|
||||
systemEmailPassword?: string;
|
||||
systemEmailUser?: string;
|
||||
systemEmailConnectionHost?: string;
|
||||
systemEmailTLSServername?: string;
|
||||
systemEmailSecure: boolean;
|
||||
systemEmailPool: boolean;
|
||||
systemEmailIgnoreTLS: boolean;
|
||||
systemEmailRequireTLS: boolean;
|
||||
systemEmailRejectUnauthorized: boolean;
|
||||
}
|
||||
|
||||
const SystemSettingsSchema = new Schema<ISystemSettings>(
|
||||
{
|
||||
_id: { type: String, default: "global", immutable: true },
|
||||
systemEmailHost: { type: String },
|
||||
systemEmailPort: { type: Number },
|
||||
systemEmailAddress: { type: String },
|
||||
systemEmailPassword: { type: String },
|
||||
systemEmailUser: { type: String },
|
||||
systemEmailConnectionHost: { type: String, default: "localhost" },
|
||||
systemEmailTLSServername: { type: String },
|
||||
systemEmailSecure: { type: Boolean, default: false },
|
||||
systemEmailPool: { type: Boolean, default: false },
|
||||
systemEmailIgnoreTLS: { type: Boolean, default: false },
|
||||
systemEmailRequireTLS: { type: Boolean, default: false },
|
||||
systemEmailRejectUnauthorized: { type: Boolean, default: true },
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
collection: "system_settings",
|
||||
}
|
||||
);
|
||||
|
||||
SystemSettingsSchema.index({ _id: 1 }, { unique: true });
|
||||
|
||||
export const SystemSettings = mongoose.model<ISystemSettings>(
|
||||
"SystemSettings",
|
||||
SystemSettingsSchema
|
||||
);
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
RecoveryController,
|
||||
ProfileController,
|
||||
IncidentsController,
|
||||
SettingsController,
|
||||
} from "@/controllers/index.js";
|
||||
|
||||
export const initControllers = (services: any) => {
|
||||
@@ -65,5 +66,9 @@ export const initControllers = (services: any) => {
|
||||
services.monitorService
|
||||
);
|
||||
|
||||
controllers.settingsController = new SettingsController(
|
||||
services.settingsService
|
||||
);
|
||||
|
||||
return controllers;
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
RecoveryRoutes,
|
||||
ProfileRoutes,
|
||||
IncidentsRoutes,
|
||||
SettingsRoutes,
|
||||
} from "@/routes/index.js";
|
||||
import { errorHandler } from "@/middleware/ErrorHandler.js";
|
||||
export const initRoutes = (controllers: any, app: Express) => {
|
||||
@@ -44,6 +45,7 @@ export const initRoutes = (controllers: any, app: Express) => {
|
||||
const recoveryRoutes = new RecoveryRoutes(controllers.recoveryController);
|
||||
const profileRoutes = new ProfileRoutes(controllers.profileController);
|
||||
const incidentsRoutes = new IncidentsRoutes(controllers.incidentsController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
|
||||
app.use("/api/v1/auth", authRoutes.getRouter());
|
||||
app.use("/api/v1/invite", inviteRoutes.getRouter());
|
||||
@@ -63,5 +65,6 @@ export const initRoutes = (controllers: any, app: Express) => {
|
||||
app.use("/api/v1/recovery", recoveryRoutes.getRouter());
|
||||
app.use("/api/v1/profile", profileRoutes.getRouter());
|
||||
app.use("/api/v1/incidents", incidentsRoutes.getRouter());
|
||||
app.use("/api/v1/settings", settingsRoutes.getRouter());
|
||||
app.use(errorHandler);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
DiagnosticService,
|
||||
RecoveryService,
|
||||
IncidentService,
|
||||
SettingsService,
|
||||
} from "@/services/index.js";
|
||||
|
||||
export const initServices = async () => {
|
||||
@@ -63,6 +64,8 @@ export const initServices = async () => {
|
||||
const statusPageService = new StatusPageService();
|
||||
const diagnosticService = new DiagnosticService(jobQueue);
|
||||
const recoveryService = new RecoveryService();
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
const services = {
|
||||
checkService,
|
||||
inviteService,
|
||||
@@ -89,6 +92,7 @@ export const initServices = async () => {
|
||||
diagnosticService,
|
||||
recoveryService,
|
||||
incidentService,
|
||||
settingsService,
|
||||
};
|
||||
|
||||
Object.values(services).forEach((service) => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DiagnosticController } from "@/controllers/index.js";
|
||||
import { verifyToken } from "@/middleware/VerifyToken.js";
|
||||
import { verifyOrgPermission } from "@/middleware/VerifyPermission.js";
|
||||
import { addUserContext } from "@/middleware/AddUserContext.js";
|
||||
import { FeatureFlags } from "@/utils/FeatureFlags.js";
|
||||
import { PERMISSIONS } from "@/services/business/AuthService.js";
|
||||
|
||||
class DiagnosticRoutes {
|
||||
|
||||
@@ -15,3 +15,4 @@ export { default as DiagnosticRoutes } from "./diagnostic.js";
|
||||
export { default as RecoveryRoutes } from "./recovery.js";
|
||||
export { default as ProfileRoutes } from "./profile.js";
|
||||
export { default as IncidentsRoutes } from "./incidents.js";
|
||||
export { default as SettingsRoutes } from "./settings.js";
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Router } from "express";
|
||||
import { SettingsController } from "@/controllers/index.js";
|
||||
import { verifyToken } from "@/middleware/VerifyToken.js";
|
||||
import { verifyTeamPermission } from "@/middleware/VerifyPermission.js";
|
||||
import { addUserContext } from "@/middleware/AddUserContext.js";
|
||||
import { PERMISSIONS } from "@/services/business/AuthService.js";
|
||||
import { validateBody } from "@/middleware/validation.js";
|
||||
import { systemSettingsSchema } from "@/validation/index.js";
|
||||
import { verify } from "node:crypto";
|
||||
|
||||
class SettingsRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(settingsController: SettingsController) {
|
||||
this.router = Router();
|
||||
this.controller = settingsController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.get(
|
||||
"/",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
verifyTeamPermission([PERMISSIONS.master]),
|
||||
this.controller.get
|
||||
);
|
||||
|
||||
this.router.patch(
|
||||
"/",
|
||||
verifyToken,
|
||||
addUserContext,
|
||||
verifyTeamPermission([PERMISSIONS.master]),
|
||||
validateBody(systemSettingsSchema),
|
||||
this.controller.update
|
||||
);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsRoutes;
|
||||
@@ -22,5 +22,6 @@ export { default as JobQueue } from "@/services/infrastructure/JobQueue.js";
|
||||
export { default as JobGenerator } from "@/services/infrastructure/JobGenerator.js";
|
||||
|
||||
export { default as ServiceRegistry } from "@/services/system/ServiceRegistry.js";
|
||||
export { default as SettingsService } from "@/services/system/SettingsService.js";
|
||||
|
||||
export * from "@/services/infrastructure/NotificationServices/index.js";
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ISystemSettings, SystemSettings } from "@/db/models/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
export interface ISettingsService {
|
||||
get: () => Promise<ISystemSettings>;
|
||||
update: (settings: Partial<ISystemSettings>) => Promise<ISystemSettings>;
|
||||
}
|
||||
|
||||
class SettingsService implements ISettingsService {
|
||||
public SERVICE_NAME: string;
|
||||
|
||||
constructor() {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
}
|
||||
|
||||
get = async (): Promise<ISystemSettings> => {
|
||||
let settings = await SystemSettings.findById("global");
|
||||
|
||||
if (!settings) {
|
||||
settings = await SystemSettings.create({ _id: "global" });
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
throw new ApiError("Unable to load system settings", 500);
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
update = async (
|
||||
payload: Partial<ISystemSettings>
|
||||
): Promise<ISystemSettings> => {
|
||||
const { _id, createdAt, updatedAt, ...updates } = payload as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const settings = await SystemSettings.findOneAndUpdate(
|
||||
{ _id: "global" },
|
||||
{ $set: updates },
|
||||
{ new: true, upsert: true, setDefaultsOnInsert: true }
|
||||
);
|
||||
|
||||
if (!settings) {
|
||||
throw new ApiError("Unable to update system settings", 500);
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
}
|
||||
|
||||
export default SettingsService;
|
||||
@@ -532,3 +532,46 @@ export const getIncidentsQuerySchema = z.object({
|
||||
export const patchIncidentsBodySchema = z.object({
|
||||
resolutionNote: z.string().max(200).optional(),
|
||||
});
|
||||
|
||||
export const systemSettingsSchema = z
|
||||
.object({
|
||||
systemEmailHost: z.string().trim().min(1).max(255).optional(),
|
||||
systemEmailPort: z
|
||||
.union([z.number().int(), z.string().regex(/^\d+$/)])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? Number(val) : val))
|
||||
.refine(
|
||||
(val) =>
|
||||
val === undefined ||
|
||||
(Number.isInteger(val) && val > 0 && val < 65536),
|
||||
{
|
||||
message: "Port must be between 1 and 65535",
|
||||
}
|
||||
),
|
||||
systemEmailAddress: z.email().optional(),
|
||||
systemEmailPassword: z.string().optional(),
|
||||
systemEmailUser: z.string().trim().max(255).optional(),
|
||||
systemEmailConnectionHost: z.string().trim().max(255).optional(),
|
||||
systemEmailTLSServername: z.string().trim().max(255).optional(),
|
||||
systemEmailSecure: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val)),
|
||||
systemEmailPool: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val)),
|
||||
systemEmailIgnoreTLS: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val)),
|
||||
systemEmailRequireTLS: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val)),
|
||||
systemEmailRejectUnauthorized: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.optional()
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val)),
|
||||
})
|
||||
.strip();
|
||||
Reference in new issue
Block a user