Skip to content

refactor(afternote): 추모 영상·썸네일을 값 객체로 묶고 picked/server 축을 사진과 대칭으로 (#1406 후속) #1561

Closed

Description

@OnlyReadOneBook

작업 유형

refactor — 동작 변경 없는 구조·책임·경계 개선

주 담당 모듈

afternote — 애프터노트

개요

AfternoteTypeForm.Memorial 의 미디어 칸이 사진 축과 영상 축에서 서로 다른 방식으로 출처를 표현한다. 영상 축만 칸이 늘고 규칙이 손으로 지켜진다.

사진 — 칸이 출처를 말한다

val pickedPhotoUri: String? = null   // 이 폼에서 고른 것
val photoUrl: String? = null         // 서버에 저장된 것
fun displayPhotoUri(): String? = pickedPhotoUri ?: photoUrl

삭제는 pickedPhotoUri = null 한 줄이고 표시는 자동으로 서버로 폴백한다.

영상 — 한 칸이 둘을 겸하고, 출처를 문자열에서 추론한다

videoUrl 이 로컬 픽과 서버 URL 을 겸하므로 출처 판정이 videoUrl?.isLocalContentUri() 다(MemorialMediaSourceBottomSheet.removableMemorialMediaTargets). 로컬로 덮이는 순간 서버 값이 그 칸에서 사라져, #1406 이 그림자 두 칸(serverVideoUrl·serverThumbnailUrl)을 붙여 되돌아갈 자리를 만들었다. 결과적으로 영상 축만 4칸이고, 삭제는 EditorFormMutations.withMemorialVideo 의 명시 복원 분기가 됐다.

게다가 영상과 썸네일이 항상 함께 움직여야 한다는 규칙이 타입에 없다. 「영상이 바뀌면 썸네일 무효」·「되돌리면 둘 다 되돌림」을 호출부마다 손으로 지키고 있고, 실제로 한쪽만 바뀌는 상태가 만들어진다 — 영상을 지운 뒤 늦게 도착한 썸네일 업로드가 setMemorialThumbnail 로 썸네일 칸만 채우면 videoUrl = null, thumbnailUrl = <url> 이 된다(#1550 이 다루는 레이스). 서버 계약에서도 이 둘은 짝이다 — BE AfternotePlaylist.MemorialVideo 가 @Embeddable 로 두 칸을 묶어 통째 교체한다.

필요한 동작

  1. 영상과 썸네일을 값 객체로 묶는다. 썸네일만 nullable 로 두어 「영상 없이 썸네일만」이라는 상태를 타입에서 배제한다.
data class MemorialVideoAttachment(val url: String, val thumbnailUrl: String? = null)
  1. 사진과 같은 picked/server 축으로 정리한다 — pickedVideo: MemorialVideoAttachment? + serverVideo: MemorialVideoAttachment?, 표시는 pickedVideo ?: serverVideo. 영상 축 4칸이 2칸이 된다.
  2. 삭제는 pickedVideo = null 로 좁힌다 — withMemorialVideo 의 복원 분기가 사라지고, 삭제 가능 판정도 스킴 추론(isLocalContentUri()) 대신 pickedVideo != null 이 된다.
  3. enteredContentOrNull 의 제외 마스크가 copy(serverVideo = null) 하나로 줄어든다. 지금은 세 칸을 손으로 나열한다.
  4. 소비처를 함께 옮긴다 — EditorFormState 파생 getter, EditorFormSnapshot(프로세스 데스 왕복), prefill(AfternoteTypeForm.applyPrefill), 저장 매퍼(videoMediaInput·MemorialMediaUrls), MemorialVideoUpload 파라미터.
  5. 동작 변경이 없음을 기존 테스트로 고정한다 — MemorialVideoServerOriginTest·AfternoteEditorMemorialMediaRemoveTest·MemorialMediaRemovableTargetsTest·EditorContentSignatureCompletenessTest 가 수정 없이(또는 타입 치환만으로) 통과해야 한다.

자식 이슈

No response

참고

Activity

  1. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    area:afternote애프터노트 기능 영역
    on Aug 30, 2026
  2. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  3. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    착수 순서를 참고란에 적어 둔 것과 반대로 뒤집었습니다 — 리팩터가 먼저입니다(#1565), #1550 은 그 위에 스택(#1567).

    이유는 두 가지입니다.

    1. 리팩터가 fix(afternote): 수정 모드 서버 영상→로컬 교체 후 삭제는 여전히 거짓 삭제 — 폼은 비지만 저장 후 서버 영상 잔존 #1406 을 구조로 해소합니다. picked/server 로 가르는 순간 삭제가 고른 것만 걷어내고 표시가 서버 값으로 저절로 돌아가므로, fix(afternote): 서버 영상을 로컬로 교체한 뒤 지우면 서버 표시로 되돌린다 (#1406) #1527 이 그림자 칸으로 고치던 거짓 삭제가 사라집니다. 그래서 fix(afternote): 서버 영상을 로컬로 교체한 뒤 지우면 서버 표시로 되돌린다 (#1406) #1527 은 닫고 회귀 테스트만 refactor(afternote): 추모 영상 편집 상태를 값 객체로 캡슐화한다 (#1561) #1565 로 옮겼습니다.
    2. fix(afternote): 추모 영상 썸네일 실패에 재시도 수단이 없다 — 결손이 저장으로 영구 확정된다 (#705 5번 분리) #1550 을 옛 모양 위에 먼저 올리면 곧바로 다시 쓰게 됩니다. 리팩터가 같은 파일을 건드리므로, 리뷰어가 곧 갈아엎을 코드를 두 번 읽게 됩니다.

    참고란의 「#1527 머지 이후에 착수한다」·「#1550 이 먼저」는 이 두 PR 로 대체됐습니다.

  4. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:afternote애프터노트 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions