feat(sources): site center backend (credentials, refresh login, user data, health check, delete)

PUT /sites/{id}/credentials by kind (moved from auth-settings, legacy body
accepted), POST refresh-login (async task or wait), POST health-check,
GET user-data history, DELETE /sites/{id} (soft delete, releases kept),
DELETE /site-accounts/{id}, GET /sites?include=accounts; refreshSiteAccount
task with login-failure decisions and auto re-login on auth_expired;
scheduled jobs sites.user_data (6h) and sites.health_check (1h);
settings.sites defaults for rate limit, snapshot interval, UA and proxy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 15:18:40 +08:00
1 parent d6fca25679
commit 05a7706dde
15 files changed
+1602 -179

No files matched your search

@@ -1,6 +1,7 @@
import { defineTask, type OutboxRouteFn, type TaskRegistry } from "@umt/tasks";
import { z } from "zod";
import type { SourcesRuntime } from "./runtime.ts";
import { refreshSiteAccount } from "./service/account.ts";
import { ingestFeed } from "./service/feeds.ts";
import { refreshMetadata } from "./service/metadata.ts";
import { resolveRelease } from "./service/resolve.ts";
@@ -76,6 +77,23 @@ export function registerSourcesTasks(registry: TaskRegistry, rt: () => SourcesRu
snapshotSite(rt(), { siteId: t.input.siteId, kind: t.input.kind, maxPages: t.input.maxPages }),
),
}),
defineTask({
name: "refreshSiteAccount",
version: 1,
inputSchema: z.object({ accountId: id, mode: z.enum(["login", "userData"]).default("login") }),
// 网络失败只记 last_error 不抛错;不自动重试(站点登录频繁重试有封号风险)
retryPolicy: { maximumAttempts: 1 },
handler: (t) =>
t.step.run("refresh", async () => {
const exists = await rt()
.ctx.db.selectFrom("site_account")
.select("id")
.where("id", "=", t.input.accountId)
.executeTakeFirst();
if (!exists) return { skipped: "not_found" };
return refreshSiteAccount(rt(), t.input.accountId, { mode: t.input.mode });
}),
}),
);
}