fix(audit-wave-10): types-auditor fixes — Tx type, BerthDetailData, parseBody, toAuditJson
Address the CRITICAL + high-leverage HIGH items from the types-auditor:
**C1 — `tx: any` in client-restore.service**
Export a canonical `Tx` type from `lib/db/utils.ts` (derived from
Drizzle's `db.transaction` callback shape) and use it in
`applyReversal` so the 12+ downstream tx writes get full inference.
**C2 — berth-detail page stacked `useQuery<any>` escape hatches**
Export `BerthDetailData` from berth-detail-header and consume it
through useQuery + apiFetch. Removed three `any` escapes in the
highest-traffic detail page. Also collapsed the duplicate `BerthData`
in berth-tabs.tsx to import from berth-detail-header so the two
types can't drift.
**C3 — parseBody migration for portal/public routes**
Replace raw `await req.json() + schema.parse(body)` with the
project-standard `parseBody(req, schema)` helper across 7 routes:
- portal/auth/{change-password, activate, reset-password}
- auth/set-password
- public/{interests, residential-inquiries}
Skipped the three anti-enumeration routes (forgot-password, sign-in,
sign-in-by-identifier) where the manual validation gives opaque
errors on purpose. website-inquiries already wraps the parse in a
custom 400 — left as-is.
**HIGH #5 — `toAuditJson<T>` helper (21 → 0 inline casts)**
Introduce `toAuditJson<T extends object>(row: T): Record<string,
unknown>` in lib/audit.ts (mirrors gdpr-bundle-builder's `toJsonRow`
that already exists for the same reason). Codemod 21 `<row> as unknown
as Record<string, unknown>` sites across:
- invoices.ts × 6
- expenses.ts × 6
- berths.service × 2
- documents.service × 2
- ocr-config.service × 2
- ai-budget.service × 2
- yachts.service, companies.service, company-memberships.service × 1 each
document-templates' `payload as unknown as Record<...>` is a different
shape (Documenso form-values widening, not an audit log) — kept the
manual cast there. Tests stay 1315/1315.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ import {
|
||||
CommandList,
|
||||
} from '@/components/ui/command';
|
||||
|
||||
type BerthDetailData = {
|
||||
export type BerthDetailData = {
|
||||
id: string;
|
||||
mooringNumber: string;
|
||||
area: string | null;
|
||||
@@ -80,6 +80,8 @@ type BerthDetailData = {
|
||||
mooringType: string | null;
|
||||
access: string | null;
|
||||
berthApproved: boolean | null;
|
||||
statusLastChangedReason: string | null;
|
||||
statusLastModified: string | null;
|
||||
tags: Array<{ id: string; name: string; color: string }>;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DetailLayout } from '@/components/shared/detail-layout';
|
||||
import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { useRealtimeInvalidation } from '@/hooks/use-realtime-invalidation';
|
||||
import { BerthDetailHeader } from './berth-detail-header';
|
||||
import { BerthDetailHeader, type BerthDetailData } from './berth-detail-header';
|
||||
import { BerthForm } from './berth-form';
|
||||
import { buildBerthTabs } from './berth-tabs';
|
||||
|
||||
@@ -17,12 +17,10 @@ interface BerthDetailProps {
|
||||
}
|
||||
|
||||
export function BerthDetail({ berthId }: BerthDetailProps) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { data, isLoading } = useQuery<any>({
|
||||
const { data, isLoading } = useQuery<BerthDetailData>({
|
||||
queryKey: ['berth', berthId],
|
||||
queryFn: () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
apiFetch<{ data: any }>(`/api/v1/berths/${berthId}`).then((r) => r.data),
|
||||
apiFetch<{ data: BerthDetailData }>(`/api/v1/berths/${berthId}`).then((r) => r.data),
|
||||
});
|
||||
|
||||
useRealtimeInvalidation({
|
||||
@@ -58,8 +56,7 @@ export function BerthDetail({ berthId }: BerthDetailProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const berth = data as any;
|
||||
const berth = data;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -29,44 +29,7 @@ import { BerthInterestPulse } from './berth-interest-pulse';
|
||||
import { BerthDocumentsTab } from './berth-documents-tab';
|
||||
import { BerthDealDocumentsTab } from './berth-deal-documents-tab';
|
||||
|
||||
type BerthData = {
|
||||
id: string;
|
||||
mooringNumber: string;
|
||||
area: string | null;
|
||||
status: string;
|
||||
lengthFt: string | null;
|
||||
lengthM: string | null;
|
||||
widthFt: string | null;
|
||||
widthM: string | null;
|
||||
draftFt: string | null;
|
||||
draftM: string | null;
|
||||
widthIsMinimum: boolean | null;
|
||||
nominalBoatSize: string | null;
|
||||
nominalBoatSizeM: string | null;
|
||||
waterDepth: string | null;
|
||||
waterDepthM: string | null;
|
||||
waterDepthIsMinimum: boolean | null;
|
||||
sidePontoon: string | null;
|
||||
powerCapacity: string | null;
|
||||
voltage: string | null;
|
||||
mooringType: string | null;
|
||||
cleatType: string | null;
|
||||
cleatCapacity: string | null;
|
||||
bollardType: string | null;
|
||||
bollardCapacity: string | null;
|
||||
access: string | null;
|
||||
price: string | null;
|
||||
priceCurrency: string;
|
||||
bowFacing: string | null;
|
||||
berthApproved: boolean | null;
|
||||
tenureType: string;
|
||||
tenureYears: number | null;
|
||||
tenureStartDate: string | null;
|
||||
tenureEndDate: string | null;
|
||||
statusLastChangedReason: string | null;
|
||||
statusLastModified: string | null;
|
||||
tags: Array<{ id: string; name: string; color: string }>;
|
||||
};
|
||||
import type { BerthDetailData as BerthData } from './berth-detail-header';
|
||||
|
||||
/**
|
||||
* Compact ft/m segmented control for the Specifications card. Two
|
||||
|
||||
Reference in New Issue
Block a user