diff --git a/src/integrations/schema.js b/src/integrations/schema.js index dce8074..f7a5486 100644 --- a/src/integrations/schema.js +++ b/src/integrations/schema.js @@ -24,8 +24,8 @@ export const INTEGRATIONS = { }, sambapos: { name: 'SambaPOS', - configFields: ['host', 'port', 'database', 'graphql_endpoint', 'username'], - secretFields: ['password'], + configFields: ['sql_host', 'sql_port', 'sql_database', 'sql_username', 'graphql_endpoint', 'graphql_username', 'graphql_client_id'], + secretFields: ['sql_password', 'graphql_password'], }, workforce: { name: 'Workforce', diff --git a/src/routes/integrations.js b/src/routes/integrations.js index 7ac6b75..e9e5605 100644 --- a/src/routes/integrations.js +++ b/src/routes/integrations.js @@ -139,6 +139,51 @@ export async function integrationRoutes(app) { if (res.status === 401 || res.status === 403) throw new Error('Invalid Azure API key') if (!res.ok) throw new Error(`Azure returned ${res.status}`) } + else if (req.params.slug === 'sambapos') { + const errors = [] + // SQL test + try { + if (!creds.sql_host || !creds.sql_database || !creds.sql_username || !creds.sql_password) + throw new Error('SQL host, database, username and password are required') + const { default: pg } = await import('pg') + const sqlPool = new pg.Pool({ + host: creds.sql_host, port: parseInt(creds.sql_port || '5432'), + database: creds.sql_database, user: creds.sql_username, password: creds.sql_password, + connectionTimeoutMillis: 8000, max: 1, + }) + await sqlPool.query('SELECT 1') + await sqlPool.end() + } catch (e) { errors.push(`SQL: ${e.message}`) } + // GraphQL test — obtain token via password grant then run a minimal query + try { + if (!creds.graphql_endpoint || !creds.graphql_username || !creds.graphql_password || !creds.graphql_client_id) + throw new Error('GraphQL endpoint, username, password and client ID are required') + const base = creds.graphql_endpoint.replace(/\/$/, '') + const tokenRes = await fetch(`${base}/connect/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'password', + username: creds.graphql_username, + password: creds.graphql_password, + client_id: creds.graphql_client_id, + scope: 'api', + }), + signal: AbortSignal.timeout(8000), + }) + if (!tokenRes.ok) throw new Error(`Token request failed (${tokenRes.status})`) + const { access_token } = await tokenRes.json() + if (!access_token) throw new Error('No access token returned') + const gqlRes = await fetch(`${base}/api`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${access_token}` }, + body: JSON.stringify({ query: '{ __typename }' }), + signal: AbortSignal.timeout(8000), + }) + if (!gqlRes.ok) throw new Error(`GraphQL request failed (${gqlRes.status})`) + } catch (e) { errors.push(`GraphQL: ${e.message}`) } + if (errors.length) throw new Error(errors.join(' | ')) + } else return reply.status(400).send({ error: `No test available for ${req.params.slug}` }) return { ok: true } } catch (e) {