initial commit

This commit is contained in:
chungyeong
2026-02-19 17:28:58 +09:00
commit 02970df6af
34 changed files with 5673 additions and 0 deletions

152
src/dashboardApi.js Normal file
View File

@@ -0,0 +1,152 @@
"use strict";
const {
buildAlertRules,
inferAlertOn,
normalizeAlertOn,
parseTargetPrice,
} = require("./alertRules");
const { createHttpError, parseBoolean } = require("./dashboardUtils");
const { extractFlightSearchRequest } = require("./llmParameterExtractor");
function readInput(body) {
const input = typeof body.input === "string" ? body.input.trim() : "";
if (!input) {
throw createHttpError(400, "input 문자열이 필요합니다.");
}
return input;
}
function hasOwnProperty(source, key) {
return Object.prototype.hasOwnProperty.call(source, key);
}
function createDashboardApi({ watcher, store }) {
async function parseInput(body = {}) {
const input = readInput(body);
return extractFlightSearchRequest(input, {
preferRuleParser: parseBoolean(body.useLlm, true) === false,
});
}
async function createWatch(body = {}) {
const extracted = await parseInput(body);
const input = readInput(body);
const alertRules = buildAlertRules({
targetPrice: body.targetPrice,
alertOn: body.alertOn || "both",
});
const watchId = watcher.addWatch({
rawInput: input,
searchParams: extracted.params,
alertRules,
pollingEnabled: parseBoolean(body.pollingEnabled, true),
alertsEnabled: parseBoolean(body.alertsEnabled, true),
});
const created = watcher.getWatch(watchId);
await store.saveWatch(created);
if (parseBoolean(body.pollNow, false)) {
await watcher.pollWatch(watchId);
}
return {
watch: watcher.getWatch(watchId),
parserSource: extracted.source,
};
}
async function updateSystem(body = {}) {
const controls = watcher.setGlobalControls({
crawlingEnabled: parseBoolean(body.crawlingEnabled, watcher.getGlobalControls().crawlingEnabled),
alertsEnabled: parseBoolean(body.alertsEnabled, watcher.getGlobalControls().alertsEnabled),
});
await store.setGlobalControls(controls);
return { controls };
}
function listWatches() {
return {
controls: watcher.getGlobalControls(),
watches: watcher.listWatches(),
};
}
async function listEvents(rawLimit) {
const limit = Number(rawLimit);
const events = await store.listEvents(Number.isFinite(limit) ? limit : 50);
return { events };
}
async function pollWatch(watchId) {
const existing = watcher.getWatch(watchId);
if (!existing) {
throw createHttpError(404, `watch를 찾을 수 없습니다: ${watchId}`);
}
const pollResult = await watcher.pollWatch(watchId);
return {
watch: watcher.getWatch(watchId),
pollResult,
};
}
async function updateWatch(watchId, body = {}) {
const existing = watcher.getWatch(watchId);
if (!existing) {
throw createHttpError(404, `watch를 찾을 수 없습니다: ${watchId}`);
}
const patch = {};
if (hasOwnProperty(body, "pollingEnabled")) {
patch.pollingEnabled = parseBoolean(body.pollingEnabled, existing.pollingEnabled);
}
if (hasOwnProperty(body, "alertsEnabled")) {
patch.alertsEnabled = parseBoolean(body.alertsEnabled, existing.alertsEnabled);
}
const hasAlertModePatch = hasOwnProperty(body, "alertOn") || hasOwnProperty(body, "targetPrice");
if (hasAlertModePatch) {
const targetPrice = hasOwnProperty(body, "targetPrice")
? parseTargetPrice(body.targetPrice)
: existing.alertRules.targetPrice;
const alertOn = hasOwnProperty(body, "alertOn")
? normalizeAlertOn(body.alertOn)
: inferAlertOn(existing.alertRules);
patch.alertRules = buildAlertRules({
targetPrice,
alertOn,
});
}
const updated = watcher.updateWatch(watchId, patch);
await store.saveWatch(updated);
return { watch: updated };
}
async function deleteWatch(watchId) {
const existed = watcher.removeWatch(watchId);
await store.deleteWatch(watchId);
return { deleted: existed };
}
return {
createWatch,
deleteWatch,
listEvents,
listWatches,
parseInput,
pollWatch,
updateSystem,
updateWatch,
};
}
module.exports = {
createDashboardApi,
};