Skip to content

[DONE] 커뮤니티 API 가 본문이 null 인 요청에 500 대신 400 과 까닭을 돌려준다 - #147

Merged
patternflow-bot merged 3 commits into
devfrom
claude/pf-round-48-1x0txs
Oct 22, 2026
Merged

patternflow-bot merged 3 commits into
devfrom
claude/pf-round-48-1x0txs

Conversation

@patternflow-bot

@patternflow-bot patternflow-bot commented Oct 22, 2026 •

Copy link
Copy Markdown

lsh / https://claude.ai/code/session_01Yb77jeCcRxZfC8EHziz4Gg

커뮤니티 쓰기 라우트 22곳은 본문 null 을 받으면 첫 칸을 읽다 TypeError 로 죽었고, Next.js 는 빈 500 을 CORS 머리 없이 돌려줬어요. 머지하면 21개 파일이 readJsonObject 하나로 본문을 읽고, 객체가 아닌 본문에 {"error":"Expected a JSON object."} 와 400 을 돌려줘요. 다른 출처의 Pattern Lab 도 그 까닭을 읽을 수 있어요.

좋아지는 것: 없음

할 일

한 일

web/src/lib/community/jsonBody.ts 의 readJsonObject(request) 는 본문을 읽어 객체면 그대로, 아니면 400 응답을 돌려줘요. JSON 이 아니면 지금처럼 「Invalid JSON body.」, null 이나 배열, 수, 글자면 「Expected a JSON object.」예요. 라우트는 두 줄로 불러요.

const raw = await readJsonObject(request);
if (raw instanceof Response) return raw;

try/catch 와 as Record<string, unknown> 을 이 두 줄로 바꾼 곳은 21개 파일이에요. atlas(POST, DELETE), builds, comments/[id], decks, decks/[id], featured, patterns, patterns/[id], 그 아래 comments·header·performance, ports/[id], posts, posts/[id], 그 아래 comments·pin, presence, reports, reports/[id], territories, territories/[code] 이에요. 400 은 라우트 안에서 돌려주니 다른 400 처럼 withCors 를 지나요.

시험은 둘이에요. jsonBody.test.ts 는 객체, 빈 객체, 객체가 아닌 JSON 여섯 가지, 본문 없음, 빈 본문, 끊긴 객체를 봐요. app/api/community/nullBody.test.ts 는 decks/[id]/route.test.ts 처럼 제 데이터베이스에 관리자 하나와 패턴, 덱, 댓글, 포트, 영역, 글, 신고를 넣고 22곳에 로그인과 Lab 의 Origin 을 달아 null 을 보내요. 400, 그 JSON, Access-Control-Allow-Origin 셋을 봐요.

증거

고치기 전 npx vitest run src/app/api/community/nullBody.test.ts. 22곳이 다 같은 꼴로 죽어요.

      7 TypeError: Cannot read properties of null (reading 'title')
      3 TypeError: Cannot read properties of null (reading 'body')
      2 TypeError: Cannot read properties of null (reading 'codeCpp')
      2 TypeError: Cannot read properties of null (reading 'patternId')
      1 … (reading 'code') · 'format' · 'patternIds' · 'performanceJson' · 'pinned' · 'status' · 'targetType' · 'x'
      Tests  22 failed (22)

고친 뒤.

 ✓ src/lib/community/jsonBody.test.ts (11 tests) 17ms
 ✓ src/app/api/community/nullBody.test.ts (22 tests) 1594ms
 ✓ src/app/api/community/decks/[id]/route.test.ts (2 tests) 1696ms
      Tests  35 passed (35)

dev(7a58ce2)와 이 브랜치를 각각 npm run build 하고 next start 로 3101, 3100 에 띄웠어요. 가입해 받은 쿠키와 Origin: https://lab.patternflow.test 를 달고 POST /api/community/patterns 에 본문 셋을 보냈어요.

== dev 7a58ce2
null  HTTP/1.1 500 Internal Server Error   (본문 0 바이트, content-type 도 access-control-allow-origin 도 없음)
      서버 로그: ⨯ TypeError: Cannot read properties of null (reading 'title')
[]    HTTP/1.1 400  access-control-allow-origin: https://lab.patternflow.test  {"error":"Title is required (max 80 chars)."}
{}    HTTP/1.1 400  access-control-allow-origin: https://lab.patternflow.test  {"error":"Title is required (max 80 chars)."}
== 이 브랜치 b1efdeb
null  HTTP/1.1 400  access-control-allow-origin: https://lab.patternflow.test  {"error":"Expected a JSON object."}
[]    HTTP/1.1 400  access-control-allow-origin: https://lab.patternflow.test  {"error":"Expected a JSON object."}
{}    HTTP/1.1 400  access-control-allow-origin: https://lab.patternflow.test  {"error":"Title is required (max 80 chars)."}

web/ 에서 다섯 검사의 끝줄이에요.

npm ci                              → exit 0
npm run lint -- --max-warnings=0    → lint exit 0
npm run typecheck                   → typecheck exit 0
npm run build                       → ✓ Compiled successfully in 17.7s · build exit 0
npm run check:ci                    → Test Files 80 passed (80) · Tests 840 passed (840) · passed virtual-panel · check:ci exit 0

