teqo/src/utilities/campaignJsonMutationRoute.ts
Francisco Ignácio Fontoura Solla 2bbb630af6 simplify(B27): fix optimistic-state clobber, unify JSON route error shell, dedupe advisor id resolution
- MunicipalityListAdvisorsControl: stop advancing lastPropsIDsRef from a
  successful delta response — it exists only to detect a genuine external
  prop change, and writing it there made a later stale re-render of the
  pre-edit content look external and clobber optimistic state back to it.
  Also reconcile with the latest confirmed server set on the error path
  once every in-flight delta settles (previously only the success path did),
  and consolidate chip/tooltip name resolution onto the shared
  advisorEntriesFromIds helper instead of a bespoke lookup with an
  '#id' placeholder.
- Extract campaignJsonMutationErrorResponse/parseCampaignJsonRequestBody
  (src/utilities/campaignJsonMutationRoute.ts) and adopt them in both
  municipios/advisors/route.ts and municipios/expected-votes/route.ts,
  removing the duplicated body-parse + error-mapping ladder.
- Municipality.ts's validateMunicipalityAdvisors now uses
  uniqueRelationshipIds instead of re-inlining the Set+map+filter dance
  the helper was extracted for.

Verified: tsc --noEmit, lint, format:check, check:cycles all clean;
full unit suite (450 tests) and the municipality/advisor int suites pass.
knip's pre-existing payload.config.ts load error is unrelated (P3).
Playwright's spawned webServer failed to boot in this sandbox (unrelated
module-resolution environment issue, reproduced even before this diff);
manual pnpm dev boots fine and the change is covered by unit/int tests.
2026-07-26 03:51:32 -03:00

57 lines
1.8 KiB
TypeScript

import { NextResponse } from 'next/server'
import {
CAMPAIGN_SESSION_EXPIRED_MESSAGE,
mapCampaignFormActionError,
} from '@/utilities/campaignFormActionError'
export type CampaignJsonErrorBody = { status: 'error'; message: string }
/**
* Shared shell for `POST /campanha/**` JSON mutation routes (list-popover
* auto-save): parses the body, mapping a malformed one to the same 400 both
* routes already used. Domain parsing/success payloads stay per-route.
*/
export const parseCampaignJsonRequestBody = async (
request: Request,
): Promise<
{ ok: true; body: unknown } | { ok: false; response: NextResponse<CampaignJsonErrorBody> }
> => {
try {
return { ok: true, body: await request.json() }
} catch {
return {
ok: false,
response: NextResponse.json(
{ status: 'error', message: 'Corpo da requisição inválido.' },
{ status: 400 },
),
}
}
}
/**
* Maps a thrown error to the JSON error response: safe messages (incl. the
* two auth ones, appended once here) pass through as 400, an expired/missing
* session is 401, anything else collapses to `genericMessage`.
*/
export const campaignJsonMutationErrorResponse = (
error: unknown,
{ safeMessages, genericMessage }: { safeMessages: readonly string[]; genericMessage: string },
): NextResponse<CampaignJsonErrorBody> => {
const mapped = mapCampaignFormActionError({
error,
safeMessages: [...safeMessages, 'Autenticação necessária.', CAMPAIGN_SESSION_EXPIRED_MESSAGE],
genericMessage,
})
const isAuthError =
error instanceof Error &&
(error.message === 'Autenticação necessária.' ||
error.message === CAMPAIGN_SESSION_EXPIRED_MESSAGE)
return NextResponse.json(
{ status: 'error', message: mapped.message ?? genericMessage },
{ status: isAuthError ? 401 : 400 },
)
}