tt-help-cli-ycl 1.3.44 → 1.3.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/attach.js +16 -4
- package/src/lib/args.js +7 -0
- package/src/lib/constants.js +3 -1
- package/src/watch/data-store.js +147 -15
- package/src/watch/public/index.html +61 -1
- package/src/watch/server.js +19 -3
package/package.json
CHANGED
package/src/cli/attach.js
CHANGED
|
@@ -76,12 +76,12 @@ async function recycleScraper(scraper, reason) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export async function handleAttach(options) {
|
|
79
|
-
const { attachParallel, attachInterval, serverUrl, showHelp } = options;
|
|
79
|
+
const { attachParallel, attachInterval, serverUrl, attachCountries, showHelp } = options;
|
|
80
80
|
let shuttingDown = false;
|
|
81
81
|
let forceExitTimer = null;
|
|
82
82
|
|
|
83
83
|
if (showHelp) {
|
|
84
|
-
attachLog("用法: tt-help attach [-p 并行数] [-i 间隔秒数] [-s 服务端地址]");
|
|
84
|
+
attachLog("用法: tt-help attach [-p 并行数] [-i 间隔秒数] [-s 服务端地址] [-c 国家列表]");
|
|
85
85
|
attachLog("");
|
|
86
86
|
attachLog("参数:");
|
|
87
87
|
attachLog(" -p, --parallel <N> 并行抓取数(默认: 1)");
|
|
@@ -89,6 +89,9 @@ export async function handleAttach(options) {
|
|
|
89
89
|
attachLog(
|
|
90
90
|
" -s, --server <URL> 服务端地址(默认: http://127.0.0.1:3001)",
|
|
91
91
|
);
|
|
92
|
+
attachLog(
|
|
93
|
+
" -c, --countries <A,B,C> 猜测国家列表(逗号分隔,如 PL,DE,FR),服务端优先返回这些国家的任务",
|
|
94
|
+
);
|
|
92
95
|
attachLog("");
|
|
93
96
|
attachLog("说明:");
|
|
94
97
|
attachLog(
|
|
@@ -101,11 +104,16 @@ export async function handleAttach(options) {
|
|
|
101
104
|
attachLog(" tt-help attach");
|
|
102
105
|
attachLog(" tt-help attach -p 5 -i 10");
|
|
103
106
|
attachLog(" tt-help attach -p 3 -i 5 -s http://127.0.0.1:3001");
|
|
107
|
+
attachLog(" tt-help attach -c PL,DE,FR -p 5");
|
|
104
108
|
return;
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
const countryStr =
|
|
112
|
+
attachCountries && attachCountries.length > 0
|
|
113
|
+
? `, 猜测国家: ${attachCountries.join(", ")}`
|
|
114
|
+
: "";
|
|
107
115
|
attachLog(
|
|
108
|
-
`[Attach] 并行数: ${attachParallel}, 空闲间隔: ${attachInterval}秒, 服务端: ${serverUrl}`,
|
|
116
|
+
`[Attach] 并行数: ${attachParallel}, 空闲间隔: ${attachInterval}秒, 服务端: ${serverUrl}${countryStr}`,
|
|
109
117
|
);
|
|
110
118
|
|
|
111
119
|
const scraper = new TikTokScraper();
|
|
@@ -162,8 +170,12 @@ export async function handleAttach(options) {
|
|
|
162
170
|
process.exit(0);
|
|
163
171
|
}
|
|
164
172
|
|
|
173
|
+
const countryParam =
|
|
174
|
+
attachCountries && attachCountries.length > 0
|
|
175
|
+
? `&countries=${encodeURIComponent(attachCountries.join(","))}`
|
|
176
|
+
: "";
|
|
165
177
|
const { total, tasks } = await apiGet(
|
|
166
|
-
`${serverUrl}/api/user-update-tasks?limit=${attachParallel}`,
|
|
178
|
+
`${serverUrl}/api/user-update-tasks?limit=${attachParallel}${countryParam}`,
|
|
167
179
|
);
|
|
168
180
|
|
|
169
181
|
if (!tasks || tasks.length === 0) {
|
package/src/lib/args.js
CHANGED
|
@@ -463,6 +463,7 @@ function parseAttachArgs(args) {
|
|
|
463
463
|
let parallel = 1;
|
|
464
464
|
let interval = 10;
|
|
465
465
|
let serverUrl = defaultServer;
|
|
466
|
+
let countries = [];
|
|
466
467
|
|
|
467
468
|
for (let i = 0; i < args.length; i++) {
|
|
468
469
|
const arg = args[i];
|
|
@@ -472,6 +473,11 @@ function parseAttachArgs(args) {
|
|
|
472
473
|
interval = parseInt(args[++i], 10) || 10;
|
|
473
474
|
} else if (arg === "-s" || arg === "--server") {
|
|
474
475
|
serverUrl = args[++i];
|
|
476
|
+
} else if (arg === "-c" || arg === "--countries") {
|
|
477
|
+
countries = args[++i]
|
|
478
|
+
.split(",")
|
|
479
|
+
.map((c) => c.trim().toUpperCase())
|
|
480
|
+
.filter(Boolean);
|
|
475
481
|
}
|
|
476
482
|
}
|
|
477
483
|
|
|
@@ -480,6 +486,7 @@ function parseAttachArgs(args) {
|
|
|
480
486
|
attachParallel: parallel,
|
|
481
487
|
attachInterval: interval,
|
|
482
488
|
serverUrl,
|
|
489
|
+
attachCountries: countries,
|
|
483
490
|
urls: [],
|
|
484
491
|
outputFormat: "json",
|
|
485
492
|
exploreCount: 0,
|
package/src/lib/constants.js
CHANGED
|
@@ -146,12 +146,14 @@ const HELP_TEXT = [
|
|
|
146
146
|
" 示例: tt-help info https://www.tiktok.com/@nike",
|
|
147
147
|
" tt-help info https://www.tiktok.com/@nike/video/7234567890 --onlyvideo",
|
|
148
148
|
"",
|
|
149
|
-
" tt-help attach [-p 并行数] [-i 间隔秒数] [-s 服务端地址]",
|
|
149
|
+
" tt-help attach [-p 并行数] [-i 间隔秒数] [-s 服务端地址] [-c 国家列表]",
|
|
150
150
|
" 后台轮询服务端任务接口,自动抓取 TikTok 用户信息",
|
|
151
151
|
" -p, --parallel <N> 并行抓取数(默认: 1)",
|
|
152
152
|
" -i, --interval <N> 无任务时轮询间隔,单位秒(默认: 10)",
|
|
153
153
|
" -s, --server <URL> 服务端地址(默认: http://127.0.0.1:3001)",
|
|
154
|
+
" -c, --countries <A,B> 猜测国家列表(逗号分隔,如 PL,DE,FR)",
|
|
154
155
|
" 示例: tt-help attach -p 5 -i 10",
|
|
156
|
+
" tt-help attach -c PL,DE,FR -p 5",
|
|
155
157
|
"",
|
|
156
158
|
" tt-help watch -o <db路径> [-p 端口]",
|
|
157
159
|
" 启动监控服务;运行期主数据仅来自 SQLite",
|
package/src/watch/data-store.js
CHANGED
|
@@ -990,6 +990,117 @@ function restoreRawJobsByCountry(country) {
|
|
|
990
990
|
return { restored: count, country: normalizedCountry };
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
+
function restoreRawJobById(uniqueId) {
|
|
994
|
+
if (!db) {
|
|
995
|
+
return { restored: 0, uniqueId, error: "db not ready" };
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const safeId = String(uniqueId).trim();
|
|
999
|
+
if (!safeId) {
|
|
1000
|
+
return { restored: 0, uniqueId: safeId, error: "uniqueId is required" };
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const exists =
|
|
1004
|
+
db
|
|
1005
|
+
.prepare("SELECT COUNT(*) as c FROM raw_jobs WHERE unique_id = ?")
|
|
1006
|
+
.get(safeId)?.c || 0;
|
|
1007
|
+
|
|
1008
|
+
if (!exists) {
|
|
1009
|
+
return { restored: 0, uniqueId: safeId };
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const restoreTxn = db.transaction(() => {
|
|
1013
|
+
db.prepare(
|
|
1014
|
+
`
|
|
1015
|
+
INSERT OR REPLACE INTO jobs (
|
|
1016
|
+
unique_id, nickname, status, sources, claimed_by, claimed_at, error,
|
|
1017
|
+
pinned, no_video, restricted, user_update_count, tt_seller, verified,
|
|
1018
|
+
video_count, comment_count, guessed_location, location_created,
|
|
1019
|
+
follower_count, following_count, heart_count, refresh_time,
|
|
1020
|
+
processed, processed_at, created_at, updated_at, region, signature, sec_uid
|
|
1021
|
+
)
|
|
1022
|
+
SELECT
|
|
1023
|
+
unique_id, nickname, status, sources, claimed_by, claimed_at, error,
|
|
1024
|
+
pinned, no_video, restricted, user_update_count, tt_seller, verified,
|
|
1025
|
+
video_count, comment_count, guessed_location, location_created,
|
|
1026
|
+
follower_count, following_count, heart_count, refresh_time,
|
|
1027
|
+
processed, processed_at, created_at, updated_at, region, signature, sec_uid
|
|
1028
|
+
FROM raw_jobs WHERE unique_id = ?
|
|
1029
|
+
`,
|
|
1030
|
+
).run(safeId);
|
|
1031
|
+
|
|
1032
|
+
db.prepare("DELETE FROM raw_jobs WHERE unique_id = ?").run(safeId);
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
restoreTxn();
|
|
1036
|
+
return { restored: 1, uniqueId: safeId };
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function restoreRawJobsByFilter({ search, location }) {
|
|
1040
|
+
if (!db) {
|
|
1041
|
+
return { restored: 0, error: "db not ready" };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const where = [];
|
|
1045
|
+
const args = [];
|
|
1046
|
+
|
|
1047
|
+
if (search) {
|
|
1048
|
+
where.push(
|
|
1049
|
+
"(LOWER(unique_id) LIKE ? OR LOWER(COALESCE(nickname, '')) LIKE ?)",
|
|
1050
|
+
);
|
|
1051
|
+
const likeVal = `%${search.toLowerCase()}%`;
|
|
1052
|
+
args.push(likeVal, likeVal);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (location) {
|
|
1056
|
+
where.push("COALESCE(guessed_location, '未知') = ?");
|
|
1057
|
+
args.push(location);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (where.length === 0) {
|
|
1061
|
+
return { restored: 0, error: "at least one filter is required" };
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const whereSql = where.join(" AND ");
|
|
1065
|
+
|
|
1066
|
+
const count =
|
|
1067
|
+
db
|
|
1068
|
+
.prepare(
|
|
1069
|
+
`SELECT COUNT(*) as c FROM raw_jobs WHERE ${whereSql}`,
|
|
1070
|
+
)
|
|
1071
|
+
.get(...args)?.c || 0;
|
|
1072
|
+
|
|
1073
|
+
if (!count) {
|
|
1074
|
+
return { restored: 0 };
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const restoreTxn = db.transaction(() => {
|
|
1078
|
+
db.prepare(
|
|
1079
|
+
`
|
|
1080
|
+
INSERT OR REPLACE INTO jobs (
|
|
1081
|
+
unique_id, nickname, status, sources, claimed_by, claimed_at, error,
|
|
1082
|
+
pinned, no_video, restricted, user_update_count, tt_seller, verified,
|
|
1083
|
+
video_count, comment_count, guessed_location, location_created,
|
|
1084
|
+
follower_count, following_count, heart_count, refresh_time,
|
|
1085
|
+
processed, processed_at, created_at, updated_at, region, signature, sec_uid
|
|
1086
|
+
)
|
|
1087
|
+
SELECT
|
|
1088
|
+
unique_id, nickname, status, sources, claimed_by, claimed_at, error,
|
|
1089
|
+
pinned, no_video, restricted, user_update_count, tt_seller, verified,
|
|
1090
|
+
video_count, comment_count, guessed_location, location_created,
|
|
1091
|
+
follower_count, following_count, heart_count, refresh_time,
|
|
1092
|
+
processed, processed_at, created_at, updated_at, region, signature, sec_uid
|
|
1093
|
+
FROM raw_jobs WHERE ${whereSql}
|
|
1094
|
+
`,
|
|
1095
|
+
).run(...args);
|
|
1096
|
+
|
|
1097
|
+
db.prepare(`DELETE FROM raw_jobs WHERE ${whereSql}`).run(...args);
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
restoreTxn();
|
|
1101
|
+
return { restored: count };
|
|
1102
|
+
}
|
|
1103
|
+
|
|
993
1104
|
function getRawJobsPageFromDb({ search, location, limit, offset }) {
|
|
994
1105
|
if (!db) return null;
|
|
995
1106
|
|
|
@@ -2474,21 +2585,33 @@ export function createStore(filePath) {
|
|
|
2474
2585
|
return Array.from(clientErrors.values());
|
|
2475
2586
|
}
|
|
2476
2587
|
|
|
2477
|
-
function getPendingUserUpdateTasks(limit) {
|
|
2588
|
+
function getPendingUserUpdateTasks(limit, countries) {
|
|
2589
|
+
const targetCountries = countries
|
|
2590
|
+
? countries.map((c) => String(c).trim().toUpperCase())
|
|
2591
|
+
: [];
|
|
2592
|
+
const hasCountryFilter = targetCountries.length > 0;
|
|
2593
|
+
|
|
2478
2594
|
if (db) {
|
|
2479
2595
|
const l = Math.max(1, parseInt(limit) || 5);
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
)
|
|
2491
|
-
|
|
2596
|
+
|
|
2597
|
+
let sql = `
|
|
2598
|
+
SELECT *
|
|
2599
|
+
FROM jobs
|
|
2600
|
+
WHERE COALESCE(tt_seller, '') = ''
|
|
2601
|
+
AND COALESCE(user_update_count, 0) <= 0
|
|
2602
|
+
`;
|
|
2603
|
+
const sqlParams = [];
|
|
2604
|
+
|
|
2605
|
+
if (hasCountryFilter) {
|
|
2606
|
+
const placeholders = targetCountries.map(() => "?").join(", ");
|
|
2607
|
+
sql += ` AND UPPER(COALESCE(guessed_location, '')) IN (${placeholders})`;
|
|
2608
|
+
sqlParams.push(...targetCountries);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
sql += ` ORDER BY created_at ASC, unique_id ASC LIMIT ?`;
|
|
2612
|
+
sqlParams.push(l);
|
|
2613
|
+
|
|
2614
|
+
const rows = db.prepare(sql).all(...sqlParams);
|
|
2492
2615
|
if (rows.length === 0) return [];
|
|
2493
2616
|
const now = Date.now();
|
|
2494
2617
|
const bumpStmt = db.prepare(
|
|
@@ -2520,9 +2643,16 @@ export function createStore(filePath) {
|
|
|
2520
2643
|
const ttSellerEmpty =
|
|
2521
2644
|
u.ttSeller === null || u.ttSeller === undefined || u.ttSeller === "";
|
|
2522
2645
|
if (!ttSellerEmpty) return false;
|
|
2523
|
-
|
|
2646
|
+
if (
|
|
2524
2647
|
updateCount === null || updateCount === undefined || updateCount <= 0
|
|
2525
|
-
)
|
|
2648
|
+
) {
|
|
2649
|
+
if (hasCountryFilter) {
|
|
2650
|
+
const loc = (u.guessedLocation || "").toUpperCase();
|
|
2651
|
+
return targetCountries.includes(loc);
|
|
2652
|
+
}
|
|
2653
|
+
return true;
|
|
2654
|
+
}
|
|
2655
|
+
return false;
|
|
2526
2656
|
})
|
|
2527
2657
|
.slice(0, l);
|
|
2528
2658
|
// 接受任务时 userUpdateCount + 1
|
|
@@ -2814,6 +2944,8 @@ export function createStore(filePath) {
|
|
|
2814
2944
|
moveJobsToRawByCountry,
|
|
2815
2945
|
restoreAttachStuckByCountry,
|
|
2816
2946
|
restoreRawJobsByCountry,
|
|
2947
|
+
restoreRawJobById,
|
|
2948
|
+
restoreRawJobsByFilter,
|
|
2817
2949
|
getUsersPage: getUsersPageFromDb,
|
|
2818
2950
|
getRawJobsPage: getRawJobsPageFromDb,
|
|
2819
2951
|
getTargetUsers: getTargetUsersFromDb,
|
|
@@ -1265,6 +1265,7 @@
|
|
|
1265
1265
|
<option value="">全部国家</option>
|
|
1266
1266
|
</select>
|
|
1267
1267
|
<button onclick="clearRawFilters()">清空筛选</button>
|
|
1268
|
+
<button onclick="restoreFilteredRawJobs()" style="background:#22c55e;color:#fff;border:none;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px;">恢复当前筛选到 jobs</button>
|
|
1268
1269
|
</div>
|
|
1269
1270
|
<div class="table-scroll">
|
|
1270
1271
|
<table>
|
|
@@ -1279,6 +1280,7 @@
|
|
|
1279
1280
|
<th>来源</th>
|
|
1280
1281
|
<th>状态</th>
|
|
1281
1282
|
<th>创建时间</th>
|
|
1283
|
+
<th>操作</th>
|
|
1282
1284
|
</tr>
|
|
1283
1285
|
</thead>
|
|
1284
1286
|
<tbody id="rawTable"></tbody>
|
|
@@ -2181,7 +2183,7 @@
|
|
|
2181
2183
|
function renderRawJobsTable(users) {
|
|
2182
2184
|
const el = document.getElementById('rawTable');
|
|
2183
2185
|
if (!users.length) {
|
|
2184
|
-
el.innerHTML = '<tr><td colspan="
|
|
2186
|
+
el.innerHTML = '<tr><td colspan="10" style="color:#666;text-align:center;padding:24px">暂无毛料库任务</td></tr>';
|
|
2185
2187
|
return;
|
|
2186
2188
|
}
|
|
2187
2189
|
el.innerHTML = users.map(u => {
|
|
@@ -2203,6 +2205,7 @@
|
|
|
2203
2205
|
<td data-label="来源">${sources || '-'}</td>
|
|
2204
2206
|
<td data-label="状态">${statusTag}</td>
|
|
2205
2207
|
<td data-label="创建时间" style="font-size:11px;color:#888">${created}</td>
|
|
2208
|
+
<td data-label="操作" style="text-align:center"><button onclick="restoreRawJob('${u.uniqueId}')" style="background:#22c55e;color:#fff;border:none;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:11px;">恢复</button></td>
|
|
2206
2209
|
</tr>`;
|
|
2207
2210
|
}).join('');
|
|
2208
2211
|
}
|
|
@@ -2237,6 +2240,63 @@
|
|
|
2237
2240
|
fetchRawJobs();
|
|
2238
2241
|
}
|
|
2239
2242
|
|
|
2243
|
+
async function restoreRawJob(uniqueId) {
|
|
2244
|
+
if (!window.confirm(`确认将 @${uniqueId} 从毛料库恢复到 jobs 队列吗?`)) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
try {
|
|
2248
|
+
const res = await fetch('/api/raw-jobs/restore', {
|
|
2249
|
+
method: 'POST',
|
|
2250
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2251
|
+
body: JSON.stringify({ uniqueId })
|
|
2252
|
+
});
|
|
2253
|
+
const data = await res.json();
|
|
2254
|
+
if (!res.ok || data.error) {
|
|
2255
|
+
showToast(data.error || '恢复失败', true);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
showToast(`@${uniqueId} 已恢复到队列`);
|
|
2259
|
+
await fetchStats();
|
|
2260
|
+
await fetchRawByCountry();
|
|
2261
|
+
await fetchRawJobs();
|
|
2262
|
+
} catch (e) {
|
|
2263
|
+
showToast('恢复失败: ' + e.message, true);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
async function restoreFilteredRawJobs() {
|
|
2268
|
+
const search = document.getElementById('rawSearchInput').value.trim();
|
|
2269
|
+
const location = currentRawLocation;
|
|
2270
|
+
let desc = '当前筛选条件';
|
|
2271
|
+
if (search && location) desc = `搜索="${search}" + 国家=${location}`;
|
|
2272
|
+
else if (search) desc = `搜索="${search}"`;
|
|
2273
|
+
else if (location) desc = `国家=${location}`;
|
|
2274
|
+
if (!window.confirm(`确认将毛料库中符合【${desc}】的任务恢复到 jobs 队列吗?`)) {
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
try {
|
|
2278
|
+
const body = {};
|
|
2279
|
+
if (search) body.search = search;
|
|
2280
|
+
if (location) body.location = location;
|
|
2281
|
+
const res = await fetch('/api/raw-jobs/restore', {
|
|
2282
|
+
method: 'POST',
|
|
2283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2284
|
+
body: JSON.stringify(body)
|
|
2285
|
+
});
|
|
2286
|
+
const data = await res.json();
|
|
2287
|
+
if (!res.ok || data.error) {
|
|
2288
|
+
showToast(data.error || '恢复失败', true);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
showToast(`已恢复 ${data.restored} 条任务到队列`);
|
|
2292
|
+
await fetchStats();
|
|
2293
|
+
await fetchRawByCountry();
|
|
2294
|
+
await fetchRawJobs();
|
|
2295
|
+
} catch (e) {
|
|
2296
|
+
showToast('恢复失败: ' + e.message, true);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2240
2300
|
async function restoreRawJobsByCountry(country, count) {
|
|
2241
2301
|
const countText = count != null ? `将恢复 ${formatStatNum(count)} 条。` : '';
|
|
2242
2302
|
if (!window.confirm(`确认将 ${country} 从毛料库恢复到 jobs 队列吗?${countText}`)) {
|
package/src/watch/server.js
CHANGED
|
@@ -333,9 +333,12 @@ export function startWatchServer(dataAnchor, port = 3000, existingStore) {
|
|
|
333
333
|
|
|
334
334
|
if (req.method === "GET" && routePath === "/api/user-update-tasks") {
|
|
335
335
|
const limit = params.limit;
|
|
336
|
-
const
|
|
336
|
+
const countries = params.countries
|
|
337
|
+
? params.countries.split(",").map((c) => c.trim().toUpperCase()).filter(Boolean)
|
|
338
|
+
: [];
|
|
339
|
+
const tasks = store.getPendingUserUpdateTasks(limit, countries);
|
|
337
340
|
const ts = new Date().toISOString().slice(11, 19);
|
|
338
|
-
console.error(`[JOB ${ts}] USER-UPDATE-TASKS: ${tasks.length} tasks`);
|
|
341
|
+
console.error(`[JOB ${ts}] USER-UPDATE-TASKS: ${tasks.length} tasks${countries.length ? ` (countries: ${countries.join(",")})` : ""}`);
|
|
339
342
|
sendJSON(res, 200, { total: tasks.length, tasks });
|
|
340
343
|
return;
|
|
341
344
|
}
|
|
@@ -540,7 +543,20 @@ export function startWatchServer(dataAnchor, port = 3000, existingStore) {
|
|
|
540
543
|
if (req.method === "POST" && routePath === "/api/raw-jobs/restore") {
|
|
541
544
|
try {
|
|
542
545
|
const body = await readBody(req);
|
|
543
|
-
|
|
546
|
+
let result;
|
|
547
|
+
if (body.uniqueId) {
|
|
548
|
+
result = store.restoreRawJobById(body.uniqueId);
|
|
549
|
+
} else if (body.search || body.location) {
|
|
550
|
+
result = store.restoreRawJobsByFilter({
|
|
551
|
+
search: body.search || "",
|
|
552
|
+
location: body.location || "",
|
|
553
|
+
});
|
|
554
|
+
} else if (body.country) {
|
|
555
|
+
result = store.restoreRawJobsByCountry(body.country);
|
|
556
|
+
} else {
|
|
557
|
+
sendJSON(res, 400, { error: "missing filter: uniqueId, country, or search/location" });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
544
560
|
if (result.error) {
|
|
545
561
|
sendJSON(res, 400, result);
|
|
546
562
|
return;
|