Release 2.16.1
This commit is contained in:
1 parent
3514d514a3
commit
8ce8228091
25 files changed
+1307
-613
No files matched your search
@@ -1,385 +1,151 @@
|
||||
// API source route registration.
|
||||
|
||||
const { buildPagination } = require('#src/web/lib/pagination');
|
||||
const renderApiSourcesPage = require('#src/web/routes/data-sources/api-sources/list');
|
||||
const renderApiSourceAddPage = require('#src/web/routes/data-sources/api-sources/add');
|
||||
const renderApiSourceEditPage = require('#src/web/routes/data-sources/api-sources/edit');
|
||||
const { buildDuplicateApiSourceName, buildDuplicateApiSource } = require('#src/web/routes/data-sources/api-sources/duplicate');
|
||||
const { fetchAppSettings } = require('#src/data/app-settings');
|
||||
|
||||
function toIsoTimestamp(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
||||
}
|
||||
|
||||
async function getDataSourceUsageMaps(pool, common) {
|
||||
const [slides] = await pool.query('SELECT content_json FROM c_slides WHERE content_json IS NOT NULL');
|
||||
const apiSourceIds = new Set();
|
||||
|
||||
for (let index = 0; index < slides.length; index += 1) {
|
||||
const content = typeof common.parseJsonSafe === 'function' ? common.parseJsonSafe(slides[index].content_json) : null;
|
||||
if (!content || typeof content !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
(function walk(value) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(walk);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(value, 'source_id')) {
|
||||
const sourceId = Number(value.source_id);
|
||||
if (Number.isFinite(sourceId)) {
|
||||
apiSourceIds.add(sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(value).forEach(function (key) {
|
||||
walk(value[key]);
|
||||
});
|
||||
})(content);
|
||||
}
|
||||
|
||||
return apiSourceIds;
|
||||
}
|
||||
|
||||
async function isApiSourceInUse(pool, common, apiSourceId) {
|
||||
const usageIds = await getDataSourceUsageMaps(pool, common);
|
||||
return usageIds.has(Number(apiSourceId));
|
||||
}
|
||||
|
||||
async function sendRefreshTaskState(req, res, dataSourceTasks, sourceId) {
|
||||
const taskId = Number(req.query.refresh_task_id);
|
||||
if (!Number.isFinite(taskId) || taskId <= 0) {
|
||||
return res.status(400).json({ error: 'Missing refresh task id.' });
|
||||
}
|
||||
|
||||
const task = await dataSourceTasks.getTaskStatusById(taskId);
|
||||
const expectedKey = 'api-source-refresh:' + Number(sourceId);
|
||||
if (!task || task.key !== expectedKey) {
|
||||
return res.status(404).json({ error: 'Refresh task not found.' });
|
||||
}
|
||||
|
||||
res.json(task);
|
||||
}
|
||||
const { buildApiSourcePayload, testApiSourceConnection, testApiSourceAuthentication } = require('#src/data/api-sources');
|
||||
const { buildApiGroupPayload, fetchApiGroupById, fetchApiGroupsPage, getApiRequestUsage, saveApiGroup } = require('#src/data/api-source-groups');
|
||||
const { hasPermission } = require('#src/rbac');
|
||||
|
||||
module.exports = function registerApiSourceRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
const common = deps.common;
|
||||
const pages = deps.pages;
|
||||
const backgroundTaskQueue = deps.backgroundTaskQueue;
|
||||
const dataSourceTasks = deps.dataSourceTasks;
|
||||
const getAuditUserId = deps.getAuditUserId;
|
||||
const redirectAfterSave = deps.redirectAfterSave;
|
||||
const formatDashboardDate = deps.formatDashboardDate || function (value) {
|
||||
return value ? String(value) : '';
|
||||
};
|
||||
const requirePermission = deps.requirePermission;
|
||||
const { pool, common, backgroundTaskQueue, dataSourceTasks, getAuditUserId, redirectAfterSave, requirePermission } = deps;
|
||||
const formatDate = deps.formatDashboardDate || String;
|
||||
const base = '/data-sources/api-sources';
|
||||
|
||||
const LIST_PAGE_SIZE = 25;
|
||||
function syncSchedules(group, removed, actorId) {
|
||||
(removed || []).forEach(function (id) { dataSourceTasks.removeRecurringRefresh('api-source', id); });
|
||||
group.requests.forEach(function (request) {
|
||||
if (!request.enabled) return dataSourceTasks.removeRecurringRefresh('api-source', request.id);
|
||||
dataSourceTasks.registerRecurringRefresh('api-source', request.id, group.name + ' / ' + request.name, request.update_interval_value || request.updateIntervalValue, request.update_interval_unit || request.updateIntervalUnit, function () {
|
||||
return dataSourceTasks.refreshApiSourceInBackground(request.id, actorId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/data-sources/api-sources', requirePermission('api-sources.read'), async function (req, res, next) {
|
||||
async function enqueue(request, group, actorId) {
|
||||
return backgroundTaskQueue.enqueueTask({
|
||||
key: 'api-source-refresh:' + request.id, title: 'API request refresh', category: 'data-source', taskType: 'data-source-refresh',
|
||||
payload: { sourceType: 'api-source', sourceId: request.id, sourceName: group.name + ' / ' + request.name, actorId: actorId },
|
||||
metadata: { sourceType: 'api-source', sourceId: request.id, sourceName: group.name + ' / ' + request.name }
|
||||
});
|
||||
}
|
||||
|
||||
async function testConnection(req, res) {
|
||||
res.set('Cache-Control', 'no-store');
|
||||
try {
|
||||
const body = Object.assign({}, req.body, { name: 'Connection test' });
|
||||
const authOnly = String(req.path || '').endsWith('/auth-test');
|
||||
if (authOnly) body.api_url = 'https://connection.test';
|
||||
const payload = buildApiSourcePayload({ body: body });
|
||||
res.json(await (authOnly ? testApiSourceAuthentication(payload) : testApiSourceConnection(payload)));
|
||||
} catch (error) {
|
||||
res.status(error.statusCode === 400 ? 400 : 500).json({ ok: false, error: error.statusCode === 400 ? error.message : 'Unable to test API connection.', steps: [] });
|
||||
}
|
||||
}
|
||||
app.post(base + '/test', requirePermission('api-sources.create'), testConnection);
|
||||
app.post(base + '/auth-test', requirePermission('api-sources.create'), testConnection);
|
||||
app.post(base + '/:id/test', requirePermission('api-sources.update'), testConnection);
|
||||
app.post(base + '/:id/auth-test', requirePermission('api-sources.update'), testConnection);
|
||||
|
||||
app.get(base, requirePermission('api-sources.read'), async function (req, res, next) {
|
||||
try {
|
||||
const page = Math.max(1, Math.floor(Number(req.query.page) || 1));
|
||||
const search = common.getSearchQuery(req);
|
||||
const sort = common.getSortQuery(req);
|
||||
const direction = common.getSortDirectionQuery(req);
|
||||
const data = await common.fetchApiSourcesPage(pool, page, LIST_PAGE_SIZE, search, sort, direction);
|
||||
const usageIds = await getDataSourceUsageMaps(pool, common);
|
||||
const apiSources = (data.apiSources || []).map(function (apiSource) {
|
||||
return Object.assign({}, apiSource, {
|
||||
authMethod: apiSource.auth_method || 'none',
|
||||
authUsername: apiSource.auth_username || '',
|
||||
authPassword: apiSource.auth_password || '',
|
||||
authBearerToken: apiSource.auth_bearer_token || '',
|
||||
authHeaderName: apiSource.auth_header_name || 'X-API-Key',
|
||||
authHeaderValue: apiSource.auth_header_value || '',
|
||||
tokenUrl: apiSource.token_url || '',
|
||||
tokenResponsePath: apiSource.token_response_path || 'access_token',
|
||||
tokenRefreshUrl: apiSource.token_refresh_url || '',
|
||||
tokenRefreshRequestBodyJson: apiSource.token_refresh_request_body_json || '',
|
||||
tokenRefreshResponsePath: apiSource.token_refresh_response_path || 'refresh_token',
|
||||
itemsPath: apiSource.items_path || '',
|
||||
intervalLabel: apiSource.update_interval_unit === 'seconds'
|
||||
? (Math.max(1, Number(apiSource.update_interval_value) || 0) === 1 ? 'Every second' : `Every ${Math.max(1, Number(apiSource.update_interval_value) || 0)} seconds`)
|
||||
: apiSource.update_interval_unit === 'hours'
|
||||
? (Math.max(1, Number(apiSource.update_interval_value) || 0) === 1 ? 'Every hour' : `Every ${Math.max(1, Number(apiSource.update_interval_value) || 0)} hours`)
|
||||
: (Math.max(1, Number(apiSource.update_interval_value) || 0) === 1 ? 'Every minute' : `Every ${Math.max(1, Number(apiSource.update_interval_value) || 0)} minutes`),
|
||||
lastPullLabel: apiSource.last_pulled_at ? formatDashboardDate(apiSource.last_pulled_at) : 'Never',
|
||||
lastPulledAtValue: apiSource.last_pulled_at ? new Date(apiSource.last_pulled_at).toISOString() : '',
|
||||
inUse: usageIds.has(Number(apiSource.id))
|
||||
});
|
||||
});
|
||||
|
||||
res.send(renderApiSourcesPage({
|
||||
apiSources: apiSources,
|
||||
pagination: buildPagination(data.totalItems, data.currentPage, 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'API sources', 'API source pages')
|
||||
}, req.query.message ? String(req.query.message) : '', req.currentUser, formatDashboardDate));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data-sources/api-sources/new', requirePermission('api-sources.create'), async function (req, res, next) {
|
||||
try {
|
||||
const settings = await fetchAppSettings(pool);
|
||||
res.send(renderApiSourceAddPage({
|
||||
updateIntervalValue: settings['data-sources.api_default_interval_value'],
|
||||
updateIntervalUnit: settings['data-sources.api_default_interval_unit']
|
||||
}, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data-sources/api-sources/:id/edit', requirePermission('api-sources.update'), async function (req, res, next) {
|
||||
try {
|
||||
const apiSource = await common.fetchApiSourceById(pool, Number(req.params.id));
|
||||
if (!apiSource) {
|
||||
return res.status(404).send('API source not found');
|
||||
}
|
||||
|
||||
const [slides] = await pool.query('SELECT content_json FROM c_slides WHERE content_json IS NOT NULL');
|
||||
const inUse = (slides || []).some(function (row) {
|
||||
const content = typeof common.parseJsonSafe === 'function' ? common.parseJsonSafe(row.content_json) : null;
|
||||
if (!content || typeof content !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stack = [content];
|
||||
while (stack.length) {
|
||||
const value = stack.pop();
|
||||
if (!value || typeof value !== 'object') {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(function (item) { stack.push(item); });
|
||||
continue;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(value, 'source_id') && Number(value.source_id) === Number(apiSource.id)) {
|
||||
return true;
|
||||
}
|
||||
Object.keys(value).forEach(function (key) {
|
||||
stack.push(value[key]);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
res.send(renderApiSourceEditPage(Object.assign({}, apiSource, {
|
||||
apiUrl: apiSource.api_url,
|
||||
authMethod: apiSource.auth_method || 'none',
|
||||
authUsername: apiSource.auth_username || '',
|
||||
authPassword: apiSource.auth_password || '',
|
||||
authBearerToken: apiSource.auth_bearer_token || '',
|
||||
authHeaderName: apiSource.auth_header_name || 'X-API-Key',
|
||||
authHeaderValue: apiSource.auth_header_value || '',
|
||||
tokenUrl: apiSource.token_url || '',
|
||||
tokenRequestBodyJson: apiSource.token_request_body_json || '',
|
||||
tokenResponsePath: apiSource.token_response_path || 'access_token',
|
||||
tokenRefreshUrl: apiSource.token_refresh_url || '',
|
||||
tokenRefreshRequestBodyJson: apiSource.token_refresh_request_body_json || '',
|
||||
tokenRefreshResponsePath: apiSource.token_refresh_response_path || 'refresh_token',
|
||||
tokenHeaderName: apiSource.token_header_name || 'Authorization',
|
||||
tokenHeaderPrefix: apiSource.token_header_prefix || 'Bearer',
|
||||
itemsPath: apiSource.items_path || '',
|
||||
updateIntervalValue: apiSource.update_interval_value,
|
||||
updateIntervalUnit: apiSource.update_interval_unit || 'minutes',
|
||||
lastPulledAtValue: toIsoTimestamp(apiSource.last_pulled_at),
|
||||
lastPulledAtLabel: apiSource.last_pulled_at ? String(apiSource.last_pulled_at) : '',
|
||||
lastPullError: apiSource.last_pull_error || '',
|
||||
lastResponseStatus: apiSource.last_response_status,
|
||||
lastResponseContentType: apiSource.last_response_content_type,
|
||||
lastResponseJson: apiSource.last_response_json || '',
|
||||
inUse: inUse
|
||||
}), {
|
||||
lastResponseJson: apiSource.last_response_json || '',
|
||||
lastPullError: apiSource.last_pull_error || ''
|
||||
}, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data-sources/api-sources/:id/duplicate', requirePermission('api-sources.read'), requirePermission('api-sources.create'), async function (req, res, next) {
|
||||
try {
|
||||
const apiSource = await common.fetchApiSourceById(pool, Number(req.params.id));
|
||||
if (!apiSource) {
|
||||
return res.status(404).send('API source not found');
|
||||
}
|
||||
|
||||
let duplicateName = buildDuplicateApiSourceName(apiSource.name);
|
||||
let duplicateIndex = 2;
|
||||
while (await common.fetchDuplicateName(pool, 'i_api_sources', duplicateName)) {
|
||||
duplicateName = buildDuplicateApiSourceName(apiSource.name) + ' (' + duplicateIndex + ')';
|
||||
duplicateIndex += 1;
|
||||
}
|
||||
|
||||
res.send(renderApiSourceAddPage(buildDuplicateApiSource(apiSource, duplicateName), req.query.message ? String(req.query.message) : 'Review the copied values and save when ready.', req.currentUser, {
|
||||
messageVariant: 'info',
|
||||
showSaveSecondaryActions: true
|
||||
}));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data-sources/api-sources/:id/state', requirePermission('api-sources.update'), function (req, res, next) {
|
||||
sendRefreshTaskState(req, res, dataSourceTasks, Number(req.params.id)).catch(next);
|
||||
});
|
||||
|
||||
app.post('/data-sources/api-sources', requirePermission('api-sources.create'), async function (req, res, next) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
const payload = common.buildApiSourcePayload(req, null);
|
||||
if (await common.fetchDuplicateName(pool, 'i_api_sources', payload.name)) {
|
||||
return res.redirect('/data-sources/api-sources/new?message=' + encodeURIComponent('An API source with that name already exists.'));
|
||||
}
|
||||
|
||||
const actorId = getAuditUserId(req);
|
||||
await connection.beginTransaction();
|
||||
const [result] = await connection.query(
|
||||
'INSERT INTO i_api_sources (name, api_url, request_method, request_body_json, auth_method, auth_username, auth_password, auth_bearer_token, auth_header_name, auth_header_value, token_url, token_request_body_json, token_response_path, token_refresh_url, token_refresh_request_body_json, token_refresh_response_path, token_header_name, token_header_prefix, items_path, update_interval_value, update_interval_unit, last_pulled_at, last_pull_error, last_response_status, last_response_content_type, last_response_json, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[payload.name, payload.apiUrl, payload.requestMethod, payload.requestBodyJson || null, payload.authMethod, payload.authUsername || null, payload.authPassword || null, payload.authBearerToken || null, payload.authHeaderName || null, payload.authHeaderValue || null, payload.tokenUrl || null, payload.tokenRequestBodyJson || null, payload.tokenResponsePath || null, payload.tokenRefreshUrl || null, payload.tokenRefreshRequestBodyJson || null, payload.tokenRefreshResponsePath || null, payload.tokenHeaderName || null, payload.tokenHeaderPrefix || null, payload.itemsPath || null, payload.updateIntervalValue, payload.updateIntervalUnit, null, null, null, null, null, actorId, actorId]
|
||||
);
|
||||
await connection.commit();
|
||||
dataSourceTasks.registerRecurringRefresh('api-source', result.insertId, payload.name, payload.updateIntervalValue, payload.updateIntervalUnit, function () {
|
||||
return dataSourceTasks.refreshApiSourceInBackground(result.insertId, actorId);
|
||||
});
|
||||
const refreshTask = await backgroundTaskQueue.enqueueTask({
|
||||
key: 'api-source-refresh:' + result.insertId,
|
||||
title: 'API source refresh',
|
||||
category: 'data-source',
|
||||
taskType: 'data-source-refresh',
|
||||
payload: {
|
||||
sourceType: 'api-source',
|
||||
sourceId: result.insertId,
|
||||
sourceName: payload.name,
|
||||
actorId: actorId
|
||||
},
|
||||
metadata: {
|
||||
sourceType: 'api-source',
|
||||
sourceId: result.insertId,
|
||||
sourceName: payload.name
|
||||
}
|
||||
});
|
||||
const message = 'API source created. Refresh is running in the background.';
|
||||
res.redirect('/data-sources/api-sources/' + result.insertId + '/edit?message=' + encodeURIComponent(message) + '&refresh_task_id=' + encodeURIComponent(refreshTask && refreshTask.id ? refreshTask.id : ''));
|
||||
} catch (error) {
|
||||
try {
|
||||
await connection.rollback();
|
||||
} catch (_rollbackError) {
|
||||
// Ignore rollback failures and surface the original error.
|
||||
}
|
||||
next(error);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/data-sources/api-sources/:id', requirePermission('api-sources.update'), async function (req, res, next) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
const apiSource = await common.fetchApiSourceById(pool, Number(req.params.id));
|
||||
if (!apiSource) {
|
||||
return res.status(404).send('API source not found');
|
||||
}
|
||||
|
||||
if (String(req.body && req.body.data_source_action || '').trim() === 'toggle') {
|
||||
const enabled = apiSource.enabled === 0 || apiSource.enabled === false ? 1 : 0;
|
||||
await connection.query('UPDATE i_api_sources SET enabled = ?, modified_by = ? WHERE id = ?', [enabled, getAuditUserId(req), apiSource.id]);
|
||||
if (enabled) {
|
||||
dataSourceTasks.registerRecurringRefresh('api-source', apiSource.id, apiSource.name, apiSource.update_interval_value, apiSource.update_interval_unit, function () {
|
||||
return dataSourceTasks.refreshApiSourceInBackground(apiSource.id, getAuditUserId(req));
|
||||
});
|
||||
} else {
|
||||
dataSourceTasks.removeRecurringRefresh('api-source', apiSource.id);
|
||||
}
|
||||
return res.redirect('/data-sources/api-sources/' + apiSource.id + '/edit?message=' + encodeURIComponent(enabled ? 'API source enabled.' : 'API source disabled.'));
|
||||
}
|
||||
|
||||
const payload = common.buildApiSourcePayload(req, apiSource);
|
||||
if (await common.fetchDuplicateName(pool, 'i_api_sources', payload.name, apiSource.id)) {
|
||||
return res.redirect('/data-sources/api-sources/' + apiSource.id + '/edit?message=' + encodeURIComponent('An API source with that name already exists.'));
|
||||
}
|
||||
const actorId = getAuditUserId(req);
|
||||
await connection.beginTransaction();
|
||||
await connection.query(
|
||||
'UPDATE i_api_sources SET name = ?, api_url = ?, request_method = ?, request_body_json = ?, auth_method = ?, auth_username = ?, auth_password = ?, auth_bearer_token = ?, auth_header_name = ?, auth_header_value = ?, token_url = ?, token_request_body_json = ?, token_response_path = ?, token_refresh_url = ?, token_refresh_request_body_json = ?, token_refresh_response_path = ?, token_header_name = ?, token_header_prefix = ?, items_path = ?, update_interval_value = ?, update_interval_unit = ?, modified_by = ? WHERE id = ?',
|
||||
[payload.name, payload.apiUrl, payload.requestMethod, payload.requestBodyJson || null, payload.authMethod, payload.authUsername || null, payload.authPassword || null, payload.authBearerToken || null, payload.authHeaderName || null, payload.authHeaderValue || null, payload.tokenUrl || null, payload.tokenRequestBodyJson || null, payload.tokenResponsePath || null, payload.tokenRefreshUrl || null, payload.tokenRefreshRequestBodyJson || null, payload.tokenRefreshResponsePath || null, payload.tokenHeaderName || null, payload.tokenHeaderPrefix || null, payload.itemsPath || null, payload.updateIntervalValue, payload.updateIntervalUnit, actorId, apiSource.id]
|
||||
);
|
||||
await connection.commit();
|
||||
if (apiSource.enabled === 0 || apiSource.enabled === false) {
|
||||
dataSourceTasks.removeRecurringRefresh('api-source', apiSource.id);
|
||||
} else {
|
||||
dataSourceTasks.registerRecurringRefresh('api-source', apiSource.id, payload.name, payload.updateIntervalValue, payload.updateIntervalUnit, function () {
|
||||
return dataSourceTasks.refreshApiSourceInBackground(apiSource.id, actorId);
|
||||
});
|
||||
}
|
||||
redirectAfterSave(req, res, '/data-sources/api-sources/' + apiSource.id + '/edit', {
|
||||
closeUrl: '/data-sources/api-sources',
|
||||
newUrl: '/data-sources/api-sources/new',
|
||||
message: 'API source updated.'
|
||||
});
|
||||
} catch (error) {
|
||||
try {
|
||||
await connection.rollback();
|
||||
} catch (_rollbackError) {
|
||||
// Ignore rollback failures and surface the original error.
|
||||
}
|
||||
next(error);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/data-sources/api-sources/:id/refresh', requirePermission('api-sources.allow'), async function (req, res, next) {
|
||||
try {
|
||||
const apiSource = await common.fetchApiSourceById(pool, Number(req.params.id));
|
||||
if (!apiSource) return res.status(404).send('API source not found');
|
||||
if (apiSource.enabled === 0 || apiSource.enabled === false) {
|
||||
return res.redirect('/data-sources/api-sources/' + apiSource.id + '/edit?message=' + encodeURIComponent('API source is disabled.'));
|
||||
}
|
||||
await backgroundTaskQueue.enqueueTask({ key: 'api-source-refresh:' + apiSource.id, title: 'API source refresh', category: 'data-source', taskType: 'data-source-refresh', payload: { sourceType: 'api-source', sourceId: apiSource.id, sourceName: apiSource.name, actorId: getAuditUserId(req) }, metadata: { sourceType: 'api-source', sourceId: apiSource.id, sourceName: apiSource.name } });
|
||||
res.redirect('/data-sources/api-sources/' + apiSource.id + '/edit?message=' + encodeURIComponent('API source refresh queued.'));
|
||||
const data = await fetchApiGroupsPage(pool, Math.max(1, Number(req.query.page) || 1), 25, search, sort, direction);
|
||||
res.send(renderApiSourcesPage({ apiSources: data.apiSources.map(function (group) {
|
||||
return Object.assign({}, group, { lastPullLabel: group.last_pulled_at ? formatDate(group.last_pulled_at) : 'Never', lastPulledAtValue: group.last_pulled_at ? new Date(group.last_pulled_at).toISOString() : '' });
|
||||
}), pagination: buildPagination(data.totalItems, data.currentPage, 'page', { search, sort, direction }, 25, 'API sources', 'API source pages') }, req.query.message || '', req.currentUser, formatDate));
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
app.post('/data-sources/api-sources/:id/delete', requirePermission('api-sources.delete'), async function (req, res, next) {
|
||||
app.get(base + '/new', requirePermission('api-sources.create'), async function (req, res, next) {
|
||||
try {
|
||||
const apiSource = await common.fetchApiSourceById(pool, Number(req.params.id));
|
||||
if (!apiSource) {
|
||||
return res.status(404).send('API source not found');
|
||||
}
|
||||
const settings = await fetchAppSettings(pool);
|
||||
res.send(renderApiSourceAddPage({ updateIntervalValue: settings['data-sources.api_default_interval_value'], updateIntervalUnit: settings['data-sources.api_default_interval_unit'] }, req.query.message || '', req.currentUser));
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
if (await isApiSourceInUse(pool, common, apiSource.id)) {
|
||||
return res.redirect('/data-sources/api-sources?message=' + encodeURIComponent('This API source is still used by one or more slides.'));
|
||||
}
|
||||
app.get(base + '/:id/edit', requirePermission('api-sources.update'), async function (req, res, next) {
|
||||
try {
|
||||
const group = await fetchApiGroupById(pool, Number(req.params.id));
|
||||
if (!group) return res.status(404).send('API source not found');
|
||||
const usage = await getApiRequestUsage(pool);
|
||||
group.requests = group.requests.map(function (request) { return Object.assign(request, { inUse: usage.has(Number(request.id)) }); });
|
||||
group.inUse = group.requests.some(function (request) { return request.inUse; });
|
||||
res.send(renderApiSourceEditPage(group, {}, req.query.message || '', req.currentUser));
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
await connection.query('DELETE FROM i_api_sources WHERE id = ?', [apiSource.id]);
|
||||
await connection.commit();
|
||||
dataSourceTasks.removeRecurringRefresh('api-source', apiSource.id);
|
||||
} catch (error) {
|
||||
app.get(base + '/:id/duplicate', requirePermission('api-sources.read'), requirePermission('api-sources.create'), async function (req, res, next) {
|
||||
try {
|
||||
const group = await fetchApiGroupById(pool, Number(req.params.id));
|
||||
if (!group) return res.status(404).send('API source not found');
|
||||
const initialName = buildDuplicateApiSourceName(group.name);
|
||||
let name = initialName;
|
||||
let suffix = 2;
|
||||
while (await common.fetchDuplicateName(pool, 'i_api_source_groups', name)) name = initialName + ' (' + suffix++ + ')';
|
||||
const duplicate = buildDuplicateApiSource(group, name);
|
||||
res.send(renderApiSourceAddPage(duplicate, 'Review the copied values and save when ready.', req.currentUser, { messageVariant: 'info' }));
|
||||
} catch (error) { next(error); }
|
||||
});
|
||||
|
||||
async function save(req, res, next) {
|
||||
try {
|
||||
const id = req.params.id ? Number(req.params.id) : null;
|
||||
if (req.body.data_source_action === 'toggle') {
|
||||
const group = await fetchApiGroupById(pool, id);
|
||||
if (!group) return res.status(404).send('API source not found');
|
||||
const enabled = group.requests.some(function (request) { return request.enabled; }) ? 0 : 1;
|
||||
await pool.query('UPDATE i_api_sources SET enabled = ?, modified_by = ? WHERE auth_source_id = ?', [enabled, getAuditUserId(req), id]);
|
||||
group.requests.forEach(function (request) { request.enabled = enabled; });
|
||||
syncSchedules(group, [], getAuditUserId(req));
|
||||
return res.redirect(base + '/' + id + '/edit?message=' + encodeURIComponent(enabled ? 'API requests enabled.' : 'API requests disabled.'));
|
||||
}
|
||||
const payload = buildApiGroupPayload(req.body);
|
||||
const result = await saveApiGroup(pool, id, payload, getAuditUserId(req), hasPermission(req.currentUser, 'api-sources.delete'));
|
||||
syncSchedules({ name: payload.name, requests: result.requests }, result.removed, getAuditUserId(req));
|
||||
if (!id) {
|
||||
for (const request of result.requests) { if (request.enabled) await enqueue(request, payload, getAuditUserId(req)); }
|
||||
}
|
||||
redirectAfterSave(req, res, base + '/' + result.id + '/edit', { closeUrl: base, newUrl: base + '/new', message: id ? 'API source updated.' : 'API source created. Initial requests queued.' });
|
||||
} catch (error) { next(error); }
|
||||
}
|
||||
app.post(base, requirePermission('api-sources.create'), save);
|
||||
app.post(base + '/:id', requirePermission('api-sources.update'), save);
|
||||
|
||||
async function refresh(req, res, next) {
|
||||
try {
|
||||
const group = await fetchApiGroupById(pool, Number(req.params.id));
|
||||
if (!group) return res.status(404).send('API source not found');
|
||||
const requests = req.params.requestId ? group.requests.filter(function (request) { return Number(request.id) === Number(req.params.requestId); }) : group.requests;
|
||||
if (!requests.length) return res.status(404).send('API request not found');
|
||||
for (const request of requests) { if (request.enabled) await enqueue(request, group, getAuditUserId(req)); }
|
||||
res.redirect(base + '/' + group.id + '/edit?message=' + encodeURIComponent('Enabled API request refreshes queued.'));
|
||||
} catch (error) { next(error); }
|
||||
}
|
||||
app.post(base + '/:id/refresh', requirePermission('api-sources.allow'), refresh);
|
||||
app.post(base + '/:id/requests/:requestId/refresh', requirePermission('api-sources.allow'), refresh);
|
||||
|
||||
app.post(base + '/:id/delete', requirePermission('api-sources.delete'), async function (req, res, next) {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const [groups] = await connection.query('SELECT id FROM i_api_source_groups WHERE id = ? FOR UPDATE', [Number(req.params.id)]);
|
||||
if (!groups.length) { await connection.rollback(); return res.status(404).send('API source not found'); }
|
||||
const [requests] = await connection.query('SELECT id FROM i_api_sources WHERE auth_source_id = ? FOR UPDATE', [Number(req.params.id)]);
|
||||
const usage = await getApiRequestUsage(connection);
|
||||
if (requests.some(function (request) { return usage.has(Number(request.id)); })) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
return res.redirect(base + '?message=' + encodeURIComponent('This API source has requests used by slides.'));
|
||||
}
|
||||
res.redirect('/data-sources/api-sources?message=' + encodeURIComponent('API source deleted.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
await connection.query('DELETE FROM i_api_sources WHERE auth_source_id = ?', [Number(req.params.id)]);
|
||||
await connection.query('DELETE FROM i_api_source_groups WHERE id = ?', [Number(req.params.id)]);
|
||||
await connection.commit();
|
||||
requests.forEach(function (request) { dataSourceTasks.removeRecurringRefresh('api-source', request.id); });
|
||||
res.redirect(base + '?message=' + encodeURIComponent('API source deleted.'));
|
||||
} catch (error) { await connection.rollback(); next(error); }
|
||||
finally { connection.release(); }
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user