d58dc2a251
Introduce Draft → Confirm → Commit flow for WeChat schedule intents behind feature flags, plus h5_tasks dual-write/read aggregation and rollout scripts so reminders and automations get explicit user confirmation before persisting. Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Cleanup Tang's duplicate scheduled tasks on 103.
|
|
* Dry-run by default; pass --apply to mutate rows.
|
|
*/
|
|
import process from 'node:process';
|
|
import mysql from 'mysql2/promise';
|
|
import { loadH5Environment } from './load-env.mjs';
|
|
|
|
loadH5Environment(import.meta.dirname);
|
|
|
|
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
|
const DUPLICATE_WEATHER_TASK_ID = '66f0ee11-7777-415f-affd-34422c772d0f';
|
|
const KEEP_WEATHER_TASK_ID = '23456db0-918e-4055-9ca3-e17e83b2dc24';
|
|
|
|
const fmt = (ms) => new Date(Number(ms)).toLocaleString('zh-CN', {
|
|
timeZone: 'Asia/Shanghai',
|
|
hour12: false,
|
|
});
|
|
|
|
async function main() {
|
|
const apply = process.argv.includes('--apply');
|
|
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
|
|
const now = Date.now();
|
|
const plan = [];
|
|
|
|
const [dupRows] = await pool.query(
|
|
`SELECT id, title, status, hour, minute, last_run_at, created_at
|
|
FROM h5_scheduled_tasks
|
|
WHERE id = ? AND user_id = ?
|
|
LIMIT 1`,
|
|
[DUPLICATE_WEATHER_TASK_ID, TANG],
|
|
);
|
|
const dup = dupRows[0];
|
|
if (!dup) {
|
|
plan.push({ taskId: DUPLICATE_WEATHER_TASK_ID, action: 'skip', reason: 'not_found' });
|
|
} else if (dup.status === 'cancelled') {
|
|
plan.push({ taskId: DUPLICATE_WEATHER_TASK_ID, action: 'skip', reason: 'already_cancelled', title: dup.title });
|
|
} else {
|
|
plan.push({
|
|
taskId: DUPLICATE_WEATHER_TASK_ID,
|
|
title: dup.title,
|
|
status: dup.status,
|
|
schedule: `${dup.hour}:${String(dup.minute).padStart(2, '0')}`,
|
|
keepTaskId: KEEP_WEATHER_TASK_ID,
|
|
action: apply ? 'cancel_duplicate' : 'would_cancel_duplicate',
|
|
});
|
|
if (apply) {
|
|
await pool.query(
|
|
`UPDATE h5_scheduled_tasks
|
|
SET status = 'cancelled', updated_at = ?, last_error = NULL
|
|
WHERE id = ? AND user_id = ?`,
|
|
[now, DUPLICATE_WEATHER_TASK_ID, TANG],
|
|
);
|
|
}
|
|
}
|
|
|
|
const [activeRows] = await pool.query(
|
|
`SELECT id, title, status, hour, minute, next_run_at
|
|
FROM h5_scheduled_tasks
|
|
WHERE user_id = ? AND status IN ('active', 'locked')
|
|
ORDER BY hour, minute, created_at`,
|
|
[TANG],
|
|
);
|
|
|
|
console.log(JSON.stringify({ apply, now: fmt(now), plan, remainingActive: activeRows.map((row) => ({
|
|
id: row.id,
|
|
title: row.title,
|
|
status: row.status,
|
|
schedule: `${row.hour}:${String(row.minute).padStart(2, '0')}`,
|
|
nextRunAt: fmt(row.next_run_at),
|
|
})) }, null, 2));
|
|
|
|
await pool.end();
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|