왜
파싱 알림의 내용이 전부 title 한 줄에 있고 body 는 빈 문자열이다.
title = "${itemName} 파싱이 완료되었어요"
body = ""
OS 푸시 제목은 줄바꿈되지 않고 뒤가 잘린다. 그래서 이름이 길면 정작 무슨 일인지를 말하는 부분이 통째로 사라진다.
나이키 에어포스 1 07 화이트 로우탑 스니… ← "파싱이 완료되었어요" 가 안 보임
지금은 이름을 10글자로 잘라 막고 있는데, 이건 길이를 줄이려는 게 아니라 잘리는 자리를 고르려는 것이다. 그 대가로 실제 상품명 대부분이 일찍 잘린다.
푸시의 body 는 2줄까지 보이고 펼칠 수 있다. 이름을 제목에, 상태를 본문에 두면 제목이 잘려도 잃는 게 없다.
무엇을
| 알림 |
title |
body |
| 성공 (위시 출처) |
${itemName} |
위시 저장이 성공했어요 |
| 성공 (토너먼트 출처) |
${itemName} |
아이템이 등록됐어요 |
| 실패 |
(현행 유지) |
(현행 유지) |
실패는 손대지 않는다. 파싱이 실패하면 이름이 없다 - markFailed() 는 status 만 바꾸고, ItemSnapshot 주석도 "PROCESSING·FAILED 는 이름·가격이 비어 있다" 고 명시한다. ${itemName} 을 쓰면 모든 실패 알림 제목이 fallback("상품")으로 똑같아져 구분이 안 된다.
출처 분기
ItemParsingRecipientResolver.resolveRouting 이 이미 위시/토너먼트를 가른다(NotificationKind 파생에 쓰는 그 값). 새 조회 없이 그 결과로 body 문구를 고른다.
파싱 알림이 뜨는 시점엔 수신자 성격이 갈리지 않는다:
| 등록 경로 |
snapshot |
파싱 |
수신자 |
| 위시에 직접 등록 |
새로 생성 |
O |
위시 주인 |
| 토너먼트에 직접 등록 |
새로 생성 |
O |
올린 사람 |
| 위시 -> 토너먼트로 담기 |
기존 READY 재사용 |
X |
(알림 없음) |
토너먼트에 직접 등록해도 위시리스트에는 안 들어간다(TournamentItemService 가 Wish 를 만들지 않는다). 그래서 "위시 저장" 문구가 토너먼트 등록자에게 가는 일이 없다.
body 문구를 변수로 채운다
notification_templates 는 타입당 한 행(PK=type)이라 위시용·토너먼트용 body 를 따로 둘 자리가 없다. 두 문구가 구조도 달라("위시 저장이 성공했어요" vs "아이템이 등록됐어요") 공통 뼈대 + 변수로도 안 쪼개진다.
body_template 을 변수 하나로 두고 핸들러가 문장을 통째로 채운다.
대가: 백오피스(#252)에서 이 body 문구를 편집할 수 없게 된다. title 은 여전히 템플릿이 소유하므로 편집 여지가 완전히 사라지지는 않는다. 템플릿을 type+kind 복합키로 쪼개는 안도 검토했으나, 이 건 하나 때문에 스키마와 백오피스를 바꾸는 비용이 커 접었다.
절단 로직 완화
제목이 이름뿐이면 OS 가 잘라도 의미가 안 사라지므로 10글자 캡이 불필요해진다. displayName 에서 표시 글자 캡을 걷어내고, 엔티티 불변식(require(title.length <= 255))을 지키는 char 안전망만 남긴다.
옆 핸들러의 절단 누락도 함께 고친다
TournamentItemDeletedHandler 가 같은 개념(itemSnapshotRepository.findById(...)?.name ?: "상품")을 갖고 있는데 절단이 없다. 템플릿이 "${actorName}님이 '${itemName}'을(를) 삭제했어요" 라, 이름이 길면 아래 경로로 알림이 조용히 사라진다.
긴 이름 -> title 이 255자 초과 -> Notification 생성자의 require 위반
-> dispatcher 의 수신자별 runCatching 이 삼킴 -> 삭제 알림이 전 수신자에게 누락
ProductSnapshot 이 이름을 512자까지 허용하므로 도달 가능한 경로다. 이 이슈가 어차피 displayName 을 손대니, 그때 두 핸들러가 공유하도록 옮긴다. 중복된 fallback 상수(FALLBACK_NAME / FALLBACK_ITEM_NAME)도 함께 정리된다.
다만 삭제 알림은 제목에 이름이 문장 안에 박혀 있어 파싱 완료 알림과 사정이 다르다. 파싱 쪽은 제목이 이름뿐이라 캡을 걷어내지만, 삭제 쪽은 뒤에 "…을(를) 삭제했어요" 가 붙으므로 표시 글자 캡이 여전히 필요하다. 공유할 때 캡을 인자로 받게 하는 등 두 용도를 모두 만족시켜야 한다.
함께 바뀌어야 하는 것
- 클라: 알림 카드가 body 도 렌더해야 한다. 지금은
message={notification.title} 로 제목만 그린다(NotificationItem.tsx). 서버만 먼저 나가면 히스토리 카드에 상품명만 뜨고 무슨 일인지 안 보인다. 선행 또는 동시 배포가 필요하다.
*Api 문서 표·example 갱신. "body 는 현재 전 타입 빈 문자열" 서술이 더 이상 참이 아니다.
관련
왜
파싱 알림의 내용이 전부
title한 줄에 있고body는 빈 문자열이다.OS 푸시 제목은 줄바꿈되지 않고 뒤가 잘린다. 그래서 이름이 길면 정작 무슨 일인지를 말하는 부분이 통째로 사라진다.
지금은 이름을 10글자로 잘라 막고 있는데, 이건 길이를 줄이려는 게 아니라 잘리는 자리를 고르려는 것이다. 그 대가로 실제 상품명 대부분이 일찍 잘린다.
푸시의
body는 2줄까지 보이고 펼칠 수 있다. 이름을 제목에, 상태를 본문에 두면 제목이 잘려도 잃는 게 없다.무엇을
${itemName}${itemName}실패는 손대지 않는다. 파싱이 실패하면 이름이 없다 -
markFailed()는 status 만 바꾸고,ItemSnapshot주석도 "PROCESSING·FAILED 는 이름·가격이 비어 있다" 고 명시한다.${itemName}을 쓰면 모든 실패 알림 제목이 fallback("상품")으로 똑같아져 구분이 안 된다.출처 분기
ItemParsingRecipientResolver.resolveRouting이 이미 위시/토너먼트를 가른다(NotificationKind파생에 쓰는 그 값). 새 조회 없이 그 결과로 body 문구를 고른다.파싱 알림이 뜨는 시점엔 수신자 성격이 갈리지 않는다:
토너먼트에 직접 등록해도 위시리스트에는 안 들어간다(
TournamentItemService가Wish를 만들지 않는다). 그래서 "위시 저장" 문구가 토너먼트 등록자에게 가는 일이 없다.body 문구를 변수로 채운다
notification_templates는 타입당 한 행(PK=type)이라 위시용·토너먼트용 body 를 따로 둘 자리가 없다. 두 문구가 구조도 달라("위시 저장이 성공했어요" vs "아이템이 등록됐어요") 공통 뼈대 + 변수로도 안 쪼개진다.body_template을 변수 하나로 두고 핸들러가 문장을 통째로 채운다.대가: 백오피스(#252)에서 이 body 문구를 편집할 수 없게 된다.
title은 여전히 템플릿이 소유하므로 편집 여지가 완전히 사라지지는 않는다. 템플릿을 type+kind 복합키로 쪼개는 안도 검토했으나, 이 건 하나 때문에 스키마와 백오피스를 바꾸는 비용이 커 접었다.절단 로직 완화
제목이 이름뿐이면 OS 가 잘라도 의미가 안 사라지므로 10글자 캡이 불필요해진다.
displayName에서 표시 글자 캡을 걷어내고, 엔티티 불변식(require(title.length <= 255))을 지키는 char 안전망만 남긴다.옆 핸들러의 절단 누락도 함께 고친다
TournamentItemDeletedHandler가 같은 개념(itemSnapshotRepository.findById(...)?.name ?: "상품")을 갖고 있는데 절단이 없다. 템플릿이"${actorName}님이 '${itemName}'을(를) 삭제했어요"라, 이름이 길면 아래 경로로 알림이 조용히 사라진다.ProductSnapshot이 이름을 512자까지 허용하므로 도달 가능한 경로다. 이 이슈가 어차피displayName을 손대니, 그때 두 핸들러가 공유하도록 옮긴다. 중복된 fallback 상수(FALLBACK_NAME/FALLBACK_ITEM_NAME)도 함께 정리된다.다만 삭제 알림은 제목에 이름이 문장 안에 박혀 있어 파싱 완료 알림과 사정이 다르다. 파싱 쪽은 제목이 이름뿐이라 캡을 걷어내지만, 삭제 쪽은 뒤에 "…을(를) 삭제했어요" 가 붙으므로 표시 글자 캡이 여전히 필요하다. 공유할 때 캡을 인자로 받게 하는 등 두 용도를 모두 만족시켜야 한다.
함께 바뀌어야 하는 것
message={notification.title}로 제목만 그린다(NotificationItem.tsx). 서버만 먼저 나가면 히스토리 카드에 상품명만 뜨고 무슨 일인지 안 보인다. 선행 또는 동시 배포가 필요하다.*Api문서 표·example 갱신. "body 는 현재 전 타입 빈 문자열" 서술이 더 이상 참이 아니다.관련