check:ci 의 links-absolute 와 roadmap 은 gh 로그인이 없는 컨테이너라 skipped 예요. npm run check:voice 는 「read: 272 added lines in 24 files · nothing found」예요. 화면을 바꾸지 않아서 사진은 없어요.

실행법

cd web
npx vitest run src/lib/community/jsonBody.test.ts src/app/api/community/nullBody.test.ts

가정

  • 배열, 수, 글자 본문도 이제 「Expected a JSON object.」 400 이에요. dev 에선 [] 가 패턴 올리기에서 「Title is required」였어요. 쪽에서 JSON.stringify 에 객체 꼴이 아닌 값을 넘기는 일곱 곳(AtlasClient.tsx:409, ModerateVisibility.tsx:45, TerritoryEditor.tsx:114, PublishModal.tsx:185, AddHeaderModal.tsx:90, PresenceLayer.tsx:137, DeckDetailClient.tsx:145)을 따라가 보니 다 객체를 보내요.
  • 표는 띄운 사이트에서 13곳이 500 이고 아홉 파일은 코드로 봤다고 적었어요. 시험은 관리자로 로그인하고 BUILD_ENABLED=1 을 켜서, 그 아홉 파일까지 22곳 다 죽는 걸 봤어요.
  • territories/[code]/pin/route.ts:82 는 표대로 두었어요. 본문이 없어도 되는 라우트라 null 이면 메모 없이 핀을 꽂아요.
  • comments/[id] PATCH 는 본문을 읽기 전에 ?on=pattern|post 를 봐서, 시험 주소에 ?on=pattern 을 붙였어요.
  • 표 밖에서 하나 봤어요. builds/route.ts:113 의 const item = entry as Record<string, unknown> 은 {"format":"pfm","patterns":[null]} 에서 item.code 를 읽다 같은 TypeError 가 날 거예요(코드로 봤어요). 본문 null 이 아니라 배열 안의 null 이라 고치지 않았어요.

Changelog

Web

  • Community write routes answer a null body with a 400. null parses as JSON, so it got past every route's catch and threw on the first field read, and 22 handlers answered an empty 500 with no CORS headers. They now read the body through one helper that answers {"error":"Expected a JSON object."}.

경험

언제: 바퀴에서 dev 를 따로 빌드해 이 브랜치와 나란히 띄울 때
교훈: dev 워크트리의 web/node_modules 는 심볼릭 링크 말고 cp -al 로 하드 링크 복사를 한다
근거: #147 에서 ../wt-dev/web/node_modules 를 심볼릭 링크로 두자 Turbopack 이 「Symlink node_modules could not be resolved: the symlink target leaves the filesystem root」로 빌드를 멈췄고, cp -al 은 0.5초에 끝났고 그 뒤 빌드는 「Compiled successfully in 13.7s」로 통과했어요

바퀴

바퀴 48 · 후보 4 「커뮤니티 API 가 본문이 null 인 요청에 500 대신 400 과 까닭을 돌려준다」

다음 바퀴

  • 5 「끝이 잘린 코드를 붙이면 Lab 과 Live Editor 가 코드가 끝나기 전에 끊겼다고 말한다」 — lib/pattern/harness.ts:437 이 덧댄 return 이 끊긴 호출 안으로 들어가 「Unexpected token 'return'」이 떠요.
  • 6 「폰에서 Lab 의 Recent 와 Panels 메뉴가 화면 안에 열리고, 창 닫기 단추를 누르기 쉽다」 — 390px 에서 Recent 메뉴가 x −105px 부터 열려 왼쪽이 화면 밖이에요.
  • 7 「기기 주소 칸을 비우거나 http:// 를 붙여 넣어도 「Send over Wi-Fi」 링크가 그 보드로 간다」 — lib/community/deviceHost.ts:65 가 빈 칸에서 http:///patterns?src=… 를 만들어요.

https://claude.ai/code/session_01Yb77jeCcRxZfC8EHziz4Gg


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yb77jeCcRxZfC8EHziz4Gg
@patternflow-bot patternflow-bot changed the title [WIP] pf 48바퀴 — 고를 일을 찾는 중 [WIP] 커뮤니티 API 가 본문이 null 인 요청에 500 대신 400 과 까닭을 돌려준다 Oct 22, 2026
null parses as JSON, so it passed every route's catch and threw on the first field read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yb77jeCcRxZfC8EHziz4Gg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yb77jeCcRxZfC8EHziz4Gg
@patternflow-bot patternflow-bot changed the title [WIP] 커뮤니티 API 가 본문이 null 인 요청에 500 대신 400 과 까닭을 돌려준다 [DONE] 커뮤니티 API 가 본문이 null 인 요청에 500 대신 400 과 까닭을 돌려준다 Oct 22, 2026
@patternflow-bot
patternflow-bot marked this pull request as ready for review October 22, 2026 01:12
@patternflow-bot
patternflow-bot merged commit da9dcdf into dev Oct 22, 2026
2 checks passed
@patternflow-bot
patternflow-bot deleted the claude/pf-round-48-1x0txs branch October 22, 2026 01:14
engmung pushed a commit to engmung/Patternflow that referenced this pull request Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants