diff --git a/CLAUDE.md b/CLAUDE.md index 63a349ae7..1adfd703f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,19 +68,23 @@ VITE_APP_LOG_LEVEL="debug" ### Backend Layers ``` server/src/ -├── controllers/ # Route handlers (authController, monitorController, etc.) -├── service/ # Business logic -│ ├── business/ # Core monitoring logic -│ ├── infrastructure/ # Server/system utilities -│ └── system/ # App-level settings +├── api/ # HTTP layer +│ ├── controllers/ # Route handlers (authController, monitorController, etc.) +│ ├── middleware/ # verifyJWT, rateLimiter, sanitization, responseHandler +│ ├── routes/ # API route definitions +│ └── validation/ # Zod request-payload schemas +├── domain/ # Business logic + data access, one folder per entity +│ ├── monitors/ # e.g. monitor.service.ts, monitor.repository.ts, monitor.type.ts +│ ├── checks/ +│ ├── incidents/ +│ └── … # users, teams, status-pages, notifications, etc. +├── service/ # Cross-cutting services (e.g. infrastructure/) +├── config/ # App config wiring (controllers, routes, services, envValidation) ├── db/ -│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.) -│ ├── migration/ # Database migrations (run on startup) -│ └── modules/ # Database-specific modules -├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler -├── routes/v1/ # API route definitions -├── validation/ # Joi input validation schemas -└── repositories/ # Data access layer +│ └── migration/ # Database migrations (run on startup) +├── templates/ # Email/notification templates +├── types/ # Shared const tuples + derived types +└── utils/ # Shared utilities ``` ### Frontend Structure diff --git a/client/package.json b/client/package.json index 28a2a44c0..6e9844b28 100644 --- a/client/package.json +++ b/client/package.json @@ -14,7 +14,7 @@ "format-check": "prettier --check ." }, "lint-staged": { - "**/*": "prettier --write" + "**/*": "prettier --write --ignore-unknown" }, "dependencies": { "@emotion/react": "^11.13.3", diff --git a/server/eslint.config.js b/server/eslint.config.js index 817f54bc2..b10e75f6b 100755 --- a/server/eslint.config.js +++ b/server/eslint.config.js @@ -39,7 +39,7 @@ export default [ }, }, rules: { - "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], // Ignore unused variables that start with '_' + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], // Ignore unused variables that start with '_' "@typescript-eslint/no-explicit-any": "warn", // Warn on 'any' types "mocha/max-top-level-suites": "warn", // Warn if there are too many top-level suites instead of failing }, diff --git a/server/jest.config.ts b/server/jest.config.ts index 1faf98181..c56ce1419 100644 --- a/server/jest.config.ts +++ b/server/jest.config.ts @@ -8,7 +8,6 @@ const config: Config = { "^.+\\.(t|j)sx?$": ["ts-jest", { useESM: true, tsconfig: "./tsconfig.jest.json" }], }, moduleNameMapper: { - "^@/validation/(.*)\\.js$": "/src/validation/$1.js", "^@/utils/(.*)\\.js$": "/src/utils/$1.ts", "^@/(.*)\\.ts$": "/src/$1.ts", "^@/(.*)\\.js$": "/src/$1.ts", diff --git a/server/openapi.json b/server/openapi.json index 60d2d3026..2681d7e9a 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -1,11113 +1,13472 @@ { - "openapi": "3.0.0", - "info": { - "title": "Checkmate API", - "version": "1.0.0", - "description": "Generated from Zod validation schemas. Source of truth: server/src/validation/*.ts" - }, - "servers": [ - { - "url": "/api/v1", - "description": "Current server" - } - ], - "tags": [ - { - "name": "auth", - "description": "Sign up, log in, password reset, and the authenticated user's profile." - }, - { - "name": "monitors", - "description": "Configure and inspect monitors (HTTP, port, ping, hardware, docker, game, gRPC) for the caller's team." - }, - { - "name": "checks", - "description": "Individual monitoring check results, including history, summaries, and bulk deletes." - }, - { - "name": "geo-checks", - "description": "Geographic check results for monitors with multi-region probing enabled." - }, - { - "name": "incidents", - "description": "Downtime incidents derived from monitor state changes, including filtering and CSV export." - }, - { - "name": "notifications", - "description": "Notification channels (email, webhook, Slack, Discord, PagerDuty, Matrix, Teams, Telegram, Pushover, Twilio) and test alerts." - }, - { - "name": "maintenance-window", - "description": "Scheduled maintenance windows during which monitors do not generate incidents or alerts." - }, - { - "name": "status-page", - "description": "Public status pages, their configuration, and the monitors they expose." - }, - { - "name": "settings", - "description": "Global application settings (admin/superadmin)." - }, - { - "name": "invite", - "description": "Team invitations and accepting them." - }, - { - "name": "queue", - "description": "Background job queue introspection and admin actions (admin/superadmin)." - }, - { - "name": "diagnostic", - "description": "System diagnostics for the running server (admin/superadmin)." - }, - { - "name": "logs", - "description": "Application logs for the running server (admin/superadmin)." - } - ], - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "AuthPayload": { - "type": "object", - "properties": { - "user": { - "$ref": "#/components/schemas/User" - }, - "token": { - "type": "string" - } - }, - "required": ["user"], - "example": { - "user": { - "_id": "65f1c2a4d8b9e0123456789a", - "firstName": "Ada", - "lastName": "Lovelace", - "email": "ada@example.com", - "role": ["admin"], - "teamId": "65f1c2a4d8b9e01234567890", - "createdAt": "2026-04-01T10:00:00.000Z", - "updatedAt": "2026-04-15T14:30:00.000Z" - }, - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } - }, - "User": { - "type": "object", - "properties": { - "_id": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "role": { - "type": "array", - "items": { - "type": "string" - } - }, - "teamId": { - "type": "string" - }, - "profileImage": { - "type": "string", - "nullable": true - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - }, - "required": ["_id", "firstName", "lastName", "email", "role", "createdAt", "updatedAt"], - "additionalProperties": { - "nullable": true - }, - "example": { - "_id": "65f1c2a4d8b9e0123456789a", - "firstName": "Ada", - "lastName": "Lovelace", - "email": "ada@example.com", - "role": ["admin"], - "teamId": "65f1c2a4d8b9e01234567890", - "createdAt": "2026-04-01T10:00:00.000Z", - "updatedAt": "2026-04-15T14:30:00.000Z" - } - }, - "Incident": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "65f1c2a4d8b9e0123456789c" - }, - "monitorId": { - "type": "string", - "example": "65f1c2a4d8b9e0123456789a" - }, - "teamId": { - "type": "string", - "example": "65f1c2a4d8b9e01234567890" - }, - "startTime": { - "type": "string", - "example": "2026-04-15T03:21:00.000Z" - }, - "endTime": { - "type": "string", - "nullable": true, - "example": "2026-04-15T03:34:00.000Z" - }, - "status": { - "type": "boolean", - "example": true, - "description": "true = resolved, false = ongoing" - }, - "message": { - "type": "string", - "nullable": true, - "example": "HTTP 503 from origin" - }, - "statusCode": { - "type": "number", - "nullable": true, - "example": 503 - }, - "resolutionType": { - "type": "string", - "nullable": true, - "enum": ["automatic", "manual", null], - "example": "automatic" - }, - "resolvedBy": { - "type": "string", - "nullable": true - }, - "resolvedByEmail": { - "type": "string", - "nullable": true - }, - "comment": { - "type": "string", - "nullable": true - }, - "createdAt": { - "type": "string", - "example": "2026-04-15T03:21:00.000Z" - }, - "updatedAt": { - "type": "string", - "example": "2026-04-15T03:34:00.000Z" - } - }, - "required": ["id", "monitorId", "teamId", "startTime", "endTime", "status", "resolutionType", "createdAt", "updatedAt"], - "additionalProperties": { - "nullable": true - } - }, - "IncidentSummary": { - "type": "object", - "properties": { - "total": { - "type": "number", - "example": 12 - }, - "totalActive": { - "type": "number", - "example": 1 - }, - "totalManualResolutions": { - "type": "number", - "example": 2 - }, - "totalAutomaticResolutions": { - "type": "number", - "example": 9 - }, - "avgResolutionTimeHours": { - "type": "number", - "example": 0.5 - }, - "topMonitor": { - "type": "object", - "nullable": true, - "properties": { - "monitorId": { - "type": "string", - "example": "65f1c2a4d8b9e0123456789a" - }, - "monitorName": { - "type": "string", - "nullable": true, - "example": "Marketing site" - }, - "incidentCount": { - "type": "number", - "example": 4 - } - }, - "required": ["monitorId", "monitorName", "incidentCount"] - }, - "latestIncidents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IncidentSummaryItem" - } - } - }, - "required": [ - "total", - "totalActive", - "totalManualResolutions", - "totalAutomaticResolutions", - "avgResolutionTimeHours", - "topMonitor", - "latestIncidents" - ] - }, - "IncidentSummaryItem": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "65f1c2a4d8b9e0123456789c" - }, - "monitorId": { - "type": "string", - "example": "65f1c2a4d8b9e0123456789a" - }, - "monitorName": { - "type": "string", - "nullable": true, - "example": "Marketing site" - }, - "status": { - "type": "boolean", - "example": true - }, - "startTime": { - "type": "string", - "example": "2026-04-15T03:21:00.000Z" - }, - "endTime": { - "type": "string", - "nullable": true, - "example": "2026-04-15T03:34:00.000Z" - }, - "resolutionType": { - "type": "string", - "nullable": true, - "enum": ["automatic", "manual", null], - "example": "automatic" - }, - "message": { - "type": "string", - "nullable": true, - "example": "HTTP 503 from origin" - }, - "statusCode": { - "type": "number", - "nullable": true, - "example": 503 - }, - "createdAt": { - "type": "string", - "example": "2026-04-15T03:21:00.000Z" - } - }, - "required": ["id", "monitorId", "monitorName", "status", "startTime", "endTime", "resolutionType", "message", "statusCode", "createdAt"] - }, - "Monitor": { - "type": "object", - "properties": { - "_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "url": { - "type": "string" - }, - "port": { - "type": "number" - }, - "isActive": { - "type": "boolean" - }, - "interval": { - "type": "number" - }, - "status": { - "type": "string", - "enum": ["up", "down", "paused", "initializing", "maintenance", "breached"] - }, - "statusWindowSize": { - "type": "number" - }, - "statusWindowThreshold": { - "type": "number" - }, - "ignoreTlsErrors": { - "type": "boolean" - }, - "useAdvancedMatching": { - "type": "boolean" - }, - "jsonPath": { - "type": "string" - }, - "expectedValue": { - "type": "string" - }, - "matchMethod": { - "type": "string", - "enum": ["equal", "include", "regex"] - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "secret": { - "type": "string" - }, - "cpuAlertThreshold": { - "type": "number" - }, - "memoryAlertThreshold": { - "type": "number" - }, - "diskAlertThreshold": { - "type": "number" - }, - "tempAlertThreshold": { - "type": "number" - }, - "selectedDisks": { - "type": "array", - "items": { - "type": "string" - } - }, - "gameId": { - "type": "string" - }, - "grpcServiceName": { - "type": "string" - }, - "group": { - "type": "string", - "nullable": true - }, - "geoCheckEnabled": { - "type": "boolean" - }, - "geoCheckLocations": { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - }, - "geoCheckInterval": { - "type": "number" - }, - "dnsServer": { - "type": "string" - }, - "dnsRecordType": { - "type": "string", - "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] - }, - "teamId": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - }, - "required": [ - "_id", - "name", - "type", - "url", - "isActive", - "interval", - "status", - "statusWindowSize", - "statusWindowThreshold", - "ignoreTlsErrors", - "useAdvancedMatching", - "notifications", - "tags", - "cpuAlertThreshold", - "memoryAlertThreshold", - "diskAlertThreshold", - "tempAlertThreshold", - "selectedDisks", - "geoCheckEnabled", - "geoCheckLocations", - "geoCheckInterval", - "teamId", - "userId", - "createdAt", - "updatedAt" - ], - "additionalProperties": { - "nullable": true - }, - "example": { - "_id": "65f1c2a4d8b9e0123456789a", - "name": "Marketing site", - "description": "Production marketing site monitored from the EU region", - "type": "http", - "url": "https://www.example.com", - "port": 443, - "isActive": true, - "interval": 60000, - "status": "up", - "statusWindowSize": 5, - "statusWindowThreshold": 3, - "ignoreTlsErrors": false, - "useAdvancedMatching": false, - "notifications": ["65f1c2a4d8b9e0123456789b"], - "cpuAlertThreshold": 90, - "memoryAlertThreshold": 90, - "diskAlertThreshold": 90, - "tempAlertThreshold": 80, - "selectedDisks": [], - "geoCheckEnabled": false, - "geoCheckLocations": [], - "geoCheckInterval": 300000, - "teamId": "65f1c2a4d8b9e01234567890", - "userId": "65f1c2a4d8b9e01234567891", - "createdAt": "2026-04-01T10:00:00.000Z", - "updatedAt": "2026-04-15T14:30:00.000Z" - } - }, - "NotificationChannelBody": { - "oneOf": [ - { - "$ref": "#/components/schemas/EmailNotification" - }, - { - "$ref": "#/components/schemas/WebhookNotification" - }, - { - "$ref": "#/components/schemas/SlackNotification" - }, - { - "$ref": "#/components/schemas/DiscordNotification" - }, - { - "$ref": "#/components/schemas/PagerDutyNotification" - }, - { - "$ref": "#/components/schemas/MatrixNotification" - }, - { - "$ref": "#/components/schemas/TeamsNotification" - }, - { - "$ref": "#/components/schemas/TelegramNotification" - }, - { - "$ref": "#/components/schemas/PushoverNotification" - }, - { - "$ref": "#/components/schemas/TwilioNotification" - }, - { - "$ref": "#/components/schemas/NtfyNotification" - } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "email": "#/components/schemas/EmailNotification", - "webhook": "#/components/schemas/WebhookNotification", - "slack": "#/components/schemas/SlackNotification", - "discord": "#/components/schemas/DiscordNotification", - "pager_duty": "#/components/schemas/PagerDutyNotification", - "matrix": "#/components/schemas/MatrixNotification", - "teams": "#/components/schemas/TeamsNotification", - "telegram": "#/components/schemas/TelegramNotification", - "pushover": "#/components/schemas/PushoverNotification", - "twilio": "#/components/schemas/TwilioNotification", - "ntfy": "#/components/schemas/NtfyNotification" - } - } - }, - "EmailNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["email"] - }, - "address": { - "type": "string", - "format": "email" - }, - "homeserverUrl": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "roomId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "accessToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "Ops on-call email", - "type": "email", - "address": "alerts@example.com" - } - }, - "WebhookNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["webhook"] - }, - "address": { - "type": "string", - "format": "uri" - }, - "homeserverUrl": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "roomId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "accessToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "Custom webhook", - "type": "webhook", - "address": "https://example.com/hooks/checkmate" - } - }, - "SlackNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["slack"] - }, - "address": { - "type": "string", - "format": "uri" - }, - "homeserverUrl": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "roomId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "accessToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "#alerts", - "type": "slack", - "address": "https://hooks.slack.com/services/T000/B000/XXXX" - } - }, - "DiscordNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["discord"] - }, - "address": { - "type": "string", - "format": "uri" - }, - "homeserverUrl": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "roomId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "accessToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "#status", - "type": "discord", - "address": "https://discord.com/api/webhooks/123/abc" - } - }, - "PagerDutyNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["pager_duty"] - }, - "address": { - "type": "string", - "minLength": 1 - }, - "homeserverUrl": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "roomId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "accessToken": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "PagerDuty primary", - "type": "pager_duty", - "address": "R01XXXXXXXXXXXXXXXXXXXXXXX" - } - }, - "MatrixNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["matrix"] - }, - "address": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "homeserverUrl": { - "type": "string", - "format": "uri" - }, - "roomId": { - "type": "string", - "minLength": 1 - }, - "accessToken": { - "type": "string", - "minLength": 1 - } - }, - "required": ["notificationName", "type", "homeserverUrl", "roomId", "accessToken"], - "example": { - "notificationName": "Matrix room", - "type": "matrix", - "homeserverUrl": "https://matrix.example.com", - "roomId": "!abc123:example.com", - "accessToken": "syt_xxx" - } - }, - "TeamsNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["teams"] - }, - "address": { - "type": "string", - "format": "uri" - } - }, - "required": ["notificationName", "type", "address"], - "example": { - "notificationName": "Teams ops channel", - "type": "teams", - "address": "https://outlook.office.com/webhook/..." - } - }, - "TelegramNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["telegram"] - }, - "address": { - "type": "string", - "minLength": 1 - }, - "accessToken": { - "type": "string", - "minLength": 1 - } - }, - "required": ["notificationName", "type", "address", "accessToken"], - "example": { - "notificationName": "Telegram bot", - "type": "telegram", - "address": "-1001234567890", - "accessToken": "123456:ABC-DEF" - } - }, - "PushoverNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["pushover"] - }, - "address": { - "type": "string", - "minLength": 1 - }, - "accessToken": { - "type": "string", - "minLength": 1 - } - }, - "required": ["notificationName", "type", "address", "accessToken"], - "example": { - "notificationName": "Pushover personal", - "type": "pushover", - "address": "u1234567890abcdef", - "accessToken": "a1234567890abcdef" - } - }, - "TwilioNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["twilio"] - }, - "accountSid": { - "type": "string", - "minLength": 1 - }, - "accessToken": { - "type": "string", - "minLength": 1 - }, - "phone": { - "type": "string", - "minLength": 1 - }, - "twilioPhoneNumber": { - "type": "string", - "minLength": 1 - } - }, - "required": ["notificationName", "type", "accountSid", "accessToken", "phone", "twilioPhoneNumber"], - "example": { - "notificationName": "Twilio SMS", - "type": "twilio", - "accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "accessToken": "your-auth-token", - "phone": "+15551234567", - "twilioPhoneNumber": "+15557654321" - } - }, - "NtfyNotification": { - "type": "object", - "properties": { - "notificationName": { - "type": "string", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": ["ntfy"] - }, - "address": { - "type": "string", - "format": "uri" - }, - "topic": { - "type": "string", - "minLength": 1 - } - }, - "required": ["notificationName", "type", "address", "topic"], - "example": { - "notificationName": "ntfy topic", - "type": "ntfy", - "address": "https://ntfy.sh", - "topic": "checkmate-alerts" - } - } - }, - "parameters": {} - }, - "paths": { - "/auth/register": { - "post": { - "tags": ["auth"], - "summary": "Register a new user (first user becomes superadmin)", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "firstName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "lastName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - }, - "profileImage": { - "type": "string", - "format": "binary" - }, - "inviteToken": { - "type": "string", - "default": "" - } - }, - "required": ["firstName", "lastName", "email", "password", "profileImage"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/AuthPayload" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/login": { - "post": { - "tags": ["auth"], - "summary": "Log in", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "minLength": 1 - } - }, - "required": ["email", "password"] - }, - "example": { - "email": "ada@example.com", - "password": "S3cure!Passw0rd" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/AuthPayload" - } - }, - "required": ["success", "msg", "data"] - }, - "example": { - "success": true, - "msg": "OK", - "data": { - "user": { - "_id": "65f1c2a4d8b9e0123456789a", - "firstName": "Ada", - "lastName": "Lovelace", - "email": "ada@example.com", - "role": ["admin"], - "teamId": "65f1c2a4d8b9e01234567890", - "createdAt": "2026-04-01T10:00:00.000Z", - "updatedAt": "2026-04-15T14:30:00.000Z" - }, - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } - } - } - } - }, - "401": { - "description": "Invalid credentials", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/recovery/request": { - "post": { - "tags": ["auth"], - "summary": "Request a password recovery email", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email" - } - }, - "required": ["email"] - }, - "example": { - "email": "ada@example.com" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/recovery/validate": { - "post": { - "tags": ["auth"], - "summary": "Validate a password recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "recoveryToken": { - "type": "string", - "minLength": 1 - } - }, - "required": ["recoveryToken"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/recovery/reset": { - "post": { - "tags": ["auth"], - "summary": "Reset password using a recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "recoveryToken": { - "type": "string", - "minLength": 1 - }, - "password": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - }, - "confirm": { - "type": "string" - } - }, - "required": ["recoveryToken", "password"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/users/superadmin": { - "get": { - "tags": ["auth"], - "summary": "Check whether a superadmin user exists", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "superAdminExists": { - "type": "boolean" - } - }, - "required": ["superAdminExists"] - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/users": { - "get": { - "tags": ["auth"], - "summary": "List all users (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/User" - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "post": { - "tags": ["auth"], - "summary": "Create a new user (superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "firstName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "lastName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - }, - "role": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "admin", "superadmin", "demo"] - }, - "minItems": 1 - }, - "profileImage": { - "type": "string", - "format": "binary" - } - }, - "required": ["firstName", "lastName", "email", "password", "role", "profileImage"] - } - } - } - }, - "responses": { - "201": { - "description": "User created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/User" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/users/{userId}": { - "get": { - "tags": ["auth"], - "summary": "Get a user by id", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "userId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/User" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "patch": { - "tags": ["auth"], - "summary": "Edit a user (superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "userId", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "firstName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "lastName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "role": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "admin", "superadmin", "demo"] - }, - "minItems": 1 - } - }, - "required": ["firstName", "lastName", "role"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["auth"], - "summary": "Delete a user (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "userId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/users/{userId}/password": { - "patch": { - "tags": ["auth"], - "summary": "Change a user's password (superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "userId", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "password": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - } - }, - "required": ["password"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/auth/user": { - "patch": { - "tags": ["auth"], - "summary": "Edit the currently authenticated user", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "firstName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "lastName": { - "type": "string", - "maxLength": 50, - "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" - }, - "email": { - "type": "string", - "format": "email" - }, - "profilePicture": { - "type": "string" - }, - "password": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - }, - "newPassword": { - "type": "string", - "minLength": 8, - "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" - }, - "profileImage": { - "type": "string", - "format": "binary" - } - }, - "required": ["profileImage"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/User" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["auth"], - "summary": "Delete the currently authenticated user", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/checks/team/summary": { - "get": { - "tags": ["checks"], - "summary": "Aggregate check summary for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": false, - "name": "dateRange", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/checks/team": { - "get": { - "tags": ["checks"], - "summary": "List checks across the team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": true, - "name": "sortOrder", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "limit", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": true, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["all", "up", "down", "resolve"] - }, - "required": false, - "name": "filter", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "ack", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "rowsPerPage", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["checks"], - "summary": "Delete all checks for the team (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "deletedCount": { - "type": "number" - } - }, - "required": ["deletedCount"] - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/checks/{monitorId}": { - "get": { - "tags": ["checks"], - "summary": "List checks for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "required": false, - "name": "type", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": true, - "name": "sortOrder", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "limit", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": true, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["all", "up", "down", "resolve"] - }, - "required": false, - "name": "filter", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "ack", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "rowsPerPage", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "status", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - } - ] - }, - "required": false, - "name": "continent", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["checks"], - "summary": "Delete checks for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/diagnostic/system": { - "get": { - "tags": ["diagnostic"], - "summary": "Get system diagnostics (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/geo-checks/{monitorId}": { - "get": { - "tags": ["geo-checks"], - "summary": "Get geo check results for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "required": false, - "name": "type", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": true, - "name": "sortOrder", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "limit", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": true, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["all", "up", "down", "resolve"] - }, - "required": false, - "name": "filter", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "ack", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "rowsPerPage", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "status", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - } - ] - }, - "required": false, - "name": "continent", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/incidents/team": { - "get": { - "tags": ["incidents"], - "summary": "List incidents for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": true, - "name": "sortOrder", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"], - "default": "all" - }, - "required": false, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "integer", - "nullable": true, - "minimum": 0 - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": true, - "name": "rowsPerPage", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "status", - "in": "query" - }, - { - "schema": { - "type": "string" - }, - "required": false, - "name": "monitorId", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["manual", "automatic"] - }, - "required": false, - "name": "resolutionType", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Incident" - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/incidents/team/summary": { - "get": { - "tags": ["incidents"], - "summary": "Incident summary for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "integer", - "minimum": 1 - }, - "required": false, - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/IncidentSummary" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/incidents/{incidentId}": { - "get": { - "tags": ["incidents"], - "summary": "Get an incident by id", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "incidentId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "incident": { - "$ref": "#/components/schemas/Incident" - }, - "monitor": { - "nullable": true - }, - "user": { - "nullable": true - } - }, - "required": ["incident"] - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/incidents/{incidentId}/resolve": { - "put": { - "tags": ["incidents"], - "summary": "Manually resolve an incident (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string" - }, - "required": true, - "name": "incidentId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/invite/send": { - "post": { - "tags": ["invite"], - "summary": "Send an invite email (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "role": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "admin", "superadmin", "demo"] - }, - "minItems": 1 - }, - "teamId": { - "type": "string", - "minLength": 1 - } - }, - "required": ["email", "role", "teamId"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/invite/verify": { - "post": { - "tags": ["invite"], - "summary": "Verify an invite token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "token": { - "type": "string", - "minLength": 1 - } - }, - "required": ["token"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "role": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["email"], - "additionalProperties": { - "nullable": true - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/invite": { - "post": { - "tags": ["invite"], - "summary": "Create an invite token (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "role": { - "type": "array", - "items": { - "type": "string", - "enum": ["user", "admin", "superadmin", "demo"] - }, - "minItems": 1 - }, - "teamId": { - "type": "string", - "minLength": 1 - } - }, - "required": ["email", "role", "teamId"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "token": { - "type": "string" - } - }, - "required": ["token"], - "additionalProperties": { - "nullable": true - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/logs": { - "get": { - "tags": ["logs"], - "summary": "Get application logs (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/maintenance-window": { - "post": { - "tags": ["maintenance-window"], - "summary": "Create one or more maintenance windows", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "monitors": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - }, - "name": { - "type": "string", - "minLength": 1 - }, - "active": { - "type": "boolean" - }, - "duration": { - "type": "number", - "minimum": 1 - }, - "durationUnit": { - "type": "string", - "enum": ["seconds", "minutes", "hours", "days"] - }, - "start": { - "type": "string", - "nullable": true, - "format": "date-time" - }, - "end": { - "type": "string", - "nullable": true, - "format": "date-time" - }, - "repeat": { - "type": "number", - "minimum": 0 - }, - "expiry": { - "type": "string", - "nullable": true, - "format": "date-time" - } - }, - "required": ["monitors", "name", "duration", "durationUnit", "start", "end", "repeat"], - "additionalProperties": false - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/maintenance-window/team": { - "get": { - "tags": ["maintenance-window"], - "summary": "List maintenance windows for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "active", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "rowsPerPage", - "in": "query" - }, - { - "schema": { - "type": "string" - }, - "required": false, - "name": "field", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": false, - "name": "order", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/maintenance-window/monitor/{monitorId}": { - "get": { - "tags": ["maintenance-window"], - "summary": "List maintenance windows for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/maintenance-window/{id}": { - "get": { - "tags": ["maintenance-window"], - "summary": "Get a maintenance window by id", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "patch": { - "tags": ["maintenance-window"], - "summary": "Edit a maintenance window", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "active": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "repeat": { - "type": "number" - }, - "start": { - "type": "string", - "nullable": true, - "format": "date-time" - }, - "end": { - "type": "string", - "nullable": true, - "format": "date-time" - }, - "expiry": { - "type": "string", - "nullable": true, - "format": "date-time" - }, - "monitors": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - }, - "duration": { - "type": "number" - }, - "durationUnit": { - "type": "string", - "enum": ["seconds", "minutes", "hours", "days"] - } - }, - "additionalProperties": false - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["maintenance-window"], - "summary": "Delete a maintenance window", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/team": { - "get": { - "tags": ["monitors"], - "summary": "List monitors for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - } - } - ] - }, - "required": false, - "name": "type", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "required": false, - "name": "filter", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "required": false, - "name": "tags", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Monitor" - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/team/with-checks": { - "get": { - "tags": ["monitors"], - "summary": "List team monitors with their most recent checks (paginated)", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - }, - "required": false, - "name": "limit", - "in": "query" - }, - { - "schema": { - "type": "integer", - "nullable": true, - "minimum": 0 - }, - "required": false, - "name": "page", - "in": "query" - }, - { - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100 - }, - "required": false, - "name": "rowsPerPage", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "required": false, - "name": "filter", - "in": "query" - }, - { - "schema": { - "type": "string" - }, - "required": false, - "name": "field", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": false, - "name": "order", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - } - } - ] - }, - "required": false, - "name": "type", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "required": false, - "name": "tags", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "explain", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/team/groups": { - "get": { - "tags": ["monitors"], - "summary": "List monitor groups for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/uptime/details/{monitorId}": { - "get": { - "tags": ["monitors"], - "summary": "Get uptime details for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": true, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "normalize", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/hardware/details/{monitorId}": { - "get": { - "tags": ["monitors"], - "summary": "Get hardware metrics detail for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": false, - "name": "dateRange", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/pagespeed/details/{monitorId}": { - "get": { - "tags": ["monitors"], - "summary": "Get PageSpeed detail for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": false, - "name": "dateRange", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/{monitorId}/geo-checks": { - "get": { - "tags": ["monitors"], - "summary": "Get geo check results for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "status", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["asc", "desc"] - }, - "required": false, - "name": "sortOrder", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "limit", - "in": "query" - }, - { - "schema": { - "type": "string", - "enum": ["recent", "hour", "day", "week", "month", "all"] - }, - "required": false, - "name": "dateRange", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "numToDisplay", - "in": "query" - }, - { - "schema": { - "type": "boolean" - }, - "required": false, - "name": "normalize", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - } - ] - }, - "required": false, - "name": "continent", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/pause/{monitorId}": { - "post": { - "tags": ["monitors"], - "summary": "Toggle pause state for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/Monitor" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/certificate/{monitorId}": { - "get": { - "tags": ["monitors"], - "summary": "Get SSL certificate info for a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "certificateDate": { - "type": "string" - } - }, - "required": ["certificateDate"] - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/notifications": { - "patch": { - "tags": ["monitors"], - "summary": "Bulk update notifications across monitors", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "monitorIds": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "maxItems": 100 - }, - "notificationIds": { - "type": "array", - "items": { - "type": "string" - }, - "maxItems": 100 - }, - "action": { - "type": "string", - "enum": ["add", "remove", "set"] - } - }, - "required": ["monitorIds", "notificationIds", "action"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "modifiedCount": { - "type": "number" - } - }, - "required": ["modifiedCount"] - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors": { - "post": { - "tags": ["monitors"], - "summary": "Create a new monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "_id": { - "type": "string" - }, - "name": { - "type": "string", - "minLength": 1 - }, - "description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "statusWindowSize": { - "type": "number", - "minimum": 1, - "maximum": 20, - "default": 5 - }, - "statusWindowThreshold": { - "type": "number", - "minimum": 1, - "maximum": 100, - "default": 60 - }, - "url": { - "type": "string", - "minLength": 1 - }, - "ignoreTlsErrors": { - "type": "boolean", - "default": false - }, - "useAdvancedMatching": { - "type": "boolean", - "default": false - }, - "port": { - "type": "number" - }, - "isActive": { - "type": "boolean" - }, - "interval": { - "type": "number" - }, - "cpuAlertThreshold": { - "type": "number" - }, - "memoryAlertThreshold": { - "type": "number" - }, - "diskAlertThreshold": { - "type": "number" - }, - "tempAlertThreshold": { - "type": "number" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "secret": { - "type": "string" - }, - "jsonPath": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "expectedValue": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "matchMethod": { - "anyOf": [ - { - "type": "string", - "enum": ["equal", "include", "regex"] - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "gameId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "grpcServiceName": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ], - "default": "" - }, - "strategy": { - "type": "string", - "enum": ["desktop", "mobile"] - }, - "selectedDisks": { - "type": "array", - "items": { - "type": "string" - } - }, - "group": { - "anyOf": [ - { - "type": "string", - "maxLength": 50 - }, - { - "nullable": true - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "geoCheckEnabled": { - "type": "boolean" - }, - "geoCheckLocations": { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - }, - "geoCheckInterval": { - "type": "number", - "minimum": 300000 - }, - "dnsServer": { - "type": "string", - "minLength": 1 - }, - "dnsRecordType": { - "type": "string", - "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] - } - }, - "required": ["name", "type", "url"] - } - } - } - }, - "responses": { - "200": { - "description": "Monitor created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/Monitor" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["monitors"], - "summary": "Delete every monitor (superadmin only)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/demo": { - "post": { - "tags": ["monitors"], - "summary": "Insert preconfigured demo monitors", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "number" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/export/json": { - "get": { - "tags": ["monitors"], - "summary": "Export all team monitors as JSON", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/import/json": { - "post": { - "tags": ["monitors"], - "summary": "Import monitors from JSON", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "monitors": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "teamId": { - "type": "string" - }, - "name": { - "type": "string", - "minLength": 1 - }, - "description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "status": { - "type": "string", - "enum": ["up", "down", "paused", "initializing", "maintenance", "breached"], - "default": "initializing" - }, - "statusWindow": { - "type": "array", - "items": { - "type": "boolean" - }, - "default": [] - }, - "statusWindowSize": { - "type": "number", - "minimum": 1, - "maximum": 20, - "default": 5 - }, - "statusWindowThreshold": { - "type": "number", - "minimum": 1, - "maximum": 100, - "default": 60 - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "ignoreTlsErrors": { - "type": "boolean", - "default": false - }, - "useAdvancedMatching": { - "type": "boolean", - "default": false - }, - "jsonPath": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "expectedValue": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "matchMethod": { - "anyOf": [ - { - "type": "string", - "enum": ["equal", "include", "regex"] - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "url": { - "type": "string", - "minLength": 1 - }, - "port": { - "type": "number" - }, - "isActive": { - "type": "boolean", - "default": true - }, - "interval": { - "type": "number", - "default": 60000 - }, - "uptimePercentage": { - "type": "number" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - }, - "default": [] - }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "default": [] - }, - "secret": { - "type": "string" - }, - "cpuAlertThreshold": { - "type": "number", - "default": 100 - }, - "cpuAlertCounter": { - "type": "number", - "default": 5 - }, - "memoryAlertThreshold": { - "type": "number", - "default": 100 - }, - "memoryAlertCounter": { - "type": "number", - "default": 5 - }, - "diskAlertThreshold": { - "type": "number", - "default": 100 - }, - "diskAlertCounter": { - "type": "number", - "default": 5 - }, - "tempAlertThreshold": { - "type": "number", - "default": 100 - }, - "tempAlertCounter": { - "type": "number", - "default": 5 - }, - "selectedDisks": { - "type": "array", - "items": { - "type": "string" - }, - "default": [] - }, - "gameId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "grpcServiceName": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ], - "default": "" - }, - "strategy": { - "type": "string", - "enum": ["desktop", "mobile"] - }, - "group": { - "anyOf": [ - { - "type": "string", - "maxLength": 50 - }, - { - "nullable": true - } - ], - "default": null - }, - "geoCheckEnabled": { - "type": "boolean", - "default": false - }, - "geoCheckLocations": { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - }, - "default": [] - }, - "geoCheckInterval": { - "type": "number", - "minimum": 300000, - "default": 300000 - }, - "dnsServer": { - "type": "string", - "minLength": 1 - }, - "dnsRecordType": { - "type": "string", - "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - }, - "required": ["name", "type", "url"] - }, - "minItems": 1 - } - }, - "required": ["monitors"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "imported": { - "type": "number" - } - }, - "required": ["imported"], - "additionalProperties": { - "nullable": true - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/games": { - "get": { - "tags": ["monitors"], - "summary": "List supported game-server types", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/monitors/{monitorId}": { - "get": { - "tags": ["monitors"], - "summary": "Get a monitor by id", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/Monitor" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "patch": { - "tags": ["monitors"], - "summary": "Edit a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] - }, - "url": { - "type": "string" - }, - "statusWindowSize": { - "type": "number", - "minimum": 1, - "maximum": 20, - "default": 5 - }, - "statusWindowThreshold": { - "type": "number", - "minimum": 1, - "maximum": 100, - "default": 60 - }, - "description": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "interval": { - "type": "number" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "secret": { - "type": "string" - }, - "ignoreTlsErrors": { - "type": "boolean" - }, - "useAdvancedMatching": { - "type": "boolean" - }, - "jsonPath": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "expectedValue": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "matchMethod": { - "anyOf": [ - { - "type": "string", - "enum": ["equal", "include", "regex"] - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "port": { - "type": "number", - "minimum": 1, - "maximum": 65535 - }, - "cpuAlertThreshold": { - "type": "number" - }, - "memoryAlertThreshold": { - "type": "number" - }, - "diskAlertThreshold": { - "type": "number" - }, - "tempAlertThreshold": { - "type": "number" - }, - "gameId": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "grpcServiceName": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "strategy": { - "type": "string", - "enum": ["desktop", "mobile"] - }, - "selectedDisks": { - "type": "array", - "items": { - "type": "string" - } - }, - "group": { - "anyOf": [ - { - "type": "string", - "maxLength": 50 - }, - { - "nullable": true - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "geoCheckEnabled": { - "type": "boolean" - }, - "geoCheckLocations": { - "type": "array", - "items": { - "type": "string", - "enum": ["EU", "NA", "AS", "SA", "AF", "OC"] - } - }, - "geoCheckInterval": { - "type": "number", - "minimum": 300000 - }, - "dnsServer": { - "type": "string", - "minLength": 1 - }, - "dnsRecordType": { - "type": "string", - "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/Monitor" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["monitors"], - "summary": "Delete a monitor", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "monitorId", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "$ref": "#/components/schemas/Monitor" - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/notifications": { - "post": { - "tags": ["notifications"], - "summary": "Create a notification channel", - "description": "Create a notification channel of any supported type. The `type` field discriminates the body shape.", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotificationChannelBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/notifications/test/all": { - "post": { - "tags": ["notifications"], - "summary": "Send a test alert through every notification channel for the team", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "monitorId": { - "type": "string", - "minLength": 1 - } - }, - "required": ["monitorId"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/notifications/test": { - "post": { - "tags": ["notifications"], - "summary": "Send a test alert through a single notification channel", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotificationChannelBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - }, - "required": ["success"], - "additionalProperties": { - "nullable": true - } - } - }, - "required": ["success", "msg", "data"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/notifications/team": { - "get": { - "tags": ["notifications"], - "summary": "List notification channels for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/notifications/{id}": { - "get": { - "tags": ["notifications"], - "summary": "Get a notification channel by id", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["notifications"], - "summary": "Delete a notification channel", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "patch": { - "tags": ["notifications"], - "summary": "Edit a notification channel", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "id", - "in": "path" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotificationChannelBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/queue/jobs": { - "get": { - "tags": ["queue"], - "summary": "List queued monitor jobs (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/queue/metrics": { - "get": { - "tags": ["queue"], - "summary": "Get queue runtime metrics (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/queue/all-metrics": { - "get": { - "tags": ["queue"], - "summary": "Get queue metrics across all monitors (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/queue/flush": { - "post": { - "tags": ["queue"], - "summary": "Flush the monitor job queue (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/settings": { - "get": { - "tags": ["settings"], - "summary": "Get application settings", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "patch": { - "tags": ["settings"], - "summary": "Update application settings (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "checkTTL": { - "type": "integer", - "minimum": 1, - "maximum": 366 - }, - "systemEmailPort": { - "type": "number", - "nullable": true - }, - "pagespeedApiKey": { - "type": "string", - "nullable": true - }, - "language": { - "type": "string" - }, - "timezone": { - "type": "string" - }, - "systemEmailHost": { - "type": "string", - "nullable": true - }, - "systemEmailAddress": { - "type": "string", - "nullable": true - }, - "systemEmailDisplayName": { - "type": "string", - "nullable": true, - "maxLength": 100 - }, - "systemEmailPassword": { - "type": "string", - "nullable": true - }, - "systemEmailUser": { - "type": "string", - "nullable": true - }, - "systemEmailConnectionHost": { - "type": "string", - "nullable": true - }, - "systemEmailTLSServername": { - "type": "string", - "nullable": true - }, - "showURL": { - "type": "boolean" - }, - "systemEmailSecure": { - "type": "boolean" - }, - "systemEmailPool": { - "type": "boolean" - }, - "systemEmailIgnoreTLS": { - "type": "boolean" - }, - "systemEmailRequireTLS": { - "type": "boolean" - }, - "systemEmailRejectUnauthorized": { - "type": "boolean" - }, - "globalThresholds": { - "type": "object", - "properties": { - "cpu": { - "type": "number", - "minimum": 1, - "maximum": 100 - }, - "memory": { - "type": "number", - "minimum": 1, - "maximum": 100 - }, - "disk": { - "type": "number", - "minimum": 1, - "maximum": 100 - }, - "temperature": { - "type": "number", - "minimum": 1, - "maximum": 150 - } - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/settings/test-email": { - "post": { - "tags": ["settings"], - "summary": "Send a test email using current SMTP settings (admin/superadmin)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "to": { - "type": "string", - "minLength": 1 - }, - "systemEmailHost": { - "type": "string" - }, - "systemEmailPort": { - "type": "number" - }, - "systemEmailSecure": { - "type": "boolean" - }, - "systemEmailPool": { - "type": "boolean" - }, - "systemEmailAddress": { - "type": "string" - }, - "systemEmailDisplayName": { - "type": "string" - }, - "systemEmailPassword": { - "type": "string" - }, - "systemEmailUser": { - "type": "string" - }, - "systemEmailConnectionHost": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - }, - "systemEmailIgnoreTLS": { - "type": "boolean" - }, - "systemEmailRequireTLS": { - "type": "boolean" - }, - "systemEmailRejectUnauthorized": { - "type": "boolean" - }, - "systemEmailTLSServername": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "string", - "enum": [""] - } - ] - } - }, - "required": ["to"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/status-page/team": { - "get": { - "tags": ["status-page"], - "summary": "List status pages for the caller's team", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/status-page": { - "post": { - "tags": ["status-page"], - "summary": "Create a status page (logo upload optional)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "type": { - "anyOf": [ - { - "type": "string", - "enum": ["uptime", "infrastructure"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["uptime", "infrastructure"] - } - } - ] - }, - "companyName": { - "type": "string", - "minLength": 1 - }, - "url": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "customDomain": { - "anyOf": [ - { - "type": "string", - "pattern": "^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$" - }, - { - "nullable": true - } - ] - }, - "timezone": { - "type": "string" - }, - "color": { - "type": "string" - }, - "monitors": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{24}$" - }, - "minItems": 1 - }, - "subMonitors": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{24}$" - } - }, - "deleteSubmonitors": { - "type": "boolean" - }, - "isPublished": { - "type": "boolean" - }, - "showCharts": { - "type": "boolean" - }, - "showUptimePercentage": { - "type": "boolean" - }, - "showAdminLoginLink": { - "type": "boolean" - }, - "showInfrastructure": { - "type": "boolean" - }, - "removeLogo": { - "anyOf": [ - { - "type": "string", - "enum": ["true"] - }, - { - "type": "string", - "enum": ["false"] - } - ] - }, - "theme": { - "type": "string", - "enum": ["refined", "modern", "bold", "editorial"] - }, - "themeMode": { - "type": "string", - "enum": ["auto", "light", "dark"] - }, - "logo": { - "type": "string", - "format": "binary" - } - }, - "required": ["type", "companyName", "url", "monitors", "isPublished", "showUptimePercentage", "logo"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/status-page/{id}": { - "put": { - "tags": ["status-page"], - "summary": "Update a status page (logo upload optional)", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "properties": { - "type": { - "anyOf": [ - { - "type": "string", - "enum": ["uptime", "infrastructure"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["uptime", "infrastructure"] - } - } - ] - }, - "companyName": { - "type": "string", - "minLength": 1 - }, - "url": { - "type": "string", - "pattern": "^[a-zA-Z0-9_-]+$" - }, - "customDomain": { - "anyOf": [ - { - "type": "string", - "pattern": "^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$" - }, - { - "nullable": true - } - ] - }, - "timezone": { - "type": "string" - }, - "color": { - "type": "string" - }, - "monitors": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{24}$" - }, - "minItems": 1 - }, - "subMonitors": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9a-fA-F]{24}$" - } - }, - "deleteSubmonitors": { - "type": "boolean" - }, - "isPublished": { - "type": "boolean" - }, - "showCharts": { - "type": "boolean" - }, - "showUptimePercentage": { - "type": "boolean" - }, - "showAdminLoginLink": { - "type": "boolean" - }, - "showInfrastructure": { - "type": "boolean" - }, - "removeLogo": { - "anyOf": [ - { - "type": "string", - "enum": ["true"] - }, - { - "type": "string", - "enum": ["false"] - } - ] - }, - "theme": { - "type": "string", - "enum": ["refined", "modern", "bold", "editorial"] - }, - "themeMode": { - "type": "string", - "enum": ["auto", "light", "dark"] - }, - "logo": { - "type": "string", - "format": "binary" - } - }, - "required": ["type", "companyName", "url", "monitors", "isPublished", "showUptimePercentage", "logo"] - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - }, - "delete": { - "tags": ["status-page"], - "summary": "Delete a status page", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - } - }, - "required": ["success", "msg"] - }, - "example": { - "success": true, - "msg": "OK" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/status-page/resolve": { - "get": { - "tags": ["status-page"], - "summary": "Resolve a published status page by custom domain", - "parameters": [ - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["uptime", "infrastructure"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["uptime", "infrastructure"] - } - } - ] - }, - "required": true, - "name": "type", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "timeFrame", - "in": "query" - }, - { - "schema": { - "type": "string" - }, - "required": false, - "name": "domain", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "404": { - "description": "Status page not found" - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - }, - "/status-page/{url}": { - "get": { - "tags": ["status-page"], - "summary": "Get a public status page by its URL slug", - "parameters": [ - { - "schema": { - "type": "string", - "minLength": 1 - }, - "required": true, - "name": "url", - "in": "path" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "enum": ["uptime", "infrastructure"] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": ["uptime", "infrastructure"] - } - } - ] - }, - "required": true, - "name": "type", - "in": "query" - }, - { - "schema": { - "type": "number", - "nullable": true - }, - "required": false, - "name": "timeFrame", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [true] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - }, - "404": { - "description": "Status page not found" - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "enum": [false] - }, - "msg": { - "type": "string" - }, - "data": { - "nullable": true - } - }, - "required": ["success", "msg"] - } - } - } - } - } - } - } - } + "openapi": "3.0.0", + "info": { + "title": "Checkmate API", + "version": "1.0.0", + "description": "Generated from Zod validation schemas. Source of truth: server/src/api/validation/*.ts" + }, + "servers": [ + { + "url": "/api/v1", + "description": "Current server" + } + ], + "tags": [ + { + "name": "auth", + "description": "Sign up, log in, password reset, and the authenticated user's profile." + }, + { + "name": "monitors", + "description": "Configure and inspect monitors (HTTP, port, ping, hardware, docker, game, gRPC) for the caller's team." + }, + { + "name": "checks", + "description": "Individual monitoring check results, including history, summaries, and bulk deletes." + }, + { + "name": "geo-checks", + "description": "Geographic check results for monitors with multi-region probing enabled." + }, + { + "name": "incidents", + "description": "Downtime incidents derived from monitor state changes, including filtering and CSV export." + }, + { + "name": "notifications", + "description": "Notification channels (email, webhook, Slack, Discord, PagerDuty, Matrix, Teams, Telegram, Pushover, Twilio) and test alerts." + }, + { + "name": "maintenance-window", + "description": "Scheduled maintenance windows during which monitors do not generate incidents or alerts." + }, + { + "name": "status-page", + "description": "Public status pages, their configuration, and the monitors they expose." + }, + { + "name": "settings", + "description": "Global application settings (admin/superadmin)." + }, + { + "name": "invite", + "description": "Team invitations and accepting them." + }, + { + "name": "queue", + "description": "Background job queue introspection and admin actions (admin/superadmin)." + }, + { + "name": "diagnostic", + "description": "System diagnostics for the running server (admin/superadmin)." + }, + { + "name": "logs", + "description": "Application logs for the running server (admin/superadmin)." + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "AuthPayload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/User" + }, + "token": { + "type": "string" + } + }, + "required": [ + "user" + ], + "example": { + "user": { + "_id": "65f1c2a4d8b9e0123456789a", + "firstName": "Ada", + "lastName": "Lovelace", + "email": "ada@example.com", + "role": [ + "admin" + ], + "teamId": "65f1c2a4d8b9e01234567890", + "createdAt": "2026-04-01T10:00:00.000Z", + "updatedAt": "2026-04-15T14:30:00.000Z" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } + }, + "User": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "role": { + "type": "array", + "items": { + "type": "string" + } + }, + "teamId": { + "type": "string" + }, + "profileImage": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "_id", + "firstName", + "lastName", + "email", + "role", + "createdAt", + "updatedAt" + ], + "additionalProperties": { + "nullable": true + }, + "example": { + "_id": "65f1c2a4d8b9e0123456789a", + "firstName": "Ada", + "lastName": "Lovelace", + "email": "ada@example.com", + "role": [ + "admin" + ], + "teamId": "65f1c2a4d8b9e01234567890", + "createdAt": "2026-04-01T10:00:00.000Z", + "updatedAt": "2026-04-15T14:30:00.000Z" + } + }, + "Incident": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "65f1c2a4d8b9e0123456789c" + }, + "monitorId": { + "type": "string", + "example": "65f1c2a4d8b9e0123456789a" + }, + "teamId": { + "type": "string", + "example": "65f1c2a4d8b9e01234567890" + }, + "startTime": { + "type": "string", + "example": "2026-04-15T03:21:00.000Z" + }, + "endTime": { + "type": "string", + "nullable": true, + "example": "2026-04-15T03:34:00.000Z" + }, + "status": { + "type": "boolean", + "example": true, + "description": "true = resolved, false = ongoing" + }, + "message": { + "type": "string", + "nullable": true, + "example": "HTTP 503 from origin" + }, + "statusCode": { + "type": "number", + "nullable": true, + "example": 503 + }, + "resolutionType": { + "type": "string", + "nullable": true, + "enum": [ + "automatic", + "manual", + null + ], + "example": "automatic" + }, + "resolvedBy": { + "type": "string", + "nullable": true + }, + "resolvedByEmail": { + "type": "string", + "nullable": true + }, + "comment": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "example": "2026-04-15T03:21:00.000Z" + }, + "updatedAt": { + "type": "string", + "example": "2026-04-15T03:34:00.000Z" + } + }, + "required": [ + "id", + "monitorId", + "teamId", + "startTime", + "endTime", + "status", + "resolutionType", + "createdAt", + "updatedAt" + ], + "additionalProperties": { + "nullable": true + } + }, + "IncidentSummary": { + "type": "object", + "properties": { + "total": { + "type": "number", + "example": 12 + }, + "totalActive": { + "type": "number", + "example": 1 + }, + "totalManualResolutions": { + "type": "number", + "example": 2 + }, + "totalAutomaticResolutions": { + "type": "number", + "example": 9 + }, + "avgResolutionTimeHours": { + "type": "number", + "example": 0.5 + }, + "topMonitor": { + "type": "object", + "nullable": true, + "properties": { + "monitorId": { + "type": "string", + "example": "65f1c2a4d8b9e0123456789a" + }, + "monitorName": { + "type": "string", + "nullable": true, + "example": "Marketing site" + }, + "incidentCount": { + "type": "number", + "example": 4 + } + }, + "required": [ + "monitorId", + "monitorName", + "incidentCount" + ] + }, + "latestIncidents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncidentSummaryItem" + } + } + }, + "required": [ + "total", + "totalActive", + "totalManualResolutions", + "totalAutomaticResolutions", + "avgResolutionTimeHours", + "topMonitor", + "latestIncidents" + ] + }, + "IncidentSummaryItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "65f1c2a4d8b9e0123456789c" + }, + "monitorId": { + "type": "string", + "example": "65f1c2a4d8b9e0123456789a" + }, + "monitorName": { + "type": "string", + "nullable": true, + "example": "Marketing site" + }, + "status": { + "type": "boolean", + "example": true + }, + "startTime": { + "type": "string", + "example": "2026-04-15T03:21:00.000Z" + }, + "endTime": { + "type": "string", + "nullable": true, + "example": "2026-04-15T03:34:00.000Z" + }, + "resolutionType": { + "type": "string", + "nullable": true, + "enum": [ + "automatic", + "manual", + null + ], + "example": "automatic" + }, + "message": { + "type": "string", + "nullable": true, + "example": "HTTP 503 from origin" + }, + "statusCode": { + "type": "number", + "nullable": true, + "example": 503 + }, + "createdAt": { + "type": "string", + "example": "2026-04-15T03:21:00.000Z" + } + }, + "required": [ + "id", + "monitorId", + "monitorName", + "status", + "startTime", + "endTime", + "resolutionType", + "message", + "statusCode", + "createdAt" + ] + }, + "Monitor": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "url": { + "type": "string" + }, + "port": { + "type": "number" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "number" + }, + "status": { + "type": "string", + "enum": [ + "up", + "down", + "paused", + "initializing", + "maintenance", + "breached" + ] + }, + "statusWindowSize": { + "type": "number" + }, + "statusWindowThreshold": { + "type": "number" + }, + "ignoreTlsErrors": { + "type": "boolean" + }, + "useAdvancedMatching": { + "type": "boolean" + }, + "jsonPath": { + "type": "string" + }, + "expectedValue": { + "type": "string" + }, + "matchMethod": { + "type": "string", + "enum": [ + "equal", + "include", + "regex" + ] + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "customUpCodes": { + "type": "array", + "items": { + "type": "number" + } + }, + "secret": { + "type": "string" + }, + "cpuAlertThreshold": { + "type": "number" + }, + "memoryAlertThreshold": { + "type": "number" + }, + "diskAlertThreshold": { + "type": "number" + }, + "tempAlertThreshold": { + "type": "number" + }, + "selectedDisks": { + "type": "array", + "items": { + "type": "string" + } + }, + "gameId": { + "type": "string" + }, + "grpcServiceName": { + "type": "string" + }, + "group": { + "type": "string", + "nullable": true + }, + "geoCheckEnabled": { + "type": "boolean" + }, + "geoCheckLocations": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + }, + "geoCheckInterval": { + "type": "number" + }, + "dnsServer": { + "type": "string" + }, + "dnsRecordType": { + "type": "string", + "enum": [ + "A", + "AAAA", + "CNAME", + "MX", + "TXT", + "NS" + ] + }, + "teamId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "_id", + "name", + "type", + "url", + "isActive", + "interval", + "status", + "statusWindowSize", + "statusWindowThreshold", + "ignoreTlsErrors", + "useAdvancedMatching", + "notifications", + "tags", + "cpuAlertThreshold", + "memoryAlertThreshold", + "diskAlertThreshold", + "tempAlertThreshold", + "selectedDisks", + "geoCheckEnabled", + "geoCheckLocations", + "geoCheckInterval", + "teamId", + "userId", + "createdAt", + "updatedAt" + ], + "additionalProperties": { + "nullable": true + }, + "example": { + "_id": "65f1c2a4d8b9e0123456789a", + "name": "Marketing site", + "description": "Production marketing site monitored from the EU region", + "type": "http", + "url": "https://www.example.com", + "port": 443, + "isActive": true, + "interval": 60000, + "status": "up", + "statusWindowSize": 5, + "statusWindowThreshold": 3, + "ignoreTlsErrors": false, + "useAdvancedMatching": false, + "notifications": [ + "65f1c2a4d8b9e0123456789b" + ], + "cpuAlertThreshold": 90, + "memoryAlertThreshold": 90, + "diskAlertThreshold": 90, + "tempAlertThreshold": 80, + "selectedDisks": [], + "geoCheckEnabled": false, + "geoCheckLocations": [], + "geoCheckInterval": 300000, + "teamId": "65f1c2a4d8b9e01234567890", + "userId": "65f1c2a4d8b9e01234567891", + "createdAt": "2026-04-01T10:00:00.000Z", + "updatedAt": "2026-04-15T14:30:00.000Z" + } + }, + "NotificationChannelBody": { + "oneOf": [ + { + "$ref": "#/components/schemas/EmailNotification" + }, + { + "$ref": "#/components/schemas/WebhookNotification" + }, + { + "$ref": "#/components/schemas/SlackNotification" + }, + { + "$ref": "#/components/schemas/DiscordNotification" + }, + { + "$ref": "#/components/schemas/PagerDutyNotification" + }, + { + "$ref": "#/components/schemas/MatrixNotification" + }, + { + "$ref": "#/components/schemas/TeamsNotification" + }, + { + "$ref": "#/components/schemas/TelegramNotification" + }, + { + "$ref": "#/components/schemas/PushoverNotification" + }, + { + "$ref": "#/components/schemas/TwilioNotification" + }, + { + "$ref": "#/components/schemas/NtfyNotification" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "email": "#/components/schemas/EmailNotification", + "webhook": "#/components/schemas/WebhookNotification", + "slack": "#/components/schemas/SlackNotification", + "discord": "#/components/schemas/DiscordNotification", + "pager_duty": "#/components/schemas/PagerDutyNotification", + "matrix": "#/components/schemas/MatrixNotification", + "teams": "#/components/schemas/TeamsNotification", + "telegram": "#/components/schemas/TelegramNotification", + "pushover": "#/components/schemas/PushoverNotification", + "twilio": "#/components/schemas/TwilioNotification", + "ntfy": "#/components/schemas/NtfyNotification" + } + } + }, + "EmailNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "email" + ] + }, + "address": { + "type": "string", + "format": "email" + }, + "homeserverUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "roomId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "accessToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "Ops on-call email", + "type": "email", + "address": "alerts@example.com" + } + }, + "WebhookNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + }, + "address": { + "type": "string", + "format": "uri" + }, + "homeserverUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "roomId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "accessToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "Custom webhook", + "type": "webhook", + "address": "https://example.com/hooks/checkmate" + } + }, + "SlackNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "slack" + ] + }, + "address": { + "type": "string", + "format": "uri" + }, + "homeserverUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "roomId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "accessToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "#alerts", + "type": "slack", + "address": "https://hooks.slack.com/services/T000/B000/XXXX" + } + }, + "DiscordNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "discord" + ] + }, + "address": { + "type": "string", + "format": "uri" + }, + "homeserverUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "roomId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "accessToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "#status", + "type": "discord", + "address": "https://discord.com/api/webhooks/123/abc" + } + }, + "PagerDutyNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "pager_duty" + ] + }, + "address": { + "type": "string", + "minLength": 1 + }, + "homeserverUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "roomId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "accessToken": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "PagerDuty primary", + "type": "pager_duty", + "address": "R01XXXXXXXXXXXXXXXXXXXXXXX" + } + }, + "MatrixNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "matrix" + ] + }, + "address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "homeserverUrl": { + "type": "string", + "format": "uri" + }, + "roomId": { + "type": "string", + "minLength": 1 + }, + "accessToken": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "notificationName", + "type", + "homeserverUrl", + "roomId", + "accessToken" + ], + "example": { + "notificationName": "Matrix room", + "type": "matrix", + "homeserverUrl": "https://matrix.example.com", + "roomId": "!abc123:example.com", + "accessToken": "syt_xxx" + } + }, + "TeamsNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "teams" + ] + }, + "address": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "notificationName", + "type", + "address" + ], + "example": { + "notificationName": "Teams ops channel", + "type": "teams", + "address": "https://outlook.office.com/webhook/..." + } + }, + "TelegramNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "telegram" + ] + }, + "address": { + "type": "string", + "minLength": 1 + }, + "accessToken": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "notificationName", + "type", + "address", + "accessToken" + ], + "example": { + "notificationName": "Telegram bot", + "type": "telegram", + "address": "-1001234567890", + "accessToken": "123456:ABC-DEF" + } + }, + "PushoverNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "pushover" + ] + }, + "address": { + "type": "string", + "minLength": 1 + }, + "accessToken": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "notificationName", + "type", + "address", + "accessToken" + ], + "example": { + "notificationName": "Pushover personal", + "type": "pushover", + "address": "u1234567890abcdef", + "accessToken": "a1234567890abcdef" + } + }, + "TwilioNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "twilio" + ] + }, + "accountSid": { + "type": "string", + "minLength": 1 + }, + "accessToken": { + "type": "string", + "minLength": 1 + }, + "phone": { + "type": "string", + "minLength": 1 + }, + "twilioPhoneNumber": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "notificationName", + "type", + "accountSid", + "accessToken", + "phone", + "twilioPhoneNumber" + ], + "example": { + "notificationName": "Twilio SMS", + "type": "twilio", + "accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "accessToken": "your-auth-token", + "phone": "+15551234567", + "twilioPhoneNumber": "+15557654321" + } + }, + "NtfyNotification": { + "type": "object", + "properties": { + "notificationName": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "ntfy" + ] + }, + "address": { + "type": "string", + "format": "uri" + }, + "topic": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "notificationName", + "type", + "address", + "topic" + ], + "example": { + "notificationName": "ntfy topic", + "type": "ntfy", + "address": "https://ntfy.sh", + "topic": "checkmate-alerts" + } + } + }, + "parameters": {} + }, + "paths": { + "/auth/register": { + "post": { + "tags": [ + "auth" + ], + "summary": "Register a new user (first user becomes superadmin)", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "lastName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + }, + "profileImage": { + "type": "string", + "format": "binary" + }, + "inviteToken": { + "type": "string", + "default": "" + } + }, + "required": [ + "firstName", + "lastName", + "email", + "password", + "profileImage" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/AuthPayload" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": [ + "auth" + ], + "summary": "Log in", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "email", + "password" + ] + }, + "example": { + "email": "ada@example.com", + "password": "S3cure!Passw0rd" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/AuthPayload" + } + }, + "required": [ + "success", + "msg", + "data" + ] + }, + "example": { + "success": true, + "msg": "OK", + "data": { + "user": { + "_id": "65f1c2a4d8b9e0123456789a", + "firstName": "Ada", + "lastName": "Lovelace", + "email": "ada@example.com", + "role": [ + "admin" + ], + "teamId": "65f1c2a4d8b9e01234567890", + "createdAt": "2026-04-01T10:00:00.000Z", + "updatedAt": "2026-04-15T14:30:00.000Z" + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } + } + } + } + }, + "401": { + "description": "Invalid credentials", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/recovery/request": { + "post": { + "tags": [ + "auth" + ], + "summary": "Request a password recovery email", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + } + }, + "required": [ + "email" + ] + }, + "example": { + "email": "ada@example.com" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": [ + "auth" + ], + "summary": "Validate a password recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "recoveryToken": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "recoveryToken" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": [ + "auth" + ], + "summary": "Reset password using a recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "recoveryToken": { + "type": "string", + "minLength": 1 + }, + "password": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + }, + "confirm": { + "type": "string" + } + }, + "required": [ + "recoveryToken", + "password" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/users/superadmin": { + "get": { + "tags": [ + "auth" + ], + "summary": "Check whether a superadmin user exists", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "superAdminExists": { + "type": "boolean" + } + }, + "required": [ + "superAdminExists" + ] + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/users": { + "get": { + "tags": [ + "auth" + ], + "summary": "List all users (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "post": { + "tags": [ + "auth" + ], + "summary": "Create a new user (superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "lastName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "user", + "admin", + "superadmin", + "demo" + ] + }, + "minItems": 1 + }, + "profileImage": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "firstName", + "lastName", + "email", + "password", + "role", + "profileImage" + ] + } + } + } + }, + "responses": { + "201": { + "description": "User created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/User" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/users/{userId}": { + "get": { + "tags": [ + "auth" + ], + "summary": "Get a user by id", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "userId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/User" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "patch": { + "tags": [ + "auth" + ], + "summary": "Edit a user (superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "userId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "lastName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "user", + "admin", + "superadmin", + "demo" + ] + }, + "minItems": 1 + } + }, + "required": [ + "firstName", + "lastName", + "role" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "auth" + ], + "summary": "Delete a user (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "userId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/users/{userId}/password": { + "patch": { + "tags": [ + "auth" + ], + "summary": "Change a user's password (superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "userId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + } + }, + "required": [ + "password" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/auth/user": { + "patch": { + "tags": [ + "auth" + ], + "summary": "Edit the currently authenticated user", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "firstName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "lastName": { + "type": "string", + "maxLength": 50, + "pattern": "^(?=.*[\\p{L}\\p{Sc}])[\\p{L}\\p{Sc}\\s'\\-().]+$/u" + }, + "email": { + "type": "string", + "format": "email" + }, + "profilePicture": { + "type": "string" + }, + "password": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + }, + "newPassword": { + "type": "string", + "minLength": 8, + "pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/])[A-Za-z0-9!?@#$%^&*()\\-_=+[\\]{};:'\",.~`|\\\\/]+$" + }, + "profileImage": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "profileImage" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/User" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "auth" + ], + "summary": "Delete the currently authenticated user", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/checks/team/summary": { + "get": { + "tags": [ + "checks" + ], + "summary": "Aggregate check summary for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": false, + "name": "dateRange", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/checks/team": { + "get": { + "tags": [ + "checks" + ], + "summary": "List checks across the team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": true, + "name": "sortOrder", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": true, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "all", + "up", + "down", + "resolve" + ] + }, + "required": false, + "name": "filter", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "ack", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "rowsPerPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "checks" + ], + "summary": "Delete all checks for the team (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "deletedCount": { + "type": "number" + } + }, + "required": [ + "deletedCount" + ] + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/checks/{monitorId}": { + "get": { + "tags": [ + "checks" + ], + "summary": "List checks for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": true, + "name": "sortOrder", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": true, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "all", + "up", + "down", + "resolve" + ] + }, + "required": false, + "name": "filter", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "ack", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "rowsPerPage", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "status", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + } + ] + }, + "required": false, + "name": "continent", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "checks" + ], + "summary": "Delete checks for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/diagnostic/system": { + "get": { + "tags": [ + "diagnostic" + ], + "summary": "Get system diagnostics (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/geo-checks/{monitorId}": { + "get": { + "tags": [ + "geo-checks" + ], + "summary": "Get geo check results for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": true, + "name": "sortOrder", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": true, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "all", + "up", + "down", + "resolve" + ] + }, + "required": false, + "name": "filter", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "ack", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "rowsPerPage", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "status", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + } + ] + }, + "required": false, + "name": "continent", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/incidents/team": { + "get": { + "tags": [ + "incidents" + ], + "summary": "List incidents for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": true, + "name": "sortOrder", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ], + "default": "all" + }, + "required": false, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "minimum": 0 + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": true, + "name": "rowsPerPage", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "status", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "monitorId", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "manual", + "automatic" + ] + }, + "required": false, + "name": "resolutionType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Incident" + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/incidents/team/summary": { + "get": { + "tags": [ + "incidents" + ], + "summary": "Incident summary for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1 + }, + "required": false, + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/IncidentSummary" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/incidents/{incidentId}": { + "get": { + "tags": [ + "incidents" + ], + "summary": "Get an incident by id", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "incidentId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "incident": { + "$ref": "#/components/schemas/Incident" + }, + "monitor": { + "nullable": true + }, + "user": { + "nullable": true + } + }, + "required": [ + "incident" + ] + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/incidents/{incidentId}/resolve": { + "put": { + "tags": [ + "incidents" + ], + "summary": "Manually resolve an incident (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "incidentId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/invite/send": { + "post": { + "tags": [ + "invite" + ], + "summary": "Send an invite email (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "user", + "admin", + "superadmin", + "demo" + ] + }, + "minItems": 1 + }, + "teamId": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "email", + "role", + "teamId" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/invite/verify": { + "post": { + "tags": [ + "invite" + ], + "summary": "Verify an invite token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "token" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "role": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "email" + ], + "additionalProperties": { + "nullable": true + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/invite": { + "post": { + "tags": [ + "invite" + ], + "summary": "Create an invite token (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "role": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "user", + "admin", + "superadmin", + "demo" + ] + }, + "minItems": 1 + }, + "teamId": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "email", + "role", + "teamId" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + }, + "required": [ + "token" + ], + "additionalProperties": { + "nullable": true + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/logs": { + "get": { + "tags": [ + "logs" + ], + "summary": "Get application logs (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/maintenance-window": { + "post": { + "tags": [ + "maintenance-window" + ], + "summary": "Create one or more maintenance windows", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "monitors": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "active": { + "type": "boolean" + }, + "duration": { + "type": "number", + "minimum": 1 + }, + "durationUnit": { + "type": "string", + "enum": [ + "seconds", + "minutes", + "hours", + "days" + ] + }, + "start": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "end": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "repeat": { + "type": "number", + "minimum": 0 + }, + "expiry": { + "type": "string", + "nullable": true, + "format": "date-time" + } + }, + "required": [ + "monitors", + "name", + "duration", + "durationUnit", + "start", + "end", + "repeat" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/maintenance-window/team": { + "get": { + "tags": [ + "maintenance-window" + ], + "summary": "List maintenance windows for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "active", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "rowsPerPage", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": false, + "name": "order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/maintenance-window/monitor/{monitorId}": { + "get": { + "tags": [ + "maintenance-window" + ], + "summary": "List maintenance windows for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/maintenance-window/{id}": { + "get": { + "tags": [ + "maintenance-window" + ], + "summary": "Get a maintenance window by id", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "patch": { + "tags": [ + "maintenance-window" + ], + "summary": "Edit a maintenance window", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "repeat": { + "type": "number" + }, + "start": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "end": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "expiry": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "monitors": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "duration": { + "type": "number" + }, + "durationUnit": { + "type": "string", + "enum": [ + "seconds", + "minutes", + "hours", + "days" + ] + } + }, + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "maintenance-window" + ], + "summary": "Delete a maintenance window", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/team": { + "get": { + "tags": [ + "monitors" + ], + "summary": "List monitors for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + } + } + ] + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "required": false, + "name": "filter", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "required": false, + "name": "tags", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Monitor" + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/team/with-checks": { + "get": { + "tags": [ + "monitors" + ], + "summary": "List team monitors with their most recent checks (paginated)", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "integer", + "nullable": true, + "minimum": 0 + }, + "required": false, + "name": "page", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100 + }, + "required": false, + "name": "rowsPerPage", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "required": false, + "name": "filter", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "field", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": false, + "name": "order", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + } + } + ] + }, + "required": false, + "name": "type", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "required": false, + "name": "tags", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "explain", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/team/groups": { + "get": { + "tags": [ + "monitors" + ], + "summary": "List monitor groups for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/uptime/details/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get uptime details for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": true, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "normalize", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/hardware/details/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get hardware metrics detail for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": false, + "name": "dateRange", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/pagespeed/details/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get PageSpeed detail for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": false, + "name": "dateRange", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/{monitorId}/geo-checks": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get geo check results for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "status", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "required": false, + "name": "sortOrder", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "limit", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "recent", + "hour", + "day", + "week", + "month", + "all" + ] + }, + "required": false, + "name": "dateRange", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "numToDisplay", + "in": "query" + }, + { + "schema": { + "type": "boolean" + }, + "required": false, + "name": "normalize", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + } + ] + }, + "required": false, + "name": "continent", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/pause/{monitorId}": { + "post": { + "tags": [ + "monitors" + ], + "summary": "Toggle pause state for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/Monitor" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/certificate/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get SSL certificate info for a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "certificateDate": { + "type": "string" + } + }, + "required": [ + "certificateDate" + ] + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/notifications": { + "patch": { + "tags": [ + "monitors" + ], + "summary": "Bulk update notifications across monitors", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "monitorIds": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "maxItems": 100 + }, + "notificationIds": { + "type": "array", + "items": { + "type": "string" + }, + "maxItems": 100 + }, + "action": { + "type": "string", + "enum": [ + "add", + "remove", + "set" + ] + } + }, + "required": [ + "monitorIds", + "notificationIds", + "action" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "modifiedCount": { + "type": "number" + } + }, + "required": [ + "modifiedCount" + ] + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors": { + "post": { + "tags": [ + "monitors" + ], + "summary": "Create a new monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "type": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "statusWindowSize": { + "type": "number", + "minimum": 1, + "maximum": 20, + "default": 5 + }, + "statusWindowThreshold": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 60 + }, + "url": { + "type": "string", + "minLength": 1 + }, + "ignoreTlsErrors": { + "type": "boolean", + "default": false + }, + "useAdvancedMatching": { + "type": "boolean", + "default": false + }, + "port": { + "type": "number" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "number" + }, + "cpuAlertThreshold": { + "type": "number" + }, + "memoryAlertThreshold": { + "type": "number" + }, + "diskAlertThreshold": { + "type": "number" + }, + "tempAlertThreshold": { + "type": "number" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "customUpCodes": { + "type": "array", + "items": { + "type": "number" + }, + "default": [] + }, + "secret": { + "type": "string" + }, + "jsonPath": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "expectedValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "matchMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "equal", + "include", + "regex" + ] + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "gameId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "grpcServiceName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ], + "default": "" + }, + "strategy": { + "type": "string", + "enum": [ + "desktop", + "mobile" + ] + }, + "selectedDisks": { + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "nullable": true + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "geoCheckEnabled": { + "type": "boolean" + }, + "geoCheckLocations": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + }, + "geoCheckInterval": { + "type": "number", + "minimum": 300000 + }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": [ + "A", + "AAAA", + "CNAME", + "MX", + "TXT", + "NS" + ] + } + }, + "required": [ + "name", + "type", + "url" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Monitor created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/Monitor" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "monitors" + ], + "summary": "Delete every monitor (superadmin only)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/demo": { + "post": { + "tags": [ + "monitors" + ], + "summary": "Insert preconfigured demo monitors", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "number" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/export/json": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Export all team monitors as JSON", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/import/json": { + "post": { + "tags": [ + "monitors" + ], + "summary": "Import monitors from JSON", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "monitors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "status": { + "type": "string", + "enum": [ + "up", + "down", + "paused", + "initializing", + "maintenance", + "breached" + ], + "default": "initializing" + }, + "statusWindow": { + "type": "array", + "items": { + "type": "boolean" + }, + "default": [] + }, + "statusWindowSize": { + "type": "number", + "minimum": 1, + "maximum": 20, + "default": 5 + }, + "statusWindowThreshold": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 60 + }, + "type": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "ignoreTlsErrors": { + "type": "boolean", + "default": false + }, + "useAdvancedMatching": { + "type": "boolean", + "default": false + }, + "jsonPath": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "expectedValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "matchMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "equal", + "include", + "regex" + ] + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "url": { + "type": "string", + "minLength": 1 + }, + "port": { + "type": "number" + }, + "isActive": { + "type": "boolean", + "default": true + }, + "interval": { + "type": "number", + "default": 60000 + }, + "uptimePercentage": { + "type": "number" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "customUpCodes": { + "type": "array", + "items": { + "type": "number" + }, + "default": [] + }, + "secret": { + "type": "string" + }, + "cpuAlertThreshold": { + "type": "number", + "default": 100 + }, + "cpuAlertCounter": { + "type": "number", + "default": 5 + }, + "memoryAlertThreshold": { + "type": "number", + "default": 100 + }, + "memoryAlertCounter": { + "type": "number", + "default": 5 + }, + "diskAlertThreshold": { + "type": "number", + "default": 100 + }, + "diskAlertCounter": { + "type": "number", + "default": 5 + }, + "tempAlertThreshold": { + "type": "number", + "default": 100 + }, + "tempAlertCounter": { + "type": "number", + "default": 5 + }, + "selectedDisks": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "gameId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "grpcServiceName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ], + "default": "" + }, + "strategy": { + "type": "string", + "enum": [ + "desktop", + "mobile" + ] + }, + "group": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "nullable": true + } + ], + "default": null + }, + "geoCheckEnabled": { + "type": "boolean", + "default": false + }, + "geoCheckLocations": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + }, + "default": [] + }, + "geoCheckInterval": { + "type": "number", + "minimum": 300000, + "default": 300000 + }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": [ + "A", + "AAAA", + "CNAME", + "MX", + "TXT", + "NS" + ] + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "name", + "type", + "url" + ] + }, + "minItems": 1 + } + }, + "required": [ + "monitors" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "imported": { + "type": "number" + } + }, + "required": [ + "imported" + ], + "additionalProperties": { + "nullable": true + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/games": { + "get": { + "tags": [ + "monitors" + ], + "summary": "List supported game-server types", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/monitors/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "summary": "Get a monitor by id", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/Monitor" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "patch": { + "tags": [ + "monitors" + ], + "summary": "Edit a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed", + "hardware", + "docker", + "port", + "game", + "grpc", + "websocket", + "dns", + "unknown" + ] + }, + "url": { + "type": "string" + }, + "statusWindowSize": { + "type": "number", + "minimum": 1, + "maximum": 20, + "default": 5 + }, + "statusWindowThreshold": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 60 + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "interval": { + "type": "number" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "customUpCodes": { + "type": "array", + "items": { + "type": "number" + } + }, + "secret": { + "type": "string" + }, + "ignoreTlsErrors": { + "type": "boolean" + }, + "useAdvancedMatching": { + "type": "boolean" + }, + "jsonPath": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "expectedValue": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "matchMethod": { + "anyOf": [ + { + "type": "string", + "enum": [ + "equal", + "include", + "regex" + ] + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "port": { + "type": "number", + "minimum": 1, + "maximum": 65535 + }, + "cpuAlertThreshold": { + "type": "number" + }, + "memoryAlertThreshold": { + "type": "number" + }, + "diskAlertThreshold": { + "type": "number" + }, + "tempAlertThreshold": { + "type": "number" + }, + "gameId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "grpcServiceName": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "strategy": { + "type": "string", + "enum": [ + "desktop", + "mobile" + ] + }, + "selectedDisks": { + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "anyOf": [ + { + "type": "string", + "maxLength": 50 + }, + { + "nullable": true + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "geoCheckEnabled": { + "type": "boolean" + }, + "geoCheckLocations": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "EU", + "NA", + "AS", + "SA", + "AF", + "OC" + ] + } + }, + "geoCheckInterval": { + "type": "number", + "minimum": 300000 + }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": [ + "A", + "AAAA", + "CNAME", + "MX", + "TXT", + "NS" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/Monitor" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "monitors" + ], + "summary": "Delete a monitor", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "monitorId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/Monitor" + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/notifications": { + "post": { + "tags": [ + "notifications" + ], + "summary": "Create a notification channel", + "description": "Create a notification channel of any supported type. The `type` field discriminates the body shape.", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationChannelBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/notifications/test/all": { + "post": { + "tags": [ + "notifications" + ], + "summary": "Send a test alert through every notification channel for the team", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "monitorId": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "monitorId" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/notifications/test": { + "post": { + "tags": [ + "notifications" + ], + "summary": "Send a test alert through a single notification channel", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationChannelBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "additionalProperties": { + "nullable": true + } + } + }, + "required": [ + "success", + "msg", + "data" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/notifications/team": { + "get": { + "tags": [ + "notifications" + ], + "summary": "List notification channels for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/notifications/{id}": { + "get": { + "tags": [ + "notifications" + ], + "summary": "Get a notification channel by id", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "notifications" + ], + "summary": "Delete a notification channel", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "patch": { + "tags": [ + "notifications" + ], + "summary": "Edit a notification channel", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "id", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationChannelBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/queue/jobs": { + "get": { + "tags": [ + "queue" + ], + "summary": "List queued monitor jobs (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/queue/metrics": { + "get": { + "tags": [ + "queue" + ], + "summary": "Get queue runtime metrics (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/queue/all-metrics": { + "get": { + "tags": [ + "queue" + ], + "summary": "Get queue metrics across all monitors (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/queue/flush": { + "post": { + "tags": [ + "queue" + ], + "summary": "Flush the monitor job queue (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/settings": { + "get": { + "tags": [ + "settings" + ], + "summary": "Get application settings", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "patch": { + "tags": [ + "settings" + ], + "summary": "Update application settings (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "checkTTL": { + "type": "integer", + "minimum": 1, + "maximum": 366 + }, + "systemEmailPort": { + "type": "number", + "nullable": true + }, + "pagespeedApiKey": { + "type": "string", + "nullable": true + }, + "language": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "systemEmailHost": { + "type": "string", + "nullable": true + }, + "systemEmailAddress": { + "type": "string", + "nullable": true + }, + "systemEmailDisplayName": { + "type": "string", + "nullable": true, + "maxLength": 100 + }, + "systemEmailPassword": { + "type": "string", + "nullable": true + }, + "systemEmailUser": { + "type": "string", + "nullable": true + }, + "systemEmailConnectionHost": { + "type": "string", + "nullable": true + }, + "systemEmailTLSServername": { + "type": "string", + "nullable": true + }, + "showURL": { + "type": "boolean" + }, + "systemEmailSecure": { + "type": "boolean" + }, + "systemEmailPool": { + "type": "boolean" + }, + "systemEmailIgnoreTLS": { + "type": "boolean" + }, + "systemEmailRequireTLS": { + "type": "boolean" + }, + "systemEmailRejectUnauthorized": { + "type": "boolean" + }, + "globalThresholds": { + "type": "object", + "properties": { + "cpu": { + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "memory": { + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "disk": { + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "temperature": { + "type": "number", + "minimum": 1, + "maximum": 150 + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/settings/test-email": { + "post": { + "tags": [ + "settings" + ], + "summary": "Send a test email using current SMTP settings (admin/superadmin)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "to": { + "type": "string", + "minLength": 1 + }, + "systemEmailHost": { + "type": "string" + }, + "systemEmailPort": { + "type": "number" + }, + "systemEmailSecure": { + "type": "boolean" + }, + "systemEmailPool": { + "type": "boolean" + }, + "systemEmailAddress": { + "type": "string" + }, + "systemEmailDisplayName": { + "type": "string" + }, + "systemEmailPassword": { + "type": "string" + }, + "systemEmailUser": { + "type": "string" + }, + "systemEmailConnectionHost": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "systemEmailIgnoreTLS": { + "type": "boolean" + }, + "systemEmailRequireTLS": { + "type": "boolean" + }, + "systemEmailRejectUnauthorized": { + "type": "boolean" + }, + "systemEmailTLSServername": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "" + ] + } + ] + } + }, + "required": [ + "to" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/status-page/team": { + "get": { + "tags": [ + "status-page" + ], + "summary": "List status pages for the caller's team", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/status-page": { + "post": { + "tags": [ + "status-page" + ], + "summary": "Create a status page (logo upload optional)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + } + } + ] + }, + "companyName": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "customDomain": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$" + }, + { + "nullable": true + } + ] + }, + "timezone": { + "type": "string" + }, + "color": { + "type": "string" + }, + "monitors": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{24}$" + }, + "minItems": 1 + }, + "subMonitors": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{24}$" + } + }, + "deleteSubmonitors": { + "type": "boolean" + }, + "isPublished": { + "type": "boolean" + }, + "showCharts": { + "type": "boolean" + }, + "showUptimePercentage": { + "type": "boolean" + }, + "showAdminLoginLink": { + "type": "boolean" + }, + "showInfrastructure": { + "type": "boolean" + }, + "removeLogo": { + "anyOf": [ + { + "type": "string", + "enum": [ + "true" + ] + }, + { + "type": "string", + "enum": [ + "false" + ] + } + ] + }, + "theme": { + "type": "string", + "enum": [ + "refined", + "modern", + "bold", + "editorial" + ] + }, + "themeMode": { + "type": "string", + "enum": [ + "auto", + "light", + "dark" + ] + }, + "logo": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "type", + "companyName", + "url", + "monitors", + "isPublished", + "showUptimePercentage", + "logo" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/status-page/{id}": { + "put": { + "tags": [ + "status-page" + ], + "summary": "Update a status page (logo upload optional)", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + } + } + ] + }, + "companyName": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$" + }, + "customDomain": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?=.{1,253}$)([a-zA-Z0-9_](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$" + }, + { + "nullable": true + } + ] + }, + "timezone": { + "type": "string" + }, + "color": { + "type": "string" + }, + "monitors": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{24}$" + }, + "minItems": 1 + }, + "subMonitors": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-fA-F]{24}$" + } + }, + "deleteSubmonitors": { + "type": "boolean" + }, + "isPublished": { + "type": "boolean" + }, + "showCharts": { + "type": "boolean" + }, + "showUptimePercentage": { + "type": "boolean" + }, + "showAdminLoginLink": { + "type": "boolean" + }, + "showInfrastructure": { + "type": "boolean" + }, + "removeLogo": { + "anyOf": [ + { + "type": "string", + "enum": [ + "true" + ] + }, + { + "type": "string", + "enum": [ + "false" + ] + } + ] + }, + "theme": { + "type": "string", + "enum": [ + "refined", + "modern", + "bold", + "editorial" + ] + }, + "themeMode": { + "type": "string", + "enum": [ + "auto", + "light", + "dark" + ] + }, + "logo": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "type", + "companyName", + "url", + "monitors", + "isPublished", + "showUptimePercentage", + "logo" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "status-page" + ], + "summary": "Delete a status page", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + } + }, + "required": [ + "success", + "msg" + ] + }, + "example": { + "success": true, + "msg": "OK" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/status-page/resolve": { + "get": { + "tags": [ + "status-page" + ], + "summary": "Resolve a published status page by custom domain", + "parameters": [ + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + } + } + ] + }, + "required": true, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "timeFrame", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "domain", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "404": { + "description": "Status page not found" + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + }, + "/status-page/{url}": { + "get": { + "tags": [ + "status-page" + ], + "summary": "Get a public status page by its URL slug", + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1 + }, + "required": true, + "name": "url", + "in": "path" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "uptime", + "infrastructure" + ] + } + } + ] + }, + "required": true, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "number", + "nullable": true + }, + "required": false, + "name": "timeFrame", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + true + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + }, + "404": { + "description": "Status page not found" + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "enum": [ + false + ] + }, + "msg": { + "type": "string" + }, + "data": { + "nullable": true + } + }, + "required": [ + "success", + "msg" + ] + } + } + } + } + } + } + } + } } diff --git a/server/openapi/index.ts b/server/openapi/index.ts index 9758c9d54..ef3e0b1d0 100644 --- a/server/openapi/index.ts +++ b/server/openapi/index.ts @@ -28,7 +28,7 @@ export function getOpenApiSpec(): JsonObject { info: { title: "Checkmate API", version: "1.0.0", - description: "Generated from Zod validation schemas. Source of truth: server/src/validation/*.ts", + description: "Generated from Zod validation schemas. Source of truth: server/src/api/validation/*.ts", }, servers: [{ url: "/api/v1", description: "Current server" }], tags: [ diff --git a/server/openapi/routes/auth.ts b/server/openapi/routes/auth.ts index 66b74382b..72f4cd2d2 100644 --- a/server/openapi/routes/auth.ts +++ b/server/openapi/routes/auth.ts @@ -7,7 +7,7 @@ import { recoveryValidation, recoveryTokenBodyValidation, newPasswordValidation, -} from "@/validation/authValidation.js"; +} from "@/api/validation/authValidation.js"; import { getUserByIdParamValidation, editUserByIdParamValidation, @@ -16,7 +16,7 @@ import { createUserBodyValidation, editUserBodyValidation, userResponseSchema, -} from "@/validation/userValidation.js"; +} from "@/api/validation/userValidation.js"; const tags = ["auth"]; diff --git a/server/openapi/routes/check.ts b/server/openapi/routes/check.ts index a9b6b3804..98ec87b7b 100644 --- a/server/openapi/routes/check.ts +++ b/server/openapi/routes/check.ts @@ -7,7 +7,7 @@ import { getTeamChecksQueryValidation, deleteChecksParamValidation, getChecksSummaryByTeamIdQueryValidation, -} from "@/validation/checkValidation.js"; +} from "@/api/validation/checkValidation.js"; const tags = ["checks"]; diff --git a/server/openapi/routes/geoCheck.ts b/server/openapi/routes/geoCheck.ts index 7b90d0a0d..2650fb3be 100644 --- a/server/openapi/routes/geoCheck.ts +++ b/server/openapi/routes/geoCheck.ts @@ -1,6 +1,6 @@ import { registry } from "../registry.js"; import { bearer, okUnknown, standardErrors } from "../helpers.js"; -import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js"; +import { getChecksParamValidation, getChecksQueryValidation } from "@/api/validation/checkValidation.js"; registry.registerPath({ method: "get", diff --git a/server/openapi/routes/incident.ts b/server/openapi/routes/incident.ts index 6497a5bd0..6621adc2b 100644 --- a/server/openapi/routes/incident.ts +++ b/server/openapi/routes/incident.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { registry } from "../registry.js"; import { bearer, okJson, okJsonNoData, standardErrors } from "../helpers.js"; -import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/validation/incidentValidation.js"; +import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/api/validation/incidentValidation.js"; const tags = ["incidents"]; diff --git a/server/openapi/routes/invite.ts b/server/openapi/routes/invite.ts index bf9b568b3..7e31bd2e0 100644 --- a/server/openapi/routes/invite.ts +++ b/server/openapi/routes/invite.ts @@ -1,7 +1,7 @@ import { registry } from "../registry.js"; import { bearer, json, okJson, okJsonNoData, standardErrors } from "../helpers.js"; import { z } from "zod"; -import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/authValidation.js"; +import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js"; const tags = ["invite"]; diff --git a/server/openapi/routes/maintenanceWindow.ts b/server/openapi/routes/maintenanceWindow.ts index 6afc57031..8d0506846 100644 --- a/server/openapi/routes/maintenanceWindow.ts +++ b/server/openapi/routes/maintenanceWindow.ts @@ -8,7 +8,7 @@ import { deleteMaintenanceWindowByIdParamValidation, editMaintenanceWindowByIdParamValidation, editMaintenanceByIdWindowBodyValidation, -} from "@/validation/maintenanceWindowValidation.js"; +} from "@/api/validation/maintenanceWindowValidation.js"; const tags = ["maintenance-window"]; diff --git a/server/openapi/routes/monitor.ts b/server/openapi/routes/monitor.ts index 10f988fa8..a941e54bf 100644 --- a/server/openapi/routes/monitor.ts +++ b/server/openapi/routes/monitor.ts @@ -16,8 +16,8 @@ import { getHardwareDetailsByIdParamValidation, getHardwareDetailsByIdQueryValidation, monitorResponseSchema, -} from "@/validation/monitorValidation.js"; -import { updateNotificationsValidation } from "@/validation/notificationValidation.js"; +} from "@/api/validation/monitorValidation.js"; +import { updateNotificationsValidation } from "@/api/validation/notificationValidation.js"; const tags = ["monitors"]; diff --git a/server/openapi/routes/notification.ts b/server/openapi/routes/notification.ts index c66e387f6..f1c70ca6d 100644 --- a/server/openapi/routes/notification.ts +++ b/server/openapi/routes/notification.ts @@ -7,7 +7,7 @@ import { getNotificationByIdParamValidation, editNotificationParamValidation, testAllNotificationsBodyValidation, -} from "@/validation/notificationValidation.js"; +} from "@/api/validation/notificationValidation.js"; const tags = ["notifications"]; diff --git a/server/openapi/routes/settings.ts b/server/openapi/routes/settings.ts index 1297c3e85..4871d9c7b 100644 --- a/server/openapi/routes/settings.ts +++ b/server/openapi/routes/settings.ts @@ -1,7 +1,7 @@ import { registry } from "../registry.js"; import { bearer, json, okJsonNoData, okUnknown, standardErrors } from "../helpers.js"; -import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js"; -import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js"; +import { updateAppSettingsBodyValidation } from "@/api/validation/settingsValidation.js"; +import { sendTestEmailBodyValidation } from "@/api/validation/notificationValidation.js"; const tags = ["settings"]; diff --git a/server/openapi/routes/statusPage.ts b/server/openapi/routes/statusPage.ts index d17ab88f5..7ecc6e6d7 100644 --- a/server/openapi/routes/statusPage.ts +++ b/server/openapi/routes/statusPage.ts @@ -5,7 +5,7 @@ import { getStatusPageParamValidation, getStatusPageQueryValidation, resolveStatusPageQueryValidation, -} from "@/validation/statusPageValidation.js"; +} from "@/api/validation/statusPageValidation.js"; const tags = ["status-page"]; diff --git a/server/package.json b/server/package.json index 2812b9b1f..2ac0d47f9 100755 --- a/server/package.json +++ b/server/package.json @@ -22,7 +22,7 @@ "format-check": "prettier --check ." }, "lint-staged": { - "**/*": "prettier --write" + "**/*": "prettier --write --ignore-unknown" }, "keywords": [], "author": "", diff --git a/server/src/controllers/authController.ts b/server/src/api/controllers/authController.ts similarity index 97% rename from server/src/controllers/authController.ts rename to server/src/api/controllers/authController.ts index 20303b4eb..45f806a8c 100755 --- a/server/src/controllers/authController.ts +++ b/server/src/api/controllers/authController.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { AppError } from "@/utils/AppError.js"; -import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/controllers/controllerUtils.js"; +import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/api/controllers/controllerUtils.js"; import { registrationBodyValidation, @@ -8,7 +8,7 @@ import { recoveryValidation, recoveryTokenBodyValidation, newPasswordValidation, -} from "@/validation/authValidation.js"; +} from "@/api/validation/authValidation.js"; import { editUserBodyValidation, @@ -18,8 +18,8 @@ import { editUserByIdBodyValidation, editSuperadminUserByIdBodyValidation, editUserPasswordByIdBodyValidation, -} from "@/validation/userValidation.js"; -import { IUserService } from "@/service/index.js"; +} from "@/api/validation/userValidation.js"; +import { IUserService } from "@/domain/users/user.service.js"; const SERVICE_NAME = "authController"; diff --git a/server/src/controllers/checkController.ts b/server/src/api/controllers/checkController.ts similarity index 95% rename from server/src/controllers/checkController.ts rename to server/src/api/controllers/checkController.ts index dfe64e80a..23fbe2227 100755 --- a/server/src/controllers/checkController.ts +++ b/server/src/api/controllers/checkController.ts @@ -6,9 +6,9 @@ import { getTeamChecksQueryValidation, getChecksSummaryByTeamIdQueryValidation, deleteChecksParamValidation, -} from "@/validation/checkValidation.js"; -import { ICheckService } from "@/service/index.js"; -import { requireTeamId } from "@/controllers/controllerUtils.js"; +} from "@/api/validation/checkValidation.js"; +import { ICheckService } from "@/domain/checks/check.service.js"; +import { requireTeamId } from "@/api/controllers/controllerUtils.js"; const SERVICE_NAME = "checkController"; diff --git a/server/src/controllers/controllerUtils.ts b/server/src/api/controllers/controllerUtils.ts similarity index 96% rename from server/src/controllers/controllerUtils.ts rename to server/src/api/controllers/controllerUtils.ts index 67dc71a70..45bf3fa9f 100755 --- a/server/src/controllers/controllerUtils.ts +++ b/server/src/api/controllers/controllerUtils.ts @@ -1,5 +1,6 @@ import { AppError } from "@/utils/AppError.js"; -import { Monitor, type MonitorType, MonitorTypes, UserRole } from "@/types/index.js"; +import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js"; +import { UserRole } from "@/domain/users/user.type.js"; import sslChecker, { SSLDetails } from "ssl-checker"; type SSLCheckerType = typeof sslChecker; diff --git a/server/src/controllers/diagnosticController.ts b/server/src/api/controllers/diagnosticController.ts similarity index 91% rename from server/src/controllers/diagnosticController.ts rename to server/src/api/controllers/diagnosticController.ts index ffe7c6f5f..efc383e1a 100755 --- a/server/src/controllers/diagnosticController.ts +++ b/server/src/api/controllers/diagnosticController.ts @@ -1,4 +1,4 @@ -import { IDiagnosticService } from "@/service/index.js"; +import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js"; import { Request, Response, NextFunction } from "express"; const SERVICE_NAME = "diagnosticController"; diff --git a/server/src/controllers/geoCheckController.ts b/server/src/api/controllers/geoCheckController.ts similarity index 90% rename from server/src/controllers/geoCheckController.ts rename to server/src/api/controllers/geoCheckController.ts index 96f9beeac..a50176188 100644 --- a/server/src/controllers/geoCheckController.ts +++ b/server/src/api/controllers/geoCheckController.ts @@ -1,8 +1,7 @@ import { Request, Response, NextFunction } from "express"; -import { getChecksParamValidation, getChecksQueryValidation } from "@/validation/checkValidation.js"; -import type { IGeoChecksService } from "@/service/business/geoChecksService.js"; +import { getChecksParamValidation, getChecksQueryValidation } from "@/api/validation/checkValidation.js"; +import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js"; import { requireTeamId } from "./controllerUtils.js"; -import { AppError } from "@/utils/AppError.js"; const SERVICE_NAME = "geoCheckController"; diff --git a/server/src/controllers/incidentController.ts b/server/src/api/controllers/incidentController.ts similarity index 96% rename from server/src/controllers/incidentController.ts rename to server/src/api/controllers/incidentController.ts index 03f1c7fe4..6358d2a89 100644 --- a/server/src/controllers/incidentController.ts +++ b/server/src/api/controllers/incidentController.ts @@ -1,8 +1,8 @@ import { AppError } from "@/utils/AppError.js"; import { Request, Response, NextFunction } from "express"; import { requireTeamId, requireUserId, requireUserEmail, extractString } from "./controllerUtils.js"; -import { IIncidentService } from "@/service/index.js"; -import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/validation/incidentValidation.js"; +import { IIncidentService } from "@/domain/incidents/incident.service.js"; +import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/api/validation/incidentValidation.js"; const SERVICE_NAME = "IncidentController"; diff --git a/server/src/controllers/inviteController.ts b/server/src/api/controllers/inviteController.ts similarity index 94% rename from server/src/controllers/inviteController.ts rename to server/src/api/controllers/inviteController.ts index 700a7bc6a..c77385f7b 100755 --- a/server/src/controllers/inviteController.ts +++ b/server/src/api/controllers/inviteController.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; -import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/validation/authValidation.js"; -import { requireFirstName, requireTeamId, requireUserRoles } from "@/controllers/controllerUtils.js"; -import { IInviteService } from "@/service/index.js"; +import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js"; +import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js"; +import { IInviteService } from "@/domain/invites/invite.service.js"; const SERVICE_NAME = "inviteController"; export interface IInviteController { diff --git a/server/src/controllers/logController.ts b/server/src/api/controllers/logController.ts similarity index 100% rename from server/src/controllers/logController.ts rename to server/src/api/controllers/logController.ts diff --git a/server/src/controllers/maintenanceWindowController.ts b/server/src/api/controllers/maintenanceWindowController.ts similarity index 95% rename from server/src/controllers/maintenanceWindowController.ts rename to server/src/api/controllers/maintenanceWindowController.ts index 441acfa80..ddbf4deca 100644 --- a/server/src/controllers/maintenanceWindowController.ts +++ b/server/src/api/controllers/maintenanceWindowController.ts @@ -7,9 +7,9 @@ import { getMaintenanceWindowsByMonitorIdParamValidation, getMaintenanceWindowsByTeamIdQueryValidation, deleteMaintenanceWindowByIdParamValidation, -} from "@/validation/maintenanceWindowValidation.js"; -import { requireTeamId } from "@/controllers/controllerUtils.js"; -import { IMaintenanceWindowService } from "@/service/index.js"; +} from "@/api/validation/maintenanceWindowValidation.js"; +import { requireTeamId } from "@/api/controllers/controllerUtils.js"; +import { IMaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js"; export interface IMaintenanceWindowController { createMaintenanceWindows: (req: Request, res: Response, next: NextFunction) => Promise; diff --git a/server/src/controllers/monitorController.ts b/server/src/api/controllers/monitorController.ts similarity index 97% rename from server/src/controllers/monitorController.ts rename to server/src/api/controllers/monitorController.ts index 25946ccdf..a0cb073d2 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/api/controllers/monitorController.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from "express"; -import { updateNotificationsValidation } from "@/validation/notificationValidation.js"; +import { updateNotificationsValidation } from "@/api/validation/notificationValidation.js"; import { getMonitorByIdParamValidation, getMonitorByIdQueryValidation, @@ -15,11 +15,12 @@ import { getUptimeDetailsByIdQueryValidation, importMonitorsBodyValidation, bulkPauseMonitorBodyValidation, -} from "@/validation/monitorValidation.js"; +} from "@/api/validation/monitorValidation.js"; import sslChecker from "ssl-checker"; -import { fetchMonitorCertificate, requireTeamId, requireUserId } from "@/controllers/controllerUtils.js"; +import { fetchMonitorCertificate, requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.js"; import { AppError } from "@/utils/AppError.js"; -import { IMonitorService, INotificationsService } from "@/service/index.js"; +import { IMonitorService } from "@/domain/monitors/monitor.service.js"; +import { INotificationsService } from "@/domain/notifications/notification.service.js"; const SERVICE_NAME = "monitorController"; diff --git a/server/src/controllers/notificationController.ts b/server/src/api/controllers/notificationController.ts similarity index 95% rename from server/src/controllers/notificationController.ts rename to server/src/api/controllers/notificationController.ts index 301c15446..3aecfcad8 100644 --- a/server/src/controllers/notificationController.ts +++ b/server/src/api/controllers/notificationController.ts @@ -7,11 +7,11 @@ import { testNotificationBodyValidation, editNotificationParamValidation, testAllNotificationsBodyValidation, -} from "@/validation/notificationValidation.js"; +} from "@/api/validation/notificationValidation.js"; import { AppError } from "@/utils/AppError.js"; -import { INotificationsService } from "@/service/index.js"; +import { INotificationsService } from "@/domain/notifications/notification.service.js"; import { requireTeamId, requireUserId } from "./controllerUtils.js"; -import { IMonitorsRepository } from "@/repositories/index.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; const SERVICE_NAME = "NotificationController"; diff --git a/server/src/controllers/queueController.ts b/server/src/api/controllers/queueController.ts similarity index 93% rename from server/src/controllers/queueController.ts rename to server/src/api/controllers/queueController.ts index 3b0bb64ce..0561ba6f0 100755 --- a/server/src/controllers/queueController.ts +++ b/server/src/api/controllers/queueController.ts @@ -1,5 +1,5 @@ -import { IJobQueue } from "@/service/index.js"; -import { getQueueJobsQueryValidation } from "@/validation/queueValidation.js"; +import { IJobQueue } from "@/service/job-queues/job-queue.interface.js"; +import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js"; import { Request, Response, NextFunction } from "express"; const SERVICE_NAME = "JobQueueController"; diff --git a/server/src/controllers/settingsController.ts b/server/src/api/controllers/settingsController.ts similarity index 91% rename from server/src/controllers/settingsController.ts rename to server/src/api/controllers/settingsController.ts index 485a90d64..4acfdf552 100644 --- a/server/src/controllers/settingsController.ts +++ b/server/src/api/controllers/settingsController.ts @@ -1,9 +1,10 @@ import { Request, Response, NextFunction } from "express"; -import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js"; -import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js"; +import { updateAppSettingsBodyValidation } from "@/api/validation/settingsValidation.js"; +import { sendTestEmailBodyValidation } from "@/api/validation/notificationValidation.js"; import { AppError } from "@/utils/AppError.js"; -import { IEmailService, ISettingsService } from "@/service/index.js"; -import { Settings } from "@/types/settings.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; +import { IEmailService } from "@/service/emailService.js"; +import { Settings } from "@/domain/app-settings/app-settings.type.js"; const SERVICE_NAME = "SettingsController"; diff --git a/server/src/controllers/statusPageController.ts b/server/src/api/controllers/statusPageController.ts similarity index 93% rename from server/src/controllers/statusPageController.ts rename to server/src/api/controllers/statusPageController.ts index 1a7ed2347..394d76e23 100644 --- a/server/src/controllers/statusPageController.ts +++ b/server/src/api/controllers/statusPageController.ts @@ -6,15 +6,15 @@ import { getStatusPageQueryValidation, imageValidation, resolveStatusPageQueryValidation, -} from "@/validation/statusPageValidation.js"; +} from "@/api/validation/statusPageValidation.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 { requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.js"; +import { IStatusPageService } from "@/domain/status-pages/status-page.service.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import { NormalizeData } from "@/utils/dataUtils.js"; import { resolveStatusPageDomainFromRequest } from "@/utils/statusPageDomain.js"; -import type { StatusPage } from "@/types/statusPage.js"; +import type { StatusPage } from "@/domain/status-pages/status-page.type.js"; const SERVICE_NAME = "statusPageController"; diff --git a/server/src/controllers/statusPageThemeFlag.ts b/server/src/api/controllers/statusPageThemeFlag.ts similarity index 95% rename from server/src/controllers/statusPageThemeFlag.ts rename to server/src/api/controllers/statusPageThemeFlag.ts index bd65d5cb2..0fc2baedc 100644 --- a/server/src/controllers/statusPageThemeFlag.ts +++ b/server/src/api/controllers/statusPageThemeFlag.ts @@ -1,4 +1,4 @@ -import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, type StatusPage } from "@/types/statusPage.js"; +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, type StatusPage } from "@/domain/status-pages/status-page.type.js"; /** * When STATUS_PAGE_THEMES_ENABLED is false, the controller drops incoming diff --git a/server/src/controllers/tagController.ts b/server/src/api/controllers/tagController.ts similarity index 96% rename from server/src/controllers/tagController.ts rename to server/src/api/controllers/tagController.ts index 66b21dc6b..95dd81296 100644 --- a/server/src/controllers/tagController.ts +++ b/server/src/api/controllers/tagController.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from "express"; -import { ITagsService } from "@/service/index.js"; +import { ITagsService } from "@/domain/tags/tag.service.js"; import { requireTeamId } from "./controllerUtils.js"; import { createTagBodyValidation, @@ -7,7 +7,7 @@ import { getTagByIdParamValidation, editTagParamValidation, deleteTagParamValidation, -} from "@/validation/index.js"; +} from "@/api/validation/index.js"; export interface ITagsController { createTag: (req: Request, res: Response, next: NextFunction) => Promise; diff --git a/server/src/middleware/handleErrors.ts b/server/src/api/middleware/handleErrors.ts similarity index 100% rename from server/src/middleware/handleErrors.ts rename to server/src/api/middleware/handleErrors.ts diff --git a/server/src/middleware/isAllowed.ts b/server/src/api/middleware/isAllowed.ts similarity index 92% rename from server/src/middleware/isAllowed.ts rename to server/src/api/middleware/isAllowed.ts index 21690ee38..67cc68f46 100755 --- a/server/src/middleware/isAllowed.ts +++ b/server/src/api/middleware/isAllowed.ts @@ -1,7 +1,7 @@ import type { Request, Response, NextFunction } from "express"; const SERVICE_NAME = "allowedRoles"; import { AppError } from "@/utils/AppError.js"; -import type { UserRole } from "@/types/index.js"; +import type { UserRole } from "@/domain/users/user.type.js"; const isAllowed = (allowedRoles: UserRole[]) => { return (req: Request, res: Response, next: NextFunction) => { diff --git a/server/src/middleware/rateLimiter.ts b/server/src/api/middleware/rateLimiter.ts similarity index 100% rename from server/src/middleware/rateLimiter.ts rename to server/src/api/middleware/rateLimiter.ts diff --git a/server/src/middleware/sanitization.ts b/server/src/api/middleware/sanitization.ts similarity index 100% rename from server/src/middleware/sanitization.ts rename to server/src/api/middleware/sanitization.ts diff --git a/server/src/middleware/statusPageCorsOrigin.ts b/server/src/api/middleware/statusPageCorsOrigin.ts similarity index 89% rename from server/src/middleware/statusPageCorsOrigin.ts rename to server/src/api/middleware/statusPageCorsOrigin.ts index 0ec25c1c0..7a48ca45f 100644 --- a/server/src/middleware/statusPageCorsOrigin.ts +++ b/server/src/api/middleware/statusPageCorsOrigin.ts @@ -1,5 +1,5 @@ import type { CorsOptions } from "cors"; -import type { IStatusPagesRepository } from "@/repositories/index.js"; +import type { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js"; export const createStatusPageCorsOrigin = (clientHost: string, statusPagesRepository: IStatusPagesRepository): CorsOptions["origin"] => { diff --git a/server/src/middleware/verifyJWT.ts b/server/src/api/middleware/verifyJWT.ts similarity index 93% rename from server/src/middleware/verifyJWT.ts rename to server/src/api/middleware/verifyJWT.ts index 63218566c..0f0f28021 100644 --- a/server/src/middleware/verifyJWT.ts +++ b/server/src/api/middleware/verifyJWT.ts @@ -2,8 +2,8 @@ import { NextFunction, Request, Response } from "express"; import jwt from "jsonwebtoken"; import { AppError } from "@/utils/AppError.js"; -import type { ISettingsService } from "@/service/system/settingsService.js"; -import type { User } from "@/types/user.js"; +import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; +import type { User } from "@/domain/users/user.type.js"; const SERVICE_NAME = "verifyJWT"; const TOKEN_PREFIX = "Bearer "; diff --git a/server/src/middleware/verifyStatusPageAccess.ts b/server/src/api/middleware/verifyStatusPageAccess.ts similarity index 88% rename from server/src/middleware/verifyStatusPageAccess.ts rename to server/src/api/middleware/verifyStatusPageAccess.ts index 25ea29c51..bd2d062a3 100644 --- a/server/src/middleware/verifyStatusPageAccess.ts +++ b/server/src/api/middleware/verifyStatusPageAccess.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, RequestHandler, Response } from "express"; -import { IStatusPagesRepository } from "@/repositories/index.js"; +import { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; import { AppError } from "@/utils/AppError.js"; export const createVerifyStatusPageAccess = (statusPagesRepository: IStatusPagesRepository, verifyJWT: RequestHandler) => { diff --git a/server/src/routes/authRoute.ts b/server/src/api/routes/authRoute.ts old mode 100755 new mode 100644 similarity index 96% rename from server/src/routes/authRoute.ts rename to server/src/api/routes/authRoute.ts index af6fd4266..22055ae40 --- a/server/src/routes/authRoute.ts +++ b/server/src/api/routes/authRoute.ts @@ -1,7 +1,7 @@ import { Router, RequestHandler } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; import multer from "multer"; -import { IAuthController } from "@/controllers/authController.js"; +import { IAuthController } from "@/api/controllers/authController.js"; const upload = multer(); diff --git a/server/src/routes/checkRoute.ts b/server/src/api/routes/checkRoute.ts old mode 100755 new mode 100644 similarity index 91% rename from server/src/routes/checkRoute.ts rename to server/src/api/routes/checkRoute.ts index 712bae969..171d79ab4 --- a/server/src/routes/checkRoute.ts +++ b/server/src/api/routes/checkRoute.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; -import { ICheckController } from "@/controllers/checkController.js"; +import { ICheckController } from "@/api/controllers/checkController.js"; class CheckRoutes { private router: Router; diff --git a/server/src/routes/diagnosticRoute.ts b/server/src/api/routes/diagnosticRoute.ts old mode 100755 new mode 100644 similarity index 88% rename from server/src/routes/diagnosticRoute.ts rename to server/src/api/routes/diagnosticRoute.ts index 32b857e8d..3fe29bc1e --- a/server/src/routes/diagnosticRoute.ts +++ b/server/src/api/routes/diagnosticRoute.ts @@ -1,6 +1,6 @@ import { RequestHandler, Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; -import { IDiagnosticController } from "@/controllers/diagnosticController.js"; +import { IDiagnosticController } from "@/api/controllers/diagnosticController.js"; class DiagnosticRoutes { private router: Router; diff --git a/server/src/routes/geoCheckRoutes.ts b/server/src/api/routes/geoCheckRoutes.ts similarity index 85% rename from server/src/routes/geoCheckRoutes.ts rename to server/src/api/routes/geoCheckRoutes.ts index 1fc48d7b3..bf9178afe 100644 --- a/server/src/routes/geoCheckRoutes.ts +++ b/server/src/api/routes/geoCheckRoutes.ts @@ -1,4 +1,4 @@ -import { IGeoCheckController } from "@/controllers/geoCheckController.js"; +import { IGeoCheckController } from "@/api/controllers/geoCheckController.js"; import { Router } from "express"; class GeoCheckRoutes { diff --git a/server/src/routes/incidentRoute.ts b/server/src/api/routes/incidentRoute.ts similarity index 91% rename from server/src/routes/incidentRoute.ts rename to server/src/api/routes/incidentRoute.ts index fdbb6f532..0f2f4485c 100644 --- a/server/src/routes/incidentRoute.ts +++ b/server/src/api/routes/incidentRoute.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; -import { IIncidentController } from "@/controllers/incidentController.js"; +import { IIncidentController } from "@/api/controllers/incidentController.js"; class IncidentRoutes { private router: Router; diff --git a/server/src/routes/inviteRoute.ts b/server/src/api/routes/inviteRoute.ts old mode 100755 new mode 100644 similarity index 91% rename from server/src/routes/inviteRoute.ts rename to server/src/api/routes/inviteRoute.ts index 4542bd21e..2e25694d2 --- a/server/src/routes/inviteRoute.ts +++ b/server/src/api/routes/inviteRoute.ts @@ -1,6 +1,6 @@ import { RequestHandler, Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; -import { IInviteController } from "@/controllers/inviteController.js"; +import { IInviteController } from "@/api/controllers/inviteController.js"; class InviteRoutes { private router: Router; diff --git a/server/src/routes/logRoutes.ts b/server/src/api/routes/logRoutes.ts old mode 100755 new mode 100644 similarity index 77% rename from server/src/routes/logRoutes.ts rename to server/src/api/routes/logRoutes.ts index ea03d690e..ef98b5189 --- a/server/src/routes/logRoutes.ts +++ b/server/src/api/routes/logRoutes.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "@/middleware/isAllowed.js"; -import { ILogController } from "@/controllers/logController.js"; +import { isAllowed } from "@/api/middleware/isAllowed.js"; +import { ILogController } from "@/api/controllers/logController.js"; class LogRoutes { private router: Router; private logController: ILogController; diff --git a/server/src/routes/maintenanceWindowRoute.ts b/server/src/api/routes/maintenanceWindowRoute.ts old mode 100755 new mode 100644 similarity index 90% rename from server/src/routes/maintenanceWindowRoute.ts rename to server/src/api/routes/maintenanceWindowRoute.ts index 1c2c139c0..43d03ff34 --- a/server/src/routes/maintenanceWindowRoute.ts +++ b/server/src/api/routes/maintenanceWindowRoute.ts @@ -1,4 +1,4 @@ -import { IMaintenanceWindowController } from "@/controllers/maintenanceWindowController.js"; +import { IMaintenanceWindowController } from "@/api/controllers/maintenanceWindowController.js"; import { Router } from "express"; class MaintenanceWindowRoutes { diff --git a/server/src/routes/monitorRoute.ts b/server/src/api/routes/monitorRoute.ts old mode 100755 new mode 100644 similarity index 95% rename from server/src/routes/monitorRoute.ts rename to server/src/api/routes/monitorRoute.ts index 0b5e24dd0..29834ce70 --- a/server/src/routes/monitorRoute.ts +++ b/server/src/api/routes/monitorRoute.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "@/middleware/isAllowed.js"; -import { IMonitorController } from "@/controllers/monitorController.js"; +import { isAllowed } from "@/api/middleware/isAllowed.js"; +import { IMonitorController } from "@/api/controllers/monitorController.js"; class MonitorRoutes { private router: Router; diff --git a/server/src/routes/notificationRoute.ts b/server/src/api/routes/notificationRoute.ts old mode 100755 new mode 100644 similarity index 91% rename from server/src/routes/notificationRoute.ts rename to server/src/api/routes/notificationRoute.ts index 4ec6ce3b6..16d9a7086 --- a/server/src/routes/notificationRoute.ts +++ b/server/src/api/routes/notificationRoute.ts @@ -1,4 +1,4 @@ -import { INotificationController } from "@/controllers/notificationController.js"; +import { INotificationController } from "@/api/controllers/notificationController.js"; import { Router } from "express"; class NotificationRoutes { private router: Router; diff --git a/server/src/routes/queueRoute.ts b/server/src/api/routes/queueRoute.ts old mode 100755 new mode 100644 similarity index 84% rename from server/src/routes/queueRoute.ts rename to server/src/api/routes/queueRoute.ts index d5d35147c..0a9d41bb1 --- a/server/src/routes/queueRoute.ts +++ b/server/src/api/routes/queueRoute.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -import { isAllowed } from "@/middleware/isAllowed.js"; -import { IJobQueueController } from "@/controllers/queueController.js"; +import { isAllowed } from "@/api/middleware/isAllowed.js"; +import { IJobQueueController } from "@/api/controllers/queueController.js"; class QueueRoutes { private router: Router; private queueController: IJobQueueController; diff --git a/server/src/routes/settingsRoute.ts b/server/src/api/routes/settingsRoute.ts old mode 100755 new mode 100644 similarity index 90% rename from server/src/routes/settingsRoute.ts rename to server/src/api/routes/settingsRoute.ts index aec2319c2..3bb0e75fd --- a/server/src/routes/settingsRoute.ts +++ b/server/src/api/routes/settingsRoute.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import { isAllowed } from "../middleware/isAllowed.js"; -import { ISettingsController } from "@/controllers/settingsController.js"; +import { ISettingsController } from "@/api/controllers/settingsController.js"; class SettingsRoutes { private router: Router; diff --git a/server/src/routes/statusPageRoute.ts b/server/src/api/routes/statusPageRoute.ts old mode 100755 new mode 100644 similarity index 93% rename from server/src/routes/statusPageRoute.ts rename to server/src/api/routes/statusPageRoute.ts index 29cdab609..a2e1d168f --- a/server/src/routes/statusPageRoute.ts +++ b/server/src/api/routes/statusPageRoute.ts @@ -1,4 +1,4 @@ -import { IStatusPageController } from "@/controllers/statusPageController.js"; +import { IStatusPageController } from "@/api/controllers/statusPageController.js"; import { RequestHandler, Router } from "express"; import multer from "multer"; const upload = multer(); diff --git a/server/src/routes/tagRoutes.ts b/server/src/api/routes/tagRoutes.ts similarity index 90% rename from server/src/routes/tagRoutes.ts rename to server/src/api/routes/tagRoutes.ts index 9b237c976..c1a3f7972 100644 --- a/server/src/routes/tagRoutes.ts +++ b/server/src/api/routes/tagRoutes.ts @@ -1,4 +1,4 @@ -import { ITagsController } from "@/controllers/tagController.js"; +import { ITagsController } from "@/api/controllers/tagController.js"; import { Router } from "express"; class TagRoutes { private router: Router; diff --git a/server/src/validation/authValidation.ts b/server/src/api/validation/authValidation.ts similarity index 96% rename from server/src/validation/authValidation.ts rename to server/src/api/validation/authValidation.ts index d8f592cee..be30272e6 100644 --- a/server/src/validation/authValidation.ts +++ b/server/src/api/validation/authValidation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { passwordPattern, nameValidation, lowercaseEmailValidation } from "./shared.js"; -import { UserRoles } from "@/types/user.js"; +import { UserRoles } from "@/domain/users/user.type.js"; //**************************************** // Auth Validations diff --git a/server/src/validation/checkValidation.ts b/server/src/api/validation/checkValidation.ts similarity index 91% rename from server/src/validation/checkValidation.ts rename to server/src/api/validation/checkValidation.ts index 0c58b9df1..db0978357 100644 --- a/server/src/validation/checkValidation.ts +++ b/server/src/api/validation/checkValidation.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { booleanCoercion } from "./shared.js"; -import { GeoContinents } from "@/types/geoCheck.js"; -import { MonitorTypes } from "@/types/monitor.js"; +import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js"; +import { MonitorTypes } from "@/domain/monitors/monitor.types.js"; //**************************************** // Check Validations diff --git a/server/src/validation/incidentValidation.ts b/server/src/api/validation/incidentValidation.ts similarity index 100% rename from server/src/validation/incidentValidation.ts rename to server/src/api/validation/incidentValidation.ts diff --git a/server/src/validation/index.ts b/server/src/api/validation/index.ts similarity index 94% rename from server/src/validation/index.ts rename to server/src/api/validation/index.ts index 8627d3892..55c586769 100644 --- a/server/src/validation/index.ts +++ b/server/src/api/validation/index.ts @@ -9,7 +9,7 @@ export * from "./shared.js"; // Environment validation -export * from "./envValidation.js"; +export * from "@/config/envValidation.js"; // Domain-specific validations export * from "./authValidation.js"; diff --git a/server/src/validation/maintenanceWindowValidation.ts b/server/src/api/validation/maintenanceWindowValidation.ts similarity index 96% rename from server/src/validation/maintenanceWindowValidation.ts rename to server/src/api/validation/maintenanceWindowValidation.ts index 837db28e8..2ee22bc19 100644 --- a/server/src/validation/maintenanceWindowValidation.ts +++ b/server/src/api/validation/maintenanceWindowValidation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { booleanCoercion } from "./shared.js"; -import { DurationUnits } from "@/types/maintenanceWindow.js"; +import { DurationUnits } from "@/domain/maintenance-windows/maintenance-window.type.js"; const dateToString = z.coerce.date().transform((d) => d.toISOString()); diff --git a/server/src/validation/monitorValidation.ts b/server/src/api/validation/monitorValidation.ts similarity index 94% rename from server/src/validation/monitorValidation.ts rename to server/src/api/validation/monitorValidation.ts index d5164b8ef..f44cc88ae 100644 --- a/server/src/validation/monitorValidation.ts +++ b/server/src/api/validation/monitorValidation.ts @@ -1,7 +1,16 @@ import { z } from "zod"; import { booleanCoercion, dnsHostnameRegex, dnsServerValidation } from "./shared.js"; -import { GeoContinents } from "@/types/geoCheck.js"; -import { DnsRecordTypes, MonitorMatchMethods, MonitorStatuses, MonitorTypes, PageSpeedStrategies } from "@/types/monitor.js"; +import { GeoContinents } from "@/domain/geo-checks/geo-check.type.js"; +import { + DnsRecordTypes, + HttpStatusCodeSet, + MonitorMatchMethods, + MonitorStatuses, + MonitorTypes, + PageSpeedStrategies, +} from "@/domain/monitors/monitor.types.js"; + +const httpStatusCode = z.number().refine((code) => HttpStatusCodeSet.has(code), { message: "Must be a valid HTTP status code" }); export const getMonitorByIdParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required"), @@ -81,6 +90,7 @@ export const createMonitorBodyValidation = z tempAlertThreshold: z.number().optional(), notifications: z.array(z.string()).optional(), tags: z.array(z.string()).optional(), + customUpCodes: z.array(httpStatusCode).default([]), secret: z.string().optional(), jsonPath: z.union([z.string(), z.literal("")]).optional(), expectedValue: z.union([z.string(), z.literal("")]).optional(), @@ -110,6 +120,7 @@ export const editMonitorBodyValidation = z interval: z.number().optional(), notifications: z.array(z.string()).optional(), tags: z.array(z.string()).optional(), + customUpCodes: z.array(httpStatusCode).optional(), secret: z.string().optional(), ignoreTlsErrors: z.boolean().optional(), useAdvancedMatching: z.boolean().optional(), @@ -180,6 +191,7 @@ const importedMonitorSchema = z uptimePercentage: z.number().optional(), notifications: z.array(z.string()).default([]), tags: z.array(z.string()).default([]), + customUpCodes: z.array(httpStatusCode).default([]), secret: z.string().optional(), cpuAlertThreshold: z.number().default(100), cpuAlertCounter: z.number().default(5), @@ -241,6 +253,7 @@ export const monitorResponseSchema = z matchMethod: z.enum(MonitorMatchMethods).optional(), notifications: z.array(z.string()), tags: z.array(z.string()), + customUpCodes: z.array(httpStatusCode).optional(), secret: z.string().optional(), cpuAlertThreshold: z.number(), memoryAlertThreshold: z.number(), diff --git a/server/src/validation/notificationValidation.ts b/server/src/api/validation/notificationValidation.ts similarity index 100% rename from server/src/validation/notificationValidation.ts rename to server/src/api/validation/notificationValidation.ts diff --git a/server/src/validation/queueValidation.ts b/server/src/api/validation/queueValidation.ts similarity index 100% rename from server/src/validation/queueValidation.ts rename to server/src/api/validation/queueValidation.ts diff --git a/server/src/validation/settingsValidation.ts b/server/src/api/validation/settingsValidation.ts similarity index 95% rename from server/src/validation/settingsValidation.ts rename to server/src/api/validation/settingsValidation.ts index db58c50ed..d118cbd4f 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/api/validation/settingsValidation.ts @@ -1,4 +1,4 @@ -import { CHECK_TTL_SENTINEL } from "@/types/check.js"; +import { CHECK_TTL_SENTINEL } from "@/domain/checks/check.type.js"; import { z } from "zod"; //**************************************** diff --git a/server/src/validation/shared.ts b/server/src/api/validation/shared.ts similarity index 97% rename from server/src/validation/shared.ts rename to server/src/api/validation/shared.ts index a3e17866b..18b8ba463 100644 --- a/server/src/validation/shared.ts +++ b/server/src/api/validation/shared.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { type UserRole } from "@/types/user.js"; +import { type UserRole } from "@/domain/users/user.type.js"; export const passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.~`|\\/]+$/; diff --git a/server/src/validation/statusPageValidation.ts b/server/src/api/validation/statusPageValidation.ts similarity index 98% rename from server/src/validation/statusPageValidation.ts rename to server/src/api/validation/statusPageValidation.ts index 8d7e8edf9..2fa24be45 100644 --- a/server/src/validation/statusPageValidation.ts +++ b/server/src/api/validation/statusPageValidation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { booleanCoercion, dnsHostnameRegex } from "./shared.js"; -import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/types/statusPage.js"; +import { StatusPageTypes, StatusPageThemes, StatusPageThemeModes } from "@/domain/status-pages/status-page.type.js"; import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js"; //**************************************** diff --git a/server/src/validation/tagValidation.ts b/server/src/api/validation/tagValidation.ts similarity index 100% rename from server/src/validation/tagValidation.ts rename to server/src/api/validation/tagValidation.ts diff --git a/server/src/validation/userValidation.ts b/server/src/api/validation/userValidation.ts similarity index 97% rename from server/src/validation/userValidation.ts rename to server/src/api/validation/userValidation.ts index 40e7d8b54..21fb55317 100644 --- a/server/src/validation/userValidation.ts +++ b/server/src/api/validation/userValidation.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { UserRoles } from "@/types/user.js"; +import { UserRoles } from "@/domain/users/user.type.js"; import { nameValidation, lowercaseEmailValidation, passwordValidation } from "./shared.js"; //**************************************** diff --git a/server/src/app.ts b/server/src/app.ts index 02802fc83..5e61a0ab0 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -5,14 +5,14 @@ import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; import swaggerUi, { type JsonObject } from "swagger-ui-express"; -import { handleErrors } from "@/middleware/handleErrors.js"; -import { generalApiLimiter } from "@/middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js"; +import { handleErrors } from "@/api/middleware/handleErrors.js"; +import { generalApiLimiter } from "@/api/middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "@/api/middleware/sanitization.js"; import { setupRoutes } from "@/config/routes.js"; import { InitializedServices } from "@/config/services.js"; import { InitializedControllers } from "@/config/controllers.js"; -import { EnvConfig } from "@/service/system/settingsService.js"; -import { createStatusPageCorsOrigin } from "@/middleware/statusPageCorsOrigin.js"; +import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; +import { createStatusPageCorsOrigin } from "@/api/middleware/statusPageCorsOrigin.js"; export const createApp = ({ services, diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts index e757cfa22..09dc6651b 100644 --- a/server/src/config/controllers.ts +++ b/server/src/config/controllers.ts @@ -1,17 +1,17 @@ -import MonitorController from "../controllers/monitorController.js"; -import AuthController from "../controllers/authController.js"; -import SettingsController from "../controllers/settingsController.js"; -import CheckController from "../controllers/checkController.js"; -import GeoCheckController from "../controllers/geoCheckController.js"; -import InviteController from "../controllers/inviteController.js"; -import MaintenanceWindowController from "../controllers/maintenanceWindowController.js"; -import QueueController from "../controllers/queueController.js"; -import LogController from "../controllers/logController.js"; -import StatusPageController from "../controllers/statusPageController.js"; -import NotificationController from "../controllers/notificationController.js"; -import TagsController from "../controllers/tagController.js"; -import DiagnosticController from "../controllers/diagnosticController.js"; -import IncidentController from "../controllers/incidentController.js"; +import MonitorController from "../api/controllers/monitorController.js"; +import AuthController from "../api/controllers/authController.js"; +import SettingsController from "../api/controllers/settingsController.js"; +import CheckController from "../api/controllers/checkController.js"; +import GeoCheckController from "../api/controllers/geoCheckController.js"; +import InviteController from "../api/controllers/inviteController.js"; +import MaintenanceWindowController from "../api/controllers/maintenanceWindowController.js"; +import QueueController from "../api/controllers/queueController.js"; +import LogController from "../api/controllers/logController.js"; +import StatusPageController from "../api/controllers/statusPageController.js"; +import NotificationController from "../api/controllers/notificationController.js"; +import TagsController from "../api/controllers/tagController.js"; +import DiagnosticController from "../api/controllers/diagnosticController.js"; +import IncidentController from "../api/controllers/incidentController.js"; import type { InitializedServices } from "@/config/services.js"; export interface InitializedControllers { diff --git a/server/src/validation/envValidation.ts b/server/src/config/envValidation.ts similarity index 90% rename from server/src/validation/envValidation.ts rename to server/src/config/envValidation.ts index 5280a8036..9d83336dc 100644 --- a/server/src/validation/envValidation.ts +++ b/server/src/config/envValidation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { DbTypes, QueueTypes, QueueModes } from "@/types/settings.js"; -import { booleanCoercion } from "./shared.js"; +import { DbTypes, QueueTypes, QueueModes } from "@/domain/app-settings/app-settings.type.js"; +import { booleanCoercion } from "@/api/validation/shared.js"; const envSchema = z.object({ // Server Configuration diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index cff5e6b1d..d3fbab2c7 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -1,25 +1,25 @@ import type { Application } from "express"; -import { createVerifyJWT } from "../middleware/verifyJWT.js"; -import { createVerifyStatusPageAccess } from "../middleware/verifyStatusPageAccess.js"; -import { authApiLimiter } from "../middleware/rateLimiter.js"; +import { createVerifyJWT } from "../api/middleware/verifyJWT.js"; +import { createVerifyStatusPageAccess } from "../api/middleware/verifyStatusPageAccess.js"; +import { authApiLimiter } from "../api/middleware/rateLimiter.js"; import type { InitializedControllers } from "./controllers.js"; import type { InitializedServices } from "./services.js"; -import AuthRoutes from "../routes/authRoute.js"; -import InviteRoutes from "../routes/inviteRoute.js"; -import MonitorRoutes from "../routes/monitorRoute.js"; -import CheckRoutes from "../routes/checkRoute.js"; -import GeoCheckRoutes from "../routes/geoCheckRoutes.js"; -import SettingsRoutes from "../routes/settingsRoute.js"; -import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js"; -import StatusPageRoutes from "../routes/statusPageRoute.js"; -import QueueRoutes from "../routes/queueRoute.js"; -import LogRoutes from "../routes/logRoutes.js"; -import DiagnosticRoutes from "../routes/diagnosticRoute.js"; -import NotificationRoutes from "../routes/notificationRoute.js"; -import TagRoutes from "../routes/tagRoutes.js"; +import AuthRoutes from "../api/routes/authRoute.js"; +import InviteRoutes from "../api/routes/inviteRoute.js"; +import MonitorRoutes from "../api/routes/monitorRoute.js"; +import CheckRoutes from "../api/routes/checkRoute.js"; +import GeoCheckRoutes from "../api/routes/geoCheckRoutes.js"; +import SettingsRoutes from "../api/routes/settingsRoute.js"; +import MaintenanceWindowRoutes from "../api/routes/maintenanceWindowRoute.js"; +import StatusPageRoutes from "../api/routes/statusPageRoute.js"; +import QueueRoutes from "../api/routes/queueRoute.js"; +import LogRoutes from "../api/routes/logRoutes.js"; +import DiagnosticRoutes from "../api/routes/diagnosticRoute.js"; +import NotificationRoutes from "../api/routes/notificationRoute.js"; +import TagRoutes from "../api/routes/tagRoutes.js"; -import IncidentRoutes from "../routes/incidentRoute.js"; +import IncidentRoutes from "../api/routes/incidentRoute.js"; export const setupRoutes = (app: Application, controllers: InitializedControllers, services: InitializedServices) => { const verifyJWT = createVerifyJWT(services.settingsService); diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 818ef1663..9aed9c968 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -1,75 +1,52 @@ import { Mongoose } from "mongoose"; -import MongoDB from "../db/MongoDB.js"; -import { IDb } from "@/db/IDb.js"; -import { - // Service classes - NetworkService, - EmailService, - BufferService, - GlobalPingService, - LessSimpleQueue, - SuperSimpleQueue, - QueueHelper, - NotificationsService, - TagsService, - StatusService, - NotificationMessageBuilder, - MonitorService, - StatusPageService, - UserService, - CheckService, - GeoChecksService, - DiagnosticService, - InviteService, - MaintenanceWindowService, - IncidentService, - // Notification providers - WebhookProvider, - SlackProvider, - EmailProvider, - DiscordProvider, - PagerDutyProvider, - MatrixProvider, - TeamsProvider, - TelegramProvider, - PushoverProvider, - TwilioProvider, - NtfyProvider, - // Interfaces - INetworkService, - IEmailService, - IBufferService, - IJobQueue, - INotificationsService, - ITagsService, - IStatusService, - IMonitorService, - IUserService, - ICheckService, - IGeoChecksService, - IDiagnosticService, - IInviteService, - IMaintenanceWindowService, - IStatusPageService, - IIncidentService, - INotificationMessageBuilder, - ISettingsService, - SettingsService, - EnvConfig, -} from "@/service/index.js"; +import MongoDB from "../db/db.mongo.js"; +import { IDb } from "@/db/db.interface.js"; +import { EnvConfig, ISettingsService, SettingsService } from "@/domain/app-settings/app-settings.service.js"; +import { CheckService, ICheckService } from "@/domain/checks/check.service.js"; +import { DiagnosticService, IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js"; +import { GeoChecksService, IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js"; +import { IIncidentService, IncidentService } from "@/domain/incidents/incident.service.js"; +import { IInviteService, InviteService } from "@/domain/invites/invite.service.js"; +import { IMaintenanceWindowService, MaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js"; +import { IMonitorService, MonitorService } from "@/domain/monitors/monitor.service.js"; +import { INotificationMessageBuilder, NotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js"; +import { INotificationsService, NotificationsService } from "@/domain/notifications/notification.service.js"; +import { DiscordProvider } from "@/domain/notifications/providers/discord.js"; +import { EmailProvider } from "@/domain/notifications/providers/email.js"; +import { MatrixProvider } from "@/domain/notifications/providers/matrix.js"; +import { NtfyProvider } from "@/domain/notifications/providers/ntfy.js"; +import { PagerDutyProvider } from "@/domain/notifications/providers/pagerduty.js"; +import { PushoverProvider } from "@/domain/notifications/providers/pushover.js"; +import { SlackProvider } from "@/domain/notifications/providers/slack.js"; +import { TeamsProvider } from "@/domain/notifications/providers/teams.js"; +import { TelegramProvider } from "@/domain/notifications/providers/telegram.js"; +import { TwilioProvider } from "@/domain/notifications/providers/twilio.js"; +import { WebhookProvider } from "@/domain/notifications/providers/webhook.js"; +import { IStatusPageService, StatusPageService } from "@/domain/status-pages/status-page.service.js"; +import { ITagsService, TagsService } from "@/domain/tags/tag.service.js"; +import { IUserService, UserService } from "@/domain/users/user.service.js"; +import { BufferService, IBufferService } from "@/service/bufferService.js"; +import { EmailService, IEmailService } from "@/service/emailService.js"; +import { GlobalPingService } from "@/service/globalPingService.js"; +import { QueueHelper } from "@/service/job-queues/job-queue.helper.js"; +import { IJobQueue } from "@/service/job-queues/job-queue.interface.js"; +import { LessSimpleQueue } from "@/service/job-queues/job-queue.less-simple.js"; +import { SuperSimpleQueue } from "@/service/job-queues/job-queue.sumper-simple.js"; +import { INetworkService, NetworkService } from "@/service/networkService.js"; +import { IStatusService, StatusService } from "@/service/statusService.js"; // Network providers -import { PingProvider } from "@/service/infrastructure/network/PingProvider.js"; -import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js"; -import { AdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js"; -import { PageSpeedProvider } from "@/service/infrastructure/network/PageSpeedProvider.js"; -import { HardwareProvider } from "@/service/infrastructure/network/HardwareProvider.js"; -import { DockerProvider } from "@/service/infrastructure/network/DockerProvider.js"; -import { PortProvider } from "@/service/infrastructure/network/PortProvider.js"; -import { GameProvider } from "@/service/infrastructure/network/GameProvider.js"; -import { GrpcProvider } from "@/service/infrastructure/network/GrpcProvider.js"; -import { WebSocketProvider } from "@/service/infrastructure/network/WebSocketProvider.js"; -import { DNSProvider } from "@/service/infrastructure/network/DNSProvider.js"; +import { PingProvider } from "@/service/network/PingProvider.js"; +import { HttpProvider } from "@/service/network/HttpProvider.js"; +import { AdvancedMatcher } from "@/service/network/AdvancedMatcher.js"; +import { PageSpeedProvider } from "@/service/network/PageSpeedProvider.js"; +import { HardwareProvider } from "@/service/network/HardwareProvider.js"; +import { DockerProvider } from "@/service/network/DockerProvider.js"; +import { PortProvider } from "@/service/network/PortProvider.js"; +import { GameProvider } from "@/service/network/GameProvider.js"; +import { GrpcProvider } from "@/service/network/GrpcProvider.js"; +import { WebSocketProvider } from "@/service/network/WebSocketProvider.js"; +import { DNSProvider } from "@/service/network/DNSProvider.js"; // Third-party import { Resolver } from "dns/promises"; @@ -93,40 +70,38 @@ import * as protoLoader from "@grpc/proto-loader"; import WebSocket from "ws"; // Repositories -import { - MongoMonitorsRepository, - MongoChecksRepository, - MongoGeoChecksRepository, - MongoMonitorStatsRepository, - MongoStatusPagesRepository, - MongoUsersRepository, - MongoInvitesRepository, - MongoRecoveryTokensRepository, - MongoNotificationsRepository, - MongoTagsRepository, - MongoIncidentsRepository, - MongoTeamsRepository, - MongoMaintenanceWindowsRepository, - MongoSettingsRepository, - MongoQueueWorkersRepository, - IMonitorsRepository, - IChecksRepository, - IGeoChecksRepository, - IMonitorStatsRepository, - IStatusPagesRepository, - IUsersRepository, - IInvitesRepository, - IRecoveryTokensRepository, - ISettingsRepository, - INotificationsRepository, - ITagsRepository, - IIncidentsRepository, - ITeamsRepository, - IMaintenanceWindowsRepository, -} from "@/repositories/index.js"; +import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js"; +import MongoSettingsRepository from "@/domain/app-settings/app-settings.repository.mongo.js"; +import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; +import MongoChecksRepository from "@/domain/checks/check.repository.mongo.js"; +import { IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js"; +import MongoGeoChecksRepository from "@/domain/geo-checks/geo-check.repository.mongo.js"; +import { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; +import MongoIncidentsRepository from "@/domain/incidents/incident.repository.mongo.js"; +import { IInvitesRepository } from "@/domain/invites/invite.repository.interface.js"; +import MongoInvitesRepository from "@/domain/invites/invite.repository.mongo.js"; +import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js"; +import MongoMaintenanceWindowsRepository from "@/domain/maintenance-windows/maintenance-window.repository.mongo.js"; +import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; +import MongoMonitorStatsRepository from "@/domain/monitor-stats/monitor-stats.repository.mongo.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import MongoMonitorsRepository from "@/domain/monitors/monitor.repository.mongo.js"; +import { INotificationsRepository } from "@/domain/notifications/notification.repository.interface.js"; +import MongoNotificationsRepository from "@/domain/notifications/notification.repository.mongo.js"; +import MongoQueueWorkersRepository from "@/domain/queue-workers/queue-worker.repository.mongo.js"; +import { IRecoveryTokensRepository } from "@/domain/recovery-tokens/recovery-token.repository.interface.js"; +import MongoRecoveryTokensRepository from "@/domain/recovery-tokens/recovery-token.repository.mongo.js"; +import { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; +import MongoStatusPagesRepository from "@/domain/status-pages/status-page-repository.mongo.js"; +import { ITagsRepository } from "@/domain/tags/tag.repository.interface.js"; +import MongoTagsRepository from "@/domain/tags/tag.repository.mongo.js"; +import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; +import MongoTeamsRepository from "@/domain/teams/team.repository.model.js"; +import { IUsersRepository } from "@/domain/users/user.repository.interface.js"; +import MongoUsersRepository from "@/domain/users/user.repository.mongo.js"; import { ILogger } from "@/utils/logger.js"; import { AppError } from "@/utils/AppError.js"; -import { type QueueMode } from "@/types/settings.js"; +import { type QueueMode } from "@/domain/app-settings/app-settings.type.js"; export type InitializedServices = { settingsService: ISettingsService; diff --git a/server/src/db/IDb.ts b/server/src/db/db.interface.ts similarity index 100% rename from server/src/db/IDb.ts rename to server/src/db/db.interface.ts diff --git a/server/src/db/MongoDB.ts b/server/src/db/db.mongo.ts similarity index 93% rename from server/src/db/MongoDB.ts rename to server/src/db/db.mongo.ts index 4531ce5a9..2585eb58b 100755 --- a/server/src/db/MongoDB.ts +++ b/server/src/db/db.mongo.ts @@ -1,11 +1,11 @@ import { Mongoose } from "mongoose"; import mongoose from "mongoose"; -import AppSettings from "@/db/models/AppSettings.js"; +import AppSettings from "@/domain/app-settings/app-settings.model.js"; import { runMigrations } from "@/db/migration/index.js"; import { ILogger } from "@/utils/logger.js"; -import { EnvConfig } from "@/service/system/settingsService.js"; +import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; const SERVICE_NAME = "MongoDB"; -import { IDb } from "@/db/IDb.js"; +import { IDb } from "@/db/db.interface.js"; import { AppError } from "@/utils/AppError.js"; class MongoDB implements IDb { diff --git a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts index d9dd12aa1..c620ffc5f 100644 --- a/server/src/db/migration/0001_migrateStatusWindowThreshold.ts +++ b/server/src/db/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,4 @@ -import Monitor from "@/db/models/Monitor.js"; +import Monitor from "@/domain/monitors/monitor.model.js"; async function migrateStatusWindowThreshold() { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); for (const monitor of monitors) { diff --git a/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts b/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts index c4dff2fc0..f772b5229 100644 --- a/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts +++ b/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts @@ -1,4 +1,4 @@ -import { MonitorStatsModel } from "../models/MonitorStats.js"; +import { MonitorStatsModel } from "../../domain/monitor-stats/monitor-stats.model.js"; import { logger } from "@/utils/logger.js"; /** diff --git a/server/src/db/migration/0004_fixInfrastructureThresholds.ts b/server/src/db/migration/0004_fixInfrastructureThresholds.ts index bf6458565..dfc45c4fe 100644 --- a/server/src/db/migration/0004_fixInfrastructureThresholds.ts +++ b/server/src/db/migration/0004_fixInfrastructureThresholds.ts @@ -1,4 +1,4 @@ -import { MonitorModel } from "../models/Monitor.js"; +import { MonitorModel } from "../../domain/monitors/monitor.model.js"; import { logger } from "@/utils/logger.js"; /** diff --git a/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts index 0b13d1cb6..d5dcafbd1 100644 --- a/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts +++ b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts @@ -1,6 +1,6 @@ import mongoose from "mongoose"; import { logger } from "@/utils/logger.js"; -import StatusPageModel from "../models/StatusPage.js"; +import StatusPageModel from "../../domain/status-pages/status-page.model.js"; export async function migrateStatusPageTypeToArray(): Promise { const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray"; diff --git a/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts b/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts index 814b19776..4a34e9926 100644 --- a/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts +++ b/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts @@ -1,4 +1,4 @@ -import { MonitorStatsModel } from "../models/MonitorStats.js"; +import { MonitorStatsModel } from "../../domain/monitor-stats/monitor-stats.model.js"; import { logger } from "@/utils/logger.js"; /** diff --git a/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts b/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts index a60b8786a..bf5766000 100644 --- a/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts +++ b/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts @@ -1,7 +1,7 @@ import mongoose from "mongoose"; import type { AnyBulkWriteOperation, Document } from "mongodb"; import { logger } from "@/utils/logger.js"; -import MaintenanceWindowModel from "../models/MaintenanceWindow.js"; +import MaintenanceWindowModel from "../../domain/maintenance-windows/maintenance-window.model.js"; type GroupResult = { _id: { diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts index 61785118e..ac78cc813 100644 --- a/server/src/db/migration/index.ts +++ b/server/src/db/migration/index.ts @@ -2,7 +2,7 @@ import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshol import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js"; import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js"; import { fixInfrastructureThresholds } from "./0004_fixInfrastructureThresholds.js"; -import MigrationModel from "../models/Migration.js"; +import MigrationModel from "../../domain/migrations/migration.model.js"; import { migrateStatusPageTypeToArray } from "./0005_migrateStatusPageTypeToArray.js"; import { cleanupDuplicateMonitorStatsForUniqueIndex } from "./0006_cleanupDuplicateMonitorStatsForUniqueIndex.js"; import { migrateMaintenanceWindowMonitorIdToArray } from "./0007_migrateMaintenanceWindowMonitorIdToArray.js"; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts deleted file mode 100644 index b825ca503..000000000 --- a/server/src/db/models/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -export * from "@/db/models/Monitor.js"; -export { default as MonitorModel } from "@/db/models/Monitor.js"; - -export * from "@/db/models/Check.js"; -export { default as CheckModel } from "@/db/models/Check.js"; - -export * from "@/db/models/MonitorStats.js"; -export { default as MonitorStatsModel } from "@/db/models/MonitorStats.js"; - -export * from "@/db/models/StatusPage.js"; -export { default as StatusPageModel } from "@/db/models/StatusPage.js"; - -export * from "@/db/models/User.js"; -export { default as UserModel } from "@/db/models/User.js"; - -export * from "@/db/models/Invite.js"; -export { default as InviteModel } from "@/db/models/Invite.js"; - -export * from "@/db/models/AppSettings.js"; -export { default as AppSettingsModel } from "@/db/models/AppSettings.js"; - -export * from "@/db/models/RecoveryToken.js"; -export { default as RecoveryTokenModel } from "@/db/models/RecoveryToken.js"; - -export * from "@/db/models/Notification.js"; -export { default as NotificationModel } from "@/db/models/Notification.js"; - -export * from "@/db/models/Incident.js"; -export { default as IncidentModel } from "@/db/models/Incident.js"; - -export * from "@/db/models/Team.js"; -export { default as TeamModel } from "@/db/models/Team.js"; - -export * from "@/db/models/MaintenanceWindow.js"; -export { default as MaintenanceWindowModel } from "@/db/models/MaintenanceWindow.js"; - -export * from "@/db/models/GeoCheck.js"; -export { default as GeoCheckModel } from "@/db/models/GeoCheck.js"; - -export * from "@/db/models/DLQItem.js"; -export { default as DLQItemModel } from "@/db/models/DLQItem.js"; -export * from "@/db/models/Tag.js"; -export { default as TagModel } from "@/db/models/Tag.js"; - -export * from "@/db/models/QueueWorker.js"; -export { default as QueueWorkerModel } from "@/db/models/QueueWorker.js"; diff --git a/server/src/repositories/settings/ISettingsRepository.ts b/server/src/domain/app-settings/app-settings-repository.interface.ts similarity index 75% rename from server/src/repositories/settings/ISettingsRepository.ts rename to server/src/domain/app-settings/app-settings-repository.interface.ts index 0485a5aea..401d867f7 100644 --- a/server/src/repositories/settings/ISettingsRepository.ts +++ b/server/src/domain/app-settings/app-settings-repository.interface.ts @@ -1,4 +1,4 @@ -import type { Settings, SettingsUpdate } from "@/types/index.js"; +import type { Settings, SettingsUpdate } from "@/domain/app-settings/app-settings.type.js"; export interface ISettingsRepository { // create create(settings: Partial): Promise; diff --git a/server/src/db/models/AppSettings.ts b/server/src/domain/app-settings/app-settings.model.ts similarity index 94% rename from server/src/db/models/AppSettings.ts rename to server/src/domain/app-settings/app-settings.model.ts index 195f43044..da766fa83 100644 --- a/server/src/db/models/AppSettings.ts +++ b/server/src/domain/app-settings/app-settings.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { Settings, SettingsThresholds } from "@/types/settings.js"; +import type { Settings, SettingsThresholds } from "@/domain/app-settings/app-settings.type.js"; interface AppSettingsDocument extends Omit { _id: Types.ObjectId; diff --git a/server/src/repositories/settings/MongoSettingsRepository.ts b/server/src/domain/app-settings/app-settings.repository.mongo.ts similarity index 91% rename from server/src/repositories/settings/MongoSettingsRepository.ts rename to server/src/domain/app-settings/app-settings.repository.mongo.ts index 3117a94ea..ab242e4b7 100644 --- a/server/src/repositories/settings/MongoSettingsRepository.ts +++ b/server/src/domain/app-settings/app-settings.repository.mongo.ts @@ -1,7 +1,7 @@ import mongoose, { type UpdateQuery } from "mongoose"; -import { ISettingsRepository } from "@/repositories/settings/ISettingsRepository.js"; -import type { Settings, SettingsUpdate } from "@/types/index.js"; -import { AppSettingsModel, type AppSettingsDocument } from "@/db/models/index.js"; +import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js"; +import type { Settings, SettingsUpdate } from "@/domain/app-settings/app-settings.type.js"; +import { AppSettingsModel, type AppSettingsDocument } from "@/domain/app-settings/app-settings.model.js"; class MongoSettingsRepository implements ISettingsRepository { private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => { diff --git a/server/src/service/system/settingsService.ts b/server/src/domain/app-settings/app-settings.service.ts similarity index 89% rename from server/src/service/system/settingsService.ts rename to server/src/domain/app-settings/app-settings.service.ts index dfbf6cfdc..ef14a932b 100755 --- a/server/src/service/system/settingsService.ts +++ b/server/src/domain/app-settings/app-settings.service.ts @@ -1,7 +1,7 @@ -import { ISettingsRepository } from "@/repositories/index.js"; -import { type DbType, type QueueType, type QueueMode, Settings, SettingsUpdate } from "@/types/index.js"; +import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js"; +import { Settings, SettingsUpdate, type DbType, type QueueMode, type QueueType } from "@/domain/app-settings/app-settings.type.js"; import { AppError } from "@/utils/AppError.js"; -import { ValidatedEnv } from "@/validation/envValidation.js"; +import { ValidatedEnv } from "@/config/envValidation.js"; import type { StringValue } from "ms"; const SERVICE_NAME = "SettingsService"; diff --git a/server/src/types/settings.ts b/server/src/domain/app-settings/app-settings.type.ts similarity index 75% rename from server/src/types/settings.ts rename to server/src/domain/app-settings/app-settings.type.ts index 4a4ff02a2..d1b4c254b 100644 --- a/server/src/types/settings.ts +++ b/server/src/domain/app-settings/app-settings.type.ts @@ -7,12 +7,6 @@ export type QueueType = (typeof QueueTypes)[number]; export const QueueModes = ["primary", "worker"] as const; export type QueueMode = (typeof QueueModes)[number]; -export type QueueWorker = { - workerId: string; // hostname:pid:uuid - mode: QueueMode; - lastSeenAt: number; // epoch ms of the last heartbeat -}; - export interface SettingsThresholds { cpu?: number; memory?: number; @@ -50,3 +44,20 @@ export interface Settings { createdAt: string; updatedAt: string; } + +export type EmailTransportConfig = Pick< + Settings, + | "systemEmailHost" + | "systemEmailPort" + | "systemEmailAddress" + | "systemEmailDisplayName" + | "systemEmailPassword" + | "systemEmailUser" + | "systemEmailConnectionHost" + | "systemEmailTLSServername" + | "systemEmailSecure" + | "systemEmailPool" + | "systemEmailIgnoreTLS" + | "systemEmailRequireTLS" + | "systemEmailRejectUnauthorized" +>; diff --git a/server/src/db/models/Check.ts b/server/src/domain/checks/check.model.ts similarity index 98% rename from server/src/db/models/Check.ts rename to server/src/domain/checks/check.model.ts index 964936af9..82be6937c 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/domain/checks/check.model.ts @@ -1,5 +1,5 @@ import { Schema, model, Types } from "mongoose"; -import { MonitorTypes, type MonitorType } from "@/types/monitor.js"; +import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js"; import type { Check, CheckAudits, @@ -13,7 +13,7 @@ import type { CheckNetworkInterfaceInfo, GotTimings, ILighthouseAudit, -} from "@/types/check.js"; +} from "@/domain/checks/check.type.js"; type CheckMetadataDocument = Omit & { monitorId: Types.ObjectId; diff --git a/server/src/repositories/checks/IChecksRepository.ts b/server/src/domain/checks/check.repository.interface.ts similarity index 87% rename from server/src/repositories/checks/IChecksRepository.ts rename to server/src/domain/checks/check.repository.interface.ts index b96d14933..46c2a12c6 100644 --- a/server/src/repositories/checks/IChecksRepository.ts +++ b/server/src/domain/checks/check.repository.interface.ts @@ -2,12 +2,12 @@ import type { Check, ChecksQueryResult, ChecksSummary, - MonitorType, - PageSpeedChecksResult, HardwareChecksResult, + PageSpeedChecksResult, UptimeChecksResult, -} from "@/types/index.js"; -import type { LatestChecksMap } from "@/repositories/checks/MongoChecksRepistory.js"; +} from "@/domain/checks/check.type.js"; +import type { MonitorType } from "@/domain/monitors/monitor.types.js"; +import type { LatestChecksMap } from "@/domain/checks/check.repository.mongo.js"; export interface IChecksRepository { // create diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/domain/checks/check.repository.mongo.ts similarity index 98% rename from server/src/repositories/checks/MongoChecksRepistory.ts rename to server/src/domain/checks/check.repository.mongo.ts index 4055ddfc3..35d3440c8 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/domain/checks/check.repository.mongo.ts @@ -1,4 +1,4 @@ -import { IChecksRepository } from "@/repositories/index.js"; +import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; import type { Check, CheckAudits, @@ -11,9 +11,9 @@ import type { CheckMetadata, CheckNetworkInterfaceInfo, GotTimings, - MonitorType, -} from "@/types/index.js"; -import { CheckModel, type CheckDocument } from "@/db/models/index.js"; +} from "@/domain/checks/check.type.js"; +import type { MonitorType } from "@/domain/monitors/monitor.types.js"; +import { CheckModel, type CheckDocument } from "@/domain/checks/check.model.js"; import mongoose from "mongoose"; import { getDateForRange } from "@/utils/dataUtils.js"; import { ILogger } from "@/utils/logger.js"; diff --git a/server/src/service/business/checkService.ts b/server/src/domain/checks/check.service.ts similarity index 94% rename from server/src/service/business/checkService.ts rename to server/src/domain/checks/check.service.ts index dd2a66202..58990eaaf 100644 --- a/server/src/service/business/checkService.ts +++ b/server/src/domain/checks/check.service.ts @@ -1,14 +1,8 @@ import { Types } from "mongoose"; -import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js"; -import type { - MonitorStatusResponse, - CheckErrorInfo, - Check, - ILighthouseAudit, - ChecksQueryResult, - ChecksSummary, - MonitorPayloadMap, -} from "@/types/index.js"; +import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { Check, CheckErrorInfo, ChecksQueryResult, ChecksSummary, ILighthouseAudit } from "@/domain/checks/check.type.js"; +import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.js"; import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; import { ILogger } from "@/utils/logger.js"; diff --git a/server/src/types/check.ts b/server/src/domain/checks/check.type.ts similarity index 98% rename from server/src/types/check.ts rename to server/src/domain/checks/check.type.ts index f4c64f926..fe733e4e9 100644 --- a/server/src/types/check.ts +++ b/server/src/domain/checks/check.type.ts @@ -1,4 +1,4 @@ -import type { MonitorType } from "@/types/index.js"; +import type { MonitorType } from "@/domain/monitors/monitor.types.js"; import type { Response } from "got"; export const CHECK_TTL_SENTINEL = 366; diff --git a/server/src/service/business/diagnosticService.ts b/server/src/domain/diagnostics/diagnostic.service.ts similarity index 92% rename from server/src/service/business/diagnosticService.ts rename to server/src/domain/diagnostics/diagnostic.service.ts index 2d9968240..408b19cb5 100644 --- a/server/src/service/business/diagnosticService.ts +++ b/server/src/domain/diagnostics/diagnostic.service.ts @@ -1,6 +1,6 @@ import v8 from "v8"; import os from "os"; -import { IDb } from "@/db/IDb.js"; +import { IDb } from "@/db/db.interface.js"; import { Mongoose } from "mongoose"; import { AppError } from "@/utils/AppError.js"; @@ -87,9 +87,9 @@ export class DiagnosticService implements IDiagnosticService { const entry: CollectionDiagnostics = { name: collection.collectionName, documentCount, - storageSize: stats.storageSize, - totalIndexSize: stats.totalIndexSize, - totalSize: stats.totalSize, + storageSize: stats.storageSize ?? 0, + totalIndexSize: stats.totalIndexSize ?? 0, + totalSize: stats.totalSize ?? (stats.storageSize ?? 0) + (stats.totalIndexSize ?? 0), }; if (stats.timeseries?.bucketCount !== undefined) { entry.bucketCount = stats.timeseries.bucketCount; @@ -103,7 +103,7 @@ export class DiagnosticService implements IDiagnosticService { host: mongo.connection.host, port: mongo.connection.port, dbName: mongo.connection.name, - totalSize: dbStats.totalSize, + totalSize: dbStats.totalSize ?? (dbStats.storageSize ?? 0) + (dbStats.indexSize ?? 0), collections, }; }; diff --git a/server/src/db/models/DLQItem.ts b/server/src/domain/dlq/dlq.model.ts similarity index 93% rename from server/src/db/models/DLQItem.ts rename to server/src/domain/dlq/dlq.model.ts index 707846adb..1ece5a028 100644 --- a/server/src/db/models/DLQItem.ts +++ b/server/src/domain/dlq/dlq.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import { DLQItemTypes, DLQItemStatuses, type DLQItem } from "@/types/dlqItem.js"; +import { DLQItemTypes, DLQItemStatuses, type DLQItem } from "@/domain/dlq/dlq.type.js"; export interface DLQItemDocument extends Omit { _id: Types.ObjectId; diff --git a/server/src/repositories/dlq/IDLQRepository.ts b/server/src/domain/dlq/dlq.repository.interface.ts similarity index 90% rename from server/src/repositories/dlq/IDLQRepository.ts rename to server/src/domain/dlq/dlq.repository.interface.ts index 5e26e6096..43d4f75d5 100644 --- a/server/src/repositories/dlq/IDLQRepository.ts +++ b/server/src/domain/dlq/dlq.repository.interface.ts @@ -1,4 +1,4 @@ -import type { DLQItem, DLQItemType, DLQItemStatus } from "@/types/index.js"; +import type { DLQItem, DLQItemStatus, DLQItemType } from "@/domain/dlq/dlq.type.js"; export interface DLQQueryFilters { type?: DLQItemType; diff --git a/server/src/repositories/dlq/MongoDLQRepository.ts b/server/src/domain/dlq/dlq.repository.mongo.ts similarity index 94% rename from server/src/repositories/dlq/MongoDLQRepository.ts rename to server/src/domain/dlq/dlq.repository.mongo.ts index 8c9da76ad..4cf7bad85 100644 --- a/server/src/repositories/dlq/MongoDLQRepository.ts +++ b/server/src/domain/dlq/dlq.repository.mongo.ts @@ -1,7 +1,7 @@ -import { DLQItemModel } from "@/db/models/index.js"; -import type { DLQItemDocument } from "@/db/models/DLQItem.js"; -import type { DLQItem } from "@/types/index.js"; -import type { IDLQRepository, DLQQueryFilters, DLQStatusCount } from "@/repositories/dlq/IDLQRepository.js"; +import { DLQItemModel } from "@/domain/dlq/dlq.model.js"; +import type { DLQItemDocument } from "@/domain/dlq/dlq.model.js"; +import type { DLQItem } from "@/domain/dlq/dlq.type.js"; +import type { IDLQRepository, DLQQueryFilters, DLQStatusCount } from "@/domain/dlq/dlq.repository.interface.js"; import mongoose from "mongoose"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/types/dlqItem.ts b/server/src/domain/dlq/dlq.type.ts similarity index 100% rename from server/src/types/dlqItem.ts rename to server/src/domain/dlq/dlq.type.ts diff --git a/server/src/db/models/GeoCheck.ts b/server/src/domain/geo-checks/geo-check.model.ts similarity index 94% rename from server/src/db/models/GeoCheck.ts rename to server/src/domain/geo-checks/geo-check.model.ts index 8c0749f00..0db895d0a 100644 --- a/server/src/db/models/GeoCheck.ts +++ b/server/src/domain/geo-checks/geo-check.model.ts @@ -1,6 +1,6 @@ import { Schema, model, Types } from "mongoose"; -import { MonitorTypes, type MonitorType } from "@/types/monitor.js"; -import type { GeoCheck, GeoCheckLocation, GeoCheckMetadata, GeoCheckResult, GeoCheckTimings } from "@/types/geoCheck.js"; +import { MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js"; +import type { GeoCheck, GeoCheckLocation, GeoCheckMetadata, GeoCheckResult, GeoCheckTimings } from "@/domain/geo-checks/geo-check.type.js"; type GeoCheckMetadataDocument = Omit & { monitorId: Types.ObjectId; diff --git a/server/src/repositories/geo-checks/IGeoChecksRepository.ts b/server/src/domain/geo-checks/geo-check.repository.interface.ts similarity index 84% rename from server/src/repositories/geo-checks/IGeoChecksRepository.ts rename to server/src/domain/geo-checks/geo-check.repository.interface.ts index fe4beb796..2e6f33e33 100644 --- a/server/src/repositories/geo-checks/IGeoChecksRepository.ts +++ b/server/src/domain/geo-checks/geo-check.repository.interface.ts @@ -1,5 +1,5 @@ -import type { GeoCheck, GroupedGeoCheck } from "@/types/geoCheck.js"; -import type { GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js"; +import type { GeoCheck, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js"; +import type { GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js"; export interface GeoChecksQueryResult { geoChecksCount: number; diff --git a/server/src/repositories/geo-checks/MongoGeoChecksRepository.ts b/server/src/domain/geo-checks/geo-check.repository.mongo.ts similarity index 97% rename from server/src/repositories/geo-checks/MongoGeoChecksRepository.ts rename to server/src/domain/geo-checks/geo-check.repository.mongo.ts index 0adcf7c9f..426fb533d 100644 --- a/server/src/repositories/geo-checks/MongoGeoChecksRepository.ts +++ b/server/src/domain/geo-checks/geo-check.repository.mongo.ts @@ -1,7 +1,7 @@ -import { IGeoChecksRepository } from "./IGeoChecksRepository.js"; -import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent, FlatGeoCheck } from "@/types/geoCheck.js"; -import type { FlatGeoChecksQueryResult } from "./IGeoChecksRepository.js"; -import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/db/models/index.js"; +import { IGeoChecksRepository } from "./geo-check.repository.interface.js"; +import type { GeoCheck, GeoCheckMetadata, GeoCheckResult, GroupedGeoCheck, GeoContinent, FlatGeoCheck } from "@/domain/geo-checks/geo-check.type.js"; +import type { FlatGeoChecksQueryResult } from "./geo-check.repository.interface.js"; +import { GeoCheckMetadataDocument, GeoCheckModel, type GeoCheckDocument } from "@/domain/geo-checks/geo-check.model.js"; import mongoose, { PipelineStage } from "mongoose"; import { getDateForRange } from "@/utils/dataUtils.js"; import { ILogger } from "@/utils/logger.js"; diff --git a/server/src/service/business/geoChecksService.ts b/server/src/domain/geo-checks/geo-check.service.ts similarity index 91% rename from server/src/service/business/geoChecksService.ts rename to server/src/domain/geo-checks/geo-check.service.ts index 95b945f2a..0ace565fc 100644 --- a/server/src/service/business/geoChecksService.ts +++ b/server/src/domain/geo-checks/geo-check.service.ts @@ -1,9 +1,10 @@ -import type { Monitor, GeoCheck } from "@/types/index.js"; -import type { GeoCheckResult, GeoContinent } from "@/types/geoCheck.js"; +import type { GeoCheck } from "@/domain/geo-checks/geo-check.type.js"; +import type { Monitor } from "@/domain/monitors/monitor.types.js"; +import type { GeoCheckResult, GeoContinent } from "@/domain/geo-checks/geo-check.type.js"; import { Types } from "mongoose"; -import type { FlatGeoChecksQueryResult, IGeoChecksRepository } from "@/repositories/index.js"; -import type { IMonitorsRepository } from "@/repositories/index.js"; -import type { IGlobalPingService } from "@/service/infrastructure/globalPingService.js"; +import type { FlatGeoChecksQueryResult, IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js"; +import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { IGlobalPingService } from "@/service/globalPingService.js"; import type { ILogger } from "@/utils/logger.js"; import { AppError } from "@/utils/AppError.js"; @@ -89,7 +90,7 @@ export class GeoChecksService implements IGeoChecksService { } // Step 2: Poll for results - const results = await this.globalPingService.pollForResults(measurementId); + const results = await this.globalPingService.pollForResults(measurementId, undefined, monitor.customUpCodes ?? []); if (results.length === 0) { // No successful results (all locations timed out or failed) diff --git a/server/src/types/geoCheck.ts b/server/src/domain/geo-checks/geo-check.type.ts similarity index 95% rename from server/src/types/geoCheck.ts rename to server/src/domain/geo-checks/geo-check.type.ts index 548c4c65f..044bec3b0 100644 --- a/server/src/types/geoCheck.ts +++ b/server/src/domain/geo-checks/geo-check.type.ts @@ -1,4 +1,4 @@ -import type { MonitorType } from "@/types/index.js"; +import type { MonitorType } from "@/domain/monitors/monitor.types.js"; export const GeoContinents = ["EU", "NA", "AS", "SA", "AF", "OC"] as const; export type GeoContinent = (typeof GeoContinents)[number]; diff --git a/server/src/db/models/Incident.ts b/server/src/domain/incidents/incident.model.ts similarity index 95% rename from server/src/db/models/Incident.ts rename to server/src/domain/incidents/incident.model.ts index 82e2b5eb2..9e252bb1d 100644 --- a/server/src/db/models/Incident.ts +++ b/server/src/domain/incidents/incident.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import { IncidentResolutionTypes, type Incident } from "@/types/incident.js"; +import { IncidentResolutionTypes, type Incident } from "@/domain/incidents/incident.type.js"; type IncidentDocumentBase = Omit & { monitorId: Types.ObjectId; diff --git a/server/src/repositories/incidents/IIncidentsRepository.ts b/server/src/domain/incidents/incident.repository.interface.ts similarity index 88% rename from server/src/repositories/incidents/IIncidentsRepository.ts rename to server/src/domain/incidents/incident.repository.interface.ts index c4fcef2ae..549a33b85 100644 --- a/server/src/repositories/incidents/IIncidentsRepository.ts +++ b/server/src/domain/incidents/incident.repository.interface.ts @@ -1,5 +1,5 @@ -import type { Incident } from "@/types/index.js"; -import type { IncidentSummary } from "@/types/index.js"; +import type { Incident } from "@/domain/incidents/incident.type.js"; +import type { IncidentSummary } from "@/domain/incidents/incident.type.js"; export interface IIncidentsRepository { // create create(incident: Partial): Promise; diff --git a/server/src/repositories/incidents/MongoIncidentsRepository.ts b/server/src/domain/incidents/incident.repository.mongo.ts similarity index 96% rename from server/src/repositories/incidents/MongoIncidentsRepository.ts rename to server/src/domain/incidents/incident.repository.mongo.ts index e24551462..4cf410e88 100644 --- a/server/src/repositories/incidents/MongoIncidentsRepository.ts +++ b/server/src/domain/incidents/incident.repository.mongo.ts @@ -1,7 +1,7 @@ -import { IncidentModel } from "@/db/models/index.js"; -import type { IncidentDocument } from "@/db/models/Incident.js"; -import type { Incident, IncidentSummary } from "@/types/index.js"; -import type { IIncidentsRepository } from "@/repositories/index.js"; +import { IncidentModel } from "@/domain/incidents/incident.model.js"; +import type { IncidentDocument } from "@/domain/incidents/incident.model.js"; +import type { Incident, IncidentSummary } from "@/domain/incidents/incident.type.js"; +import type { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; import mongoose from "mongoose"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/service/business/incidentService.ts b/server/src/domain/incidents/incident.service.ts similarity index 92% rename from server/src/service/business/incidentService.ts rename to server/src/domain/incidents/incident.service.ts index 1606cc95d..d588086b2 100644 --- a/server/src/service/business/incidentService.ts +++ b/server/src/domain/incidents/incident.service.ts @@ -1,12 +1,15 @@ const SERVICE_NAME = "incidentService"; -import type { Monitor } from "@/types/monitor.js"; +import type { Monitor } from "@/domain/monitors/monitor.types.js"; import type { MonitorStatusResponse } from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; import { getDateForRange } from "@/utils/dataUtils.js"; -import type { IIncidentsRepository, IMonitorsRepository, IUsersRepository } from "@/repositories/index.js"; -import type { Incident, IncidentSummary, User } from "@/types/index.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/JobQueues/QueueHelper.js"; -import type { INotificationMessageBuilder } from "@/service/infrastructure/notificationMessageBuilder.js"; +import type { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; +import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { IUsersRepository } from "@/domain/users/user.repository.interface.js"; +import type { Incident, IncidentSummary } from "@/domain/incidents/incident.type.js"; +import type { User } from "@/domain/users/user.type.js"; +import type { MonitorActionDecision } from "@/service/job-queues/job-queue.helper.js"; +import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js"; import type { ILogger } from "@/utils/logger.js"; export interface IIncidentService { diff --git a/server/src/types/incident.ts b/server/src/domain/incidents/incident.type.ts similarity index 100% rename from server/src/types/incident.ts rename to server/src/domain/incidents/incident.type.ts diff --git a/server/src/db/models/Invite.ts b/server/src/domain/invites/invite.model.ts similarity index 93% rename from server/src/db/models/Invite.ts rename to server/src/domain/invites/invite.model.ts index 25ddd9a72..a2bd6ae74 100755 --- a/server/src/db/models/Invite.ts +++ b/server/src/domain/invites/invite.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { Invite } from "@/types/invite.js"; +import type { Invite } from "@/domain/invites/invite.type.js"; type InviteDocumentBase = Omit & { teamId: Types.ObjectId; diff --git a/server/src/repositories/invites/IInvitesRepository.ts b/server/src/domain/invites/invite.repository.interface.ts similarity index 79% rename from server/src/repositories/invites/IInvitesRepository.ts rename to server/src/domain/invites/invite.repository.interface.ts index 74a7ad68c..348731e9c 100644 --- a/server/src/repositories/invites/IInvitesRepository.ts +++ b/server/src/domain/invites/invite.repository.interface.ts @@ -1,4 +1,4 @@ -import type { Invite } from "@/types/index.js"; +import type { Invite } from "@/domain/invites/invite.type.js"; export interface IInvitesRepository { // create diff --git a/server/src/repositories/invites/MongoInviteRepository.ts b/server/src/domain/invites/invite.repository.mongo.ts similarity index 88% rename from server/src/repositories/invites/MongoInviteRepository.ts rename to server/src/domain/invites/invite.repository.mongo.ts index a8187d09a..8956a08b3 100644 --- a/server/src/repositories/invites/MongoInviteRepository.ts +++ b/server/src/domain/invites/invite.repository.mongo.ts @@ -1,6 +1,6 @@ -import { IInvitesRepository } from "@/repositories/index.js"; -import type { Invite } from "@/types/index.js"; -import { type InviteDocument, InviteModel } from "@/db/models/index.js"; +import { IInvitesRepository } from "@/domain/invites/invite.repository.interface.js"; +import type { Invite } from "@/domain/invites/invite.type.js"; +import { type InviteDocument, InviteModel } from "@/domain/invites/invite.model.js"; import { AppError } from "@/utils/AppError.js"; import mongoose from "mongoose"; import crypto from "crypto"; diff --git a/server/src/service/business/inviteService.ts b/server/src/domain/invites/invite.service.ts similarity index 88% rename from server/src/service/business/inviteService.ts rename to server/src/domain/invites/invite.service.ts index 7e2b7de52..76c5c6289 100644 --- a/server/src/service/business/inviteService.ts +++ b/server/src/domain/invites/invite.service.ts @@ -1,9 +1,10 @@ -import type { Invite, UserRole } from "@/types/index.js"; -import { canManageRole } from "@/types/user.js"; -import type { IInvitesRepository } from "@/repositories/index.js"; +import type { Invite } from "@/domain/invites/invite.type.js"; +import type { UserRole } from "@/domain/users/user.type.js"; +import { canManageRole } from "@/domain/users/user.type.js"; +import type { IInvitesRepository } from "@/domain/invites/invite.repository.interface.js"; import { AppError } from "@/utils/AppError.js"; -import { ISettingsService } from "../system/settingsService.js"; -import { IEmailService } from "../infrastructure/emailService.js"; +import { ISettingsService } from "../app-settings/app-settings.service.js"; +import { IEmailService } from "@/service/emailService.js"; const SERVICE_NAME = "inviteService"; diff --git a/server/src/types/invite.ts b/server/src/domain/invites/invite.type.ts similarity index 73% rename from server/src/types/invite.ts rename to server/src/domain/invites/invite.type.ts index cdbdb37e5..e00c75d6b 100644 --- a/server/src/types/invite.ts +++ b/server/src/domain/invites/invite.type.ts @@ -1,4 +1,4 @@ -import type { UserRole } from "@/types/user.js"; +import type { UserRole } from "@/domain/users/user.type.js"; export interface Invite { id: string; diff --git a/server/src/db/models/MaintenanceWindow.ts b/server/src/domain/maintenance-windows/maintenance-window.model.ts similarity index 91% rename from server/src/db/models/MaintenanceWindow.ts rename to server/src/domain/maintenance-windows/maintenance-window.model.ts index 654ae50dd..c42ef3fdc 100644 --- a/server/src/db/models/MaintenanceWindow.ts +++ b/server/src/domain/maintenance-windows/maintenance-window.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import { DurationUnits, type MaintenanceWindow } from "@/types/maintenanceWindow.js"; +import { DurationUnits, type MaintenanceWindow } from "@/domain/maintenance-windows/maintenance-window.type.js"; type MaintenanceWindowDocumentBase = Omit & { monitorIds: Types.ObjectId[]; diff --git a/server/src/repositories/maintenance-windows/IMaintenanceWindowsRepository.ts b/server/src/domain/maintenance-windows/maintenance-window.repository.interface.ts similarity index 89% rename from server/src/repositories/maintenance-windows/IMaintenanceWindowsRepository.ts rename to server/src/domain/maintenance-windows/maintenance-window.repository.interface.ts index 0f4ad66a2..06384aed5 100644 --- a/server/src/repositories/maintenance-windows/IMaintenanceWindowsRepository.ts +++ b/server/src/domain/maintenance-windows/maintenance-window.repository.interface.ts @@ -1,4 +1,4 @@ -import type { MaintenanceWindow } from "@/types/index.js"; +import type { MaintenanceWindow } from "@/domain/maintenance-windows/maintenance-window.type.js"; export interface IMaintenanceWindowsRepository { // create create(data: Partial): Promise; diff --git a/server/src/repositories/maintenance-windows/MongoMaintenanceWindowsRepository.ts b/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts similarity index 94% rename from server/src/repositories/maintenance-windows/MongoMaintenanceWindowsRepository.ts rename to server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts index c3f94f55d..ce32a22d1 100644 --- a/server/src/repositories/maintenance-windows/MongoMaintenanceWindowsRepository.ts +++ b/server/src/domain/maintenance-windows/maintenance-window.repository.mongo.ts @@ -1,6 +1,6 @@ -import type { MaintenanceWindow } from "@/types/index.js"; -import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/db/models/index.js"; -import { IMaintenanceWindowsRepository } from "./IMaintenanceWindowsRepository.js"; +import type { MaintenanceWindow } from "@/domain/maintenance-windows/maintenance-window.type.js"; +import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/domain/maintenance-windows/maintenance-window.model.js"; +import { IMaintenanceWindowsRepository } from "./maintenance-window.repository.interface.js"; import mongoose, { SortOrder } from "mongoose"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/service/business/maintenanceWindowService.ts b/server/src/domain/maintenance-windows/maintenance-window.service.ts similarity index 95% rename from server/src/service/business/maintenanceWindowService.ts rename to server/src/domain/maintenance-windows/maintenance-window.service.ts index 2ea4c4eae..e1158dec9 100644 --- a/server/src/service/business/maintenanceWindowService.ts +++ b/server/src/domain/maintenance-windows/maintenance-window.service.ts @@ -1,5 +1,6 @@ -import { IMaintenanceWindowsRepository, IMonitorsRepository } from "@/repositories/index.js"; -import type { DurationUnit, MaintenanceWindow } from "@/types/index.js"; +import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { DurationUnit, MaintenanceWindow } from "@/domain/maintenance-windows/maintenance-window.type.js"; import { AppError } from "@/utils/AppError.js"; import { isWindowActive } from "@/utils/maintenanceWindow.js"; diff --git a/server/src/types/maintenanceWindow.ts b/server/src/domain/maintenance-windows/maintenance-window.type.ts similarity index 100% rename from server/src/types/maintenanceWindow.ts rename to server/src/domain/maintenance-windows/maintenance-window.type.ts diff --git a/server/src/db/models/Migration.ts b/server/src/domain/migrations/migration.model.ts similarity index 100% rename from server/src/db/models/Migration.ts rename to server/src/domain/migrations/migration.model.ts diff --git a/server/src/db/models/MonitorStats.ts b/server/src/domain/monitor-stats/monitor-stats.model.ts similarity index 92% rename from server/src/db/models/MonitorStats.ts rename to server/src/domain/monitor-stats/monitor-stats.model.ts index 4ef2dab61..889b5ec60 100755 --- a/server/src/db/models/MonitorStats.ts +++ b/server/src/domain/monitor-stats/monitor-stats.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { MonitorStats as MonitorStatsEntity } from "@/types/monitorStats.js"; +import type { MonitorStats as MonitorStatsEntity } from "@/domain/monitor-stats/monitor-stats.type.js"; type MonitorStatsDocumentBase = Omit & { monitorId: Types.ObjectId; diff --git a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts b/server/src/domain/monitor-stats/monitor-stats.repository.interface.ts similarity index 85% rename from server/src/repositories/monitor-stats/IMonitorStatsRepository.ts rename to server/src/domain/monitor-stats/monitor-stats.repository.interface.ts index 101283e32..286ef3608 100644 --- a/server/src/repositories/monitor-stats/IMonitorStatsRepository.ts +++ b/server/src/domain/monitor-stats/monitor-stats.repository.interface.ts @@ -1,4 +1,4 @@ -import type { MonitorStats, CheckResultInput } from "@/types/index.js"; +import type { CheckResultInput, MonitorStats } from "@/domain/monitor-stats/monitor-stats.type.js"; export interface IMonitorStatsRepository { // create diff --git a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts b/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts similarity index 94% rename from server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts rename to server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts index ea255f7ad..7027197da 100644 --- a/server/src/repositories/monitor-stats/MongoMonitorStatsRepository.ts +++ b/server/src/domain/monitor-stats/monitor-stats.repository.mongo.ts @@ -1,6 +1,6 @@ -import { type MonitorStatsDocument, MonitorStatsModel } from "@/db/models/index.js"; -import type { MonitorStats, CheckResultInput } from "@/types/index.js"; -import { IMonitorStatsRepository } from "@/repositories/index.js"; +import { type MonitorStatsDocument, MonitorStatsModel } from "@/domain/monitor-stats/monitor-stats.model.js"; +import type { CheckResultInput, MonitorStats } from "@/domain/monitor-stats/monitor-stats.type.js"; +import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; import mongoose from "mongoose"; import { AppError } from "@/utils/AppError.js"; class MongoMonitorStatsRepository implements IMonitorStatsRepository { diff --git a/server/src/types/monitorStats.ts b/server/src/domain/monitor-stats/monitor-stats.type.ts similarity index 100% rename from server/src/types/monitorStats.ts rename to server/src/domain/monitor-stats/monitor-stats.type.ts diff --git a/server/src/db/models/Monitor.ts b/server/src/domain/monitors/monitor.model.ts similarity index 97% rename from server/src/db/models/Monitor.ts rename to server/src/domain/monitors/monitor.model.ts index e6bffddd8..e45e975e8 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/domain/monitors/monitor.model.ts @@ -1,6 +1,6 @@ import { Schema, model, Types } from "mongoose"; -import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js"; -import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies } from "@/types/monitor.js"; +import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/domain/monitors/monitor.types.js"; +import { DnsRecordTypes, MonitorTypes, MonitorStatuses, PageSpeedStrategies } from "@/domain/monitors/monitor.types.js"; import type { CheckAudits, CheckCaptureInfo, @@ -12,7 +12,7 @@ import type { CheckNetworkInterfaceInfo, GotTimings, ILighthouseAudit, -} from "@/types/check.js"; +} from "@/domain/checks/check.type.js"; type CheckSnapshotDocument = Omit & { createdAt: Date }; @@ -291,6 +291,10 @@ const MonitorSchema = new Schema( ref: "Tag", }, ], + customUpCodes: { + type: [Number], + default: [], + }, secret: { type: String, }, diff --git a/server/src/repositories/monitors/IMonitorsRepository.ts b/server/src/domain/monitors/monitor.repository.interface.ts similarity index 94% rename from server/src/repositories/monitors/IMonitorsRepository.ts rename to server/src/domain/monitors/monitor.repository.interface.ts index dcd01f3c9..e5a0fa585 100644 --- a/server/src/repositories/monitors/IMonitorsRepository.ts +++ b/server/src/domain/monitors/monitor.repository.interface.ts @@ -1,4 +1,4 @@ -import { type MonitorType, type Monitor, type MonitorStatus, type MonitorsSummary, CheckSnapshot } from "@/types/index.js"; +import type { MonitorType, Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/domain/monitors/monitor.types.js"; export interface TeamQueryConfig { limit?: number; diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/domain/monitors/monitor.repository.mongo.ts similarity index 97% rename from server/src/repositories/monitors/MongoMonitorsRepository.ts rename to server/src/domain/monitors/monitor.repository.mongo.ts index 0b9de8ace..165db24e3 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/domain/monitors/monitor.repository.mongo.ts @@ -1,11 +1,11 @@ -import { MonitorModel } from "@/db/models/index.js"; -import type { MonitorDocument, CheckSnapshotDocument } from "@/db/models/index.js"; -import type { Monitor, MonitorStatus, MonitorsSummary, CheckSnapshot } from "@/types/index.js"; +import { MonitorModel } from "@/domain/monitors/monitor.model.js"; +import type { MonitorDocument, CheckSnapshotDocument } from "@/domain/monitors/monitor.model.js"; +import type { CheckSnapshot } from "@/domain/checks/check.type.js"; +import type { Monitor, MonitorStatus, MonitorsSummary } from "@/domain/monitors/monitor.types.js"; import mongoose, { type FilterQuery, type PipelineStage } from "mongoose"; -import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { MongoBulkWriteError } from "mongodb"; import { AppError } from "@/utils/AppError.js"; - +import { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "@/domain/monitors/monitor.repository.interface.js"; class MongoMonitorsRepository implements IMonitorsRepository { create = async (monitor: Monitor, teamId: string, userId: string) => { const monitorModel = new MonitorModel({ ...monitor, teamId, userId }); @@ -548,6 +548,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { uptimePercentage: doc.uptimePercentage ?? undefined, notifications: notificationIds, tags: tagIds, + customUpCodes: doc.customUpCodes ?? [], secret: doc.secret ?? undefined, cpuAlertThreshold: doc.cpuAlertThreshold, cpuAlertCounter: doc.cpuAlertCounter, @@ -612,6 +613,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { uptimePercentage: doc.uptimePercentage ?? undefined, notifications: notificationIds, tags: tagIds, + customUpCodes: doc.customUpCodes ?? [], secret: doc.secret ?? undefined, cpuAlertThreshold: doc.cpuAlertThreshold, cpuAlertCounter: doc.cpuAlertCounter, diff --git a/server/src/service/business/monitorService.ts b/server/src/domain/monitors/monitor.service.ts similarity index 94% rename from server/src/service/business/monitorService.ts rename to server/src/domain/monitors/monitor.service.ts index d172107c9..eaa40dcfe 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/domain/monitors/monitor.service.ts @@ -1,5 +1,5 @@ import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; -import { type Monitor } from "@/types/index.js"; +import { type Monitor } from "@/domain/monitors/monitor.types.js"; import type { MonitorType, MonitorsWithChecksByTeamIdResult, @@ -8,22 +8,20 @@ import type { PageSpeedDetailsResult, GamesMap, GroupedGeoCheckResult, -} from "@/types/monitor.js"; -import { supportsGeoCheck, supportsUptimeDetails } from "@/types/monitor.js"; -import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/types/check.js"; -import type { GeoContinent } from "@/types/geoCheck.js"; -import type { - IChecksRepository, - IGeoChecksRepository, - IIncidentsRepository, - IMonitorsRepository, - IMonitorStatsRepository, - IStatusPagesRepository, -} from "@/repositories/index.js"; +} from "@/domain/monitors/monitor.types.js"; +import { supportsGeoCheck, supportsUptimeDetails } from "@/domain/monitors/monitor.types.js"; +import type { UptimeChecksResult, HardwareChecksResult, PageSpeedChecksResult } from "@/domain/checks/check.type.js"; +import type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js"; +import type { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; +import type { IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js"; +import type { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; +import type { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; +import type { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; import demoMonitorsData from "@/utils/demoMonitors.json" with { type: "json" }; import { AppError } from "@/utils/AppError.js"; -import type { ImportedMonitor } from "@/validation/monitorValidation.js"; -import { IJobQueue } from "@/service/infrastructure/JobQueues/IJobQueue.js"; +import type { ImportedMonitor } from "@/api/validation/monitorValidation.js"; +import { IJobQueue } from "@/service/job-queues/job-queue.interface.js"; import { ILogger } from "@/utils/logger.js"; const SERVICE_NAME = "MonitorService"; diff --git a/server/src/types/monitor.ts b/server/src/domain/monitors/monitor.types.ts similarity index 80% rename from server/src/types/monitor.ts rename to server/src/domain/monitors/monitor.types.ts index 0708297f3..273d8de66 100644 --- a/server/src/types/monitor.ts +++ b/server/src/domain/monitors/monitor.types.ts @@ -1,7 +1,12 @@ -import type { CheckSnapshot } from "@/types/check.js"; -export type { CheckSnapshot } from "@/types/check.js"; -import type { GeoContinent, GroupedGeoCheck } from "@/types/geoCheck.js"; -export type { GeoContinent } from "@/types/geoCheck.js"; +import type { CheckSnapshot } from "@/domain/checks/check.type.js"; +export type { CheckSnapshot } from "@/domain/checks/check.type.js"; +import type { GeoContinent, GroupedGeoCheck } from "@/domain/geo-checks/geo-check.type.js"; +export type { GeoContinent } from "@/domain/geo-checks/geo-check.type.js"; +import http from "node:http"; + +export const HttpStatusCodes = Object.keys(http.STATUS_CODES).map(Number); +export const HttpStatusCodeSet = new Set(HttpStatusCodes); +export type HttpStatusCode = number; export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] as const; export type MonitorType = (typeof MonitorTypes)[number]; @@ -60,6 +65,7 @@ export interface Monitor { uptimePercentage?: number; notifications: string[]; tags: string[]; + customUpCodes: HttpStatusCode[]; secret?: string; cpuAlertThreshold: number; cpuAlertCounter: number; @@ -107,13 +113,13 @@ export interface GroupedGeoCheckResult { export interface UptimeDetailsResult { monitorData: { monitor: Monitor; - groupedChecks: import("./check.js").GroupedCheck[]; - groupedUpChecks: import("./check.js").GroupedCheck[]; - groupedDownChecks: import("./check.js").GroupedCheck[]; + groupedChecks: import("../checks/check.type.js").GroupedCheck[]; + groupedUpChecks: import("../checks/check.type.js").GroupedCheck[]; + groupedDownChecks: import("../checks/check.type.js").GroupedCheck[]; groupedAvgResponseTime: number; groupedUptimePercentage: number; }; - monitorStats: import("./monitorStats.js").MonitorStats | null; + monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null; } export interface HardwareDiskStats { @@ -161,15 +167,15 @@ export interface HardwareStats { export interface HardwareDetailsResult { monitor: Monitor; stats: HardwareStats; - monitorStats: import("./monitorStats.js").MonitorStats | null; + monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null; } export interface PageSpeedDetailsResult { monitorData: { monitor: Monitor; - groupedChecks: import("./check.js").PageSpeedGroupedCheck[]; + groupedChecks: import("../checks/check.type.js").PageSpeedGroupedCheck[]; }; - monitorStats: import("./monitorStats.js").MonitorStats | null; + monitorStats: import("../monitor-stats/monitor-stats.type.js").MonitorStats | null; } export interface Game { diff --git a/server/src/service/infrastructure/notificationMessageBuilder.ts b/server/src/domain/notifications/notification.message-builder.ts similarity index 95% rename from server/src/service/infrastructure/notificationMessageBuilder.ts rename to server/src/domain/notifications/notification.message-builder.ts index bc1d7ac82..9d3714ae7 100644 --- a/server/src/service/infrastructure/notificationMessageBuilder.ts +++ b/server/src/domain/notifications/notification.message-builder.ts @@ -1,12 +1,13 @@ -import type { HardwareStatusPayload, Monitor, MonitorStatusResponse } from "@/types/index.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/JobQueues/QueueHelper.js"; +import type { Monitor } from "@/domain/monitors/monitor.types.js"; +import type { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js"; +import type { MonitorActionDecision } from "@/service/job-queues/job-queue.helper.js"; import type { NotificationMessage, NotificationType, NotificationSeverity, ThresholdBreach, NotificationContent, -} from "@/types/notificationMessage.js"; +} from "@/domain/notifications/notification.type.js"; export interface INotificationMessageBuilder { buildMessage( @@ -186,7 +187,7 @@ export class NotificationMessageBuilder implements INotificationMessageBuilder { const breaches: ThresholdBreach[] = []; // Check if this is a hardware monitor with threshold data - if (monitor.type !== "hardware" || !monitorStatusResponse.payload) { + if (monitor.type !== "hardware" || !monitorStatusResponse.payload || typeof monitorStatusResponse.payload === "string") { return breaches; } diff --git a/server/src/db/models/Notification.ts b/server/src/domain/notifications/notification.model.ts similarity index 92% rename from server/src/db/models/Notification.ts rename to server/src/domain/notifications/notification.model.ts index 93202bb8f..797b3e768 100755 --- a/server/src/db/models/Notification.ts +++ b/server/src/domain/notifications/notification.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { Notification, NotificationChannel } from "@/types/notification.js"; +import type { Notification, NotificationChannel } from "@/domain/notifications/notification.type.js"; interface NotificationDocument extends Omit { _id: Types.ObjectId; diff --git a/server/src/repositories/notifications/INotificationsRepository.ts b/server/src/domain/notifications/notification.repository.interface.ts similarity index 86% rename from server/src/repositories/notifications/INotificationsRepository.ts rename to server/src/domain/notifications/notification.repository.interface.ts index 0236c3a89..9de6e1e06 100644 --- a/server/src/repositories/notifications/INotificationsRepository.ts +++ b/server/src/domain/notifications/notification.repository.interface.ts @@ -1,4 +1,4 @@ -import type { Notification } from "@/types/index.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; export interface INotificationsRepository { // create create(notificationData: Partial): Promise; diff --git a/server/src/repositories/notifications/MongoNotificationsRepository.ts b/server/src/domain/notifications/notification.repository.mongo.ts similarity index 93% rename from server/src/repositories/notifications/MongoNotificationsRepository.ts rename to server/src/domain/notifications/notification.repository.mongo.ts index e556fa206..cc2729cf7 100644 --- a/server/src/repositories/notifications/MongoNotificationsRepository.ts +++ b/server/src/domain/notifications/notification.repository.mongo.ts @@ -1,7 +1,7 @@ import mongoose from "mongoose"; -import { NotificationModel, type NotificationDocument } from "@/db/models/index.js"; -import { INotificationsRepository } from "@/repositories/index.js"; -import type { Notification } from "@/types/index.js"; +import { NotificationModel, type NotificationDocument } from "@/domain/notifications/notification.model.js"; +import { INotificationsRepository } from "@/domain/notifications/notification.repository.interface.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; import { AppError } from "@/utils/AppError.js"; class MongoNotificationsRepository implements INotificationsRepository { diff --git a/server/src/service/infrastructure/notificationsService.ts b/server/src/domain/notifications/notification.service.ts similarity index 91% rename from server/src/service/infrastructure/notificationsService.ts rename to server/src/domain/notifications/notification.service.ts index 09129a8af..ea6a9265c 100644 --- a/server/src/service/infrastructure/notificationsService.ts +++ b/server/src/domain/notifications/notification.service.ts @@ -1,11 +1,14 @@ -import type { Monitor, MonitorStatusResponse, Notification } from "@/types/index.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { IMonitorsRepository, INotificationsRepository } from "@/repositories/index.js"; -import { INotificationProvider } from "./notificationProviders/INotificationProvider.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/JobQueues/QueueHelper.js"; -import type { ISettingsService } from "@/service/system/settingsService.js"; +import type { Monitor } from "@/domain/monitors/monitor.types.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import type { MonitorStatusResponse } from "@/types/network.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import { INotificationsRepository } from "@/domain/notifications/notification.repository.interface.js"; +import { INotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { MonitorActionDecision } from "@/service/job-queues/job-queue.helper.js"; +import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import { ILogger } from "@/utils/logger.js"; -import type { INotificationMessageBuilder } from "@/service/infrastructure/notificationMessageBuilder.js"; +import type { INotificationMessageBuilder } from "@/domain/notifications/notification.message-builder.js"; export interface INotificationsService { createNotification: (notificationData: Partial, userId: string, teamId: string) => Promise; diff --git a/server/src/types/notificationMessage.ts b/server/src/domain/notifications/notification.type.ts similarity index 51% rename from server/src/types/notificationMessage.ts rename to server/src/domain/notifications/notification.type.ts index f06ff1bd9..8ae617a88 100644 --- a/server/src/types/notificationMessage.ts +++ b/server/src/domain/notifications/notification.type.ts @@ -1,3 +1,62 @@ +export const NotificationChannels = [ + "email", + "slack", + "discord", + "webhook", + "pager_duty", + "matrix", + "teams", + "telegram", + "pushover", + "twilio", + "ntfy", +] as const; +export type NotificationChannel = (typeof NotificationChannels)[number]; + +export interface Notification { + id: string; + userId: string; + teamId: string; + type: NotificationChannel; + notificationName: string; + address?: string; + phone?: string; + homeserverUrl?: string; + roomId?: string; + accessToken?: string; + accountSid?: string; + twilioPhoneNumber?: string; + topic?: string; + createdAt: string; + updatedAt: string; +} + +export interface AlertPagerDutyPayload { + routing_key?: string; + dedup_key?: string; + event_action?: "trigger" | "resolve"; + payload: Record; +} + +export interface AlertMatrixPayload { + plainText: string; + htmlText: string; +} + +export interface DiscordEmbedField { + name: string; + value: string; + inline?: boolean; +} + +export interface AlertDiscordPayload { + title: string; + description: string; + color: number; + fields: DiscordEmbedField[]; + timestamp: string; +} + /** * Unified notification message types for cross-provider consistency * Part of notification system unification effort diff --git a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts b/server/src/domain/notifications/providers/INotificationProvider.ts similarity index 85% rename from server/src/service/infrastructure/notificationProviders/INotificationProvider.ts rename to server/src/domain/notifications/providers/INotificationProvider.ts index b5c886408..6e1840669 100644 --- a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts +++ b/server/src/domain/notifications/providers/INotificationProvider.ts @@ -1,5 +1,5 @@ -import type { Notification } from "@/types/index.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; import { ILogger } from "@/utils/logger.js"; import type { OptionsOfJSONResponseBody } from "got"; diff --git a/server/src/service/infrastructure/notificationProviders/discord.ts b/server/src/domain/notifications/providers/discord.ts similarity index 92% rename from server/src/service/infrastructure/notificationProviders/discord.ts rename to server/src/domain/notifications/providers/discord.ts index 942e0b578..cada9fcf2 100644 --- a/server/src/service/infrastructure/notificationProviders/discord.ts +++ b/server/src/domain/notifications/providers/discord.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "DiscordProvider"; -import type { AlertDiscordPayload, DiscordEmbedField, Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; -import type { NotificationMessage, NotificationSeverity } from "@/types/notificationMessage.js"; +import type { AlertDiscordPayload, DiscordEmbedField, Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; +import type { NotificationMessage, NotificationSeverity } from "@/domain/notifications/notification.type.js"; import got from "got"; export class DiscordProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/domain/notifications/providers/email.ts similarity index 89% rename from server/src/service/infrastructure/notificationProviders/email.ts rename to server/src/domain/notifications/providers/email.ts index 0890eac7f..fa84be66d 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/domain/notifications/providers/email.ts @@ -1,10 +1,10 @@ const SERVICE_NAME = "EmailProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import { buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import { buildTestEmail } from "@/domain/notifications/providers/utils.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; import type { ILogger } from "@/utils/logger.js"; -import { IEmailService } from "@/service/infrastructure/emailService.js"; +import { IEmailService } from "@/service/emailService.js"; export class EmailProvider extends NotificationProvider { private emailService: IEmailService; diff --git a/server/src/service/infrastructure/notificationProviders/matrix.ts b/server/src/domain/notifications/providers/matrix.ts similarity index 93% rename from server/src/service/infrastructure/notificationProviders/matrix.ts rename to server/src/domain/notifications/providers/matrix.ts index f8a093ab7..a1f35b0ca 100644 --- a/server/src/service/infrastructure/notificationProviders/matrix.ts +++ b/server/src/domain/notifications/providers/matrix.ts @@ -1,9 +1,9 @@ const SERVICE_NAME = "MatrixProvider"; import got from "got"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { AlertMatrixPayload, Notification } from "@/types/index.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { AlertMatrixPayload, Notification } from "@/domain/notifications/notification.type.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import { randomUUID } from "crypto"; export class MatrixProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/ntfy.ts b/server/src/domain/notifications/providers/ntfy.ts similarity index 90% rename from server/src/service/infrastructure/notificationProviders/ntfy.ts rename to server/src/domain/notifications/providers/ntfy.ts index 8811cce47..7ca351d87 100644 --- a/server/src/service/infrastructure/notificationProviders/ntfy.ts +++ b/server/src/domain/notifications/providers/ntfy.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "NtfyProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got from "got"; export class NtfyProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/pagerduty.ts b/server/src/domain/notifications/providers/pagerduty.ts similarity index 89% rename from server/src/service/infrastructure/notificationProviders/pagerduty.ts rename to server/src/domain/notifications/providers/pagerduty.ts index 0d2fa75be..6cb264c1d 100644 --- a/server/src/service/infrastructure/notificationProviders/pagerduty.ts +++ b/server/src/domain/notifications/providers/pagerduty.ts @@ -1,10 +1,10 @@ const SERVICE_NAME = "PagerDutyProvider"; import got from "got"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; -import { AlertPagerDutyPayload } from "@/types/index.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; +import { AlertPagerDutyPayload } from "@/domain/notifications/notification.type.js"; export class PagerDutyProvider extends NotificationProvider { async sendTestAlert(notification: Partial): Promise { diff --git a/server/src/service/infrastructure/notificationProviders/pushover.ts b/server/src/domain/notifications/providers/pushover.ts similarity index 89% rename from server/src/service/infrastructure/notificationProviders/pushover.ts rename to server/src/domain/notifications/providers/pushover.ts index 5d001892d..b1831e405 100644 --- a/server/src/service/infrastructure/notificationProviders/pushover.ts +++ b/server/src/domain/notifications/providers/pushover.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "PushoverProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got from "got"; export class PushoverProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/slack.ts b/server/src/domain/notifications/providers/slack.ts similarity index 92% rename from server/src/service/infrastructure/notificationProviders/slack.ts rename to server/src/domain/notifications/providers/slack.ts index 1ab046f7f..23f9b2f61 100644 --- a/server/src/service/infrastructure/notificationProviders/slack.ts +++ b/server/src/domain/notifications/providers/slack.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "SlackProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got, { HTTPError } from "got"; export class SlackProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/teams.ts b/server/src/domain/notifications/providers/teams.ts similarity index 94% rename from server/src/service/infrastructure/notificationProviders/teams.ts rename to server/src/domain/notifications/providers/teams.ts index f98724295..36d37a210 100644 --- a/server/src/service/infrastructure/notificationProviders/teams.ts +++ b/server/src/domain/notifications/providers/teams.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "TeamsProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got, { HTTPError } from "got"; // Types for Adaptive Card elements diff --git a/server/src/service/infrastructure/notificationProviders/telegram.ts b/server/src/domain/notifications/providers/telegram.ts similarity index 89% rename from server/src/service/infrastructure/notificationProviders/telegram.ts rename to server/src/domain/notifications/providers/telegram.ts index 9d6968fdd..72e31b5bf 100644 --- a/server/src/service/infrastructure/notificationProviders/telegram.ts +++ b/server/src/domain/notifications/providers/telegram.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "TelegramProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got from "got"; export class TelegramProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/twilio.ts b/server/src/domain/notifications/providers/twilio.ts similarity index 88% rename from server/src/service/infrastructure/notificationProviders/twilio.ts rename to server/src/domain/notifications/providers/twilio.ts index a871fe5cb..2f7937b7f 100644 --- a/server/src/service/infrastructure/notificationProviders/twilio.ts +++ b/server/src/domain/notifications/providers/twilio.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "TwilioProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got from "got"; export class TwilioProvider extends NotificationProvider { diff --git a/server/src/service/infrastructure/notificationProviders/utils.ts b/server/src/domain/notifications/providers/utils.ts similarity index 80% rename from server/src/service/infrastructure/notificationProviders/utils.ts rename to server/src/domain/notifications/providers/utils.ts index 454df1963..3eaf428fe 100644 --- a/server/src/service/infrastructure/notificationProviders/utils.ts +++ b/server/src/domain/notifications/providers/utils.ts @@ -1,4 +1,4 @@ -import { IEmailService } from "@/service/infrastructure/emailService.js"; +import { IEmailService } from "@/service/emailService.js"; export const buildTestEmail = async (emailService: IEmailService) => { const context = { testName: "Monitoring System" }; const html = await emailService.buildEmail("testEmailTemplate", context); diff --git a/server/src/service/infrastructure/notificationProviders/webhook.ts b/server/src/domain/notifications/providers/webhook.ts similarity index 90% rename from server/src/service/infrastructure/notificationProviders/webhook.ts rename to server/src/domain/notifications/providers/webhook.ts index 292650f91..fc5a80fde 100644 --- a/server/src/service/infrastructure/notificationProviders/webhook.ts +++ b/server/src/domain/notifications/providers/webhook.ts @@ -1,8 +1,8 @@ const SERVICE_NAME = "WebhookProvider"; -import type { Notification } from "@/types/index.js"; -import { NotificationProvider } from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { Notification } from "@/domain/notifications/notification.type.js"; +import { NotificationProvider } from "@/domain/notifications/providers/INotificationProvider.js"; +import type { NotificationMessage } from "@/domain/notifications/notification.type.js"; +import { getTestMessage } from "@/domain/notifications/providers/utils.js"; import got from "got"; export class WebhookProvider extends NotificationProvider { diff --git a/server/src/db/models/QueueWorker.ts b/server/src/domain/queue-workers/queue-worker.model.ts similarity index 54% rename from server/src/db/models/QueueWorker.ts rename to server/src/domain/queue-workers/queue-worker.model.ts index 76485f56c..06299d339 100644 --- a/server/src/db/models/QueueWorker.ts +++ b/server/src/domain/queue-workers/queue-worker.model.ts @@ -1,16 +1,6 @@ import { Schema, model } from "mongoose"; -import { QueueModes, type QueueMode } from "@/types/settings.js"; - -// Heartbeat presence record for a scheduler instance. _id is the scheduler's -// workerId (hostname:pid:uuid). The TTL index on lastSeenAt garbage-collects -// records for instances that have stopped heartbeating. -interface QueueWorkerDocument { - _id: string; - mode: QueueMode; - lastSeenAt: Date; - createdAt: Date; - updatedAt: Date; -} +import { QueueModes } from "@/domain/app-settings/app-settings.type.js"; +import { QueueWorkerDocument } from "@/domain/queue-workers/queue-worker.type.js"; const QueueWorkerSchema = new Schema( { diff --git a/server/src/repositories/queue-workers/IQueueWorkersRepository.ts b/server/src/domain/queue-workers/queue-worker.repository.interface.ts similarity index 56% rename from server/src/repositories/queue-workers/IQueueWorkersRepository.ts rename to server/src/domain/queue-workers/queue-worker.repository.interface.ts index b1f7e76b1..e4522c916 100644 --- a/server/src/repositories/queue-workers/IQueueWorkersRepository.ts +++ b/server/src/domain/queue-workers/queue-worker.repository.interface.ts @@ -1,4 +1,5 @@ -import type { QueueMode, QueueWorker } from "@/types/settings.js"; +import type { QueueMode } from "@/domain/app-settings/app-settings.type.js"; +import type { QueueWorker } from "@/domain/queue-workers/queue-worker.type.js"; export interface IQueueWorkersRepository { upsert(workerId: string, mode: QueueMode): Promise; diff --git a/server/src/repositories/queue-workers/MongoQueueWorkersRepository.ts b/server/src/domain/queue-workers/queue-worker.repository.mongo.ts similarity index 72% rename from server/src/repositories/queue-workers/MongoQueueWorkersRepository.ts rename to server/src/domain/queue-workers/queue-worker.repository.mongo.ts index b8155ea1d..35565bd03 100644 --- a/server/src/repositories/queue-workers/MongoQueueWorkersRepository.ts +++ b/server/src/domain/queue-workers/queue-worker.repository.mongo.ts @@ -1,7 +1,7 @@ -import type { QueueMode, QueueWorker } from "@/types/settings.js"; -import type { IQueueWorkersRepository } from "./IQueueWorkersRepository.js"; -import type { QueueWorkerDocument } from "@/db/models/QueueWorker.js"; -import { QueueWorkerModel } from "@/db/models/QueueWorker.js"; +import type { QueueMode } from "@/domain/app-settings/app-settings.type.js"; +import type { IQueueWorkersRepository } from "./queue-worker.repository.interface.js"; +import type { QueueWorker } from "@/domain/queue-workers/queue-worker.type.js"; +import { QueueWorkerModel, type QueueWorkerDocument } from "@/domain/queue-workers/queue-worker.model.js"; const SERVICE_NAME = "MongoQueueWorkersRepository"; class MongoQueueWorkersRepository implements IQueueWorkersRepository { diff --git a/server/src/domain/queue-workers/queue-worker.type.ts b/server/src/domain/queue-workers/queue-worker.type.ts new file mode 100644 index 000000000..a83c831ad --- /dev/null +++ b/server/src/domain/queue-workers/queue-worker.type.ts @@ -0,0 +1,18 @@ +import { type QueueMode } from "@/domain/app-settings/app-settings.type.js"; + +// Heartbeat presence record for a scheduler instance. _id is the scheduler's +// workerId (hostname:pid:uuid). The TTL index on lastSeenAt garbage-collects +// records for instances that have stopped heartbeating. +export interface QueueWorkerDocument { + _id: string; + mode: QueueMode; + lastSeenAt: Date; + createdAt: Date; + updatedAt: Date; +} + +export type QueueWorker = { + workerId: string; // hostname:pid:uuid + mode: QueueMode; + lastSeenAt: number; // epoch ms of the last heartbeat +}; diff --git a/server/src/db/models/RecoveryToken.ts b/server/src/domain/recovery-tokens/recovery-token.model.ts similarity index 87% rename from server/src/db/models/RecoveryToken.ts rename to server/src/domain/recovery-tokens/recovery-token.model.ts index 07b697d9e..54d77a4f2 100644 --- a/server/src/db/models/RecoveryToken.ts +++ b/server/src/domain/recovery-tokens/recovery-token.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { RecoveryToken as RecoveryTokenEntity } from "@/types/recoveryToken.js"; +import type { RecoveryToken as RecoveryTokenEntity } from "@/domain/recovery-tokens/recovery-token.type.js"; type RecoveryTokenDocumentBase = Omit & { expiry: Date; diff --git a/server/src/repositories/recovery-tokens/IRecoveryTokensRepository.ts b/server/src/domain/recovery-tokens/recovery-token.repository.interface.ts similarity index 74% rename from server/src/repositories/recovery-tokens/IRecoveryTokensRepository.ts rename to server/src/domain/recovery-tokens/recovery-token.repository.interface.ts index 9df1fa793..d5f3a30b7 100644 --- a/server/src/repositories/recovery-tokens/IRecoveryTokensRepository.ts +++ b/server/src/domain/recovery-tokens/recovery-token.repository.interface.ts @@ -1,4 +1,4 @@ -import type { RecoveryToken } from "@/types/recoveryToken.js"; +import type { RecoveryToken } from "@/domain/recovery-tokens/recovery-token.type.js"; export interface IRecoveryTokensRepository { // create diff --git a/server/src/repositories/recovery-tokens/MongoRecoveryTokensRepository.ts b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts similarity index 82% rename from server/src/repositories/recovery-tokens/MongoRecoveryTokensRepository.ts rename to server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts index 4c9f9ebb6..1e6140a88 100644 --- a/server/src/repositories/recovery-tokens/MongoRecoveryTokensRepository.ts +++ b/server/src/domain/recovery-tokens/recovery-token.repository.mongo.ts @@ -1,7 +1,7 @@ -import type { RecoveryToken } from "@/types/index.js"; -import type { IRecoveryTokensRepository } from "./IRecoveryTokensRepository.js"; -import type { RecoveryTokenDocument } from "@/db/models/RecoveryToken.js"; -import { RecoveryTokenModel } from "@/db/models/RecoveryToken.js"; +import type { RecoveryToken } from "@/domain/recovery-tokens/recovery-token.type.js"; +import type { IRecoveryTokensRepository } from "./recovery-token.repository.interface.js"; +import type { RecoveryTokenDocument } from "@/domain/recovery-tokens/recovery-token.model.js"; +import { RecoveryTokenModel } from "@/domain/recovery-tokens/recovery-token.model.js"; import mongoose from "mongoose"; import crypto from "crypto"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/types/recoveryToken.ts b/server/src/domain/recovery-tokens/recovery-token.type.ts similarity index 100% rename from server/src/types/recoveryToken.ts rename to server/src/domain/recovery-tokens/recovery-token.type.ts diff --git a/server/src/repositories/status-pages/IStatusPagesRepository.ts b/server/src/domain/status-pages/status-page-repository.interface.ts similarity index 89% rename from server/src/repositories/status-pages/IStatusPagesRepository.ts rename to server/src/domain/status-pages/status-page-repository.interface.ts index d4ca85f78..4b3eccd0a 100644 --- a/server/src/repositories/status-pages/IStatusPagesRepository.ts +++ b/server/src/domain/status-pages/status-page-repository.interface.ts @@ -1,4 +1,4 @@ -import type { StatusPage } from "@/types/statusPage.js"; +import type { StatusPage } from "@/domain/status-pages/status-page.type.js"; export interface IStatusPagesRepository { // create diff --git a/server/src/repositories/status-pages/MongoStatusPagesRepository.ts b/server/src/domain/status-pages/status-page-repository.mongo.ts similarity index 95% rename from server/src/repositories/status-pages/MongoStatusPagesRepository.ts rename to server/src/domain/status-pages/status-page-repository.mongo.ts index 785cfd139..78e43a3bd 100644 --- a/server/src/repositories/status-pages/MongoStatusPagesRepository.ts +++ b/server/src/domain/status-pages/status-page-repository.mongo.ts @@ -1,6 +1,6 @@ -import { IStatusPagesRepository } from "@/repositories/index.js"; -import { type StatusPageDocument, StatusPageModel } from "@/db/models/StatusPage.js"; -import type { StatusPage, StatusPageLogo, StatusPageLogoDocument } from "@/types/statusPage.js"; +import { IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; +import { type StatusPageDocument, StatusPageModel } from "@/domain/status-pages/status-page.model.js"; +import type { StatusPage, StatusPageLogo, StatusPageLogoDocument } from "@/domain/status-pages/status-page.type.js"; import mongoose from "mongoose"; import { AppError } from "@/utils/AppError.js"; import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js"; diff --git a/server/src/db/models/StatusPage.ts b/server/src/domain/status-pages/status-page.model.ts similarity index 94% rename from server/src/db/models/StatusPage.ts rename to server/src/domain/status-pages/status-page.model.ts index 358b0dde5..bd6801318 100644 --- a/server/src/db/models/StatusPage.ts +++ b/server/src/domain/status-pages/status-page.model.ts @@ -1,12 +1,12 @@ import { Schema, model, type Types } from "mongoose"; -import type { StatusPage, StatusPageLogoDocument } from "@/types/statusPage.js"; +import type { StatusPage, StatusPageLogoDocument } from "@/domain/status-pages/status-page.type.js"; import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, StatusPageThemeModes, StatusPageThemes, StatusPageTypes, -} from "@/types/statusPage.js"; +} from "@/domain/status-pages/status-page.type.js"; type StatusPageDocumentBase = Omit< StatusPage, diff --git a/server/src/service/business/statusPageService.ts b/server/src/domain/status-pages/status-page.service.ts similarity index 94% rename from server/src/service/business/statusPageService.ts rename to server/src/domain/status-pages/status-page.service.ts index 1b77991f3..6be395e49 100644 --- a/server/src/service/business/statusPageService.ts +++ b/server/src/domain/status-pages/status-page.service.ts @@ -1,6 +1,6 @@ -import { type IStatusPagesRepository } from "@/repositories/index.js"; -import { ISettingsService } from "@/service/system/settingsService.js"; -import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, StatusPage } from "@/types/index.js"; +import { type IStatusPagesRepository } from "@/domain/status-pages/status-page-repository.interface.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, StatusPage } from "@/domain/status-pages/status-page.type.js"; import { AppError } from "@/utils/AppError.js"; import { normalizeStatusPageDomain } from "@/utils/statusPageDomain.js"; diff --git a/server/src/types/statusPage.ts b/server/src/domain/status-pages/status-page.type.ts similarity index 100% rename from server/src/types/statusPage.ts rename to server/src/domain/status-pages/status-page.type.ts diff --git a/server/src/db/models/Tag.ts b/server/src/domain/tags/tag.model.ts similarity index 91% rename from server/src/db/models/Tag.ts rename to server/src/domain/tags/tag.model.ts index df77aa7ab..2fbfbf2c6 100644 --- a/server/src/db/models/Tag.ts +++ b/server/src/domain/tags/tag.model.ts @@ -1,5 +1,5 @@ import { Schema, model, type Types } from "mongoose"; -import type { Tag as TagEntity } from "@/types/index.js"; +import type { Tag as TagEntity } from "@/domain/tags/tag.type.js"; type TagDocumentBase = Omit; diff --git a/server/src/repositories/tags/ITagsRepository.ts b/server/src/domain/tags/tag.repository.interface.ts similarity index 88% rename from server/src/repositories/tags/ITagsRepository.ts rename to server/src/domain/tags/tag.repository.interface.ts index 42cec2a5a..ba6fe8eb3 100644 --- a/server/src/repositories/tags/ITagsRepository.ts +++ b/server/src/domain/tags/tag.repository.interface.ts @@ -1,4 +1,4 @@ -import { Tag } from "@/types/index.js"; +import { Tag } from "@/domain/tags/tag.type.js"; export interface ITagsRepository { // create diff --git a/server/src/repositories/tags/MongoTagsRepository.ts b/server/src/domain/tags/tag.repository.mongo.ts similarity index 92% rename from server/src/repositories/tags/MongoTagsRepository.ts rename to server/src/domain/tags/tag.repository.mongo.ts index 72355c7df..5d7d8d0a2 100644 --- a/server/src/repositories/tags/MongoTagsRepository.ts +++ b/server/src/domain/tags/tag.repository.mongo.ts @@ -1,6 +1,6 @@ -import { ITagsRepository } from "@/repositories/tags/ITagsRepository.js"; -import { Tag } from "@/types/tag.js"; -import { TagDocument, TagModel } from "@/db/models/index.js"; +import { ITagsRepository } from "@/domain/tags/tag.repository.interface.js"; +import { Tag } from "@/domain/tags/tag.type.js"; +import { TagDocument, TagModel } from "@/domain/tags/tag.model.js"; import mongoose from "mongoose"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/service/infrastructure/tagService.ts b/server/src/domain/tags/tag.service.ts similarity index 85% rename from server/src/service/infrastructure/tagService.ts rename to server/src/domain/tags/tag.service.ts index 5dc6f7cef..8c1806525 100644 --- a/server/src/service/infrastructure/tagService.ts +++ b/server/src/domain/tags/tag.service.ts @@ -1,5 +1,6 @@ -import { Tag } from "@/types/index.js"; -import { ITagsRepository, IMonitorsRepository } from "@/repositories/index.js"; +import { Tag } from "@/domain/tags/tag.type.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import { ITagsRepository } from "@/domain/tags/tag.repository.interface.js"; export interface ITagsService { createTag(tag: Partial, teamId: string): Promise; getTag(tagId: string, teamId: string): Promise; diff --git a/server/src/types/tag.ts b/server/src/domain/tags/tag.type.ts similarity index 100% rename from server/src/types/tag.ts rename to server/src/domain/tags/tag.type.ts diff --git a/server/src/db/models/Team.ts b/server/src/domain/teams/team.model.ts similarity index 90% rename from server/src/db/models/Team.ts rename to server/src/domain/teams/team.model.ts index 608f90ad8..5fb98b5ce 100755 --- a/server/src/db/models/Team.ts +++ b/server/src/domain/teams/team.model.ts @@ -1,6 +1,6 @@ import { Schema, model, type Types } from "mongoose"; -import type { Team } from "@/types/index.js"; +import type { Team } from "@/domain/teams/team.type.js"; type TeamDocumentBase = Omit & {}; interface TeamDocument extends TeamDocumentBase { diff --git a/server/src/repositories/teams/ITeamsRepository.ts b/server/src/domain/teams/team.repository.interface.ts similarity index 74% rename from server/src/repositories/teams/ITeamsRepository.ts rename to server/src/domain/teams/team.repository.interface.ts index ba1b353cf..83854123c 100644 --- a/server/src/repositories/teams/ITeamsRepository.ts +++ b/server/src/domain/teams/team.repository.interface.ts @@ -1,4 +1,4 @@ -import type { Team } from "@/types/index.js"; +import type { Team } from "@/domain/teams/team.type.js"; export interface ITeamsRepository { // create create(email: string): Promise; diff --git a/server/src/repositories/teams/MongoTeamsRepository.ts b/server/src/domain/teams/team.repository.model.ts similarity index 84% rename from server/src/repositories/teams/MongoTeamsRepository.ts rename to server/src/domain/teams/team.repository.model.ts index 04e2be6f4..d050d6f59 100644 --- a/server/src/repositories/teams/MongoTeamsRepository.ts +++ b/server/src/domain/teams/team.repository.model.ts @@ -1,6 +1,6 @@ -import { Team } from "@/types/index.js"; -import { TeamDocument, TeamModel } from "@/db/models/index.js"; -import { ITeamsRepository } from "@/repositories/index.js"; +import { Team } from "@/domain/teams/team.type.js"; +import { TeamDocument, TeamModel } from "@/domain/teams/team.model.js"; +import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; import mongoose from "mongoose"; class MongoTeamsRepository implements ITeamsRepository { diff --git a/server/src/types/team.ts b/server/src/domain/teams/team.type.ts similarity index 100% rename from server/src/types/team.ts rename to server/src/domain/teams/team.type.ts diff --git a/server/src/db/models/User.ts b/server/src/domain/users/user.model.ts similarity index 88% rename from server/src/db/models/User.ts rename to server/src/domain/users/user.model.ts index ac3895301..960702d89 100644 --- a/server/src/db/models/User.ts +++ b/server/src/domain/users/user.model.ts @@ -1,9 +1,9 @@ import { Schema, model, type Types } from "mongoose"; import bcrypt from "bcryptjs"; -import type { User, UserProfileImage, UserRole } from "@/types/index.js"; -import { MonitorModel } from "@/db/models/index.js"; -import Team from "./Team.js"; -import NotificationModel from "./Notification.js"; +import type { User, UserProfileImage, UserRole } from "@/domain/users/user.type.js"; +import { MonitorModel } from "@/domain/monitors/monitor.model.js"; +import Team from "../teams/team.model.js"; +import NotificationModel from "../notifications/notification.model.js"; type UserDocumentBase = Omit & { teamId?: Types.ObjectId; diff --git a/server/src/repositories/users/IUsersRepository.ts b/server/src/domain/users/user.repository.interface.ts similarity index 88% rename from server/src/repositories/users/IUsersRepository.ts rename to server/src/domain/users/user.repository.interface.ts index 3fa094c54..be6212993 100644 --- a/server/src/repositories/users/IUsersRepository.ts +++ b/server/src/domain/users/user.repository.interface.ts @@ -1,4 +1,4 @@ -import type { User } from "@/types/index.js"; +import type { User } from "@/domain/users/user.type.js"; export interface IUsersRepository { // create create(user: Partial, imageFile?: Express.Multer.File | null): Promise; diff --git a/server/src/repositories/users/MongoUsersRepository.ts b/server/src/domain/users/user.repository.mongo.ts similarity index 95% rename from server/src/repositories/users/MongoUsersRepository.ts rename to server/src/domain/users/user.repository.mongo.ts index a8525f2c8..1dc729b81 100644 --- a/server/src/repositories/users/MongoUsersRepository.ts +++ b/server/src/domain/users/user.repository.mongo.ts @@ -1,7 +1,7 @@ import mongoose from "mongoose"; -import { IUsersRepository } from "@/repositories/index.js"; -import { UserModel, type UserDocument } from "@/db/models/index.js"; -import type { User, UserProfileImage } from "@/types/index.js"; +import { IUsersRepository } from "@/domain/users/user.repository.interface.js"; +import { UserModel, type UserDocument } from "@/domain/users/user.model.js"; +import type { User, UserProfileImage } from "@/domain/users/user.type.js"; import { GenerateAvatarImage } from "@/utils/imageProcessing.js"; import { ParseBoolean } from "@/utils/utils.js"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/service/business/userService.ts b/server/src/domain/users/user.service.ts similarity index 93% rename from server/src/service/business/userService.ts rename to server/src/domain/users/user.service.ts index 8b2c52159..4edacfbc5 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/domain/users/user.service.ts @@ -1,18 +1,16 @@ -import { - IInvitesRepository, - IMonitorsRepository, - IRecoveryTokensRepository, - IUsersRepository, - ISettingsRepository, - ITeamsRepository, -} from "@/repositories/index.js"; -import type { User } from "@/types/index.js"; -import { canManageRole, type UserRole } from "@/types/user.js"; +import { ISettingsRepository } from "@/domain/app-settings/app-settings-repository.interface.js"; +import { IInvitesRepository } from "@/domain/invites/invite.repository.interface.js"; +import { IRecoveryTokensRepository } from "@/domain/recovery-tokens/recovery-token.repository.interface.js"; +import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; +import { IUsersRepository } from "@/domain/users/user.repository.interface.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { User } from "@/domain/users/user.type.js"; +import { canManageRole, type UserRole } from "@/domain/users/user.type.js"; import bcrypt from "bcryptjs"; import { AppError } from "@/utils/AppError.js"; -import { IJobQueue } from "@/service/infrastructure/JobQueues/IJobQueue.js"; -import { IEmailService } from "@/service/infrastructure/emailService.js"; -import { EnvConfig, ISettingsService } from "@/service/system/settingsService.js"; +import { IJobQueue } from "@/service/job-queues/job-queue.interface.js"; +import { IEmailService } from "@/service/emailService.js"; +import { EnvConfig, ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import { ILogger } from "@/utils/logger.js"; import jwt from "jsonwebtoken"; import crypto from "crypto"; diff --git a/server/src/types/user.ts b/server/src/domain/users/user.type.ts similarity index 100% rename from server/src/types/user.ts rename to server/src/domain/users/user.type.ts diff --git a/server/src/index.ts b/server/src/index.ts index 45c75ac8b..cd3a39cdd 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -2,13 +2,13 @@ import { initializeServices } from "./config/services.js"; import { initializeControllers } from "./config/controllers.js"; import { createApp } from "./app.js"; import { initShutdownListener } from "@/shutdown.js"; -import { validateEnv } from "@/validation/envValidation.js"; +import { validateEnv } from "@/config/envValidation.js"; import { fileURLToPath } from "url"; import path from "path"; import fs from "fs"; import Logger, { ILogger } from "@/utils/logger.js"; -import { SettingsService } from "@/service/index.js"; +import { SettingsService } from "@/domain/app-settings/app-settings.service.js"; const SERVICE_NAME = "Server"; let logger: ILogger; @@ -52,7 +52,6 @@ const startApp = async () => { frontendPath, openApiSpec, }); - const server = app.listen(env.PORT, () => { logger.info({ message: `Server started on port:${env.PORT}` }); }); diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts deleted file mode 100644 index d36d9d189..000000000 --- a/server/src/repositories/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -export * from "@/repositories/monitors/IMonitorsRepository.js"; -export { default as MongoMonitorsRepository } from "@/repositories/monitors/MongoMonitorsRepository.js"; - -export * from "@/repositories/checks/IChecksRepository.js"; -export { default as MongoChecksRepository } from "@/repositories/checks/MongoChecksRepistory.js"; - -export * from "@/repositories/monitor-stats/IMonitorStatsRepository.js"; -export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-stats/MongoMonitorStatsRepository.js"; - -export * from "@/repositories/status-pages/IStatusPagesRepository.js"; -export { default as MongoStatusPagesRepository } from "@/repositories/status-pages/MongoStatusPagesRepository.js"; - -export * from "@/repositories/users/IUsersRepository.js"; -export { default as MongoUsersRepository } from "@/repositories/users/MongoUsersRepository.js"; - -export * from "@/repositories/invites/IInvitesRepository.js"; -export { default as MongoInvitesRepository } from "@/repositories/invites/MongoInviteRepository.js"; - -export * from "@/repositories/recovery-tokens/IRecoveryTokensRepository.js"; -export { default as MongoRecoveryTokensRepository } from "@/repositories/recovery-tokens/MongoRecoveryTokensRepository.js"; - -export * from "@/repositories/settings/ISettingsRepository.js"; -export { default as MongoSettingsRepository } from "@/repositories/settings/MongoSettingsRepository.js"; - -export * from "@/repositories/notifications/INotificationsRepository.js"; -export { default as MongoNotificationsRepository } from "@/repositories/notifications/MongoNotificationsRepository.js"; - -export * from "@/repositories/tags/ITagsRepository.js"; -export { default as MongoTagsRepository } from "@/repositories/tags/MongoTagsRepository.js"; - -export * from "@/repositories/incidents/IIncidentsRepository.js"; -export { default as MongoIncidentsRepository } from "@/repositories/incidents/MongoIncidentsRepository.js"; - -export * from "@/repositories/teams/ITeamsRepository.js"; -export { default as MongoTeamsRepository } from "@/repositories/teams/MongoTeamsRepository.js"; - -export * from "@/repositories/maintenance-windows/IMaintenanceWindowsRepository.js"; -export { default as MongoMaintenanceWindowsRepository } from "@/repositories/maintenance-windows/MongoMaintenanceWindowsRepository.js"; - -export * from "@/repositories/geo-checks/IGeoChecksRepository.js"; -export { default as MongoGeoChecksRepository } from "@/repositories/geo-checks/MongoGeoChecksRepository.js"; - -export * from "@/repositories/dlq/IDLQRepository.js"; -export { default as MongoDLQRepository } from "@/repositories/dlq/MongoDLQRepository.js"; - -export * from "@/repositories/queue-workers/IQueueWorkersRepository.js"; -export { default as MongoQueueWorkersRepository } from "@/repositories/queue-workers/MongoQueueWorkersRepository.js"; diff --git a/server/src/service/infrastructure/bufferService.ts b/server/src/service/bufferService.ts similarity index 91% rename from server/src/service/infrastructure/bufferService.ts rename to server/src/service/bufferService.ts index 5a1fb3d12..38dfb3545 100755 --- a/server/src/service/infrastructure/bufferService.ts +++ b/server/src/service/bufferService.ts @@ -1,9 +1,9 @@ -import type { Check } from "@/types/index.js"; -import type { GeoCheck } from "@/types/index.js"; -import type { IGeoChecksService } from "../business/geoChecksService.js"; +import type { Check } from "@/domain/checks/check.type.js"; +import type { GeoCheck } from "@/domain/geo-checks/geo-check.type.js"; +import type { IGeoChecksService } from "../domain/geo-checks/geo-check.service.js"; import type { ILogger } from "@/utils/logger.js"; -import type { ISettingsService } from "@/service/system/settingsService.js"; -import { ICheckService } from "../business/checkService.js"; +import type { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; +import { ICheckService } from "../domain/checks/check.service.js"; const SERVICE_NAME = "BufferService"; export interface IBufferService { diff --git a/server/src/service/infrastructure/emailService.ts b/server/src/service/emailService.ts similarity index 97% rename from server/src/service/infrastructure/emailService.ts rename to server/src/service/emailService.ts index 4428b0477..ca153392d 100755 --- a/server/src/service/infrastructure/emailService.ts +++ b/server/src/service/emailService.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from "url"; -import { EmailTransportConfig } from "@/types/index.js"; -import { ISettingsService } from "@/service/system/settingsService.js"; +import { EmailTransportConfig } from "@/domain/app-settings/app-settings.type.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import { ILogger } from "@/utils/logger.js"; import fs from "node:fs"; import path from "node:path"; diff --git a/server/src/service/infrastructure/globalPingService.ts b/server/src/service/globalPingService.ts similarity index 88% rename from server/src/service/infrastructure/globalPingService.ts rename to server/src/service/globalPingService.ts index 41f96b286..cebd95408 100644 --- a/server/src/service/infrastructure/globalPingService.ts +++ b/server/src/service/globalPingService.ts @@ -1,8 +1,9 @@ -import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/types/geoCheck.js"; -import { supportsGeoCheck } from "@/types/monitor.js"; -import { MonitorType } from "@/types/index.js"; +import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/domain/geo-checks/geo-check.type.js"; +import { supportsGeoCheck, type HttpStatusCode } from "@/domain/monitors/monitor.types.js"; +import { MonitorType } from "@/domain/monitors/monitor.types.js"; import type { ILogger } from "@/utils/logger.js"; import got from "got"; +import { isStatusUp } from "@/service/network/utils.js"; const SERVICE_NAME = "GlobalPingService"; const GLOBAL_PING_API_BASE = "https://api.globalping.io/v1"; @@ -62,7 +63,7 @@ interface GlobalPingProbeResult { export interface IGlobalPingService { readonly serviceName: string; createMeasurement(monitorType: MonitorType, url: string, locations: GeoContinent[]): Promise; - pollForResults(measurementId: string, timeoutMs?: number): Promise; + pollForResults(measurementId: string, timeoutMs?: number, customUpCodes?: HttpStatusCode[]): Promise; } export class GlobalPingService implements IGlobalPingService { @@ -119,7 +120,11 @@ export class GlobalPingService implements IGlobalPingService { } } - async pollForResults(measurementId: string, timeoutMs: number = MAX_POLL_TIMEOUT_MS): Promise { + async pollForResults( + measurementId: string, + timeoutMs: number = MAX_POLL_TIMEOUT_MS, + customUpCodes: HttpStatusCode[] = [] + ): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { @@ -132,7 +137,7 @@ export class GlobalPingService implements IGlobalPingService { const measurement = response.body; if (measurement.status === "finished") { - const results = this.transformResults(measurement.results || []); + const results = this.transformResults(measurement.results || [], customUpCodes); this.logger.debug({ message: `GlobalPing measurement completed: ${measurementId}`, service: SERVICE_NAME, @@ -174,7 +179,7 @@ export class GlobalPingService implements IGlobalPingService { return []; } - private transformResults(probeResults: GlobalPingProbeResult[]): GeoCheckResult[] { + private transformResults(probeResults: GlobalPingProbeResult[], customUpCodes: HttpStatusCode[] = []): GeoCheckResult[] { const successfulResults: GeoCheckResult[] = []; for (const probeResult of probeResults) { @@ -205,7 +210,7 @@ export class GlobalPingService implements IGlobalPingService { successfulResults.push({ location, - status: probeResult.result.statusCode >= 200 && probeResult.result.statusCode < 300, + status: isStatusUp(probeResult.result.statusCode, customUpCodes), statusCode: probeResult.result.statusCode, timings, }); diff --git a/server/src/service/index.ts b/server/src/service/index.ts deleted file mode 100644 index 2a0974e27..000000000 --- a/server/src/service/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Business services -export * from "@/service/business/checkService.js"; -export * from "@/service/business/diagnosticService.js"; -export * from "@/service/business/geoChecksService.js"; -export * from "@/service/business/incidentService.js"; -export * from "@/service/business/inviteService.js"; -export * from "@/service/business/maintenanceWindowService.js"; -export * from "@/service/business/monitorService.js"; -export * from "@/service/business/statusPageService.js"; -export * from "@/service/business/userService.js"; - -// Infrastructure services -export * from "@/service/infrastructure/JobQueues/IJobQueue.js"; -export * from "@/service/infrastructure/JobQueues/SuperSimpleQueue.js"; -export * from "@/service/infrastructure/JobQueues/LessSimpleQueue.js"; -export * from "@/service/infrastructure/JobQueues/QueueHelper.js"; -export * from "@/service/infrastructure/notificationMessageBuilder.js"; -export * from "@/service/infrastructure/bufferService.js"; -export * from "@/service/infrastructure/emailService.js"; -export * from "@/service/infrastructure/globalPingService.js"; -export * from "@/service/infrastructure/networkService.js"; -export * from "@/service/infrastructure/notificationsService.js"; -export * from "@/service/infrastructure/tagService.js"; -export * from "@/service/infrastructure/statusService.js"; - -// Notification providers -export * from "@/service/infrastructure/notificationProviders/discord.js"; -export * from "@/service/infrastructure/notificationProviders/email.js"; -export * from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; -export * from "@/service/infrastructure/notificationProviders/matrix.js"; -export * from "@/service/infrastructure/notificationProviders/pagerduty.js"; -export * from "@/service/infrastructure/notificationProviders/slack.js"; -export * from "@/service/infrastructure/notificationProviders/teams.js"; -export * from "@/service/infrastructure/notificationProviders/webhook.js"; -export * from "@/service/infrastructure/notificationProviders/telegram.js"; -export * from "@/service/infrastructure/notificationProviders/pushover.js"; -export * from "@/service/infrastructure/notificationProviders/twilio.js"; -export * from "@/service/infrastructure/notificationProviders/ntfy.js"; - -// System services -export * from "@/service/system/settingsService.js"; diff --git a/server/src/service/infrastructure/JobQueues/QueueHelper.ts b/server/src/service/job-queues/job-queue.helper.ts similarity index 90% rename from server/src/service/infrastructure/JobQueues/QueueHelper.ts rename to server/src/service/job-queues/job-queue.helper.ts index ea86feae0..69ecb86c6 100644 --- a/server/src/service/infrastructure/JobQueues/QueueHelper.ts +++ b/server/src/service/job-queues/job-queue.helper.ts @@ -1,29 +1,26 @@ const SERVICE_NAME = "JobQueueHelper"; -import type { Monitor } from "@/types/monitor.js"; -import { supportsGeoCheck } from "@/types/monitor.js"; +import type { Monitor } from "@/domain/monitors/monitor.types.js"; +import { supportsGeoCheck } from "@/domain/monitors/monitor.types.js"; import { AppError } from "@/utils/AppError.js"; -import { - ICheckService, - INetworkService, - INotificationsService, - ISettingsService, - IStatusService, - IncidentService, - type IGeoChecksService, -} from "@/service/index.js"; -import { CHECK_TTL_SENTINEL, type StatusChangeResult } from "@/types/index.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; +import { ICheckService } from "@/domain/checks/check.service.js"; +import { type IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js"; +import { IncidentService } from "@/domain/incidents/incident.service.js"; +import { INotificationsService } from "@/domain/notifications/notification.service.js"; +import { INetworkService } from "@/service/networkService.js"; +import { IStatusService } from "@/service/statusService.js"; +import { CHECK_TTL_SENTINEL } from "@/domain/checks/check.type.js"; +import { type StatusChangeResult } from "@/types/network.js"; import { isWindowActive } from "@/utils/maintenanceWindow.js"; -import { - IMaintenanceWindowsRepository, - IMonitorsRepository, - ITeamsRepository, - IMonitorStatsRepository, - IChecksRepository, - IIncidentsRepository, - IGeoChecksRepository, -} from "@/repositories/index.js"; +import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; +import { IGeoChecksRepository } from "@/domain/geo-checks/geo-check.repository.interface.js"; +import { IIncidentsRepository } from "@/domain/incidents/incident.repository.interface.js"; +import { IMaintenanceWindowsRepository } from "@/domain/maintenance-windows/maintenance-window.repository.interface.js"; +import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import { ITeamsRepository } from "@/domain/teams/team.repository.interface.js"; import { ILogger } from "@/utils/logger.js"; -import { IBufferService } from "@/service/index.js"; +import { IBufferService } from "@/service/bufferService.js"; export interface IQueueHelper { readonly serviceName: string; diff --git a/server/src/service/infrastructure/JobQueues/IJobQueue.ts b/server/src/service/job-queues/job-queue.interface.ts similarity index 92% rename from server/src/service/infrastructure/JobQueues/IJobQueue.ts rename to server/src/service/job-queues/job-queue.interface.ts index ccbca625a..3e8267738 100644 --- a/server/src/service/infrastructure/JobQueues/IJobQueue.ts +++ b/server/src/service/job-queues/job-queue.interface.ts @@ -1,5 +1,5 @@ -import { Monitor } from "@/types/monitor.js"; -import { QueueWorker } from "@/types/settings.js"; +import { Monitor } from "@/domain/monitors/monitor.types.js"; +import { QueueWorker } from "@/domain/queue-workers/queue-worker.type.js"; export type QueueJobFailure = { monitorId: string | number; diff --git a/server/src/service/infrastructure/JobQueues/LessSimpleQueue.ts b/server/src/service/job-queues/job-queue.less-simple.ts similarity index 95% rename from server/src/service/infrastructure/JobQueues/LessSimpleQueue.ts rename to server/src/service/job-queues/job-queue.less-simple.ts index 963b41267..ad12341c1 100644 --- a/server/src/service/infrastructure/JobQueues/LessSimpleQueue.ts +++ b/server/src/service/job-queues/job-queue.less-simple.ts @@ -1,11 +1,12 @@ -import { IMonitorsRepository, IQueueWorkersRepository } from "@/repositories/index.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import { IQueueWorkersRepository } from "@/domain/queue-workers/queue-worker.repository.interface.js"; import { ILogger } from "@/utils/logger.js"; import { MongoStore, Scheduler, IJob, AddJobInput } from "less-simple-scheduler"; -import { IQueueHelper } from "@/service/infrastructure/JobQueues/QueueHelper.js"; -import { Monitor, supportsGeoCheck } from "@/types/monitor.js"; -import { type QueueMetrics, type QueueJobsPagination, type QueueJobsPage, IJobQueue } from "@/service/infrastructure/JobQueues/IJobQueue.js"; -import { type QueueMode } from "@/types/settings.js"; -import { EnvConfig } from "@/service/system/settingsService.js"; +import { IQueueHelper } from "@/service/job-queues/job-queue.helper.js"; +import { Monitor, supportsGeoCheck } from "@/domain/monitors/monitor.types.js"; +import { type QueueMetrics, type QueueJobsPagination, type QueueJobsPage, IJobQueue } from "@/service/job-queues/job-queue.interface.js"; +import { type QueueMode } from "@/domain/app-settings/app-settings.type.js"; +import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; const SERVICE_NAME = "JobQueue"; // How long after its last heartbeat a worker is still considered alive. Must be longer than the scheduler's heartbeat diff --git a/server/src/service/infrastructure/JobQueues/SuperSimpleQueue.ts b/server/src/service/job-queues/job-queue.sumper-simple.ts similarity index 96% rename from server/src/service/infrastructure/JobQueues/SuperSimpleQueue.ts rename to server/src/service/job-queues/job-queue.sumper-simple.ts index 42409696b..c53ca5860 100644 --- a/server/src/service/infrastructure/JobQueues/SuperSimpleQueue.ts +++ b/server/src/service/job-queues/job-queue.sumper-simple.ts @@ -1,12 +1,12 @@ -import { IMonitorsRepository } from "@/repositories/index.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; import { ILogger } from "@/utils/logger.js"; import Scheduler from "super-simple-scheduler"; -import { IQueueHelper } from "@/service/infrastructure/JobQueues/QueueHelper.js"; -import { Monitor, MonitorType, supportsGeoCheck } from "@/types/monitor.js"; +import { IQueueHelper } from "@/service/job-queues/job-queue.helper.js"; +import { Monitor, MonitorType, supportsGeoCheck } from "@/domain/monitors/monitor.types.js"; import { hostname } from "node:os"; import { randomUUID } from "node:crypto"; -import { QueueMode } from "@/types/settings.js"; -import { IJobQueue, type QueueJobSummary } from "@/service/infrastructure/JobQueues/IJobQueue.js"; +import { QueueMode } from "@/domain/app-settings/app-settings.type.js"; +import { IJobQueue, type QueueJobSummary } from "@/service/job-queues/job-queue.interface.js"; const SERVICE_NAME = "JobQueue"; diff --git a/server/src/service/infrastructure/network/AdvancedMatcher.ts b/server/src/service/network/AdvancedMatcher.ts similarity index 96% rename from server/src/service/infrastructure/network/AdvancedMatcher.ts rename to server/src/service/network/AdvancedMatcher.ts index d6350ef63..8b25c53e8 100644 --- a/server/src/service/infrastructure/network/AdvancedMatcher.ts +++ b/server/src/service/network/AdvancedMatcher.ts @@ -1,4 +1,4 @@ -import { Monitor } from "@/types/monitor.js"; +import { Monitor } from "@/domain/monitors/monitor.types.js"; import jmespath from "jmespath"; type JmesPath = typeof jmespath; diff --git a/server/src/service/infrastructure/network/DNSProvider.ts b/server/src/service/network/DNSProvider.ts similarity index 91% rename from server/src/service/infrastructure/network/DNSProvider.ts rename to server/src/service/network/DNSProvider.ts index 92b4eb333..1aeff7e14 100644 --- a/server/src/service/infrastructure/network/DNSProvider.ts +++ b/server/src/service/network/DNSProvider.ts @@ -1,9 +1,9 @@ import { DNSStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import type { Resolver } from "dns/promises"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { AppError } from "@/utils/AppError.js"; -import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js"; const SERVICE_NAME = "DNSProvider"; diff --git a/server/src/service/infrastructure/network/DockerProvider.ts b/server/src/service/network/DockerProvider.ts similarity index 95% rename from server/src/service/infrastructure/network/DockerProvider.ts rename to server/src/service/network/DockerProvider.ts index 1d4b98818..0cfb133af 100644 --- a/server/src/service/infrastructure/network/DockerProvider.ts +++ b/server/src/service/network/DockerProvider.ts @@ -1,10 +1,10 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { DockerStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { ILogger } from "@/utils/logger.js"; import { AppError } from "@/utils/AppError.js"; import Dockerode from "dockerode"; -import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js"; type DockerodeType = typeof Dockerode; diff --git a/server/src/service/infrastructure/network/GameProvider.ts b/server/src/service/network/GameProvider.ts similarity index 89% rename from server/src/service/infrastructure/network/GameProvider.ts rename to server/src/service/network/GameProvider.ts index e2ace7433..4e063396d 100644 --- a/server/src/service/infrastructure/network/GameProvider.ts +++ b/server/src/service/network/GameProvider.ts @@ -1,9 +1,9 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { GameStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { GameDig } from "gamedig"; import { AppError } from "@/utils/AppError.js"; -import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR } from "@/service/network/utils.js"; import { ILogger } from "@/utils/logger.js"; type GameDigType = typeof GameDig; diff --git a/server/src/service/infrastructure/network/GrpcProvider.ts b/server/src/service/network/GrpcProvider.ts similarity index 96% rename from server/src/service/infrastructure/network/GrpcProvider.ts rename to server/src/service/network/GrpcProvider.ts index 14a063bca..4e79cef7f 100644 --- a/server/src/service/infrastructure/network/GrpcProvider.ts +++ b/server/src/service/network/GrpcProvider.ts @@ -1,8 +1,8 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { GrpcStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { AppError } from "@/utils/AppError.js"; -import { timeRequest } from "@/service/infrastructure/network/utils.js"; +import { timeRequest } from "@/service/network/utils.js"; import * as grpc from "@grpc/grpc-js"; import * as protoLoader from "@grpc/proto-loader"; diff --git a/server/src/service/infrastructure/network/HardwareProvider.ts b/server/src/service/network/HardwareProvider.ts similarity index 79% rename from server/src/service/infrastructure/network/HardwareProvider.ts rename to server/src/service/network/HardwareProvider.ts index a9180381e..c612bdbad 100644 --- a/server/src/service/infrastructure/network/HardwareProvider.ts +++ b/server/src/service/network/HardwareProvider.ts @@ -1,7 +1,7 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { HardwareStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; -import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; +import { HttpProvider } from "@/service/network/HttpProvider.js"; import { AppError } from "@/utils/AppError.js"; export class HardwareProvider implements IStatusProvider { diff --git a/server/src/service/infrastructure/network/HttpProvider.ts b/server/src/service/network/HttpProvider.ts similarity index 52% rename from server/src/service/infrastructure/network/HttpProvider.ts rename to server/src/service/network/HttpProvider.ts index 247965cbf..9f638dbd6 100644 --- a/server/src/service/infrastructure/network/HttpProvider.ts +++ b/server/src/service/network/HttpProvider.ts @@ -1,12 +1,12 @@ import { type Got, HTTPError, RequestError } from "got"; -import { IAdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js"; -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IAdvancedMatcher } from "@/service/network/AdvancedMatcher.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { HttpStatusPayload } from "@/types/network.js"; import { MonitorStatusResponse } from "@/types/network.js"; import { Agent as HttpsAgent } from "https"; import { Agent as HttpAgent } from "http"; -import { Monitor, MonitorType } from "@/types/monitor.js"; -import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; +import { NETWORK_ERROR, isStatusUp } from "@/service/network/utils.js"; import CacheableLookup from "cacheable-lookup"; export class HttpProvider implements IStatusProvider { @@ -40,21 +40,93 @@ export class HttpProvider implements IStatusProvider { return type === "http"; } - private handleHttpError(error: unknown, monitor: Monitor): MonitorStatusResponse { - if (error instanceof HTTPError || error instanceof RequestError) { + private buildResponse( + monitor: Monitor, + opts: { + body: string; + contentType: string; + statusCode: number; + statusUp: boolean; + message: string; + responseTime: number; + timings?: MonitorStatusResponse["timings"]; + } + ): MonitorStatusResponse { + const { body, contentType, statusCode, statusUp, message, responseTime, timings } = opts; + const isJson = contentType.includes("application/json"); + + if (monitor.jsonPath && !isJson) { return { monitorId: monitor.id, teamId: monitor.teamId, type: monitor.type, status: false, - code: error.response?.statusCode ?? NETWORK_ERROR, - message: error.message, - responseTime: error.timings?.phases?.firstByte ?? error.timings?.phases?.total ?? 0, - timings: error.timings, - payload: null as T, + code: statusCode, + message: "Response is not JSON", + responseTime, + timings, + payload: body, }; } + let payload: T | string; + if (isJson) { + try { + payload = JSON.parse(body) as T; + } catch { + payload = body; + } + } else { + payload = body; + } + + const matchResult = this.advancedMatcher.validate(payload, monitor); + + return { + monitorId: monitor.id, + teamId: monitor.teamId, + type: monitor.type, + status: statusUp && matchResult.ok, + code: statusCode, + message: matchResult.ok ? message : matchResult.message, + responseTime, + timings, + payload, + extracted: matchResult.extracted, + }; + } + + private handleHttpError(error: unknown, monitor: Monitor): MonitorStatusResponse { + if (error instanceof HTTPError || error instanceof RequestError) { + const statusCode = error.response?.statusCode; + const statusUp = isStatusUp(statusCode, monitor.customUpCodes); + const responseTime = error.timings?.phases?.firstByte ?? error.timings?.phases?.total ?? 0; + + if (!statusUp) { + return { + monitorId: monitor.id, + teamId: monitor.teamId, + type: monitor.type, + status: false, + code: statusCode ?? NETWORK_ERROR, + message: error.message, + responseTime, + timings: error.timings, + payload: null as T, + }; + } + + return this.buildResponse(monitor, { + body: (error.response?.body ?? "") as string, + contentType: error.response?.headers?.["content-type"] || "", + statusCode: statusCode ?? NETWORK_ERROR, + statusUp, + message: error.message, + responseTime, + timings: error.timings, + }); + } + return { monitorId: monitor.id, teamId: monitor.teamId, @@ -68,7 +140,7 @@ export class HttpProvider implements IStatusProvider { } async handle(monitor: Monitor): Promise> { - const { url, secret, jsonPath, ignoreTlsErrors } = monitor; + const { url, secret, ignoreTlsErrors } = monitor; if (!url) { throw new Error("URL is required for HTTP monitor"); @@ -85,47 +157,17 @@ export class HttpProvider implements IStatusProvider { try { const response = await this.got(url, options); - const contentType = response.headers["content-type"] || ""; - const isJson = contentType.includes("application/json"); + const statusUp = isStatusUp(response.statusCode, monitor.customUpCodes); - if (jsonPath && !isJson) { - return { - monitorId: monitor.id, - teamId: monitor.teamId, - type: monitor.type, - status: false, - code: response.statusCode, - message: "Response is not JSON", - responseTime: response.timings.phases.firstByte ?? 0, - timings: response.timings, - payload: response.body as unknown as T, - }; - } - - let payload: T; - if (isJson) { - try { - payload = JSON.parse(response.body) as T; - } catch { - payload = response.body as unknown as T; - } - } else { - payload = response.body as unknown as T; - } - - const matchResult = this.advancedMatcher.validate(payload, monitor); - return { - monitorId: monitor.id, - teamId: monitor.teamId, - type: monitor.type, - status: response.ok && matchResult.ok, - code: response.statusCode, - message: matchResult.ok ? (response.statusMessage ?? "OK") : matchResult.message, + return this.buildResponse(monitor, { + body: response.body, + contentType: response.headers["content-type"] || "", + statusCode: response.statusCode, + statusUp, + message: response.statusMessage ?? "OK", responseTime: response.timings.phases.firstByte ?? 0, timings: response.timings, - payload, - extracted: matchResult.extracted, - }; + }); } catch (error: unknown) { return this.handleHttpError(error, monitor); } diff --git a/server/src/service/infrastructure/network/IStatusProvider.ts b/server/src/service/network/IStatusProvider.ts similarity index 74% rename from server/src/service/infrastructure/network/IStatusProvider.ts rename to server/src/service/network/IStatusProvider.ts index bde6a2db0..f077ff9b5 100644 --- a/server/src/service/infrastructure/network/IStatusProvider.ts +++ b/server/src/service/network/IStatusProvider.ts @@ -1,4 +1,4 @@ -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { MonitorStatusResponse } from "@/types/network.js"; export interface IStatusProvider { diff --git a/server/src/service/infrastructure/network/PageSpeedProvider.ts b/server/src/service/network/PageSpeedProvider.ts similarity index 87% rename from server/src/service/infrastructure/network/PageSpeedProvider.ts rename to server/src/service/network/PageSpeedProvider.ts index 0d8560868..a609ca80b 100644 --- a/server/src/service/infrastructure/network/PageSpeedProvider.ts +++ b/server/src/service/network/PageSpeedProvider.ts @@ -1,8 +1,8 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js"; -import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/types/monitor.js"; -import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js"; -import { ISettingsService } from "@/service/system/settingsService.js"; +import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/domain/monitors/monitor.types.js"; +import { HttpProvider } from "@/service/network/HttpProvider.js"; +import { ISettingsService } from "@/domain/app-settings/app-settings.service.js"; import { ILogger } from "@/utils/logger.js"; import { AppError } from "@/utils/AppError.js"; diff --git a/server/src/service/infrastructure/network/PingProvider.ts b/server/src/service/network/PingProvider.ts similarity index 92% rename from server/src/service/infrastructure/network/PingProvider.ts rename to server/src/service/network/PingProvider.ts index 0b9691da9..ee66046f9 100644 --- a/server/src/service/infrastructure/network/PingProvider.ts +++ b/server/src/service/network/PingProvider.ts @@ -1,10 +1,10 @@ import { PingStatusPayload } from "@/types/network.js"; import { IStatusProvider } from "./IStatusProvider.js"; -import { MonitorType, Monitor } from "@/types/monitor.js"; +import { MonitorType, Monitor } from "@/domain/monitors/monitor.types.js"; import { MonitorStatusResponse } from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; import ping from "ping"; -import { timeRequest } from "@/service/infrastructure/network/utils.js"; +import { timeRequest } from "@/service/network/utils.js"; const SERVICE_NAME = "PingProvider"; type Ping = typeof ping; diff --git a/server/src/service/infrastructure/network/PortProvider.ts b/server/src/service/network/PortProvider.ts similarity index 90% rename from server/src/service/infrastructure/network/PortProvider.ts rename to server/src/service/network/PortProvider.ts index 7c1c96ce4..debcd8509 100644 --- a/server/src/service/infrastructure/network/PortProvider.ts +++ b/server/src/service/network/PortProvider.ts @@ -1,8 +1,8 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { PortStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { AppError } from "@/utils/AppError.js"; -import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js"; import * as net from "net"; type NetType = typeof net; diff --git a/server/src/service/infrastructure/network/WebSocketProvider.ts b/server/src/service/network/WebSocketProvider.ts similarity index 91% rename from server/src/service/infrastructure/network/WebSocketProvider.ts rename to server/src/service/network/WebSocketProvider.ts index 0af436067..fef5f76b1 100644 --- a/server/src/service/infrastructure/network/WebSocketProvider.ts +++ b/server/src/service/network/WebSocketProvider.ts @@ -1,8 +1,8 @@ -import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; +import { IStatusProvider } from "@/service/network/IStatusProvider.js"; import { WebSocketStatusPayload, MonitorStatusResponse } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; import { AppError } from "@/utils/AppError.js"; -import { NETWORK_ERROR, timeRequest } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR, timeRequest } from "@/service/network/utils.js"; import type WebSocket from "ws"; type WebSocketConstructor = typeof WebSocket; diff --git a/server/src/service/infrastructure/network/utils.ts b/server/src/service/network/utils.ts similarity index 65% rename from server/src/service/infrastructure/network/utils.ts rename to server/src/service/network/utils.ts index ddb962f47..8bcbc4335 100644 --- a/server/src/service/infrastructure/network/utils.ts +++ b/server/src/service/network/utils.ts @@ -1,3 +1,5 @@ +import type { HttpStatusCode } from "@/domain/monitors/monitor.types.js"; + export const timeRequest = async (operation: () => Promise): Promise<{ response: T | null; responseTime: number; error: unknown }> => { const start = process.hrtime.bigint(); try { @@ -12,3 +14,8 @@ export const timeRequest = async (operation: () => Promise): Promise<{ res export const NETWORK_ERROR = 5000; export const PING_ERROR = 5001; + +export const isStatusUp = (statusCode: number | undefined, customUpCodes: HttpStatusCode[] = []): boolean => { + if (statusCode === undefined) return false; + return (statusCode >= 200 && statusCode < 300) || customUpCodes.includes(statusCode); +}; diff --git a/server/src/service/infrastructure/networkService.ts b/server/src/service/networkService.ts similarity index 96% rename from server/src/service/infrastructure/networkService.ts rename to server/src/service/networkService.ts index ca85d12ac..3470c6086 100644 --- a/server/src/service/infrastructure/networkService.ts +++ b/server/src/service/networkService.ts @@ -1,7 +1,8 @@ -import type { Monitor, MonitorStatusResponse, MonitorPayloadMap, MonitorType } from "@/types/index.js"; +import type { Monitor, MonitorType } from "@/domain/monitors/monitor.types.js"; +import type { MonitorPayloadMap, MonitorStatusResponse } from "@/types/network.js"; import type { AxiosStatic } from "axios"; import { AppError } from "@/utils/AppError.js"; -import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js"; +import { NETWORK_ERROR } from "@/service/network/utils.js"; import { ILogger } from "@/utils/logger.js"; import { IStatusProvider } from "./network/IStatusProvider.js"; const SERVICE_NAME = "NetworkService"; diff --git a/server/src/service/infrastructure/protos/health.proto b/server/src/service/protos/health.proto similarity index 100% rename from server/src/service/infrastructure/protos/health.proto rename to server/src/service/protos/health.proto diff --git a/server/src/service/infrastructure/statusService.ts b/server/src/service/statusService.ts similarity index 95% rename from server/src/service/infrastructure/statusService.ts rename to server/src/service/statusService.ts index 617f15944..47aa8d89a 100755 --- a/server/src/service/infrastructure/statusService.ts +++ b/server/src/service/statusService.ts @@ -1,26 +1,25 @@ -import { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository } from "@/repositories/index.js"; +import { IChecksRepository } from "@/domain/checks/check.repository.interface.js"; +import { IMonitorStatsRepository } from "@/domain/monitor-stats/monitor-stats.repository.interface.js"; +import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js"; +import type { Check, CheckDiskInfo, CheckSnapshot } from "@/domain/checks/check.type.js"; +import type { Monitor, MonitorStatus } from "@/domain/monitors/monitor.types.js"; import type { - Monitor, - MonitorStatus, - MonitorStatusResponse, - StatusChangeResult, - Check, - HardwareStatusPayload, - PageSpeedStatusPayload, - PingStatusPayload, - HttpStatusPayload, DockerStatusPayload, - PortStatusPayload, GameStatusPayload, GrpcStatusPayload, - CheckSnapshot, - CheckDiskInfo, -} from "@/types/index.js"; + HardwareStatusPayload, + HttpStatusPayload, + MonitorStatusResponse, + PageSpeedStatusPayload, + PingStatusPayload, + PortStatusPayload, + StatusChangeResult, +} from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; import { ILogger } from "@/utils/logger.js"; import { IBufferService } from "./bufferService.js"; import type { HardwareStatusMetrics } from "@/types/network.js"; -import { MAX_RECENT_CHECKS } from "@/types/index.js"; +import { MAX_RECENT_CHECKS } from "@/domain/monitors/monitor.types.js"; const SERVICE_NAME = "StatusService"; const HARDWARE_ALERT_COUNTER_START = 5; diff --git a/server/src/types/alert.ts b/server/src/types/alert.ts deleted file mode 100644 index 3cb9d7734..000000000 --- a/server/src/types/alert.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { NotificationChannel } from "@/types/index.js"; -export interface AlertWebhookPayload { - body: Record; -} - -export interface AlertPagerDutyPayload { - routing_key?: string; - dedup_key?: string; - event_action?: "trigger" | "resolve"; - payload: Record; -} - -export interface AlertMatrixPayload { - plainText: string; - htmlText: string; -} - -export interface DiscordEmbedField { - name: string; - value: string; - inline?: boolean; -} - -export interface AlertDiscordPayload { - title: string; - description: string; - color: number; - fields: DiscordEmbedField[]; - timestamp: string; -} - -export interface Alert { - channel: NotificationChannel; - address?: string; - subject: string; - message: string; - html?: string; - discordContent?: Record | null; - webhook?: AlertWebhookPayload | null; - pagerDuty?: AlertPagerDutyPayload | null; - matrix?: AlertMatrixPayload | null; -} diff --git a/server/src/types/email.ts b/server/src/types/email.ts deleted file mode 100644 index 6e884319f..000000000 --- a/server/src/types/email.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Settings } from "./settings.js"; - -export type EmailTransportConfig = Pick< - Settings, - | "systemEmailHost" - | "systemEmailPort" - | "systemEmailAddress" - | "systemEmailDisplayName" - | "systemEmailPassword" - | "systemEmailUser" - | "systemEmailConnectionHost" - | "systemEmailTLSServername" - | "systemEmailSecure" - | "systemEmailPool" - | "systemEmailIgnoreTLS" - | "systemEmailRequireTLS" - | "systemEmailRejectUnauthorized" ->; diff --git a/server/src/types/express.d.ts b/server/src/types/express.d.ts index 973d2d5b9..92a4a86c6 100644 --- a/server/src/types/express.d.ts +++ b/server/src/types/express.d.ts @@ -1,4 +1,4 @@ -import type { User } from "@/types/index.js"; +import type { User } from "@/domain/users/user.type.js"; declare global { namespace Express { diff --git a/server/src/types/index.ts b/server/src/types/index.ts deleted file mode 100644 index ca055ba70..000000000 --- a/server/src/types/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export * from "@/types/check.js"; -export * from "@/types/geoCheck.js"; -export * from "@/types/monitor.js"; -export * from "@/types/monitorStats.js"; -export * from "@/types/statusPage.js"; -export * from "@/types/network.js"; -export * from "@/types/user.js"; -export * from "@/types/invite.js"; -export * from "@/types/recoveryToken.js"; -export * from "@/types/settings.js"; -export * from "@/types/notification.js"; -export * from "@/types/alert.js"; -export * from "@/types/incident.js"; -export * from "@/types/email.js"; -export * from "@/types/team.js"; -export * from "@/types/maintenanceWindow.js"; -export * from "@/types/dlqItem.js"; -export * from "@/types/tag.js"; diff --git a/server/src/types/network.ts b/server/src/types/network.ts index 19f063cc6..fe85221f2 100644 --- a/server/src/types/network.ts +++ b/server/src/types/network.ts @@ -8,12 +8,8 @@ import type { CheckNetworkInterfaceInfo, GotTimings, ILighthouseAudit, - DnsRecordType, - Monitor, - MonitorMatchMethod, - MonitorType, - MonitorStatus, -} from "@/types/index.js"; +} from "@/domain/checks/check.type.js"; +import type { DnsRecordType, Monitor, MonitorMatchMethod, MonitorStatus, MonitorType } from "@/domain/monitors/monitor.types.js"; import type { QueryResult } from "gamedig"; @@ -35,7 +31,7 @@ export interface MonitorStatusResponse< code: number; message: string; responseTime?: number; - payload?: T | null; + payload?: T | string | null; timings?: GotTimings; first_byte_took?: number; body_read_took?: number; diff --git a/server/src/types/notification.ts b/server/src/types/notification.ts deleted file mode 100644 index 0e389f9e2..000000000 --- a/server/src/types/notification.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const NotificationChannels = [ - "email", - "slack", - "discord", - "webhook", - "pager_duty", - "matrix", - "teams", - "telegram", - "pushover", - "twilio", - "ntfy", -] as const; -export type NotificationChannel = (typeof NotificationChannels)[number]; - -export interface Notification { - id: string; - userId: string; - teamId: string; - type: NotificationChannel; - notificationName: string; - address?: string; - phone?: string; - homeserverUrl?: string; - roomId?: string; - accessToken?: string; - accountSid?: string; - twilioPhoneNumber?: string; - topic?: string; - createdAt: string; - updatedAt: string; -} diff --git a/server/src/utils/dataUtils.ts b/server/src/utils/dataUtils.ts index f6d877a35..7b46619e8 100755 --- a/server/src/utils/dataUtils.ts +++ b/server/src/utils/dataUtils.ts @@ -1,4 +1,4 @@ -import type { GroupedCheck, NormalizedCheck, NormalizedUptimeCheck, HasResponseTime } from "@/types/index.js"; +import type { GroupedCheck, HasResponseTime, NormalizedCheck, NormalizedUptimeCheck } from "@/domain/checks/check.type.js"; export const getDateForRange = (dateRange: string): Date | undefined => { const now = Date.now(); diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index e3c0db169..c53e7112c 100755 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,7 +1,7 @@ import { createLogger, format, transports, Logger as WinstonLogger } from "winston"; import type { Logform } from "winston"; import dotenv from "dotenv"; -import { EnvConfig } from "@/service/system/settingsService.js"; +import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; dotenv.config(); const SERVICE_NAME = "Logger"; diff --git a/server/src/utils/maintenanceWindow.ts b/server/src/utils/maintenanceWindow.ts index aab4efd64..67351639d 100644 --- a/server/src/utils/maintenanceWindow.ts +++ b/server/src/utils/maintenanceWindow.ts @@ -1,4 +1,4 @@ -import type { MaintenanceWindow } from "@/types/index.js"; +import type { MaintenanceWindow } from "@/domain/maintenance-windows/maintenance-window.type.js"; export const isWindowActive = (window: MaintenanceWindow, now: Date = new Date()): boolean => { if (!window.active) { diff --git a/server/test/helpers/InMemoryIncidentsRepository.ts b/server/test/helpers/InMemoryIncidentsRepository.ts index f76236f89..74b79e0e3 100644 --- a/server/test/helpers/InMemoryIncidentsRepository.ts +++ b/server/test/helpers/InMemoryIncidentsRepository.ts @@ -1,5 +1,5 @@ -import type { IIncidentsRepository } from "../../src/repositories/incidents/IIncidentsRepository.ts"; -import type { Incident, IncidentSummary } from "../../src/types/index.ts"; +import type { IIncidentsRepository } from "../../src/domain/incidents/incident.repository.interface.ts"; +import type { Incident, IncidentSummary } from "../../src/domain/incidents/incident.type.ts"; import { randomUUID } from "crypto"; export class InMemoryIncidentsRepository implements IIncidentsRepository { diff --git a/server/test/helpers/InMemoryMonitorsRepository.ts b/server/test/helpers/InMemoryMonitorsRepository.ts index 756a7bfb7..c53f270a7 100644 --- a/server/test/helpers/InMemoryMonitorsRepository.ts +++ b/server/test/helpers/InMemoryMonitorsRepository.ts @@ -1,5 +1,6 @@ -import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/repositories/monitors/IMonitorsRepository.ts"; -import type { Monitor, MonitorsSummary, CheckSnapshot } from "../../src/types/index.ts"; +import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "../../src/domain/monitors/monitor.repository.interface.ts"; +import type { CheckSnapshot } from "../../src/domain/checks/check.type.ts"; +import type { Monitor, MonitorsSummary } from "../../src/domain/monitors/monitor.types.ts"; export class InMemoryMonitorsRepository implements IMonitorsRepository { private monitors: Monitor[] = []; diff --git a/server/test/helpers/heartbeatTestHarness.ts b/server/test/helpers/heartbeatTestHarness.ts index 9b0592afc..46a6fc630 100644 --- a/server/test/helpers/heartbeatTestHarness.ts +++ b/server/test/helpers/heartbeatTestHarness.ts @@ -1,14 +1,14 @@ import { jest } from "@jest/globals"; -import { QueueHelper } from "../../src/service/infrastructure/JobQueues/QueueHelper.ts"; -import { StatusService } from "../../src/service/infrastructure/statusService.ts"; -import { IncidentService } from "../../src/service/business/incidentService.ts"; +import { QueueHelper } from "../../src/service/job-queues/job-queue.helper.ts"; +import { StatusService } from "../../src/service/statusService.ts"; +import { IncidentService } from "../../src/domain/incidents/incident.service.ts"; import { InMemoryMonitorsRepository } from "./InMemoryMonitorsRepository.ts"; import { InMemoryIncidentsRepository } from "./InMemoryIncidentsRepository.ts"; import { createMockLogger } from "./createMockLogger.ts"; -import type { Monitor } from "../../src/types/monitor.ts"; +import type { Monitor } from "../../src/domain/monitors/monitor.types.ts"; import type { MonitorStatusResponse } from "../../src/types/network.ts"; -import type { Check } from "../../src/types/check.ts"; -import type { MaintenanceWindow } from "../../src/types/maintenanceWindow.ts"; +import type { Check } from "../../src/domain/checks/check.type.ts"; +import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts"; let checkCounter = 0; diff --git a/server/test/helpers/makeStatusPage.ts b/server/test/helpers/makeStatusPage.ts index 23c13bca1..56fd26596 100644 --- a/server/test/helpers/makeStatusPage.ts +++ b/server/test/helpers/makeStatusPage.ts @@ -1,4 +1,4 @@ -import type { StatusPage } from "../../src/types/index.ts"; +import type { StatusPage } from "../../src/domain/status-pages/status-page.type.ts"; export const makeStatusPage = (overrides?: Partial): StatusPage => ({ diff --git a/server/test/helpers/notificationMessage.ts b/server/test/helpers/notificationMessage.ts index c25644c81..f73ce67e7 100644 --- a/server/test/helpers/notificationMessage.ts +++ b/server/test/helpers/notificationMessage.ts @@ -1,5 +1,5 @@ -import type { Notification } from "../../src/types/index.ts"; -import type { NotificationMessage } from "../../src/types/notificationMessage.ts"; +import type { Notification } from "../../src/domain/notifications/notification.type.ts"; +import type { NotificationMessage } from "../../src/domain/notifications/notification.type.ts"; export const makeNotification = (overrides?: Partial): Notification => ({ diff --git a/server/test/helpers/notificationProviderContract.ts b/server/test/helpers/notificationProviderContract.ts index f95048f24..3f3c5b80b 100644 --- a/server/test/helpers/notificationProviderContract.ts +++ b/server/test/helpers/notificationProviderContract.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "@jest/globals"; -import type { INotificationProvider } from "../../src/service/infrastructure/notificationProviders/INotificationProvider.ts"; -import type { Notification } from "../../src/types/index.ts"; -import type { NotificationMessage } from "../../src/types/notificationMessage.ts"; +import type { INotificationProvider } from "../../src/domain/notifications/providers/INotificationProvider.ts"; +import type { Notification } from "../../src/domain/notifications/notification.type.ts"; +import type { NotificationMessage } from "../../src/domain/notifications/notification.type.ts"; const makeMessage = (): NotificationMessage => ({ type: "monitor_down", diff --git a/server/test/helpers/statusProviderContract.ts b/server/test/helpers/statusProviderContract.ts index 6f7c89466..af625f64d 100644 --- a/server/test/helpers/statusProviderContract.ts +++ b/server/test/helpers/statusProviderContract.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "@jest/globals"; -import type { IStatusProvider } from "../../src/service/infrastructure/network/IStatusProvider.ts"; -import type { Monitor } from "../../src/types/index.ts"; +import type { IStatusProvider } from "../../src/service/network/IStatusProvider.ts"; +import type { Monitor } from "../../src/domain/monitors/monitor.types.ts"; /** * Shared contract tests for all IStatusProvider implementations. diff --git a/server/test/integration/heartbeatJobMaintenance.test.ts b/server/test/integration/heartbeatJobMaintenance.test.ts index 5df287242..3e51814db 100644 --- a/server/test/integration/heartbeatJobMaintenance.test.ts +++ b/server/test/integration/heartbeatJobMaintenance.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, beforeEach } from "@jest/globals"; import { createHeartbeatTestHarness, makeMonitor, type HeartbeatTestHarness } from "../helpers/heartbeatTestHarness.ts"; -import type { MaintenanceWindow } from "../../src/types/maintenanceWindow.ts"; +import type { MaintenanceWindow } from "../../src/domain/maintenance-windows/maintenance-window.type.ts"; const makeMaintenanceWindow = (overrides?: Partial): MaintenanceWindow => { const now = new Date(); diff --git a/server/test/integration/incidentLifecycle.test.ts b/server/test/integration/incidentLifecycle.test.ts index 0fe1ddcf8..3f301113a 100644 --- a/server/test/integration/incidentLifecycle.test.ts +++ b/server/test/integration/incidentLifecycle.test.ts @@ -1,11 +1,12 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { IncidentService } from "../../src/service/business/incidentService.ts"; +import { IncidentService } from "../../src/domain/incidents/incident.service.ts"; import { InMemoryIncidentsRepository } from "../helpers/InMemoryIncidentsRepository.ts"; import { createMockLogger } from "../helpers/createMockLogger.ts"; -import type { IMonitorsRepository, IUsersRepository } from "../../src/repositories/index.ts"; -import type { INotificationMessageBuilder } from "../../src/service/infrastructure/notificationMessageBuilder.ts"; -import type { Monitor } from "../../src/types/monitor.ts"; -import type { MonitorActionDecision } from "../../src/service/infrastructure/JobQueues/QueueHelper.ts"; +import type { IMonitorsRepository } from "../../src/domain/monitors/monitor.repository.interface.ts"; +import type { IUsersRepository } from "../../src/domain/users/user.repository.interface.ts"; +import type { INotificationMessageBuilder } from "../../src/domain/notifications/notification.message-builder.ts"; +import type { Monitor } from "../../src/domain/monitors/monitor.types.ts"; +import type { MonitorActionDecision } from "../../src/service/job-queues/job-queue.helper.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/controllers/statusPageThemeFlag.test.ts b/server/test/unit/controllers/statusPageThemeFlag.test.ts index b4f9435a9..b1d51361a 100644 --- a/server/test/unit/controllers/statusPageThemeFlag.test.ts +++ b/server/test/unit/controllers/statusPageThemeFlag.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "@jest/globals"; -import { applyDefaultTheme, withoutThemeFields } from "../../../src/controllers/statusPageThemeFlag.ts"; -import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/types/statusPage.ts"; +import { applyDefaultTheme, withoutThemeFields } from "../../../src/api/controllers/statusPageThemeFlag.ts"; +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/domain/status-pages/status-page.type.ts"; import { makeStatusPage } from "../../helpers/makeStatusPage.ts"; describe("statusPageThemeFlag", () => { diff --git a/server/test/unit/providers/network/advancedMatcher.test.ts b/server/test/unit/providers/network/advancedMatcher.test.ts index e0b2c773c..f94e9bb53 100644 --- a/server/test/unit/providers/network/advancedMatcher.test.ts +++ b/server/test/unit/providers/network/advancedMatcher.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { AdvancedMatcher } from "../../../../src/service/infrastructure/network/AdvancedMatcher.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { AdvancedMatcher } from "../../../../src/service/network/AdvancedMatcher.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/dnsProvider.test.ts b/server/test/unit/providers/network/dnsProvider.test.ts index a96731be5..1c31b3b1d 100644 --- a/server/test/unit/providers/network/dnsProvider.test.ts +++ b/server/test/unit/providers/network/dnsProvider.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, jest } from "@jest/globals"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; -import { DNSProvider } from "../../../../src/service/infrastructure/network/DNSProvider.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; +import { DNSProvider } from "../../../../src/service/network/DNSProvider.ts"; // ── Mocks ──────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/dockerProvider.test.ts b/server/test/unit/providers/network/dockerProvider.test.ts index 0f3dd68f6..f0b743f16 100644 --- a/server/test/unit/providers/network/dockerProvider.test.ts +++ b/server/test/unit/providers/network/dockerProvider.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { DockerProvider } from "../../../../src/service/infrastructure/network/DockerProvider.ts"; +import { DockerProvider } from "../../../../src/service/network/DockerProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; import { createMockLogger } from "../../../helpers/createMockLogger.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/gameProvider.test.ts b/server/test/unit/providers/network/gameProvider.test.ts index d34e99621..d166c30f3 100644 --- a/server/test/unit/providers/network/gameProvider.test.ts +++ b/server/test/unit/providers/network/gameProvider.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { GameProvider } from "../../../../src/service/infrastructure/network/GameProvider.ts"; +import { GameProvider } from "../../../../src/service/network/GameProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; import { createMockLogger } from "../../../helpers/createMockLogger.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/grpcProvider.test.ts b/server/test/unit/providers/network/grpcProvider.test.ts index efa2e7992..bb1c78aac 100644 --- a/server/test/unit/providers/network/grpcProvider.test.ts +++ b/server/test/unit/providers/network/grpcProvider.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { GrpcProvider } from "../../../../src/service/infrastructure/network/GrpcProvider.ts"; +import { GrpcProvider } from "../../../../src/service/network/GrpcProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/hardwareProvider.test.ts b/server/test/unit/providers/network/hardwareProvider.test.ts index 2f51454a0..2b4583af9 100644 --- a/server/test/unit/providers/network/hardwareProvider.test.ts +++ b/server/test/unit/providers/network/hardwareProvider.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { HardwareProvider } from "../../../../src/service/infrastructure/network/HardwareProvider.ts"; +import { HardwareProvider } from "../../../../src/service/network/HardwareProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import type { HttpProvider } from "../../../../src/service/infrastructure/network/HttpProvider.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/httpProvider.test.ts b/server/test/unit/providers/network/httpProvider.test.ts index 2bcab27a3..10b66a93a 100644 --- a/server/test/unit/providers/network/httpProvider.test.ts +++ b/server/test/unit/providers/network/httpProvider.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Mocks ──────────────────────────────────────────────────────────────────── @@ -38,7 +38,7 @@ jest.unstable_mockModule("got", () => ({ }, })); -const { HttpProvider } = await import("../../../../src/service/infrastructure/network/HttpProvider.ts"); +const { HttpProvider } = await import("../../../../src/service/network/HttpProvider.ts"); const gotModule = await import("got"); const { HTTPError, RequestError } = gotModule; @@ -56,6 +56,7 @@ const makeMonitor = (overrides?: Partial): Monitor => useAdvancedMatching: false, matchMethod: undefined, expectedValue: undefined, + customUpCodes: [], ...overrides, }) as Monitor; @@ -253,14 +254,13 @@ describe("HttpProvider", () => { expect(result.extracted).toBe("value"); }); - it("sets status to false when response.ok is false even if matcher passes", async () => { + it("sets status to false when status code is non-2xx even if matcher passes", async () => { mockGot.mockResolvedValue(makeGotResponse({ ok: false, statusCode: 301 })); const matcher = createMockMatcher({ ok: true, message: "Success" }); const { provider } = createProvider(matcher); const result = await provider.handle(makeMonitor()); - // status = response.ok && matchResult.ok expect(result.status).toBe(false); }); }); @@ -345,4 +345,92 @@ describe("HttpProvider", () => { await expect(provider.handle(makeMonitor({ url: "" }))).rejects.toThrow("URL is required for HTTP monitor"); }); }); + + // ── customUpCodes ──────────────────────────────────────────────────── + + describe("customUpCodes", () => { + it("returns status true when HTTPError status code is in customUpCodes", async () => { + const err = new HTTPError("Unauthorized"); + (err as any).response = { statusCode: 401 }; + (err as any).timings = { phases: { total: 30 } }; + mockGot.mockRejectedValue(err); + const { provider } = createProvider(); + + const result = await provider.handle(makeMonitor({ customUpCodes: [401] })); + + expect(result.status).toBe(true); + expect(result.code).toBe(401); + }); + + it("returns status false when HTTPError status code is not in customUpCodes", async () => { + const err = new HTTPError("Internal Server Error"); + (err as any).response = { statusCode: 500 }; + (err as any).timings = { phases: { total: 40 } }; + mockGot.mockRejectedValue(err); + const { provider } = createProvider(); + + const result = await provider.handle(makeMonitor({ customUpCodes: [401, 403] })); + + expect(result.status).toBe(false); + expect(result.code).toBe(500); + }); + + it("returns status false when customUpCodes is empty", async () => { + const err = new HTTPError("Unauthorized"); + (err as any).response = { statusCode: 401 }; + (err as any).timings = { phases: { total: 25 } }; + mockGot.mockRejectedValue(err); + const { provider } = createProvider(); + + const result = await provider.handle(makeMonitor({ customUpCodes: [] })); + + expect(result.status).toBe(false); + expect(result.code).toBe(401); + }); + + it("treats non-2xx success response as up when status code is in customUpCodes", async () => { + mockGot.mockResolvedValue(makeGotResponse({ ok: false, statusCode: 301, statusMessage: "Moved Permanently" })); + const { provider } = createProvider(); + + const result = await provider.handle(makeMonitor({ customUpCodes: [301] })); + + expect(result.status).toBe(true); + expect(result.code).toBe(301); + }); + + it("does not override matcher failure even when status code is in customUpCodes", async () => { + mockGot.mockResolvedValue(makeGotResponse({ ok: false, statusCode: 301 })); + const matcher = createMockMatcher({ ok: false, message: "Body mismatch" }); + const { provider } = createProvider(matcher); + + const result = await provider.handle(makeMonitor({ customUpCodes: [301] })); + + expect(result.status).toBe(false); + expect(result.message).toBe("Body mismatch"); + }); + + it("does not override matcher failure on HTTPError when status code is in customUpCodes", async () => { + const err = new HTTPError("Unauthorized"); + (err as any).response = { + statusCode: 401, + body: '{"status":"down"}', + headers: { "content-type": "application/json" }, + }; + (err as any).timings = { phases: { total: 30 } }; + mockGot.mockRejectedValue(err); + const matcher = createMockMatcher({ + ok: false, + message: "Body mismatch", + extracted: "down", + }); + const { provider } = createProvider(matcher); + + const result = await provider.handle(makeMonitor({ customUpCodes: [401], useAdvancedMatching: true })); + + expect(result.status).toBe(false); + expect(result.message).toBe("Body mismatch"); + expect(result.payload).toEqual({ status: "down" }); + expect(result.extracted).toBe("down"); + }); + }); }); diff --git a/server/test/unit/providers/network/pageSpeedProvider.test.ts b/server/test/unit/providers/network/pageSpeedProvider.test.ts index c461b0ad2..df5beb0be 100644 --- a/server/test/unit/providers/network/pageSpeedProvider.test.ts +++ b/server/test/unit/providers/network/pageSpeedProvider.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { PageSpeedProvider } from "../../../../src/service/infrastructure/network/PageSpeedProvider.ts"; +import { PageSpeedProvider } from "../../../../src/service/network/PageSpeedProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; import { createMockLogger } from "../../../helpers/createMockLogger.ts"; -import type { HttpProvider } from "../../../../src/service/infrastructure/network/HttpProvider.ts"; -import type { ISettingsService } from "../../../../src/service/system/settingsService.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import type { HttpProvider } from "../../../../src/service/network/HttpProvider.ts"; +import type { ISettingsService } from "../../../../src/domain/app-settings/app-settings.service.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/pingProvider.test.ts b/server/test/unit/providers/network/pingProvider.test.ts index 2f38eef00..8f006a470 100644 --- a/server/test/unit/providers/network/pingProvider.test.ts +++ b/server/test/unit/providers/network/pingProvider.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { PingProvider } from "../../../../src/service/infrastructure/network/PingProvider.ts"; +import { PingProvider } from "../../../../src/service/network/PingProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/portProvider.test.ts b/server/test/unit/providers/network/portProvider.test.ts index cbf64af16..8a7d55c4c 100644 --- a/server/test/unit/providers/network/portProvider.test.ts +++ b/server/test/unit/providers/network/portProvider.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { PortProvider } from "../../../../src/service/infrastructure/network/PortProvider.ts"; +import { PortProvider } from "../../../../src/service/network/PortProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/network/utils.test.ts b/server/test/unit/providers/network/utils.test.ts index 56d8e7f9c..b37a5597c 100644 --- a/server/test/unit/providers/network/utils.test.ts +++ b/server/test/unit/providers/network/utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "@jest/globals"; -import { timeRequest, NETWORK_ERROR, PING_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; +import { timeRequest, NETWORK_ERROR, PING_ERROR, isStatusUp } from "../../../../src/service/network/utils.ts"; describe("network utils", () => { describe("timeRequest", () => { @@ -41,4 +41,40 @@ describe("network utils", () => { expect(PING_ERROR).toBe(5001); }); }); + + describe("isStatusUp", () => { + it("returns true for 2xx status codes", () => { + expect(isStatusUp(200)).toBe(true); + expect(isStatusUp(201)).toBe(true); + expect(isStatusUp(204)).toBe(true); + expect(isStatusUp(299)).toBe(true); + }); + + it("returns false for non-2xx status codes without customUpCodes", () => { + expect(isStatusUp(100)).toBe(false); + expect(isStatusUp(301)).toBe(false); + expect(isStatusUp(404)).toBe(false); + expect(isStatusUp(500)).toBe(false); + }); + + it("returns true when status code is in customUpCodes", () => { + expect(isStatusUp(301, [301])).toBe(true); + expect(isStatusUp(404, [200, 404])).toBe(true); + expect(isStatusUp(401, [401, 403])).toBe(true); + }); + + it("returns false when status code is not in customUpCodes", () => { + expect(isStatusUp(500, [401, 403])).toBe(false); + }); + + it("returns false when statusCode is undefined", () => { + expect(isStatusUp(undefined)).toBe(false); + expect(isStatusUp(undefined, [200])).toBe(false); + }); + + it("defaults customUpCodes to empty array", () => { + expect(isStatusUp(200)).toBe(true); + expect(isStatusUp(404)).toBe(false); + }); + }); }); diff --git a/server/test/unit/providers/network/webSocketProvider.test.ts b/server/test/unit/providers/network/webSocketProvider.test.ts index 73e4416a1..12ff32d63 100644 --- a/server/test/unit/providers/network/webSocketProvider.test.ts +++ b/server/test/unit/providers/network/webSocketProvider.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals"; -import { WebSocketProvider } from "../../../../src/service/infrastructure/network/WebSocketProvider.ts"; +import { WebSocketProvider } from "../../../../src/service/network/WebSocketProvider.ts"; import { testStatusProviderContract } from "../../../helpers/statusProviderContract.ts"; -import { NETWORK_ERROR } from "../../../../src/service/infrastructure/network/utils.ts"; -import type { Monitor } from "../../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../../src/service/network/utils.ts"; +import type { Monitor } from "../../../../src/domain/monitors/monitor.types.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/notifications/discordProvider.test.ts b/server/test/unit/providers/notifications/discordProvider.test.ts index 28c30ed22..bdb431fce 100644 --- a/server/test/unit/providers/notifications/discordProvider.test.ts +++ b/server/test/unit/providers/notifications/discordProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { DiscordProvider } = await import("../../../../src/service/infrastructure/notificationProviders/discord.ts"); +const { DiscordProvider } = await import("../../../../src/domain/notifications/providers/discord.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/emailProvider.test.ts b/server/test/unit/providers/notifications/emailProvider.test.ts index e0dc8b676..fecce8ef6 100644 --- a/server/test/unit/providers/notifications/emailProvider.test.ts +++ b/server/test/unit/providers/notifications/emailProvider.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { EmailProvider } from "../../../../src/service/infrastructure/notificationProviders/email.ts"; +import { EmailProvider } from "../../../../src/domain/notifications/providers/email.ts"; import { createMockLogger } from "../../../helpers/createMockLogger.ts"; import { makeNotification, makeMessage } from "../../../helpers/notificationMessage.ts"; import { testNotificationProviderContract } from "../../../helpers/notificationProviderContract.ts"; -import type { IEmailService } from "../../../../src/service/infrastructure/emailService.ts"; +import type { IEmailService } from "../../../../src/service/emailService.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/providers/notifications/matrixProvider.test.ts b/server/test/unit/providers/notifications/matrixProvider.test.ts index f6ad36692..65305e731 100644 --- a/server/test/unit/providers/notifications/matrixProvider.test.ts +++ b/server/test/unit/providers/notifications/matrixProvider.test.ts @@ -7,7 +7,7 @@ const mockGotPut = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { put: mockGotPut } })); jest.unstable_mockModule("crypto", () => ({ randomUUID: () => "test-uuid-1234" })); -const { MatrixProvider } = await import("../../../../src/service/infrastructure/notificationProviders/matrix.ts"); +const { MatrixProvider } = await import("../../../../src/domain/notifications/providers/matrix.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/notificationUtils.test.ts b/server/test/unit/providers/notifications/notificationUtils.test.ts index 53a2666c8..5118e570e 100644 --- a/server/test/unit/providers/notifications/notificationUtils.test.ts +++ b/server/test/unit/providers/notifications/notificationUtils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { buildTestEmail, getTestMessage } from "../../../../src/service/infrastructure/notificationProviders/utils.ts"; -import type { IEmailService } from "../../../../src/service/infrastructure/emailService.ts"; +import { buildTestEmail, getTestMessage } from "../../../../src/domain/notifications/providers/utils.ts"; +import type { IEmailService } from "../../../../src/service/emailService.ts"; describe("notification utils", () => { describe("getTestMessage", () => { diff --git a/server/test/unit/providers/notifications/ntfyProvider.test.ts b/server/test/unit/providers/notifications/ntfyProvider.test.ts index d335b5d01..ffd95a0d7 100644 --- a/server/test/unit/providers/notifications/ntfyProvider.test.ts +++ b/server/test/unit/providers/notifications/ntfyProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { NtfyProvider } = await import("../../../../src/service/infrastructure/notificationProviders/ntfy.ts"); +const { NtfyProvider } = await import("../../../../src/domain/notifications/providers/ntfy.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/pagerdutyProvider.test.ts b/server/test/unit/providers/notifications/pagerdutyProvider.test.ts index f7c9c04c8..8b8d185b4 100644 --- a/server/test/unit/providers/notifications/pagerdutyProvider.test.ts +++ b/server/test/unit/providers/notifications/pagerdutyProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { PagerDutyProvider } = await import("../../../../src/service/infrastructure/notificationProviders/pagerduty.ts"); +const { PagerDutyProvider } = await import("../../../../src/domain/notifications/providers/pagerduty.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/pushoverProvider.test.ts b/server/test/unit/providers/notifications/pushoverProvider.test.ts index 8d32573b5..a227b0b1b 100644 --- a/server/test/unit/providers/notifications/pushoverProvider.test.ts +++ b/server/test/unit/providers/notifications/pushoverProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { PushoverProvider } = await import("../../../../src/service/infrastructure/notificationProviders/pushover.ts"); +const { PushoverProvider } = await import("../../../../src/domain/notifications/providers/pushover.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/slackProvider.test.ts b/server/test/unit/providers/notifications/slackProvider.test.ts index 232eccaaa..20058ab11 100644 --- a/server/test/unit/providers/notifications/slackProvider.test.ts +++ b/server/test/unit/providers/notifications/slackProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost }, HTTPError: class extends Error {} })); -const { SlackProvider } = await import("../../../../src/service/infrastructure/notificationProviders/slack.ts"); +const { SlackProvider } = await import("../../../../src/domain/notifications/providers/slack.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/teamsProvider.test.ts b/server/test/unit/providers/notifications/teamsProvider.test.ts index d27d396a9..c3766ec16 100644 --- a/server/test/unit/providers/notifications/teamsProvider.test.ts +++ b/server/test/unit/providers/notifications/teamsProvider.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import type { Notification } from "../../../../src/types/notification.ts"; -import type { NotificationMessage } from "../../../../src/types/notificationMessage.ts"; +import type { Notification } from "../../../../src/domain/notifications/notification.type.ts"; +import type { NotificationMessage } from "../../../../src/domain/notifications/notification.type.ts"; import { createMockLogger } from "../../../helpers/createMockLogger.ts"; const mockPost = jest.fn(); @@ -9,7 +9,7 @@ jest.unstable_mockModule("got", () => ({ HTTPError: class HTTPError extends Error {}, })); -const { TeamsProvider } = await import("../../../../src/service/infrastructure/notificationProviders/teams.ts"); +const { TeamsProvider } = await import("../../../../src/domain/notifications/providers/teams.ts"); const createNotification = (overrides?: Partial): Notification => ({ id: "notif-1", diff --git a/server/test/unit/providers/notifications/telegramProvider.test.ts b/server/test/unit/providers/notifications/telegramProvider.test.ts index fe9dd2c2e..f8f853e62 100644 --- a/server/test/unit/providers/notifications/telegramProvider.test.ts +++ b/server/test/unit/providers/notifications/telegramProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { TelegramProvider } = await import("../../../../src/service/infrastructure/notificationProviders/telegram.ts"); +const { TelegramProvider } = await import("../../../../src/domain/notifications/providers/telegram.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/twilioProvider.test.ts b/server/test/unit/providers/notifications/twilioProvider.test.ts index 88bfcdf7e..896a23395 100644 --- a/server/test/unit/providers/notifications/twilioProvider.test.ts +++ b/server/test/unit/providers/notifications/twilioProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { TwilioProvider } = await import("../../../../src/service/infrastructure/notificationProviders/twilio.ts"); +const { TwilioProvider } = await import("../../../../src/domain/notifications/providers/twilio.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/providers/notifications/webhookProvider.test.ts b/server/test/unit/providers/notifications/webhookProvider.test.ts index caf17851b..fd16f2c06 100644 --- a/server/test/unit/providers/notifications/webhookProvider.test.ts +++ b/server/test/unit/providers/notifications/webhookProvider.test.ts @@ -6,7 +6,7 @@ import { testNotificationProviderContract } from "../../../helpers/notificationP const mockGotPost = jest.fn().mockResolvedValue({}); jest.unstable_mockModule("got", () => ({ default: { post: mockGotPost } })); -const { WebhookProvider } = await import("../../../../src/service/infrastructure/notificationProviders/webhook.ts"); +const { WebhookProvider } = await import("../../../../src/domain/notifications/providers/webhook.ts"); const createProvider = () => { const logger = createMockLogger(); diff --git a/server/test/unit/services/bufferService.test.ts b/server/test/unit/services/bufferService.test.ts index f1f395e2d..5a6418c70 100644 --- a/server/test/unit/services/bufferService.test.ts +++ b/server/test/unit/services/bufferService.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals"; -import { BufferService } from "../../../src/service/infrastructure/bufferService.ts"; +import { BufferService } from "../../../src/service/bufferService.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { ICheckService } from "../../../src/service/business/checkService.ts"; -import type { IGeoChecksService } from "../../../src/service/business/geoChecksService.ts"; -import type { ISettingsService } from "../../../src/service/system/settingsService.ts"; -import type { Check, GeoCheck } from "../../../src/types/index.ts"; +import type { ICheckService } from "../../../src/domain/checks/check.service.ts"; +import type { IGeoChecksService } from "../../../src/domain/geo-checks/geo-check.service.ts"; +import type { ISettingsService } from "../../../src/domain/app-settings/app-settings.service.ts"; +import type { Check } from "../../../src/domain/checks/check.type.ts"; +import type { GeoCheck } from "../../../src/domain/geo-checks/geo-check.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/checkService.test.ts b/server/test/unit/services/checkService.test.ts index 93919df5f..b658c78da 100644 --- a/server/test/unit/services/checkService.test.ts +++ b/server/test/unit/services/checkService.test.ts @@ -1,8 +1,9 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { CheckService } from "../../../src/service/business/checkService.ts"; +import { CheckService } from "../../../src/domain/checks/check.service.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { IChecksRepository, IMonitorsRepository } from "../../../src/repositories/index.ts"; -import type { MonitorStatusResponse, HardwareStatusPayload, PageSpeedStatusPayload } from "../../../src/types/index.ts"; +import type { IChecksRepository } from "../../../src/domain/checks/check.repository.interface.ts"; +import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts"; +import type { MonitorStatusResponse, HardwareStatusPayload, PageSpeedStatusPayload } from "../../../src/types/network.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/diagnosticService.test.ts b/server/test/unit/services/diagnosticService.test.ts index ae57613a1..b44de1986 100644 --- a/server/test/unit/services/diagnosticService.test.ts +++ b/server/test/unit/services/diagnosticService.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals"; -import { DiagnosticService } from "../../../src/service/business/diagnosticService.ts"; -import type { IDb } from "../../../src/db/IDb.ts"; +import { DiagnosticService } from "../../../src/domain/diagnostics/diagnostic.service.ts"; +import type { IDb } from "../../../src/db/db.interface.ts"; import type { Mongoose } from "mongoose"; // ── Test helpers ───────────────────────────────────────────────────────────── @@ -147,6 +147,42 @@ describe("DiagnosticService", () => { }); }); + it("derives mongoStats.totalSize from storageSize + indexSize when the server omits it (e.g. Atlas shared tier)", async () => { + const db = makeDb({ + collections: [{ collectionName: "monitors" }], + dbStats: { storageSize: 376832, indexSize: 1155072 }, + }); + const service = new DiagnosticService(db); + const promise = service.getSystemStats(); + jest.advanceTimersByTime(1000); + await jest.advanceTimersByTimeAsync(10); + const result = await promise; + + expect(result.mongoStats.totalSize).toBe(376832 + 1155072); + expect(Number.isFinite(result.mongoStats.totalSize)).toBe(true); + }); + + it("derives collection totalSize and defaults missing size fields to 0 when collStats omits them", async () => { + const db = makeDb({ + collections: [{ collectionName: "checks" }], + collStats: { storageSize: 200 }, + }); + const service = new DiagnosticService(db); + const promise = service.getSystemStats(); + jest.advanceTimersByTime(1000); + await jest.advanceTimersByTimeAsync(10); + const result = await promise; + + const coll = result.mongoStats.collections[0]; + expect(coll).toMatchObject({ + name: "checks", + storageSize: 200, + totalIndexSize: 0, + totalSize: 200, // storageSize (200) + totalIndexSize (0) + }); + expect(Number.isFinite(coll?.totalSize)).toBe(true); + }); + it("includes bucketCount on timeseries collections, omits it elsewhere", async () => { const db = makeDb({ collections: [{ collectionName: "checks" }], diff --git a/server/test/unit/services/emailService.test.ts b/server/test/unit/services/emailService.test.ts index d4d275ad8..fbfdf15fd 100644 --- a/server/test/unit/services/emailService.test.ts +++ b/server/test/unit/services/emailService.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { EmailService } from "../../../src/service/infrastructure/emailService.ts"; +import { EmailService } from "../../../src/service/emailService.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { ISettingsService } from "../../../src/service/system/settingsService.ts"; -import type { EmailTransportConfig } from "../../../src/types/index.ts"; +import type { ISettingsService } from "../../../src/domain/app-settings/app-settings.service.ts"; +import type { EmailTransportConfig } from "../../../src/domain/app-settings/app-settings.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/geoChecksService.test.ts b/server/test/unit/services/geoChecksService.test.ts index ab03d319e..8e50fa3e8 100644 --- a/server/test/unit/services/geoChecksService.test.ts +++ b/server/test/unit/services/geoChecksService.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { GeoChecksService } from "../../../src/service/business/geoChecksService.ts"; +import { GeoChecksService } from "../../../src/domain/geo-checks/geo-check.service.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { Monitor } from "../../../src/types/index.ts"; -import type { GeoCheckResult } from "../../../src/types/geoCheck.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; +import type { GeoCheckResult } from "../../../src/domain/geo-checks/geo-check.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/globalPingService.test.ts b/server/test/unit/services/globalPingService.test.ts index 78d195b0f..376653110 100644 --- a/server/test/unit/services/globalPingService.test.ts +++ b/server/test/unit/services/globalPingService.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { GeoContinent } from "../../../src/types/geoCheck.ts"; +import type { GeoContinent } from "../../../src/domain/geo-checks/geo-check.type.ts"; // ── got mock ───────────────────────────────────────────────────────────────── @@ -15,7 +15,7 @@ jest.unstable_mockModule("got", () => ({ })); // Dynamic import AFTER mock registration -const { GlobalPingService } = await import("../../../src/service/infrastructure/globalPingService.ts"); +const { GlobalPingService } = await import("../../../src/service/globalPingService.ts"); // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/incidentService.test.ts b/server/test/unit/services/incidentService.test.ts index 4274f67d8..f62f4ccf4 100644 --- a/server/test/unit/services/incidentService.test.ts +++ b/server/test/unit/services/incidentService.test.ts @@ -1,11 +1,13 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { IncidentService } from "../../../src/service/business/incidentService.ts"; +import { IncidentService } from "../../../src/domain/incidents/incident.service.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { IIncidentsRepository, IMonitorsRepository, IUsersRepository } from "../../../src/repositories/index.ts"; -import type { INotificationMessageBuilder } from "../../../src/service/infrastructure/notificationMessageBuilder.ts"; -import type { Monitor } from "../../../src/types/monitor.ts"; -import type { Incident } from "../../../src/types/index.ts"; -import type { MonitorActionDecision } from "../../../src/service/infrastructure/JobQueues/QueueHelper.ts"; +import type { IIncidentsRepository } from "../../../src/domain/incidents/incident.repository.interface.ts"; +import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts"; +import type { IUsersRepository } from "../../../src/domain/users/user.repository.interface.ts"; +import type { INotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; +import type { Incident } from "../../../src/domain/incidents/incident.type.ts"; +import type { MonitorActionDecision } from "../../../src/service/job-queues/job-queue.helper.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/inviteService.test.ts b/server/test/unit/services/inviteService.test.ts index da29d1834..c050d91e6 100644 --- a/server/test/unit/services/inviteService.test.ts +++ b/server/test/unit/services/inviteService.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { InviteService } from "../../../src/service/business/inviteService.ts"; -import type { Invite, UserRole } from "../../../src/types/index.ts"; +import { InviteService } from "../../../src/domain/invites/invite.service.ts"; +import type { Invite } from "../../../src/domain/invites/invite.type.ts"; +import type { UserRole } from "../../../src/domain/users/user.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/lessSimpleQueue.test.ts b/server/test/unit/services/lessSimpleQueue.test.ts index 6351dc5d5..b43383825 100644 --- a/server/test/unit/services/lessSimpleQueue.test.ts +++ b/server/test/unit/services/lessSimpleQueue.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest } from "@jest/globals"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { Monitor } from "../../../src/types/monitor.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; // ── Mock Scheduler / MongoStore ────────────────────────────────────────────── @@ -44,7 +44,7 @@ jest.unstable_mockModule("less-simple-scheduler", () => ({ MongoStore: MockMongoStore, })); -const { LessSimpleQueue } = await import("../../../src/service/infrastructure/JobQueues/LessSimpleQueue.ts"); +const { LessSimpleQueue } = await import("../../../src/service/job-queues/job-queue.less-simple.ts"); const createQueueHelper = () => ({ getHeartbeatJob: jest.fn().mockReturnValue(() => Promise.resolve()), diff --git a/server/test/unit/services/maintenanceWindowService.test.ts b/server/test/unit/services/maintenanceWindowService.test.ts index 376b8f20d..9d381ae74 100644 --- a/server/test/unit/services/maintenanceWindowService.test.ts +++ b/server/test/unit/services/maintenanceWindowService.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { MaintenanceWindowService } from "../../../src/service/business/maintenanceWindowService.ts"; -import type { IMaintenanceWindowsRepository } from "../../../src/repositories/maintenance-windows/IMaintenanceWindowsRepository.ts"; -import type { IMonitorsRepository } from "../../../src/repositories/monitors/IMonitorsRepository.ts"; -import type { MaintenanceWindow } from "../../../src/types/index.ts"; +import { MaintenanceWindowService } from "../../../src/domain/maintenance-windows/maintenance-window.service.ts"; +import type { IMaintenanceWindowsRepository } from "../../../src/domain/maintenance-windows/maintenance-window.repository.interface.ts"; +import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts"; +import type { MaintenanceWindow } from "../../../src/domain/maintenance-windows/maintenance-window.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/monitorService.test.ts b/server/test/unit/services/monitorService.test.ts index a21967de8..3dff29f26 100644 --- a/server/test/unit/services/monitorService.test.ts +++ b/server/test/unit/services/monitorService.test.ts @@ -1,13 +1,11 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { MonitorService } from "../../../src/service/business/monitorService.ts"; -import type { - IChecksRepository, - IGeoChecksRepository, - IIncidentsRepository, - IMonitorStatsRepository, - IMonitorsRepository, - IStatusPagesRepository, -} from "../../../src/repositories/index.ts"; +import { MonitorService } from "../../../src/domain/monitors/monitor.service.ts"; +import type { IChecksRepository } from "../../../src/domain/checks/check.repository.interface.ts"; +import type { IGeoChecksRepository } from "../../../src/domain/geo-checks/geo-check.repository.interface.ts"; +import type { IIncidentsRepository } from "../../../src/domain/incidents/incident.repository.interface.ts"; +import type { IMonitorStatsRepository } from "../../../src/domain/monitor-stats/monitor-stats.repository.interface.ts"; +import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts"; +import type { IStatusPagesRepository } from "../../../src/domain/status-pages/status-page-repository.interface.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; const createMonitorsRepositoryMock = () => diff --git a/server/test/unit/services/networkService.test.ts b/server/test/unit/services/networkService.test.ts index c9f496166..268b52e3a 100644 --- a/server/test/unit/services/networkService.test.ts +++ b/server/test/unit/services/networkService.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { NetworkService } from "../../../src/service/infrastructure/networkService.ts"; +import { NetworkService } from "../../../src/service/networkService.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import { NETWORK_ERROR } from "../../../src/service/infrastructure/network/utils.ts"; -import type { IStatusProvider } from "../../../src/service/infrastructure/network/IStatusProvider.ts"; -import type { Monitor, MonitorType } from "../../../src/types/index.ts"; +import { NETWORK_ERROR } from "../../../src/service/network/utils.ts"; +import type { IStatusProvider } from "../../../src/service/network/IStatusProvider.ts"; +import type { Monitor, MonitorType } from "../../../src/domain/monitors/monitor.types.ts"; import type { AxiosStatic } from "axios"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/notificationMessageBuilder.test.ts b/server/test/unit/services/notificationMessageBuilder.test.ts index 4403a8bb3..0ae4d8096 100644 --- a/server/test/unit/services/notificationMessageBuilder.test.ts +++ b/server/test/unit/services/notificationMessageBuilder.test.ts @@ -1,7 +1,8 @@ import { describe, expect, it, beforeEach } from "@jest/globals"; -import { NotificationMessageBuilder } from "../../../src/service/infrastructure/notificationMessageBuilder.ts"; -import type { Monitor, MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/index.ts"; -import type { MonitorActionDecision } from "../../../src/service/infrastructure/JobQueues/QueueHelper.ts"; +import { NotificationMessageBuilder } from "../../../src/domain/notifications/notification.message-builder.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; +import type { MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/network.ts"; +import type { MonitorActionDecision } from "../../../src/service/job-queues/job-queue.helper.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/notificationsService.test.ts b/server/test/unit/services/notificationsService.test.ts index 9527fd3b5..eca1f349b 100644 --- a/server/test/unit/services/notificationsService.test.ts +++ b/server/test/unit/services/notificationsService.test.ts @@ -1,8 +1,10 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { NotificationsService } from "../../../src/service/infrastructure/notificationsService.ts"; +import { NotificationsService } from "../../../src/domain/notifications/notification.service.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { Notification, Monitor, MonitorStatusResponse } from "../../../src/types/index.ts"; -import type { MonitorActionDecision } from "../../../src/service/infrastructure/JobQueues/QueueHelper.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; +import type { Notification } from "../../../src/domain/notifications/notification.type.ts"; +import type { MonitorStatusResponse } from "../../../src/types/network.ts"; +import type { MonitorActionDecision } from "../../../src/service/job-queues/job-queue.helper.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/settingsService.test.ts b/server/test/unit/services/settingsService.test.ts index da75d3ee5..af58b5705 100644 --- a/server/test/unit/services/settingsService.test.ts +++ b/server/test/unit/services/settingsService.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { SettingsService } from "../../../src/service/system/settingsService.ts"; -import type { ISettingsRepository } from "../../../src/repositories/settings/ISettingsRepository.ts"; -import type { ValidatedEnv } from "../../../src/validation/envValidation.ts"; -import type { Settings } from "../../../src/types/index.ts"; +import { SettingsService } from "../../../src/domain/app-settings/app-settings.service.ts"; +import type { ISettingsRepository } from "../../../src/domain/app-settings/app-settings-repository.interface.ts"; +import type { ValidatedEnv } from "../../../src/config/envValidation.ts"; +import type { Settings } from "../../../src/domain/app-settings/app-settings.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/statusPageService.test.ts b/server/test/unit/services/statusPageService.test.ts index 49879cc94..7359b015a 100644 --- a/server/test/unit/services/statusPageService.test.ts +++ b/server/test/unit/services/statusPageService.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { StatusPageService } from "../../../src/service/business/statusPageService.ts"; -import type { IStatusPagesRepository } from "../../../src/repositories/status-pages/IStatusPagesRepository.ts"; -import type { ISettingsService } from "../../../src/service/system/settingsService.ts"; -import type { StatusPage } from "../../../src/types/index.ts"; -import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/types/index.ts"; +import { StatusPageService } from "../../../src/domain/status-pages/status-page.service.ts"; +import type { IStatusPagesRepository } from "../../../src/domain/status-pages/status-page-repository.interface.ts"; +import type { ISettingsService } from "../../../src/domain/app-settings/app-settings.service.ts"; +import type { StatusPage } from "../../../src/domain/status-pages/status-page.type.ts"; +import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE } from "../../../src/domain/status-pages/status-page.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/statusService.test.ts b/server/test/unit/services/statusService.test.ts index 75323943b..2bd5fa74c 100644 --- a/server/test/unit/services/statusService.test.ts +++ b/server/test/unit/services/statusService.test.ts @@ -1,10 +1,14 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; -import { StatusService } from "../../../src/service/infrastructure/statusService.ts"; +import { StatusService } from "../../../src/service/statusService.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import { MAX_RECENT_CHECKS } from "../../../src/types/index.ts"; -import type { Monitor, MonitorStatus, MonitorStatusResponse, Check, HardwareStatusPayload } from "../../../src/types/index.ts"; -import type { IMonitorsRepository, IMonitorStatsRepository, IChecksRepository } from "../../../src/repositories/index.ts"; -import type { IBufferService } from "../../../src/service/infrastructure/bufferService.ts"; +import { MAX_RECENT_CHECKS } from "../../../src/domain/monitors/monitor.types.ts"; +import type { Monitor, MonitorStatus } from "../../../src/domain/monitors/monitor.types.ts"; +import type { Check } from "../../../src/domain/checks/check.type.ts"; +import type { MonitorStatusResponse, HardwareStatusPayload } from "../../../src/types/network.ts"; +import type { IMonitorsRepository } from "../../../src/domain/monitors/monitor.repository.interface.ts"; +import type { IMonitorStatsRepository } from "../../../src/domain/monitor-stats/monitor-stats.repository.interface.ts"; +import type { IChecksRepository } from "../../../src/domain/checks/check.repository.interface.ts"; +import type { IBufferService } from "../../../src/service/bufferService.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/superSimpleQueue.test.ts b/server/test/unit/services/superSimpleQueue.test.ts index b6da507ce..8f446b74f 100644 --- a/server/test/unit/services/superSimpleQueue.test.ts +++ b/server/test/unit/services/superSimpleQueue.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest, beforeEach } from "@jest/globals"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { Monitor } from "../../../src/types/monitor.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; // ── Mock Scheduler ─────────────────────────────────────────────────────────── @@ -35,7 +35,7 @@ jest.unstable_mockModule("super-simple-scheduler", () => ({ default: MockScheduler, })); -const { SuperSimpleQueue } = await import("../../../src/service/infrastructure/JobQueues/SuperSimpleQueue.ts"); +const { SuperSimpleQueue } = await import("../../../src/service/job-queues/job-queue.sumper-simple.ts"); const createQueueHelper = () => ({ getHeartbeatJob: jest.fn().mockReturnValue(() => Promise.resolve()), diff --git a/server/test/unit/services/superSimpleQueueHelper.test.ts b/server/test/unit/services/superSimpleQueueHelper.test.ts index 7075051eb..7a369f4db 100644 --- a/server/test/unit/services/superSimpleQueueHelper.test.ts +++ b/server/test/unit/services/superSimpleQueueHelper.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { QueueHelper } from "../../../src/service/infrastructure/JobQueues/QueueHelper.ts"; -import type { Monitor } from "../../../src/types/monitor.ts"; +import { QueueHelper } from "../../../src/service/job-queues/job-queue.helper.ts"; +import type { Monitor } from "../../../src/domain/monitors/monitor.types.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/tagsService.test.ts b/server/test/unit/services/tagsService.test.ts index d611d641e..7f99906d7 100644 --- a/server/test/unit/services/tagsService.test.ts +++ b/server/test/unit/services/tagsService.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { TagsService } from "../../../src/service/infrastructure/tagService.ts"; -import type { Tag } from "../../../src/types/index.ts"; +import { TagsService } from "../../../src/domain/tags/tag.service.ts"; +import type { Tag } from "../../../src/domain/tags/tag.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/services/userService.test.ts b/server/test/unit/services/userService.test.ts index 51590fb38..e2e11369c 100644 --- a/server/test/unit/services/userService.test.ts +++ b/server/test/unit/services/userService.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, jest } from "@jest/globals"; -import { UserService } from "../../../src/service/business/userService.ts"; +import { UserService } from "../../../src/domain/users/user.service.ts"; import { createMockLogger } from "../../helpers/createMockLogger.ts"; -import type { User, UserRole } from "../../../src/types/index.ts"; +import type { User, UserRole } from "../../../src/domain/users/user.type.ts"; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/server/test/unit/utils/maintenanceWindow.test.ts b/server/test/unit/utils/maintenanceWindow.test.ts index 5a3a39b6a..4c4ed3337 100644 --- a/server/test/unit/utils/maintenanceWindow.test.ts +++ b/server/test/unit/utils/maintenanceWindow.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "@jest/globals"; import { isWindowActive } from "../../../src/utils/maintenanceWindow.ts"; -import type { MaintenanceWindow } from "../../../src/types/index.ts"; +import type { MaintenanceWindow } from "../../../src/domain/maintenance-windows/maintenance-window.type.ts"; const makeWindow = (overrides?: Partial): MaintenanceWindow => { const now = Date.now(); diff --git a/server/test/unit/validation/maintenanceWindowValidation.test.ts b/server/test/unit/validation/maintenanceWindowValidation.test.ts index a209ba64f..d77fabd2c 100644 --- a/server/test/unit/validation/maintenanceWindowValidation.test.ts +++ b/server/test/unit/validation/maintenanceWindowValidation.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, jest, afterEach } from "@jest/globals"; import { createMaintenanceWindowBodyValidation, editMaintenanceByIdWindowBodyValidation, -} from "../../../src/validation/maintenanceWindowValidation.ts"; +} from "../../../src/api/validation/maintenanceWindowValidation.ts"; const FROZEN_NOW = new Date("2026-05-07T12:00:00Z").getTime(); diff --git a/server/test/unit/validation/monitorValidation.test.ts b/server/test/unit/validation/monitorValidation.test.ts index 057ff6ae1..c08b8be84 100644 --- a/server/test/unit/validation/monitorValidation.test.ts +++ b/server/test/unit/validation/monitorValidation.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "@jest/globals"; -import { createMonitorBodyValidation, editMonitorBodyValidation, importMonitorsBodyValidation } from "../../../src/validation/monitorValidation.ts"; +import { + createMonitorBodyValidation, + editMonitorBodyValidation, + importMonitorsBodyValidation, +} from "../../../src/api/validation/monitorValidation.ts"; const baseDnsBody = { name: "DNS check", @@ -222,3 +226,93 @@ describe("monitorValidation — strategy gating", () => { }); }); }); + +describe("monitorValidation — customUpCodes", () => { + const baseHttpBody = { + name: "HTTP check", + type: "http" as const, + url: "https://example.com", + }; + + describe("createMonitorBodyValidation", () => { + it("accepts valid HTTP status codes", () => { + const parsed = createMonitorBodyValidation.parse({ + ...baseHttpBody, + customUpCodes: [200, 301, 404, 503], + }); + expect(parsed.customUpCodes).toEqual([200, 301, 404, 503]); + }); + + it("rejects invalid HTTP status codes", () => { + expect(() => + createMonitorBodyValidation.parse({ + ...baseHttpBody, + customUpCodes: [5000], + }) + ).toThrow(); + + expect(() => + createMonitorBodyValidation.parse({ + ...baseHttpBody, + customUpCodes: [-1], + }) + ).toThrow(); + }); + + it("defaults to an empty array when not provided", () => { + const parsed = createMonitorBodyValidation.parse(baseHttpBody); + expect(parsed.customUpCodes).toEqual([]); + }); + }); + + describe("editMonitorBodyValidation", () => { + it("accepts valid HTTP status codes on edits", () => { + const parsed = editMonitorBodyValidation.parse({ + customUpCodes: [200, 201], + }); + expect(parsed.customUpCodes).toEqual([200, 201]); + }); + + it("rejects invalid HTTP status codes on edits", () => { + expect(() => + editMonitorBodyValidation.parse({ + customUpCodes: [9999], + }) + ).toThrow(); + }); + }); + + describe("importMonitorsBodyValidation", () => { + it("retains valid HTTP status codes on imported HTTP monitors", () => { + const parsed = importMonitorsBodyValidation.parse({ + monitors: [ + { + ...baseHttpBody, + customUpCodes: [404], + }, + ], + }); + expect(parsed.monitors[0].customUpCodes).toEqual([404]); + }); + + it("rejects invalid HTTP status codes on imported HTTP monitors", () => { + expect(() => + importMonitorsBodyValidation.parse({ + monitors: [ + { + ...baseHttpBody, + customUpCodes: [5000], + }, + ], + }) + ).toThrow(); + }); + + it("defaults to an empty array when not provided on import", () => { + const parsed = importMonitorsBodyValidation.parse({ + monitors: [baseHttpBody], + }); + expect(parsed.monitors[0].customUpCodes).toEqual([]); + }); + }); +}); diff --git a/server/test/unit/validation/userValidation.test.ts b/server/test/unit/validation/userValidation.test.ts index 2e263d1a9..c50a70caa 100644 --- a/server/test/unit/validation/userValidation.test.ts +++ b/server/test/unit/validation/userValidation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "@jest/globals"; -import { editUserBodyValidation } from "../../../src/validation/userValidation.ts"; +import { editUserBodyValidation } from "../../../src/api/validation/userValidation.ts"; const validPassword = "OldPass1!"; const validNewPassword = "NewPass1!"; diff --git a/server/tsconfig.json b/server/tsconfig.json index d4544d9bb..41f1ab64c 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowJs": true, "checkJs": false, + "baseUrl": ".", "paths": { "@/*": ["./src/*"] },