Implement tasks 60, 61, 62: backups, rate limiting, structured logging

Task 60 — Postgres backups:
- deploy/backups/: backup.sh (pg_dump -Fc nightly), retain.sh (14/8/6 tiers),
  restore.sh, entrypoint.sh, crontab
- famapp-backup Alpine service + backups volume added to deploy/compose.yaml
- Restore procedure in deploy/backups/README.md

Task 61 — Rate limiting on share links:
- src/lib/rate-limit.ts: Edge-compatible sliding-window counter (50/min, LRU eviction)
  with consume(), isRateLimited(), recordFailure() exports
- middleware.ts: enforces 429 with Retry-After: 60 for /s/[token] (IP + token prefix)
- /s/[token]/page.tsx: tracks only failed resolveShareToken calls via recordFailure()

Task 62 — Structured logging:
- pino + pino-pretty installed; serverExternalPackages added to next.config.ts
- src/lib/logger.ts: JSON in production, pretty in dev, level from LOG_LEVEL env
- middleware.ts: structured JSON request log (method, path, status, ms, authenticated)
- _core/push.ts, notify.ts, reminders.ts: console.error/log → logger.error/info

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ginnoir
2026-05-06 17:23:29 -05:00
co-authored by Claude Sonnet 4.6
parent b6abe052c9
commit 285a460eb8
17 changed files with 616 additions and 11 deletions
+3 -2
View File
@@ -1,5 +1,6 @@
import { eq } from "drizzle-orm";
import { db } from "@/lib/db";
import logger from "@/lib/logger";
import { notifications, users } from "./schema";
import { sendPush } from "./push";
@@ -25,7 +26,7 @@ export async function notify(userId: string, payload: NotifyPayload) {
if (channels.includes("push") && user.notifPush && pushEnabled) {
await sendPush(userId, payload).catch((err) =>
console.error("[famapp] push channel failed:", err),
logger.error({ err }, "push channel failed"),
);
}
@@ -46,7 +47,7 @@ export async function notify(userId: string, payload: NotifyPayload) {
method: "POST",
headers: { Title: payload.title, "Content-Type": "text/plain" },
body: payload.body,
}).catch((err) => console.error("[famapp] ntfy delivery failed:", err));
}).catch((err) => logger.error({ err }, "ntfy delivery failed"));
}
}
}