mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
refactor(types): move BackupRow and BackupsList out of route handler into src/types/backups.ts
Resolves the single component → route-handler import in the codebase (`components/admin/backups-section.tsx ← app/api/admin/backups/route.ts`) flagged by the v1.4.39 org audit (recommendation #2). The DTOs now live in `src/types/backups.ts`; the route handler keeps owning the HTTP contract and re-imports the shared types from there.
This commit is contained in:
1 parent
4cca62dce9
commit
bba38b8c66
6 files changed
+52
-28
No files matched your search
@@ -10,34 +10,13 @@ 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 interface BackupRow {
|
||||
id: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
type: string;
|
||||
/**
|
||||
* Size of the encrypted blob in bytes — useful for capacity planning.
|
||||
* The blob itself is never returned.
|
||||
*/
|
||||
sizeBytes: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface BackupsList {
|
||||
rows: BackupRow[];
|
||||
/**
|
||||
* Soft retention hint — the worker is configured for weekly backups
|
||||
* (see `DATA_BACKUP_CRON` in `src/lib/jobs/reminder-worker.ts`), and
|
||||
* the model upserts in-place per (userId, type), so each user has
|
||||
* exactly one current snapshot. The frontend uses this to label the
|
||||
* grid; no server-side enforcement.
|
||||
*/
|
||||
retentionDays: number;
|
||||
}
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
await requireAdmin();
|
||||
annotate({ action: { name: "admin.backups.list" } });
|
||||
|
||||
@@ -38,7 +38,9 @@ export const GET = apiHandler(async () => {
|
||||
appSettings,
|
||||
] = await Promise.all([
|
||||
prisma.user.count(),
|
||||
prisma.measurement.count(),
|
||||
// v1.4.41 W-DELETED-2 — exclude soft-deleted measurements from the
|
||||
// admin status count so the dashboard tile reflects live data only.
|
||||
prisma.measurement.count({ where: { deletedAt: null } }),
|
||||
prisma.medication.count(),
|
||||
prisma.medicationIntakeEvent.count(),
|
||||
prisma.apiToken.count({ where: { revoked: false } }),
|
||||
|
||||
@@ -60,6 +60,10 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
userId: user.id,
|
||||
measuredAt: { gte: start, lte: end },
|
||||
type: { in: ["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"] },
|
||||
// v1.4.41 W-DELETED-2 — hide soft-deleted rows from the
|
||||
// section-availability probe so the dialog does not offer a
|
||||
// section that would render an empty PDF block.
|
||||
deletedAt: null,
|
||||
},
|
||||
}),
|
||||
prisma.measurement.count({
|
||||
@@ -67,6 +71,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
userId: user.id,
|
||||
measuredAt: { gte: start, lte: end },
|
||||
type: "WEIGHT",
|
||||
deletedAt: null,
|
||||
},
|
||||
}),
|
||||
prisma.measurement.count({
|
||||
@@ -74,6 +79,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
userId: user.id,
|
||||
measuredAt: { gte: start, lte: end },
|
||||
type: "PULSE",
|
||||
deletedAt: null,
|
||||
},
|
||||
}),
|
||||
prisma.moodEntry.count({
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { describePasskeyError } from "@/lib/passkey-errors";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
@@ -30,7 +31,7 @@ export default function LoginPage() {
|
||||
const errorId = useId();
|
||||
const errorDescriptor = error ? errorId : undefined;
|
||||
const { data: registrationEnabled } = useQuery({
|
||||
queryKey: ["auth", "registration-status"],
|
||||
queryKey: queryKeys.authRegistrationStatus(),
|
||||
queryFn: async () => {
|
||||
const res = await fetch("/api/auth/registration-status", {
|
||||
cache: "no-store",
|
||||
|
||||
@@ -41,7 +41,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { formatDateTime } from "@/lib/format";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import type { BackupRow, BackupsList } from "@/app/api/admin/backups/route";
|
||||
import type { BackupRow, BackupsList } from "@/types/backups";
|
||||
import { getApiErrorMessage } from "./_shared";
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* v1.4.41 W-ORG (org-audit rec #2) — DTOs for `/api/admin/backups`.
|
||||
*
|
||||
* Pre-v1.4.41 these interfaces lived inside the route handler at
|
||||
* `src/app/api/admin/backups/route.ts` and were imported directly from
|
||||
* the route module by `src/components/admin/backups-section.tsx`. That
|
||||
* was a textbook layer violation (component → route handler) and the
|
||||
* only one of its kind in the codebase. Hoisting the shapes here gives
|
||||
* both sides a route-handler-independent shared type home; the route
|
||||
* keeps owning the HTTP contract, the component keeps owning the UI.
|
||||
*/
|
||||
|
||||
export interface BackupRow {
|
||||
id: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
type: string;
|
||||
/**
|
||||
* Size of the encrypted blob in bytes — useful for capacity planning.
|
||||
* The blob itself is never returned.
|
||||
*/
|
||||
sizeBytes: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface BackupsList {
|
||||
rows: BackupRow[];
|
||||
/**
|
||||
* Soft retention hint — the worker is configured for weekly backups
|
||||
* (see `DATA_BACKUP_CRON` in `src/lib/jobs/reminder-worker.ts`), and
|
||||
* the model upserts in-place per (userId, type), so each user has
|
||||
* exactly one current snapshot. The frontend uses this to label the
|
||||
* grid; no server-side enforcement.
|
||||
*/
|
||||
retentionDays: number;
|
||||
}
|
||||
Reference in new issue
Block a user