From b88c46a6a105c1497fe77c378b8d08e6b875cf3b Mon Sep 17 00:00:00 2001
From: Alex Holliday
Date: Fri, 21 Nov 2025 23:44:20 +0000
Subject: [PATCH] fix invite
---
README.md | 221 ++--------
client/src/components/auth-verifier/index.tsx | 3 +-
client/src/pages/auth/RegisterInvite.tsx | 4 +-
client/src/routes/Router.tsx | 1 +
server/openapi.json | 407 ++++++++++++++++++
server/package-lock.json | 39 +-
server/package.json | 1 +
server/src/index.ts | 13 +
server/src/routes/invite.ts | 2 +-
9 files changed, 507 insertions(+), 184 deletions(-)
create mode 100644 server/openapi.json
diff --git a/README.md b/README.md
index 8083a5204..de9df0d87 100644
--- a/README.md
+++ b/README.md
@@ -1,183 +1,50 @@
- 
-
-
-
-
-
-
-
-
-[](https://www.bestpractices.dev/projects/9901)
-[](https://deepwiki.com/bluewave-labs/checkmate)
+# Checkmate
-
+A full‑stack uptime and infrastructure monitoring app. The repository contains an Express/TypeScript API and a React/TypeScript client.
-An open source uptime and infrastructure monitoring application
+## Project Structure
+- `server/` – Node.js API (TypeScript, ESM)
+ - Source: `server/src`
+ - Routes mounted under `/api/v1/*` via `server/src/init/routes.ts`
+ - Tests: `server/tests`
+ - Build output: `server/dist`
+- `client/` – React + TypeScript (Vite)
+ - Source: `client/src`
+ - Public assets: `client/public`
-
+## Key Features
+- JWT authentication and permission checks (team/org).
+- Monitors API: create, list (with sorting/pagination), get checks, toggle pause, update, delete.
+- Import/Export monitors (`GET /api/v1/monitors/export`, `POST /api/v1/monitors/import`).
+- Notification channels and diagnostics endpoints.
+- System settings (SMTP) with env‑first email transport fallback to DB settings.
-This repository contains both the frontend and the backend of Checkmate, an open-source, self-hosted monitoring tool for tracking server hardware, uptime, response times, and incidents in real-time with beautiful visualizations. Checkmate regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.
+## Getting Started
+1. Prerequisites: Node 18+, MongoDB
+2. Environment
+ - Server: create `server/.env` with at least:
+ - `MONGODB_URI=mongodb://localhost:27017/saas`
+ - `JWT_SECRET=replace_me`
+ - `ORIGIN=http://localhost:5173`
+ - Optional SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`
+3. Install
+ - `cd server && npm ci`
+ - `cd ../client && npm ci`
+4. Run
+ - API: `cd server && npm run dev` (listens on `http://localhost:52345`)
+ - Client: `cd client && npm run dev` (Vite dev server)
-Checkmate also has an agent, called [Capture](https://github.com/bluewave-labs/capture), to retrieve data from remote servers. While Capture is not required to run Checkmate, it provides additional insights about your servers' CPU, RAM, disk, and temperature status. Capture can run on Linux, Windows, Mac, Raspberry Pi, or any device that can run Go.
+## Scripts
+- Server
+ - `npm run dev` – Start API in watch mode
+ - `npm run build` – Type‑check and compile (tsc + tsc‑alias)
+ - `npm test` / `npm run test:coverage` – Jest tests and coverage
+- Client
+ - `npm run dev` – Start Vite dev server
+ - `npm run build` – Type‑check and build
+ - `npm run preview` – Preview production build
-Checkmate has been stress-tested with 1000+ active monitors without any particular issues or performance bottlenecks.
-
-**If you would like to sponsor a feature, [see this link](https://checkmate.so/sponsored-features).**
-
-## 📚 Table of contents
-
-- [📦 Demo](#demo)
-- [🔗 User's guide](#users-guide)
-- [🛠️ Installation](#installation)
-- [🏁 Translations](#translations)
-- [🚀 Performance](#performance)
-- [💚 Questions & Ideas](#questions--ideas)
-- [🧩 Features](#features)
-- [🏗️ Screenshots](#screenshots)
-- [🏗️ Tech stack](#tech-stack)
-- [🔗 A few links](#a-few-links)
-- [🤝 Contributing](#contributing)
-- [💰 Our sponsors](#our-sponsors)
-
-
-## Demo
-
-You can see the latest build of [Checkmate](https://checkmate-demo.bluewavelabs.ca/) in action. The username is uptimedemo@demo.com and the password is Demouser1! (just a note that we update the demo server from time to time, so if it doesn't work for you, please ping us on the Discussions channel).
-
-## User's guide
-
-Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1). It's still WIP and some of the information there might be outdated as we continuously add features weekly. Rest assured, we are doing our best! :)
-
-## Installation
-
-See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
-
-Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
-
-### Using a Custom CA
-
-If you need to monitor internal HTTPS endpoints with certificates from private Certificate Authorities (like Smallstep), see our [Custom CA Trust Guide](./docs/custom-ca-trust.md) for Docker configuration options.
-
-For more documentation, see the [docs directory](./docs/).
-
-
-## Translations
-
-If you would like to use Checkmate in your language, please [go to this page](https://poeditor.com/join/project/lRUoGZFCsJ) and register for the language you would like to translate Checkmate to.
-
-## Performance
-
-Thanks to extensive optimizations, Checkmate operates with an exceptionally small memory footprint, requiring minimal memory and CPU resources. Here’s the memory usage of a Node.js instance running on a server that monitors 323 servers every minute:
-
-
-
-You can see the memory footprint of MongoDB and Redis on the same server (398Mb and 15Mb) for the same amount of servers:
-
-
-
-## Questions & Ideas
-
-If you have any questions, suggestions or comments, you have several options:
-
-- [Discord channel](https://discord.gg/NAb6H3UTjK)
-- [GitHub Discussions](https://github.com/bluewave-labs/bluewave-uptime/discussions)
-- [Reddit group](https://www.reddit.com/r/CheckmateMonitoring/)
-
-Feel free to ask questions or share your ideas - we'd love to hear from you!
-
-## Features
-
-- Completely open source, deployable on your servers or home devices (e.g Raspberry Pi 4 or 5)
-- Website monitoring
-- Page speed monitoring
-- Infrastructure monitoring (memory, disk usage, CPU performance, network etc) - requires [Capture](https://github.com/bluewave-labs/capture) agent
-- Docker monitoring
-- Ping monitoring
-- SSL monitoring
-- Port monitoring
-- Game server monitoring (3.0)
-- Incidents at a glance
-- Status pages
-- E-mail, Webhooks, Discord and Slack notifications
-- Scheduled maintenance
-- JSON query monitoring
-- Multi-language support for English, German, Japanese, Portuguese (Brazil), Russian, Turkish, Ukrainian, Vietnamese, Chinese (Traditional, Taiwan)
-
-**Short term roadmap:**
-
-- Plugins that will help Checkmate get any information from a remote service (e.g database, etc)
-- Better notifications
-- Network monitoring
-- ..and a few more features
-
-If you would like to sponsor an additional feature, [see this page](https://checkmate.so/sponsored-features).
-
-## Screenshots
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## Tech stack
-
-- [ReactJs](https://react.dev/)
-- [MUI (React framework)](https://mui.com/)
-- [Node.js](https://nodejs.org/en)
-- [MongoDB](https://mongodb.com)
-- [Recharts](https://recharts.org)
-- Lots of other open source components!
-
-## A few links
-
-- If you would like to support us, please consider giving it a ⭐ and click on "watch".
-- Have a question or suggestion for the roadmap/featureset? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.
-- Need a ping when there's a new release? Use [Newreleases](https://newreleases.io/), a free service to track releases.
-- Watch a Checkmate [installation and usage video](https://www.youtube.com/watch?v=GfFOc0xHIwY)
-
-## Contributing
-
-We are [Alex](http://github.com/ajhollid) (team lead), [Gorkem](http://github.com/gorkem-bwl/), [Owaise](http://github.com/Owaiseimdad), [Aryaman](https://github.com/Br0wnHammer), [Mert](https://github.com/mertssmnoglu) and [Karen](https://github.com/karenvicent) helping individuals and businesses monitor their infra and servers.
-
-We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 7000+ stars and attracted 90+ contributors from around the globe.
-
-Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us!
-
-Here's how you can contribute:
-
-0. Star this repo :)
-1. Check [Contributor's guideline](https://github.com/bluewave-labs/Checkmate/blob/develop/CONTRIBUTING.md). First timers are encouraged to check `good-first-issue` tag.
-2. Check [project structure](https://docs.checkmate.so/checkmate-2.1/developers-guide/general-project-structure) and [high level overview](https://bluewavelabs.gitbook.io/checkmate/developers-guide/high-level-overview).
-3. Read a detailed structure of [Checkmate](https://deepwiki.com/bluewave-labs/Checkmate) if you would like to deep dive into the architecture.
-4. Open an issue if you believe you've encountered a bug.
-5. Check for good-first-issue's if you are a newcomer.
-6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
-7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
-
-
-
-
-
-[](https://star-history.com/#bluewave-labs/bluewave-uptime&Date)
-
-## Our sponsors
-
-Thanks to [Gitbook](https://gitbook.io/) for giving us a free tier for their documentation platform, and [Poeditor](https://poeditor.com/) providing us a free account to use their i18n services. If you would like to sponsor Checkmate, please send an email to hello@bluewavelabs.ca
-
-If you would like to sponsor a feature, [see this page](https://checkmate.so/sponsored-features).
-
-Also check other developer and contributor-friendly projects of BlueWave:
-
-- [VerifyWise](https://github.com/bluewave-labs/verifywise), the first open source AI governance platform.
-- [DataRoom](https://github.com/bluewave-labs/bluewave-dataroom), an secure file sharing application, aka dataroom.
-- [Guidefox](https://github.com/bluewave-labs/guidefox), an application that helps new users learn how to use your product via hints, tours, popups and banners.
+## Notes
+- The API expects valid JWTs on most routes; see `server/src/routes` for middleware usage.
+- Mongo connection retries are handled in `server/src/db/MongoDB.ts`.
+- Email transport prioritizes env config; if unset, it uses values from system settings.
diff --git a/client/src/components/auth-verifier/index.tsx b/client/src/components/auth-verifier/index.tsx
index 64cbd426c..9a6ce63de 100644
--- a/client/src/components/auth-verifier/index.tsx
+++ b/client/src/components/auth-verifier/index.tsx
@@ -16,7 +16,8 @@ export const AuthVerifier = ({ children }: { children: ReactNode }) => {
const location = useLocation();
const isAuthRoute =
["/login", "/register"].includes(location.pathname) ||
- location.pathname.startsWith("/recovery");
+ location.pathname.startsWith("/recovery") ||
+ location.pathname.startsWith("/register/");
const isPublicRoute = location.pathname.startsWith("/status-pages/public");
const [isVerifying, setIsVerifying] = useState(true);
diff --git a/client/src/pages/auth/RegisterInvite.tsx b/client/src/pages/auth/RegisterInvite.tsx
index f064d16bf..c42b8a623 100644
--- a/client/src/pages/auth/RegisterInvite.tsx
+++ b/client/src/pages/auth/RegisterInvite.tsx
@@ -43,7 +43,7 @@ const RegisterInvite = () => {
if (!result) {
dispatch(setAuthenticated(false));
- navigate("/login");
+ // navigate("/login");
return;
}
@@ -51,7 +51,7 @@ const RegisterInvite = () => {
if (!user) {
dispatch(setAuthenticated(false));
- navigate("/login");
+ // navigate("/login");
return;
}
diff --git a/client/src/routes/Router.tsx b/client/src/routes/Router.tsx
index c0ed05a64..6a2dadabf 100644
--- a/client/src/routes/Router.tsx
+++ b/client/src/routes/Router.tsx
@@ -55,6 +55,7 @@ const Router = () => {
} />
} />
+ } />
} />
} />
} />
diff --git a/server/openapi.json b/server/openapi.json
new file mode 100644
index 000000000..893783710
--- /dev/null
+++ b/server/openapi.json
@@ -0,0 +1,407 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "Checkmate API",
+ "version": "1.0.0",
+ "description": "OpenAPI specification for the Checkmate server endpoints (v1)."
+ },
+ "servers": [
+ { "url": "http://localhost:52345/api/v1", "description": "Local dev" }
+ ],
+ "security": [ { "cookieAuth": [] } ],
+ "components": {
+ "securitySchemes": {
+ "cookieAuth": {
+ "type": "apiKey",
+ "in": "cookie",
+ "name": "token",
+ "description": "HTTP-only auth cookie named 'token'. Send automatically by browser on same-origin requests."
+ }
+ },
+ "schemas": {
+ "Login": {
+ "type": "object",
+ "required": ["email", "password"],
+ "properties": {
+ "email": { "type": "string", "format": "email" },
+ "password": { "type": "string", "minLength": 6 }
+ }
+ },
+ "Register": {
+ "type": "object",
+ "required": ["email", "firstName", "lastName", "password"],
+ "properties": {
+ "email": { "type": "string", "format": "email" },
+ "firstName": { "type": "string" },
+ "lastName": { "type": "string" },
+ "password": { "type": "string", "minLength": 6 }
+ }
+ },
+ "RegisterWithInvite": {
+ "type": "object",
+ "properties": {
+ "firstName": { "type": "string" },
+ "lastName": { "type": "string" },
+ "password": { "type": "string", "minLength": 6 },
+ "confirmPassword": { "type": "string", "minLength": 6 }
+ }
+ },
+ "MonitorCreate": {
+ "type": "object",
+ "required": ["type", "url", "name", "n", "interval"],
+ "properties": {
+ "type": { "type": "string", "example": "http" },
+ "url": { "type": "string", "format": "uri" },
+ "name": { "type": "string" },
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+ "n": { "type": "integer", "minimum": 1, "maximum": 25 },
+ "notificationChannels": { "type": "array", "items": { "type": "string" } },
+ "secret": { "type": "string" },
+ "interval": { "type": "integer", "minimum": 1 }
+ }
+ },
+ "MonitorPatch": {
+ "type": "object",
+ "additionalProperties": true
+ },
+ "MonitorImport": {
+ "type": "object",
+ "required": ["monitors"],
+ "properties": {
+ "monitors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["name", "url", "type", "interval", "n"],
+ "properties": {
+ "name": { "type": "string" },
+ "url": { "type": "string" },
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+ "secret": { "type": "string" },
+ "type": { "type": "string", "example": "http" },
+ "interval": { "type": "integer", "minimum": 1 },
+ "n": { "type": "integer", "minimum": 1 }
+ },
+ "additionalProperties": false
+ },
+ "minItems": 1
+ }
+ }
+ },
+ "NotificationChannel": {
+ "type": "object",
+ "required": ["name", "type", "config"],
+ "properties": {
+ "name": { "type": "string" },
+ "type": { "type": "string", "enum": ["email", "slack", "discord", "webhook"] },
+ "config": {
+ "type": "object",
+ "properties": {
+ "url": { "type": "string" },
+ "emailAddress": { "type": "string", "format": "email" }
+ }
+ }
+ }
+ },
+ "StatusPage": {
+ "type": "object",
+ "required": ["name", "url", "isPublished"],
+ "properties": {
+ "name": { "type": "string" },
+ "url": { "type": "string" },
+ "description": { "type": "string" },
+ "isPublished": { "type": "boolean" },
+ "monitors": { "type": "array", "items": { "type": "string" } }
+ }
+ },
+ "ProfilePatch": {
+ "type": "object",
+ "properties": {
+ "firstName": { "type": "string" },
+ "lastName": { "type": "string" },
+ "password": { "type": "string", "minLength": 6 },
+ "confirmPassword": { "type": "string", "minLength": 6 }
+ }
+ },
+ "SystemSettings": {
+ "type": "object",
+ "properties": {
+ "systemEmailHost": { "type": "string" },
+ "systemEmailPort": { "type": "integer" },
+ "systemEmailAddress": { "type": "string", "format": "email" },
+ "systemEmailPassword": { "type": "string" },
+ "systemEmailUser": { "type": "string" },
+ "systemEmailConnectionHost": { "type": "string" },
+ "systemEmailTLSServername": { "type": "string" },
+ "systemEmailSecure": { "type": "boolean" },
+ "systemEmailPool": { "type": "boolean" },
+ "systemEmailIgnoreTLS": { "type": "boolean" },
+ "systemEmailRequireTLS": { "type": "boolean" },
+ "systemEmailRejectUnauthorized": { "type": "boolean" }
+ }
+ },
+ "RecoveryRequest": {
+ "type": "object",
+ "required": ["email"],
+ "properties": { "email": { "type": "string", "format": "email" } }
+ },
+ "ResetPassword": {
+ "type": "object",
+ "required": ["token", "password"],
+ "properties": {
+ "token": { "type": "string" },
+ "password": { "type": "string", "minLength": 6 }
+ }
+ }
+ }
+ },
+ "paths": {
+ "/auth/register": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Register" } } } },
+ "responses": { "200": { "description": "Registered" } },
+ "security": []
+ }
+ },
+ "/auth/register/invite/{token}": {
+ "post": {
+ "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } } ],
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterWithInvite" } } } },
+ "responses": { "200": { "description": "Registered via invite" } },
+ "security": []
+ }
+ },
+ "/auth/login": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Login" } } } },
+ "responses": { "200": { "description": "Logged in" } },
+ "security": []
+ }
+ },
+ "/auth/logout": { "post": { "responses": { "200": { "description": "Logged out" } } } },
+ "/auth/me": { "get": { "responses": { "200": { "description": "Current user" } } } },
+ "/auth/cleanup": { "post": { "responses": { "200": { "description": "Cleanup executed" } } } },
+ "/auth/cleanup-monitors": { "post": { "responses": { "200": { "description": "Monitor cleanup executed" } } } },
+
+ "/monitors": {
+ "get": {
+ "parameters": [
+ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 0 } },
+ { "name": "rowsPerPage", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } },
+ { "name": "sortField", "in": "query", "schema": { "type": "string" } },
+ { "name": "sortOrder", "in": "query", "schema": { "type": "string", "enum": ["asc", "desc"] } },
+ { "name": "type", "in": "query", "schema": { "type": "array", "items": { "type": "string" } }, "style": "form", "explode": true },
+ { "name": "status", "in": "query", "schema": { "type": "array", "items": { "type": "string" } }, "style": "form", "explode": true }
+ ],
+ "responses": { "200": { "description": "List monitors" } }
+ },
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MonitorCreate" } } } },
+ "responses": { "200": { "description": "Created monitor" } }
+ }
+ },
+ "/monitors/export": { "get": { "responses": { "200": { "description": "Export monitors (JSON)" } } } },
+ "/monitors/import": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MonitorImport" } } } },
+ "responses": { "200": { "description": "Import summary" } }
+ }
+ },
+ "/monitors/{id}": {
+ "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Monitor" } } },
+ "patch": {
+ "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ],
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MonitorPatch" } } } },
+ "responses": { "200": { "description": "Updated" } }
+ },
+ "delete": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+ "/monitors/{id}/checks": {
+ "get": {
+ "parameters": [
+ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
+ { "name": "page", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 0 } },
+ { "name": "rowsPerPage", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100 } }
+ ],
+ "responses": { "200": { "description": "Monitor checks" } }
+ }
+ },
+ "/monitors/{id}/notifications/test": {
+ "post": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Test sent" } } }
+ },
+ "/monitors/{id}/active": {
+ "patch": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Toggled" } } }
+ },
+
+ "/notification-channels": {
+ "get": { "responses": { "200": { "description": "List channels" } } },
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationChannel" } } } },
+ "responses": { "200": { "description": "Created" } }
+ }
+ },
+ "/notification-channels/{id}": {
+ "get": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Channel" } } },
+ "patch": {
+ "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ],
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationChannel" } } } },
+ "responses": { "200": { "description": "Updated" } }
+ },
+ "delete": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+ "/notification-channels/{id}/active": {
+ "patch": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Toggled" } } }
+ },
+ "/notification-channels/test": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NotificationChannel" } } } },
+ "responses": { "200": { "description": "Test queued" } }
+ }
+ },
+
+ "/status-pages": {
+ "get": { "responses": { "200": { "description": "List" } } },
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusPage" } } } },
+ "responses": { "200": { "description": "Created" } }
+ }
+ },
+ "/status-pages/{id}": {
+ "get": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Status page" } } },
+ "patch": {
+ "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ],
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusPage" } } } },
+ "responses": { "200": { "description": "Updated" } }
+ },
+ "delete": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+ "/status-pages/public/{url}": {
+ "get": {
+ "parameters": [ { "in": "path", "name": "url", "required": true, "schema": { "type": "string" } } ],
+ "responses": { "200": { "description": "Public status page" } },
+ "security": []
+ }
+ },
+
+ "/checks": {
+ "get": {
+ "parameters": [
+ { "name": "status", "in": "query", "required": true, "schema": { "type": "string" } },
+ { "name": "monitorId", "in": "query", "schema": { "type": "string" } },
+ { "name": "page", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 0 } },
+ { "name": "rowsPerPage", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 100 } },
+ { "name": "range", "in": "query", "required": true, "schema": { "type": "string" } }
+ ],
+ "responses": { "200": { "description": "Checks list" } }
+ }
+ },
+
+ "/incidents": {
+ "get": {
+ "parameters": [
+ { "name": "resolutionType", "in": "query", "schema": { "type": "string" } },
+ { "name": "resolved", "in": "query", "schema": { "type": "boolean" } },
+ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 0 } },
+ { "name": "rowsPerPage", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } }
+ ],
+ "responses": { "200": { "description": "Incidents list" } }
+ }
+ },
+ "/incidents/{id}/resolve": {
+ "patch": {
+ "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ],
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
+ "responses": { "200": { "description": "Resolved" } }
+ }
+ },
+
+ "/invite": {
+ "get": { "responses": { "200": { "description": "Invites list" } } },
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "teamId", "teamRoleId"], "properties": { "email": { "type": "string", "format": "email" }, "teamId": { "type": "string" }, "teamRoleId": { "type": "string" }, "orgRoleId": { "type": "string" } } } } } },
+ "responses": { "200": { "description": "Invite created" } }
+ }
+ },
+ "/invite/{id}": {
+ "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Invite" } } },
+ "delete": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+
+ "/maintenance": {
+ "get": { "responses": { "200": { "description": "List windows" } } },
+ "post": { "responses": { "200": { "description": "Created" } } }
+ },
+ "/maintenance/{id}": {
+ "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Window" } } },
+ "patch": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Updated" } } },
+ "delete": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+ "/maintenance/{id}/active": { "patch": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Toggled" } } } },
+
+ "/profile": {
+ "get": { "responses": { "200": { "description": "Profile" } } },
+ "patch": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfilePatch" } } } },
+ "responses": { "200": { "description": "Updated" } }
+ }
+ },
+
+ "/queue/jobs": { "get": { "responses": { "200": { "description": "Jobs" } } } },
+ "/queue/metrics": { "get": { "responses": { "200": { "description": "Metrics" } } } },
+ "/queue/flush": { "post": { "responses": { "200": { "description": "Flushed" } } } },
+
+ "/recovery": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RecoveryRequest" } } } },
+ "responses": { "200": { "description": "Recovery email sent" } },
+ "security": []
+ }
+ },
+ "/recovery/reset": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResetPassword" } } } },
+ "responses": { "200": { "description": "Password reset" } },
+ "security": []
+ }
+ },
+
+ "/roles": { "get": { "responses": { "200": { "description": "Roles" } } } },
+ "/roles/team/{id}": { "get": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Team roles" } } } },
+ "/roles/{id}": { "get": { "parameters": [ { "in": "path", "name": "id", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Role" } } } },
+
+ "/settings": { "get": { "responses": { "200": { "description": "System settings" } } } },
+ "/settings/email": {
+ "patch": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SystemSettings" } } } },
+ "responses": { "200": { "description": "Updated" } }
+ }
+ },
+ "/settings/test-transport": {
+ "post": {
+ "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SystemSettings" } } } },
+ "responses": { "200": { "description": "Transport test result" } }
+ }
+ },
+
+ "/teams": {
+ "get": { "responses": { "200": { "description": "Org teams" } } },
+ "post": { "responses": { "200": { "description": "Created" } } }
+ },
+ "/teams/joined": { "get": { "responses": { "200": { "description": "Joined teams" } } } },
+ "/teams/{id}": {
+ "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Team" } } },
+ "patch": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Updated" } } },
+ "delete": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ },
+
+ "/team-members": {
+ "get": { "responses": { "200": { "description": "Team members" } } },
+ "post": { "responses": { "200": { "description": "Created" } } }
+ },
+ "/team-members/{id}": {
+ "get": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Member" } } },
+ "patch": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Updated" } } },
+ "delete": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Deleted" } } }
+ }
+ }
+}
diff --git a/server/package-lock.json b/server/package-lock.json
index ac6a22648..dd06fd89d 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -21,6 +21,7 @@
"nodemailer": "7.0.9",
"ping": "1.0.0",
"super-simple-scheduler": "1.4.6",
+ "swagger-ui-express": "5.0.1",
"winston": "3.18.3",
"zod": "4.1.12"
},
@@ -3047,6 +3048,13 @@
"node": ">= 8"
}
},
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
@@ -5359,6 +5367,7 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
@@ -8540,9 +8549,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10299,6 +10308,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/swagger-ui-dist": {
+ "version": "5.30.2",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.2.tgz",
+ "integrity": "sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@scarf/scarf": "=1.4.0"
+ }
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
+ "license": "MIT",
+ "dependencies": {
+ "swagger-ui-dist": ">=5.0.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0 || >=5.0.0-beta"
+ }
+ },
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
diff --git a/server/package.json b/server/package.json
index 4a7d9c0e9..d42801b9d 100644
--- a/server/package.json
+++ b/server/package.json
@@ -29,6 +29,7 @@
"typescript": "5.9.3"
},
"dependencies": {
+ "swagger-ui-express": "5.0.1",
"bcryptjs": "3.0.2",
"cacheable-lookup": "7.0.0",
"cookie-parser": "1.4.7",
diff --git a/server/src/index.ts b/server/src/index.ts
index 8ce4efe24..2f9124d0e 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -9,6 +9,9 @@ import { config } from "@/config/index.js";
import { getChildLogger } from "@/logger/Logger.js";
import ApiError from "./utils/ApiError.js";
const indexLogger = getChildLogger("index");
+import swaggerUi from "swagger-ui-express";
+import fs from "node:fs";
+import path from "node:path";
const createApp = async () => {
await connectDatabase();
@@ -26,6 +29,16 @@ const createApp = async () => {
})
);
const routes = initRoutes(controllers, app);
+ // Swagger UI at /api-docs
+ try {
+ // Load OpenAPI doc from project root within server folder
+ const openapiPath = path.resolve(process.cwd(), "openapi.json");
+ const openapiJson = JSON.parse(fs.readFileSync(openapiPath, "utf-8"));
+ app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openapiJson));
+ indexLogger.info("Swagger UI available at /api-docs");
+ } catch (e) {
+ indexLogger.error("Failed to load OpenAPI document for Swagger UI", e);
+ }
app.use("/api/v1/health", verifyToken, addUserContext, (req, res) => {
res.json({
status: "OK",
diff --git a/server/src/routes/invite.ts b/server/src/routes/invite.ts
index b32dd8abf..18188fc11 100644
--- a/server/src/routes/invite.ts
+++ b/server/src/routes/invite.ts
@@ -33,7 +33,7 @@ class InviteRoutes {
this.controller.getAll
);
- this.router.get("/:id", verifyToken, this.controller.get);
+ this.router.get("/:id", this.controller.get);
this.router.delete(
"/:id",