feat(storage): migrate config, dedup and delivery state to D1

- Move oversized queue payloads from KV to R2 (PAYLOAD binding, webhooks/YYYY/MM/DD/*.json, KV queue:payload:* fallback)
- Persist routes/groups to D1 (d1_routes/d1_groups) with memory -> KV -> D1 three-tier cache, seeded from legacy KV config keys
- Move webhook dedup (dedup_keys), delivery state (delivery_state) and message tracking (message_tracking) to D1 via canUseD1 probe with automatic KV fallback
- Batch send_logs inserts (recordSendBatch) and add group_id/ts index
- Add storage-prune scheduled task for expired dedup/state/tracking rows
- Add TTL to invite:group:{id} index and audit all ephemeral KV keys
- Add D1 indexes for the new tables
- Sync AGENTS.md, README.md/zh and docs/ (en/zh) with the new storage layout
This commit is contained in:
RhenCloud 2026-08-17 15:01:20 +08:00
parent 2e1b0f022e
commit 25ebae4ae5
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
46 changed files with 1450 additions and 117 deletions

View file

@ -28,6 +28,7 @@ import { clientIp } from "./auth";
import { recordAudit } from "../lib/audit";
import { sendMessage } from "../drivers/telegram/rest";
import { cfEnv } from "../cf";
import { initConfigStore } from "../config";
import type { Env, Group } from "../types";
interface PendingState {
@ -130,6 +131,7 @@ function installPage(opts: {
/** GET /auth/github — start the OAuth flow. */
export async function handleOAuthStart(event: H3Event): Promise<void> {
const env = cfEnv(event);
initConfigStore(env);
const query = getQuery(event);
const redirectTo = safeRedirectPath(String(query["redirect"] ?? ""));
const state = generateRandomHex(16);
@ -144,6 +146,7 @@ export async function handleOAuthStart(event: H3Event): Promise<void> {
/** GET /auth/github/install — post-install choice page. */
export async function handleInstallPage(event: H3Event): Promise<string | void> {
const env = cfEnv(event);
initConfigStore(env);
const query = getQuery(event);
const rawId = String(query["installation_id"] ?? "");
const installationId = Number(rawId);
@ -171,6 +174,7 @@ export async function handleInstallPage(event: H3Event): Promise<string | void>
/** POST /auth/github/install/bind — provision the chosen binding. */
export async function handleInstallBind(event: H3Event): Promise<void> {
const env = cfEnv(event);
initConfigStore(env);
const session = await getAdminSession(env.KV, getHeader(event, "cookie"));
if (!session) {
await sendRedirect(event, "/admin?error=forbidden");
@ -278,6 +282,7 @@ export async function handleInstallBind(event: H3Event): Promise<void> {
/** GET /auth/github/callback — OAuth callback. */
export async function handleOAuthCallback(event: H3Event): Promise<unknown> {
const env = cfEnv(event);
initConfigStore(env);
const query = getQuery(event);
const code = String(query["code"] ?? "");
const state = String(query["state"] ?? "");