- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained) - Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked) - Commit services/docuseal + services/legacy-db docker-compose configs - Extract client app composables: useOTP, useAddressSearch, catalog data, format utils - Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines - Clean hardcoded credentials from config.js fallback values - Add client portal: catalog, cart, checkout, OTP verification, address search - Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal - Add ops composables: useBestTech, useConversations, usePermissions, useScanner - Add field app: scanner composable, docker/nginx configs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
4.7 KiB
JavaScript
102 lines
4.7 KiB
JavaScript
'use strict'
|
|
const cfg = require('./config')
|
|
const { log, json, parseBody } = require('./helpers')
|
|
|
|
let routrPool = null, identityPool = null
|
|
|
|
function getPool (connStr, label) {
|
|
const { Pool } = require('pg')
|
|
const pool = new Pool({ connectionString: connStr, max: 3 })
|
|
pool.on('error', e => log(`${label} DB pool error:`, e.message))
|
|
return pool
|
|
}
|
|
|
|
function getRoutrPool () { return routrPool || (routrPool = getPool(cfg.ROUTR_DB_URL, 'Routr')) }
|
|
function getIdentityPool () { return identityPool || (identityPool = getPool(cfg.FNIDENTITY_DB_URL, 'Identity')) }
|
|
|
|
const ROUTR_TABLES = { trunks: 'trunks', agents: 'agents', credentials: 'credentials', numbers: 'numbers', domains: 'domains', acls: 'access_control_lists', peers: 'peers' }
|
|
const IDENTITY_TABLES = { workspaces: 'workspaces', users: 'users' }
|
|
|
|
async function handle (req, res, method, path, url) {
|
|
try {
|
|
const parts = path.replace('/telephony/', '').split('/').filter(Boolean)
|
|
const resource = parts[0], ref = parts[1] || null
|
|
|
|
if (resource === 'overview' && method === 'GET') {
|
|
const rPool = getRoutrPool(), iPool = getIdentityPool()
|
|
const [trunks, agents, creds, numbers, domains, peers, workspaces] = await Promise.all([
|
|
rPool.query('SELECT COUNT(*) FROM trunks'),
|
|
rPool.query('SELECT COUNT(*) FROM agents'),
|
|
rPool.query('SELECT COUNT(*) FROM credentials'),
|
|
rPool.query('SELECT COUNT(*) FROM numbers'),
|
|
rPool.query('SELECT COUNT(*) FROM domains'),
|
|
rPool.query('SELECT COUNT(*) FROM peers'),
|
|
iPool.query('SELECT COUNT(*) FROM workspaces'),
|
|
])
|
|
return json(res, 200, {
|
|
trunks: +trunks.rows[0].count, agents: +agents.rows[0].count,
|
|
credentials: +creds.rows[0].count, numbers: +numbers.rows[0].count,
|
|
domains: +domains.rows[0].count, peers: +peers.rows[0].count,
|
|
workspaces: +workspaces.rows[0].count,
|
|
})
|
|
}
|
|
|
|
const isIdentity = !!IDENTITY_TABLES[resource]
|
|
const tableName = IDENTITY_TABLES[resource] || ROUTR_TABLES[resource]
|
|
if (!tableName) {
|
|
return json(res, 404, { error: `Unknown resource: ${resource}. Available: overview, ${[...Object.keys(ROUTR_TABLES), ...Object.keys(IDENTITY_TABLES)].join(', ')}` })
|
|
}
|
|
const pool = isIdentity ? getIdentityPool() : getRoutrPool()
|
|
|
|
if (method === 'GET' && !ref) {
|
|
const limit = parseInt(url.searchParams.get('limit') || '100', 10)
|
|
const offset = parseInt(url.searchParams.get('offset') || '0', 10)
|
|
const result = await pool.query(`SELECT * FROM ${tableName} ORDER BY created_at DESC LIMIT $1 OFFSET $2`, [limit, offset])
|
|
const count = await pool.query(`SELECT COUNT(*) FROM ${tableName}`)
|
|
return json(res, 200, { items: result.rows, total: +count.rows[0].count })
|
|
}
|
|
|
|
if (method === 'GET' && ref) {
|
|
const result = await pool.query(`SELECT * FROM ${tableName} WHERE ref = $1`, [ref])
|
|
return result.rows.length ? json(res, 200, result.rows[0]) : json(res, 404, { error: 'Not found' })
|
|
}
|
|
|
|
if (method === 'POST' && !ref) {
|
|
const body = await parseBody(req)
|
|
if (!body.ref) body.ref = require('crypto').randomUUID()
|
|
if (!body.api_version) body.api_version = 'v2'
|
|
if (!body.created_at) body.created_at = new Date()
|
|
if (!body.updated_at) body.updated_at = new Date()
|
|
const keys = Object.keys(body), vals = Object.values(body)
|
|
const result = await pool.query(`INSERT INTO ${tableName} (${keys.join(', ')}) VALUES (${keys.map((_, i) => `$${i + 1}`).join(', ')}) RETURNING *`, vals)
|
|
log(`Telephony: created ${resource}/${result.rows[0].ref}`)
|
|
return json(res, 201, result.rows[0])
|
|
}
|
|
|
|
if (method === 'PUT' && ref) {
|
|
const body = await parseBody(req)
|
|
body.updated_at = new Date(); delete body.ref; delete body.created_at
|
|
const keys = Object.keys(body), vals = Object.values(body)
|
|
vals.push(ref)
|
|
const result = await pool.query(`UPDATE ${tableName} SET ${keys.map((k, i) => `${k} = $${i + 1}`).join(', ')} WHERE ref = $${vals.length} RETURNING *`, vals)
|
|
if (!result.rows.length) return json(res, 404, { error: 'Not found' })
|
|
log(`Telephony: updated ${resource}/${ref}`)
|
|
return json(res, 200, result.rows[0])
|
|
}
|
|
|
|
if (method === 'DELETE' && ref) {
|
|
const result = await pool.query(`DELETE FROM ${tableName} WHERE ref = $1 RETURNING ref`, [ref])
|
|
if (!result.rows.length) return json(res, 404, { error: 'Not found' })
|
|
log(`Telephony: deleted ${resource}/${ref}`)
|
|
return json(res, 200, { ok: true, deleted: ref })
|
|
}
|
|
|
|
return json(res, 405, { error: 'Method not allowed' })
|
|
} catch (e) {
|
|
log('Telephony error:', e.message)
|
|
return json(res, 500, { error: e.message })
|
|
}
|
|
}
|
|
|
|
module.exports = { handle }
|