EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
integrations.js
Go to the documentation of this file.
1/**
2 * @file integrations.js
3 * @module IntegrationsRoutes
4 * @description API routes for managing third-party integrations for tenants. Handles CRUD, enable/disable, and configuration of integrations. All routes require authentication and tenant context.
5 * @see {@link ../services/db} for database connection
6 * @see {@link ../middleware/auth} for authentication middleware
7 * @see {@link ../middleware/tenant} for tenant context utilities
8 * @apiDefine IntegrationsGroup Integrations
9 * @apiGroup Integrations
10 * @apiHeader {string} Authorization Bearer token required.
11 * @apiHeader {string} X-Tenant-ID Tenant context header required.
12 * @apiError (Error 401) Unauthorized Missing or invalid token.
13 * @apiError (Error 403) Forbidden Tenant context missing or invalid.
14 * @apiError (Error 500) ServerError Internal server error.
15 */
16
17const express = require('express');
18const router = express.Router();
19const pool = require('../services/db');
20const authenticateToken = require('../middleware/auth');
21const { getTenantFilter, setTenantContext } = require('../middleware/tenant');
22
23// Apply authentication and tenant context to all routes
24router.use(authenticateToken, setTenantContext);
25
26// GET /integrations - List all integrations for tenant
27router.get('/', async (req, res) => {
28 try {
29 const { clause: tenantClause, params: tenantParams } = getTenantFilter(req, 'i');
30
31 let query = `
32 SELECT i.integration_id, i.integration_type, i.config, i.is_active,
33 i.created_at, i.updated_at, i.tenant_id,
34 t.name as tenant_name,
35 p.name as plugin_name, p.description as plugin_description, p.logo_url, p.config_schema
36 FROM integrations i
37 LEFT JOIN tenants t ON i.tenant_id = t.tenant_id
38 LEFT JOIN plugins p ON i.integration_type = p.type
39 WHERE 1=1
40 `;
41
42 let queryParams = [];
43
44 if (tenantClause) {
45 query += ` AND ${tenantClause}`;
46 queryParams.push(...tenantParams);
47 }
48
49 query += ` ORDER BY i.integration_type ASC`;
50
51 const result = await pool.query(query, queryParams);
52 res.json(result.rows);
53 } catch (err) {
54 console.error('Error fetching integrations:', err);
55 res.status(500).json({ error: 'Server error' });
56 }
57});
58
59/**
60 * @api {get} /integrations List all integrations for tenant
61 * @apiName GetIntegrations
62 * @apiGroup Integrations
63 * @apiDescription Retrieve all integrations (with plugin metadata) for the current tenant.
64 * @apiSuccess {object[]} integrations List of integrations.
65 * @apiExample {curl} Example usage:
66 * curl -H "Authorization: Bearer <token>" -H "X-Tenant-ID: <id>" https://api.example.com/integrations
67 */
68
69// ==========================================
70// GENERIC ROUTES
71// ==========================================
72// Note: RustDesk and Guacamole specific routes removed - MeshCentral-only architecture
73
74// GET /integrations/:id - Get single integration
75router.get('/:id', async (req, res) => {
76 const { id } = req.params;
77 try {
78 const { clause: tenantClause, params: tenantParams, nextParamIndex } = getTenantFilter(req, 'i');
79
80 let query = `
81 SELECT i.integration_id, i.integration_type, i.config, i.is_active,
82 i.created_at, i.updated_at, i.tenant_id,
83 t.name as tenant_name
84 FROM integrations i
85 LEFT JOIN tenants t ON i.tenant_id = t.tenant_id
86 WHERE i.integration_id = $1
87 `;
88
89 let queryParams = [id];
90
91 if (tenantClause) {
92 query += ` AND ${tenantClause}`;
93 queryParams.push(...tenantParams);
94 }
95
96 const result = await pool.query(query, queryParams);
97
98 if (result.rows.length === 0) {
99 return res.status(404).json({ error: 'Integration not found' });
100 }
101
102 res.json(result.rows[0]);
103 } catch (err) {
104 console.error('Error fetching integration:', err);
105 res.status(500).json({ error: 'Server error' });
106 }
107});
108
109/**
110 * @api {get} /integrations/:id Get single integration
111 * @apiName GetIntegration
112 * @apiGroup Integrations
113 * @apiDescription Retrieve a single integration by ID for the current tenant.
114 * @apiParam {string} id Integration ID.
115 * @apiSuccess {object} integration Integration object.
116 * @apiError (Error 404) NotFound Integration not found.
117 * @apiExample {curl} Example usage:
118 * curl -H "Authorization: Bearer <token>" -H "X-Tenant-ID: <id>" https://api.example.com/integrations/123
119 */
120
121// POST /integrations - Create new integration
122router.post('/', async (req, res) => {
123 const { integration_type, config, is_active = true } = req.body;
124
125 if (!integration_type) {
126 return res.status(400).json({ error: 'integration_type is required' });
127 }
128
129 try {
130 const result = await pool.query(
131 `INSERT INTO integrations (integration_type, config, is_active, tenant_id, created_at, updated_at)
132 VALUES ($1, $2, $3, $4, NOW(), NOW())
133 RETURNING *`,
134 [integration_type, JSON.stringify(config || {}), is_active, req.tenant.id]
135 );
136
137 res.status(201).json(result.rows[0]);
138 } catch (err) {
139 console.error('Error creating integration:', err);
140
141 // Check for unique constraint violation (duplicate integration type per tenant)
142 if (err.code === '23505') {
143 return res.status(400).json({ error: 'This integration already exists for your tenant' });
144 }
145
146 res.status(500).json({ error: 'Server error' });
147 }
148});
149
150/**
151 * @api {post} /integrations Create new integration
152 * @apiName CreateIntegration
153 * @apiGroup Integrations
154 * @apiDescription Create a new integration for the current tenant.
155 * @apiParam {string} integration_type Type of integration (e.g., rustdesk, guacamole).
156 * @apiParam {object} [config] Integration configuration object.
157 * @apiParam {boolean} [is_active=true] Whether the integration is active.
158 * @apiSuccess {object} integration Created integration object.
159 * @apiError (Error 400) DuplicateIntegration This integration already exists for your tenant.
160 * @apiExample {curl} Example usage:
161 * curl -X POST -H "Authorization: Bearer <token>" -H "X-Tenant-ID: <id>" -d '{"integration_type":"custom","config":{}}' https://api.example.com/integrations
162 */
163
164// PUT /integrations/:id - Update integration
165router.put('/:id', async (req, res) => {
166 const { id } = req.params;
167 const { integration_type, config, is_active } = req.body;
168
169 try {
170 // First check if integration exists and belongs to tenant
171 const checkQuery = `SELECT * FROM integrations WHERE integration_id = $1 AND tenant_id = $2`;
172 const checkResult = await pool.query(checkQuery, [id, req.tenant.id]);
173
174 if (checkResult.rows.length === 0) {
175 return res.status(404).json({ error: 'Integration not found' });
176 }
177
178 // Build update query dynamically based on provided fields
179 const updates = [];
180 const values = [];
181 let paramIndex = 1;
182
183 if (integration_type !== undefined) {
184 updates.push(`integration_type = $${paramIndex++}`);
185 values.push(integration_type);
186 }
187
188 if (config !== undefined) {
189 updates.push(`config = $${paramIndex++}`);
190 values.push(JSON.stringify(config));
191 }
192
193 if (is_active !== undefined) {
194 updates.push(`is_active = $${paramIndex++}`);
195 values.push(is_active);
196 }
197
198 updates.push(`updated_at = NOW()`);
199
200 if (updates.length === 1) { // Only updated_at
201 return res.status(400).json({ error: 'No fields to update' });
202 }
203
204 values.push(id, req.tenant.id);
205
206 const query = `
207 UPDATE integrations
208 SET ${updates.join(', ')}
209 WHERE integration_id = $${paramIndex++} AND tenant_id = $${paramIndex++}
210 RETURNING *
211 `;
212
213 const result = await pool.query(query, values);
214 res.json(result.rows[0]);
215 } catch (err) {
216 console.error('Error updating integration:', err);
217 res.status(500).json({ error: 'Server error' });
218 }
219});
220
221/**
222 * @api {put} /integrations/:id Update integration
223 * @apiName UpdateIntegration
224 * @apiGroup Integrations
225 * @apiDescription Update an existing integration for the current tenant. Only provided fields will be updated.
226 * @apiParam {string} id Integration ID.
227 * @apiParam {string} [integration_type] Type of integration.
228 * @apiParam {object} [config] Integration configuration object.
229 * @apiParam {boolean} [is_active] Whether the integration is active.
230 * @apiSuccess {object} integration Updated integration object.
231 * @apiError (Error 404) NotFound Integration not found.
232 * @apiError (Error 400) NoFields No fields to update.
233 * @apiExample {curl} Example usage:
234 * curl -X PUT -H "Authorization: Bearer <token>" -H "X-Tenant-ID: <id>" -d '{"is_active":false}' https://api.example.com/integrations/123
235 */
236
237// DELETE /integrations/:id - Delete integration
238router.delete('/:id', async (req, res) => {
239 const { id } = req.params;
240
241 try {
242 const result = await pool.query(
243 `DELETE FROM integrations WHERE integration_id = $1 AND tenant_id = $2 RETURNING *`,
244 [id, req.tenant.id]
245 );
246
247 if (result.rows.length === 0) {
248 return res.status(404).json({ error: 'Integration not found' });
249 }
250
251 res.json({ message: 'Integration deleted successfully' });
252 } catch (err) {
253 console.error('Error deleting integration:', err);
254 res.status(500).json({ error: 'Server error' });
255 }
256});
257
258module.exports = router;