You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(mindrecord): 일기 작성의 본문 이미지가 fileKey 로 변환되지 않음 — #953 머지 시 깨진 이미지로 활성화 #1016
일기 작성 경로에 업로드 URL → fileKey 변환이 없다. 데일리질문(#549)에만 들어가 있고, DiaryWriteViewModel 은 presigned.fileUrl 을 그대로 돌려준다. 에디터가 그 전체 URL 을 본문 <img src> 로 넣고, 제출 시 state.content 를 손대지 않고 그대로 보낸다.
서버는 본문 img src 에서 fileKey 를 기대하고, 전체 URL 을 받으면 그 앞에 호스트를 한 번 더 붙인다(#549 에서 실측).
보낸 값: https://cdn.example.net/mindrecords/staging/13/a.png
저장된 값: https://cdn.example.net/https://cdn.example.net/mindrecords/permanent/13/a.png → 403
지금은 왜 증상이 없나 — 그리고 언제 터지나
#950(presigned contentLength 누락) 때문에 이미지 업로드가 전면 실패해 왔다. 본문에 img 가 들어간 적이 없어 이 결함이 드러나지 않았을 뿐이다.
#953(PR, #950 수정)이 머지되는 순간 활성화된다. 일기는 «이미지 없음» 에서 «깨진 이미지 + permanent 로 옮겨지지 않은 staging 파일» 로 바뀐다.
수정은 데일리질문 쪽과 같은 모양이면 된다 — 업로드한 URL 을 기억해 두고(uploadedImageUrls) 제출 직전 toUploadedFileKey() 로 치환(toWireContent). 두 ViewModel 이 같은 로직을 갖게 되므로 공통화 여부는 구현 시 판단.
두 번째 항목(전체 URL → 영구 URL)은 이미 원본 문자열이 사라져 매치되지 않습니다. 즉 AWS_PUBLIC_BASE_URL 설정값과 무관하게, 본문 HTML 경로는 전체 URL 을 보내면 항상 깨집니다. 키를 보내면 같은 첫 항목이 정상적으로 영구 URL 로 바꿔 줍니다.
참고로 단일 필드 경로(타임레터 블록 url, 추모 미디어)는 promoteMediaKey() 가 키를 복원해 키를 반환하므로 URL 이어도 안전합니다 — 이 결함은 HTML 경로 전용입니다.
2. 본문의 «이미 저장된 영구 URL 은 건드리지 말 것» 은 BE 가 이미 보장합니다
HTML 루프가 isPromotableKey() 로 걸러 영구 키는 continue 로 건너뛰고 치환 맵에 아예 넣지 않습니다 (S3Service.java:183-184, 321-323). 그래서 영구 URL 이 본문에 섞여 있어도 그대로 남습니다 — 이번 작성에서 받은 URL 만 정확히 치환한다는 방침은 그대로 유효하고, 실수로 영구분이 섞여도 서버 쪽에서 한 겹 더 막힙니다.
3. 처방이 하나 낡습니다 — toUploadedFileKey() 대신 fileKey 직접
Note 의 «toUploadedFileKey() 로 치환» 은 PR #1094 가 머지되면 쓰지 않는 편이 낫습니다. PhotoUploadRepository.upload() 가 UploadedFile(fileUrl, fileKey) 를 돌려주게 되어, URL 에서 키를 역산할 필요가 없어집니다.
역산이 위험한 이유도 위와 같은 결의 문제입니다 — toUploadedFileKey() 는 fileUrl == "<스킴>://<호스트>/" + fileKey 를 가정하는데, fileUrl 은 resolvePublicUrl() 이 buildPublicPrefix() + key 로 만들고 그 prefix 는 AWS_PUBLIC_BASE_URL 설정값이라 경로 세그먼트를 가질 수 있습니다. 그러면 역산 결과에 그 세그먼트가 섞이고, extractStorageKey() 는 http 로 시작하지 않는 값을 검증 없이 키로 인정해 틀린 키가 조용히 저장됩니다.
AWS_PUBLIC_BASE_URL 의 실제 운영값은 DEPLOY_PRODUCTION_ENV secret 안에 있어 레포에서는 확인되지 않습니다. 서버가 열리는 시간대에 presigned 응답의 fileUrl 형태로 실측해 이 스레드에 덧붙이겠습니다.
수정 자체는 데일리질문과 같은 모양으로 만들었습니다 — 업로드 URL 을 기억해 두고 제출 직전 toWireContent(uploadedImageUrls) 로 치환, 이미 저장된 영구 URL 은 건드리지 않음. 치환 로직은 두 ViewModel 이 공유하도록 HtmlText 로 올렸습니다. 단위 가드 2건은 수정 전 코드에서 실제로 실패하는 것까지 확인했습니다.
(수정 전) DiaryWriteImageKeyTest > 등록 시 방금 업로드한 이미지의 src 는 fileKey 로 나간다 FAILED
(수정 후) BUILD SUCCESSFUL
다만 «일기 경로에서 전체 URL 을 보내면 403» 이라는 전제와 어긋나는 실측이 하나 있습니다.
어제 #958 브랜치(= develop 기준이라 일기 쪽 fileKey 변환 없음)로 세로 사진을 붙여 일기를 등록했고, 그때 서버가 돌려준 목록입니다.
업로드 때 받은 키는 mindrecords/staging/13/832a3609-…png 였습니다. 즉 본문에 전체 staging URL 을 보냈는데 서버가 permanent 로 옮기고 URL 을 다시 써 줬습니다 — 호스트 이중 부착도, 403 도 없었습니다. S3Service.extractStorageKey() 가 http 로 시작하는 값도 받아 키를 복원하는 것으로 보입니다.
그래서 지금 상태로는 이 수정이 필요 없거나, 반대로 fileKey 를 보내는 쪽이 정본인데 URL 도 관용적으로 받아 주는 것뿐일 수 있습니다. 어느 쪽인지 확정하지 않고 머지하면 «고쳤다고 적힌 것» 만 남습니다.
오늘 재확인하려 했으나 dev 서버가 응답하지 않습니다 (점검 시간대로 보입니다).
$ curl -m 15 -X POST https://afternote.kro.kr/api/v1/files/presigned-url
000 (15.0s, timeout)
서버가 열리면 같은 일기 경로로 (a) 전체 URL (b) fileKey 두 형태를 각각 보내 저장 결과를 비교한 뒤 PR 을 올리겠습니다. 브랜치(fix/1016-diary-filekey)는 로컬에 두고 있습니다.
참고로 develop 그대로는 일기 본문에 <img> 자체가 들어가지 않습니다 (#731 — 리치 에디터가 크기 없는 img 를 버립니다). 방금 develop 빌드로 실기동한 등록 요청입니다.
diaryId 41542(전체 URL) · 41543(fileKey) 로 남아 있습니다. 호스트가 두 번 붙는 것도, 그 결과가 403 인 것도 이슈 본문 그대로입니다.
어제 제가 «전제와 어긋난다» 며 든 관측(diaryId 41541)은 잘못 읽은 것이었습니다. 그 기록은 #958 브랜치에서 만든 것이고, 그 브랜치의 에디터는 이미 <img src="$fileKey"> 를 넣습니다. develop 경로에서는 전체 URL 이 나가므로 결함이 그대로 있습니다.
PR #1120 을 올렸습니다 — 치환을 HtmlText.toWireContent(uploadedImageUrls) 로 올려 데일리질문과 같은 규칙 하나를 쓰게 했고, 에뮬레이터 실기동으로 diaryId 41544 가 permanent URL 로 정상 저장되는 것까지 확인했습니다.
prefix 가 호스트뿐이고 경로 세그먼트가 없습니다. 따라서 <스킴>://<호스트>/ 를 떼는 toUploadedFileKey() 방식의 역산은 지금 정확한 키를 만듭니다.
이 이슈에 대한 뜻
데일리질문(#549)과 같은 모양으로 구현해도 지금은 맞게 동작합니다. 업로드한 URL 을 기억해 뒀다가 제출 직전 toUploadedFileKey() 로 치환하는 방식에 이 실측이 막는 것은 없습니다. 본문에 이미 들어 있는 영구 URL 을 건드리면 안 된다는 제약(이슈 본문 Note)은 그대로입니다.
AWS_PUBLIC_BASE_URL 은 DEPLOY_PRODUCTION_ENV secret 안의 환경변수라 레포에서 확인되지 않고, 언제든 경로 세그먼트가 붙을 수 있습니다 (예: https://cdn.example.net/assets/). 그렇게 되면 역산이 assets/ 를 키에 섞어 조용히 틀린 키를 만듭니다 — 업로드는 성공하고 이미지만 깨지는 형태라 발견이 늦습니다.
즉 이 실측은 #1017 을 불필요하게 만드는 게 아니라 지금 당장 급하지는 않다는 것만 말해 줍니다. #1017 이 fileKey 를 그대로 넘기게 고치면 이 의존 자체가 사라집니다.
Activity
OnlyReadOneBook commentedon Aug 24, 2026
#1017 작업(PR #1094) 중 BE 실코드를 읽다가 이 이슈에 직접 걸리는 게 두 가지 나와 남깁니다. 구현은 어사인대로 그대로 두고, 재료만 전달합니다.
1. 이중 접두어가 나는 메커니즘
본문에 적힌 «호스트를 한 번 더 붙인다» 는 실측 그대로였고, 원인은
S3Service.promoteReferencedMediaInHtml()의 치환 방식입니다.registerReplacementVariants()(S3Service.java:297-305) 가 치환 맵에 네 가지를 넣는데키 → 영구 URL이 맨 먼저 들어갑니다.그리고 적용은 단순 문자열 치환을 맵 순서대로 도는 것뿐입니다 (
result.replace(...),S3Service.java:190-193). 본문이 전체 URL 을 담고 있으면 첫 항목이 URL 안의 키에 해당하는 substring 을 먼저 갈아치웁니다.두 번째 항목(
전체 URL → 영구 URL)은 이미 원본 문자열이 사라져 매치되지 않습니다. 즉AWS_PUBLIC_BASE_URL설정값과 무관하게, 본문 HTML 경로는 전체 URL 을 보내면 항상 깨집니다. 키를 보내면 같은 첫 항목이 정상적으로 영구 URL 로 바꿔 줍니다.참고로 단일 필드 경로(타임레터 블록
url, 추모 미디어)는promoteMediaKey()가 키를 복원해 키를 반환하므로 URL 이어도 안전합니다 — 이 결함은 HTML 경로 전용입니다.2. 본문의 «이미 저장된 영구 URL 은 건드리지 말 것» 은 BE 가 이미 보장합니다
HTML 루프가
isPromotableKey()로 걸러 영구 키는continue로 건너뛰고 치환 맵에 아예 넣지 않습니다 (S3Service.java:183-184,321-323). 그래서 영구 URL 이 본문에 섞여 있어도 그대로 남습니다 — 이번 작성에서 받은 URL 만 정확히 치환한다는 방침은 그대로 유효하고, 실수로 영구분이 섞여도 서버 쪽에서 한 겹 더 막힙니다.3. 처방이 하나 낡습니다 —
toUploadedFileKey()대신fileKey직접Note 의 «
toUploadedFileKey()로 치환» 은 PR #1094 가 머지되면 쓰지 않는 편이 낫습니다.PhotoUploadRepository.upload()가UploadedFile(fileUrl, fileKey)를 돌려주게 되어, URL 에서 키를 역산할 필요가 없어집니다.역산이 위험한 이유도 위와 같은 결의 문제입니다 —
toUploadedFileKey()는fileUrl == "<스킴>://<호스트>/" + fileKey를 가정하는데,fileUrl은resolvePublicUrl()이buildPublicPrefix() + key로 만들고 그 prefix 는AWS_PUBLIC_BASE_URL설정값이라 경로 세그먼트를 가질 수 있습니다. 그러면 역산 결과에 그 세그먼트가 섞이고,extractStorageKey()는 http 로 시작하지 않는 값을 검증 없이 키로 인정해 틀린 키가 조용히 저장됩니다.AWS_PUBLIC_BASE_URL의 실제 운영값은DEPLOY_PRODUCTION_ENVsecret 안에 있어 레포에서는 확인되지 않습니다. 서버가 열리는 시간대에 presigned 응답의fileUrl형태로 실측해 이 스레드에 덧붙이겠습니다.Sadturtleman commentedon Aug 24, 2026
구현은 했는데, 전제를 실측으로 재확인해야 합니다 (dev 서버 점검 중)
수정 자체는 데일리질문과 같은 모양으로 만들었습니다 — 업로드 URL 을 기억해 두고 제출 직전
toWireContent(uploadedImageUrls)로 치환, 이미 저장된 영구 URL 은 건드리지 않음. 치환 로직은 두 ViewModel 이 공유하도록HtmlText로 올렸습니다. 단위 가드 2건은 수정 전 코드에서 실제로 실패하는 것까지 확인했습니다.다만 «일기 경로에서 전체 URL 을 보내면 403» 이라는 전제와 어긋나는 실측이 하나 있습니다.
어제 #958 브랜치(= develop 기준이라 일기 쪽 fileKey 변환 없음)로 세로 사진을 붙여 일기를 등록했고, 그때 서버가 돌려준 목록입니다.
{"diaryId":41541,"title":"ratio_check", "content":"<p><img src=\"https://dpy1u4p7yz7bh.cloudfront.net/mindrecords/permanent/13/832a3609-5b65-4aeb-9c02-78ff2aba1745.png\" width=\"320\" height=\"533\" />portrait_ratio_check</p>", ...}업로드 때 받은 키는
mindrecords/staging/13/832a3609-…png였습니다. 즉 본문에 전체 staging URL 을 보냈는데 서버가 permanent 로 옮기고 URL 을 다시 써 줬습니다 — 호스트 이중 부착도, 403 도 없었습니다.S3Service.extractStorageKey()가 http 로 시작하는 값도 받아 키를 복원하는 것으로 보입니다.그래서 지금 상태로는 이 수정이 필요 없거나, 반대로 fileKey 를 보내는 쪽이 정본인데 URL 도 관용적으로 받아 주는 것뿐일 수 있습니다. 어느 쪽인지 확정하지 않고 머지하면 «고쳤다고 적힌 것» 만 남습니다.
오늘 재확인하려 했으나 dev 서버가 응답하지 않습니다 (점검 시간대로 보입니다).
서버가 열리면 같은 일기 경로로 (a) 전체 URL (b) fileKey 두 형태를 각각 보내 저장 결과를 비교한 뒤 PR 을 올리겠습니다. 브랜치(
fix/1016-diary-filekey)는 로컬에 두고 있습니다.참고로 develop 그대로는 일기 본문에
<img>자체가 들어가지 않습니다 (#731 — 리치 에디터가 크기 없는 img 를 버립니다). 방금 develop 빌드로 실기동한 등록 요청입니다.{"title":"filekey_check","content":"<p>body</p>","isDraft":false,"todayMood":"HAPPY","imageUrl":null,"receiverIds":[]}즉 이 결함이 사용자에게 보이려면 #731(PR #958)이 먼저 들어가야 하고, 그 순서는 이슈 본문의 «#953 머지 시 활성화» 와 함께 볼 필요가 있습니다.
Sadturtleman commentedon Aug 25, 2026
실측 결과 — 전제가 맞습니다. 어제 제가 적은 반대 실측은 오독이었습니다
서버가 복구돼 같은 일기 생성 API 에 두 형태를 보내 비교했습니다.
img srchttps://…cloudfront.net/mindrecords/staging/13/bbc4…pnghttps://…cloudfront.net/https://…cloudfront.net/mindrecords/permanent/13/bbc4…pngmindrecords/staging/13/e5de…pnghttps://…cloudfront.net/mindrecords/permanent/13/e5de…pngdiaryId 41542(전체 URL) ·41543(fileKey) 로 남아 있습니다. 호스트가 두 번 붙는 것도, 그 결과가 403 인 것도 이슈 본문 그대로입니다.어제 제가 «전제와 어긋난다» 며 든 관측(
diaryId 41541)은 잘못 읽은 것이었습니다. 그 기록은 #958 브랜치에서 만든 것이고, 그 브랜치의 에디터는 이미<img src="$fileKey">를 넣습니다. develop 경로에서는 전체 URL 이 나가므로 결함이 그대로 있습니다.PR #1120 을 올렸습니다 — 치환을
HtmlText.toWireContent(uploadedImageUrls)로 올려 데일리질문과 같은 규칙 하나를 쓰게 했고, 에뮬레이터 실기동으로diaryId 41544가 permanent URL 로 정상 저장되는 것까지 확인했습니다.OnlyReadOneBook commentedon Aug 25, 2026
AWS_PUBLIC_BASE_URL 실측 — 경로 세그먼트 없음
#1017 이 제기한 "URL 역산이 틀린 키를 만들 수 있다"는 위험이 현재 운영 설정에서는 실현되지 않는다는 것을 실서버 응답으로 확인했습니다.
측정: 2026-08-25 17:09 KST ·
POST /api/v1/files/presigned-url(directory=mindrecords) · 업로드는 수행하지 않음prefix 가 호스트뿐이고 경로 세그먼트가 없습니다. 따라서
<스킴>://<호스트>/를 떼는toUploadedFileKey()방식의 역산은 지금 정확한 키를 만듭니다.이 이슈에 대한 뜻
데일리질문(#549)과 같은 모양으로 구현해도 지금은 맞게 동작합니다. 업로드한 URL 을 기억해 뒀다가 제출 직전
toUploadedFileKey()로 치환하는 방식에 이 실측이 막는 것은 없습니다. 본문에 이미 들어 있는 영구 URL 을 건드리면 안 된다는 제약(이슈 본문 Note)은 그대로입니다.다만 #1017 의 값은 유지됩니다
AWS_PUBLIC_BASE_URL은DEPLOY_PRODUCTION_ENVsecret 안의 환경변수라 레포에서 확인되지 않고, 언제든 경로 세그먼트가 붙을 수 있습니다 (예:https://cdn.example.net/assets/). 그렇게 되면 역산이assets/를 키에 섞어 조용히 틀린 키를 만듭니다 — 업로드는 성공하고 이미지만 깨지는 형태라 발견이 늦습니다.즉 이 실측은 #1017 을 불필요하게 만드는 게 아니라 지금 당장 급하지는 않다는 것만 말해 줍니다. #1017 이
fileKey를 그대로 넘기게 고치면 이 의존 자체가 사라집니다.Merge pull request #1120 from Afternote/fix/1016-diary-filekey
Merge develop into fix/731-media-attachment