feat: get-voice-mailに取得範囲フラグ追加、n8nワークフローを1件ずつループ処理化
大量ファイル処理時のn8nメモリ枯渇対策として、SplitInBatches(1件ずつ)で SSHダウンロード→base64化→プリザンター登録を直列化(n8n側、API経由で反映済み)。 Webhookに?range=today|week|allを追加、app-portal側にも選択UIを実装。
This commit is contained in:
parent
6c9893c24b
commit
4b14d992af
@ -34,7 +34,15 @@ document.getElementById('cards').addEventListener('click', async (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(`/api/compose/${composeId}/${action}`, { method: 'POST' });
|
||||
let url = `/api/compose/${composeId}/${action}`;
|
||||
if (action === 'trigger') {
|
||||
const rangeSelect = document.querySelector(`select[data-range-select][data-compose-id="${composeId}"]`);
|
||||
if (rangeSelect) {
|
||||
url += `?range=${encodeURIComponent(rangeSelect.value)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
showToast(`${action} を実行しました`, false);
|
||||
} else {
|
||||
|
||||
@ -60,12 +60,19 @@ function renderCard(card) {
|
||||
meta.appType === 'web' && meta.url
|
||||
? `<a class="btn btn-outline-primary btn-sm" href="${escapeHtml(meta.url)}" target="_blank" rel="noopener">開く</a>`
|
||||
: '';
|
||||
const rangeSelect =
|
||||
meta.appType === 'batch' && meta.rangeOptions && meta.rangeOptions.length > 0
|
||||
? `<select class="form-select form-select-sm w-auto" data-range-select data-compose-id="${escapeHtml(composeId)}">
|
||||
${meta.rangeOptions.map((opt) => `<option value="${escapeHtml(opt)}">${escapeHtml(opt)}</option>`).join('')}
|
||||
</select>`
|
||||
: '';
|
||||
const actionButtons =
|
||||
meta.appType === 'web'
|
||||
? `<button class="btn btn-success btn-sm" data-action="start" data-compose-id="${escapeHtml(composeId)}">Start</button>
|
||||
<button class="btn btn-danger btn-sm" data-action="stop" data-compose-id="${escapeHtml(composeId)}">Stop</button>
|
||||
<button class="btn btn-warning btn-sm" data-action="restart" data-compose-id="${escapeHtml(composeId)}">Restart</button>`
|
||||
: `<button class="btn btn-primary btn-sm" data-action="trigger" data-compose-id="${escapeHtml(composeId)}">今すぐ実行</button>`;
|
||||
: `${rangeSelect}
|
||||
<button class="btn btn-primary btn-sm" data-action="trigger" data-compose-id="${escapeHtml(composeId)}">今すぐ実行</button>`;
|
||||
return `<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card h-100" data-compose-id="${escapeHtml(composeId)}">
|
||||
<div class="card-body d-flex flex-column">
|
||||
|
||||
@ -152,7 +152,9 @@ app.post('/api/compose/:composeId/trigger', async (req, res) => {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
const triggerRes = await fetch(`${meta.url}${meta.triggerPath}`, {
|
||||
const range = req.query.range;
|
||||
const query = meta.rangeOptions && meta.rangeOptions.includes(range) ? `?range=${encodeURIComponent(range)}` : '';
|
||||
const triggerRes = await fetch(`${meta.url}${meta.triggerPath}${query}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Portal-Secret': PORTAL_SECRET },
|
||||
});
|
||||
|
||||
@ -22,11 +22,17 @@ function extractPortalMeta(envString, fallbackLabel) {
|
||||
if (appType !== 'web' && appType !== 'batch') {
|
||||
return null;
|
||||
}
|
||||
const rangeOptions = vars.PORTAL_TRIGGER_RANGE_OPTIONS
|
||||
? vars.PORTAL_TRIGGER_RANGE_OPTIONS.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: null;
|
||||
return {
|
||||
appType,
|
||||
label: vars.PORTAL_APP_LABEL || fallbackLabel,
|
||||
url: vars.PORTAL_APP_URL || null,
|
||||
triggerPath: vars.PORTAL_TRIGGER_PATH || null,
|
||||
rangeOptions,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,13 @@ test('extractPortalMeta returns null when PORTAL_APP_TYPE is missing or invalid'
|
||||
|
||||
test('extractPortalMeta parses a web-type app, falling back to label when PORTAL_APP_LABEL is absent', () => {
|
||||
const meta = extractPortalMeta('PORTAL_APP_TYPE=web\nPORTAL_APP_URL=https://x.apps.next-hd.net', 'x');
|
||||
assert.deepStrictEqual(meta, { appType: 'web', label: 'x', url: 'https://x.apps.next-hd.net', triggerPath: null });
|
||||
assert.deepStrictEqual(meta, {
|
||||
appType: 'web',
|
||||
label: 'x',
|
||||
url: 'https://x.apps.next-hd.net',
|
||||
triggerPath: null,
|
||||
rangeOptions: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('extractPortalMeta parses a batch-type app with PORTAL_TRIGGER_PATH', () => {
|
||||
@ -28,5 +34,19 @@ test('extractPortalMeta parses a batch-type app with PORTAL_TRIGGER_PATH', () =>
|
||||
'PORTAL_APP_TYPE=batch\nPORTAL_APP_LABEL=同期バッチ\nPORTAL_APP_URL=http://sample:3000\nPORTAL_TRIGGER_PATH=/trigger',
|
||||
'fallback'
|
||||
);
|
||||
assert.deepStrictEqual(meta, { appType: 'batch', label: '同期バッチ', url: 'http://sample:3000', triggerPath: '/trigger' });
|
||||
assert.deepStrictEqual(meta, {
|
||||
appType: 'batch',
|
||||
label: '同期バッチ',
|
||||
url: 'http://sample:3000',
|
||||
triggerPath: '/trigger',
|
||||
rangeOptions: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('extractPortalMeta parses PORTAL_TRIGGER_RANGE_OPTIONS into an array', () => {
|
||||
const meta = extractPortalMeta(
|
||||
'PORTAL_APP_TYPE=batch\nPORTAL_APP_URL=http://sample:3000\nPORTAL_TRIGGER_PATH=/trigger\nPORTAL_TRIGGER_RANGE_OPTIONS=today, week ,all',
|
||||
'fallback'
|
||||
);
|
||||
assert.deepStrictEqual(meta.rangeOptions, ['today', 'week', 'all']);
|
||||
});
|
||||
|
||||
@ -7,6 +7,8 @@ PORTAL_APP_LABEL=ボイスメール取得
|
||||
PORTAL_APP_URL=http://get-voice-mail:3000
|
||||
PORTAL_TRIGGER_PATH=/trigger
|
||||
PORTAL_SECRET=
|
||||
# app-portalダッシュボードに取得範囲の選択肢を表示する(未指定ならボタンのみ、選択なしはtriggerで自動的にtoday扱い)
|
||||
PORTAL_TRIGGER_RANGE_OPTIONS=today,week,all
|
||||
|
||||
# n8nワークフロー入口(Webhookノード)のURL。/trigger起動時にPOSTする先
|
||||
N8N_WEBHOOK_URL=https://n8n32.next-hd.net/webhook/get-voice-mail
|
||||
|
||||
@ -15,6 +15,8 @@ app.get('/health', (req, res) => {
|
||||
res.status(200).json({ status: 'healthy' });
|
||||
});
|
||||
|
||||
const VALID_RANGES = ['today', 'week', 'all'];
|
||||
|
||||
app.post('/trigger', (req, res) => {
|
||||
if (!isTriggerAuthorized(req.headers['x-portal-secret'], PORTAL_SECRET)) {
|
||||
res.sendStatus(403);
|
||||
@ -25,7 +27,8 @@ app.post('/trigger', (req, res) => {
|
||||
res.sendStatus(500);
|
||||
return;
|
||||
}
|
||||
fetch(N8N_WEBHOOK_URL, {
|
||||
const range = VALID_RANGES.includes(req.query.range) ? req.query.range : 'today';
|
||||
fetch(`${N8N_WEBHOOK_URL}?range=${range}`, {
|
||||
method: 'POST',
|
||||
headers: N8N_WEBHOOK_SECRET ? { 'X-Api-Key': N8N_WEBHOOK_SECRET } : {},
|
||||
}).catch((err) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user