mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
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:
parent
2e1b0f022e
commit
25ebae4ae5
46 changed files with 1450 additions and 117 deletions
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from "./groups";
|
||||
import { findUserIdByToken } from "../github/store";
|
||||
import { cfEnv } from "../cf";
|
||||
import { initConfigStore } from "../config";
|
||||
|
||||
export interface AuthContext {
|
||||
session: AdminSession;
|
||||
|
|
@ -26,6 +27,7 @@ const AUTH_KEY = "auth";
|
|||
/** Read the admin session + access scope for a request (null when logged out). */
|
||||
export async function loadAuth(event: H3Event): Promise<AuthContext | null> {
|
||||
const env = cfEnv(event);
|
||||
initConfigStore(env);
|
||||
const session = await getAdminSession(env.KV, getHeader(event, "cookie"));
|
||||
if (!session) return null;
|
||||
const groups = await loadGroups(env.KV);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { Env, Group, GroupMember, GroupRole } from "../types";
|
|||
import { isAdminUser } from "./session";
|
||||
import { log } from "../lib/log";
|
||||
import { migrateGroups, validateGroups } from "../config/schema";
|
||||
import { getConfigStore } from "../config";
|
||||
|
||||
const GROUPS_KEY = "config:groups";
|
||||
const GROUPS_CACHE_TTL = 300_000;
|
||||
|
|
@ -40,6 +41,9 @@ export function normalizeGroupMembers(group: Group): GroupMember[] {
|
|||
}
|
||||
|
||||
export async function loadGroups(kv: KVNamespace): Promise<Group[]> {
|
||||
const store = getConfigStore(kv);
|
||||
if (store) return store.loadGroups();
|
||||
|
||||
if (groupsCache && Date.now() < groupsCache.expiresAt) {
|
||||
return groupsCache.groups;
|
||||
}
|
||||
|
|
@ -55,6 +59,12 @@ export async function loadGroups(kv: KVNamespace): Promise<Group[]> {
|
|||
}
|
||||
|
||||
export async function saveGroups(kv: KVNamespace, groups: Group[]): Promise<void> {
|
||||
const store = getConfigStore(kv);
|
||||
if (store) {
|
||||
await store.saveGroups(groups);
|
||||
groupsCache = null;
|
||||
return;
|
||||
}
|
||||
await kv.put(GROUPS_KEY, JSON.stringify(groups));
|
||||
groupsCache = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ async function readIndex(kv: KVNamespace, groupId: string): Promise<string[]> {
|
|||
}
|
||||
|
||||
async function writeIndex(kv: KVNamespace, groupId: string, tokens: string[]): Promise<void> {
|
||||
await kv.put(indexKey(groupId), JSON.stringify(tokens));
|
||||
await kv.put(indexKey(groupId), JSON.stringify(tokens), { expirationTtl: INVITE_TTL });
|
||||
}
|
||||
|
||||
async function removeFromIndex(kv: KVNamespace, groupId: string, token: string): Promise<void> {
|
||||
|
|
@ -142,7 +142,7 @@ export async function migrateInvites(kv: KVNamespace, from: string, to: string):
|
|||
moved.push(token);
|
||||
}
|
||||
}
|
||||
await kv.put(indexKey(to), JSON.stringify(moved));
|
||||
await kv.put(indexKey(to), JSON.stringify(moved), { expirationTtl: INVITE_TTL });
|
||||
await kv.delete(indexKey(from));
|
||||
} catch (err) {
|
||||
log.warn({ err, from, to }, "Failed to migrate invites on group rename");
|
||||
|
|
|
|||
|
|
@ -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"] ?? "");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue