import { ensureRedis, redis } from './redis'; export interface Task { id: string; url: string; executeAt: number; // epoch seconds headers: Record; payload: any; retries: number; retryInterval: number; // milliseconds createdAt: number; } const TASK_PREFIX = 'mortgagefi:task:'; const TASK_ZSET = 'mortgagefi:tasks:by:time'; const RATE_LIMIT_PREFIX = 'mortgagefi:ratelimit:'; export async function saveTask(task: Task): Promise { const r = ensureRedis(); const pipe = r.pipeline(); pipe.set(`${TASK_PREFIX}${task.id}`, JSON.stringify(task)); pipe.zadd(TASK_ZSET, { score: task.executeAt, member: task.id }); await pipe.exec(); } export async function getTask(id: string): Promise { const r = ensureRedis(); const data = await r.get(`${TASK_PREFIX}${id}`); if (!data) return null; try { return JSON.parse(data) as Task; } catch { return null; } } export async function deleteTask(id: string): Promise { const r = ensureRedis(); const pipe = r.pipeline(); pipe.del(`${TASK_PREFIX}${id}`); pipe.zrem(TASK_ZSET, id); await pipe.exec(); } export async function listDueTasks(before: number): Promise { const r = ensureRedis(); const ids = await r.zrange(TASK_ZSET, 0, before, { byScore: true }); if (!ids || ids.length === 0) return []; const tasks: Task[] = []; for (const id of ids) { const t = await getTask(id); if (t) tasks.push(t); } return tasks; } export async function listAllTasks(): Promise { const r = ensureRedis(); const keys = await r.keys(`${TASK_PREFIX}*`); if (!keys || keys.length === 0) return []; const tasks: Task[] = []; for (const key of keys) { const data = await r.get(key); if (data) { try { tasks.push(JSON.parse(data) as Task); } catch { /* ignore malformed */ } } } return tasks.sort((a, b) => a.executeAt - b.executeAt); } // Simple per-IP rate limiter using Redis export async function checkRateLimit(ip: string, maxRequests: number, windowSeconds: number): Promise { if (!redis) return true; // if redis not configured, allow (dev mode) const key = `${RATE_LIMIT_PREFIX}${ip}`; const now = Math.floor(Date.now() / 1000); const windowStart = now - windowSeconds; const pipe = redis.pipeline(); pipe.zremrangebyscore(key, 0, windowStart); pipe.zcard(key); pipe.zadd(key, { score: now, member: `${now}:${Math.random()}` }); pipe.expire(key, windowSeconds + 1); const results = await pipe.exec(); const count = (results?.[1] as number) || 0; return count < maxRequests; }