Repository navigation
v1.0.2 — 질문 투표 기능 + 공지 배너
질문에 투표를 붙여 토론이 성립하도록 하고, 그것을 알릴 공지 배너를 함께 추가했다.
질문 투표
질문 작성 질문 상세 답변 목록
┌─────────────┐ ┌────────────────┐ ┌──────────────────┐
│☑ 투표 붙이기 │ │ 당신의 선택은? │ │[안 봐준다] 민지 │
│ ● 봐준다 │ → │ 봐준다 23% │ → │ 한 번 하면 또… │
│ ● 안 봐준다 │ │ ▓▓▓░░░░░░░░░░░ │ │ │
│ + 선택지 추가│ │ 안 봐준다 77% │ │[봐준다] 유키 │
└─────────────┘ │ 52명 참여 │ │ 사정을 들어보는… │
└────────────────┘ └──────────────────┘
- 질문 작성 시 선택지를 2~4개까지 직접 입력한다. 선택 사항이며, 붙이지 않은 질문은 이전과 동일하게 동작한다
- 투표는 로그인이 필요하다. 비로그인 사용자에게는 좋아요와 같은 방식으로 로그인을 유도한다
- 결과는 투표해야 보인다. 투표 전에는 선택지와 참여 인원만 노출된다
- 선택은 바꿀 수 있다. 다만 이미 작성한 답변의 뱃지는 작성 시점으로 고정되어, 그때의 입장이 보존된다
- 선택지도 자동 번역된다. 제목·본문과 같은 요청에 실려 나가므로 DeepL 호출 횟수는 늘지 않는다
데이터 모델
poll.optionIds : ["o1", "o2"] 순서 보존용 배열
poll.labels : { o1: { ko, ja } } 번역 결과
poll.counts : { o1: 12 } FieldValue.increment 대상
questions/{id}/votes/{userId} = { optionId, createdAt }
counts 를 배열이 아니라 맵으로 둔 이유는 FieldValue.increment 가 필드 경로를 대상으로 하기 때문이다. 배열이면 원소만 증가시킬 수 없어 통째로 덮어써야 하고, 동시에 두 명이 투표하면 한 명의 표가 유실된다.
votes 는 기존 likes 서브컬렉션과 동일한 구조이며, toggleLike 의 트랜잭션 패턴을 따랐다.
번역 파이프라인 변경
기존에는 최상위 필드만 번역할 수 있었다. 선택지는 poll.labels.o1 같은 중첩 경로에 있고 개수도 질문마다 다르다.
- 번역 대상 목록을 상수 배열이 아니라 문서를 받아 계산하는 함수로 변경
- 점 표기 경로를 읽는
readPath를 추가. 쓰기(ref.update)는 점 경로를 원래 지원하므로 변경 없음
호환성
poll 필드가 없는 기존 질문은 투표 UI가 렌더되지 않는다. 마이그레이션이 필요 없다.
공지 배너
랜딩과 홈 상단에 공지 배너를 띄운다. 문구는 Firestore config/notice 문서 하나를 Firebase 콘솔에서 직접 수정해 배포 없이 갱신한다.
| 항목 | 선택 | 이유 |
|---|---|---|
| 캐싱 | 태그가 아니라 revalidate: 60 |
콘솔에서 사람이 고치는 데이터라 코드에서 updateTag 를 부를 수 없다. 공지에 1분 지연은 문제되지 않는다 |
| 닫기 상태 | localStorage가 아니라 쿠키 | 서버가 읽어야 닫은 사용자에게 배너를 아예 렌더하지 않는다. localStorage면 한 번 그려졌다 사라져 깜빡인다 |
| 쿠키 값 | true 가 아니라 공지 id |
id가 바뀌면 새 공지로 인식돼 다시 노출된다 |
| 렌더링 | Suspense 로 격리 |
쿠키를 읽으면 페이지가 동적이 된다. 배너만 감싸 랜딩의 정적 프리렌더(◐)를 유지했다 |
운용
- 문구만 수정 →
message만 변경 (이미 닫은 사용자에게는 계속 노출되지 않음) - 새 공지 →
id를 변경 (닫았던 사용자에게도 다시 노출됨) - 내리기 →
active=false
배포 후 필요한 작업
Firebase 콘솔 → Firestore 에 아래 문서를 만들어야 배너가 노출된다. 투표 기능은 이 작업과 무관하게 즉시 동작한다.
- 컬렉션
config/ 문서 IDnotice id(string),active(boolean),message(map:ko,ja)
Full Changelog: v1.0.1...v1.0.2