mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Medication editor overhaul, route of administration, today-tile read-flip. - Route-of-administration column (ORAL | INJECTION | OTHER) decoupled from treatmentClass; injection-site picker surfaces for any injection. Migration 0088 backfills ORAL via a constant column default. - One-time injection (oneShot + deliveryForm = INJECTION) editor path. - Profile-photo upload card on Settings → Account. - Today-tile read-flip: /api/medications/intake?scope=today and /api/dashboard/summary gate through the canonical recurrence engine, matching the reminder worker for bi-weekly, rolling, RRULE, and one-time cadences. - Measurement note cap 25 → 200; dashboard range colors on semantic tokens; admin sidebar icons + shared-helper consolidation; API-tokens multi-language snippets restored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
/**
|
|
* GET /api/admin/backups — admin-only listing of `DataBackup` rows.
|
|
*
|
|
* Returns one row per (userId, type) pair. The `data` payload is NOT
|
|
* shipped — only metadata (id, userId, username, type, size in bytes,
|
|
* createdAt). The encrypted blob remains server-side; admins can trigger
|
|
* a re-snapshot but not download another user's payload from the UI.
|
|
*/
|
|
import { prisma } from "@/lib/db";
|
|
import { apiHandler, requireAdmin } from "@/lib/api-handler";
|
|
import { apiSuccess } from "@/lib/api-response";
|
|
import { annotate } from "@/lib/logging/context";
|
|
// v1.4.41 W-ORG — `BackupRow` / `BackupsList` moved to `src/types/backups.ts`
|
|
// so callers (in particular `components/admin/backups-section.tsx`) don't
|
|
// have to reach across the component → route-handler layer boundary.
|
|
import type { BackupRow, BackupsList } from "@/types/backups";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export const GET = apiHandler(async () => {
|
|
await requireAdmin();
|
|
annotate({ action: { name: "admin.backups.list" } });
|
|
|
|
// Pull metadata only — `data` would balloon the response and reveal
|
|
// ciphertext we don't need on the listing page.
|
|
const backups = await prisma.dataBackup.findMany({
|
|
include: { user: { select: { id: true, username: true } } },
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
const rows: BackupRow[] = backups.map((b) => ({
|
|
id: b.id,
|
|
userId: b.userId,
|
|
username: b.user.username,
|
|
type: b.type,
|
|
sizeBytes: Buffer.byteLength(b.data, "utf8"),
|
|
createdAt: b.createdAt.toISOString(),
|
|
}));
|
|
|
|
const payload: BackupsList = {
|
|
rows,
|
|
// Matches the retention window the backup-prune job enforces so the
|
|
// backups page states the same number the worker acts on.
|
|
retentionDays: 90,
|
|
};
|
|
|
|
return apiSuccess(payload);
|
|
});
|