백로그
0할 일
0진행 중
0리뷰
0완료 (15일)
2오프라인 묵상 프론트엔드 (IndexedDB + Stimulus + Service Worker)
## 목표 오프라인에서 묵상 기록을 작성/저장하고, 온라인 복귀 시 자동 동기화하는 프론트엔드 구현 ## 구현 내용 ### 1. IndexedDB 래퍼 모듈 (`app/javascript/lib/offline_store.js`) - IndexedDB 'logbible' 데이터베이스 생성 (version 1) - 'pending_meditations' object store: { id(auto), qt_content_id, meditation_date, personal_meditation, action_plan, prayer_topic, mood_after, is_tongtok_completed, created_at, synced } - CRUD 메서드: save(), getAll(), getPending(), markSynced(), delete() - importmap에 등록 ### 2. offline_sync Stimulus 컨트롤러 (`app/javascript/controllers/offline_sync_controller.js`) - data-controller="offline-sync"를 묵상 폼에 추가 - 온라인/오프라인 상태 감지 (navigator.onLine + online/offline 이벤트) - 오프라인 시 폼 제출 가로채기: IndexedDB에 저장 + 성공 메시지 표시 - 온라인 복귀 시 자동 동기화: pending entries를 POST /qt/meditations/sync로 전송 - 동기화 상태 표시: 배지/아이콘으로 "N건 동기화 대기" 표시 ### 3. Service Worker 캐시 확장 (`app/views/pwa/service-worker.js`) - QT 페이지 캐시 전략 추가: /qt/today, /qt/day?day=N 페이지를 Network First + Cache Fallback - 기존 precache에 추가하지 말고, 동적 캐시로 처리 (방문한 페이지만 캐시) - 캐시 이름: 'qt-pages-v1' - 오프라인 시 캐시된 QT 페이지 제공 (기존 offline.html 대신) ### 4. 오프라인 상태 UI - 오프라인 배너: 상단에 "오프라인 모드 - 묵상이 로컬에 저장됩니다" 표시 - 동기화 진행 표시: "동기화 중..." → "동기화 완료" 토스트 - 묵상 폼에 로컬 저장 아이콘 표시 (오프라인 시) - 디자인 시스템 활용: shared/_card, shared/_badge 파셜 패턴 참고 ### 5. 묵상 폼 수정 (`app/views/qt/meditations/_form.html.erb`) - 폼에 data-controller="offline-sync" 추가 - data-action="submit->offline-sync#handleSubmit" 추가 - 동기화 상태 표시 영역 추가 ## 파일 목록 (이 파일들만 수정/생성) - `app/javascript/lib/offline_store.js` (생성) - `app/javascript/controllers/offline_sync_controller.js` (생성) - `app/views/pwa/service-worker.js` (수정 - 캐시 전략 추가만) - `app/views/qt/meditations/_form.html.erb` (수정 - data attributes 추가) - `app/views/qt/today.html.erb` (수정 - 오프라인 배너 추가) - `config/importmap.rb` (수정 - offline_store 핀 추가) ## 주의사항 - Importmap 환경: npm 패키지 사용 불가, 순수 JS만 사용 - 기존 Stimulus 컨트롤러 패턴 참고 (app/javascript/controllers/) - Tailwind CSS v4 사용 중 (app/assets/tailwind/application.css 참고) - 기존 Service Worker 구조 유지하면서 확장 (덮어쓰기 금지) - turbo:submit-end 이벤트 활용 가능 - 테스트는 백엔드 에이전트가 담당 (프론트엔드는 구현만)
오프라인 묵상 백엔드 (Sync API + 테스트)
## 목표 오프라인 저장된 묵상 데이터를 서버에 동기화하는 API 엔드포인트 + 테스트 ## 구현 내용 ### 1. Sync API 라우트 (`config/routes.rb`) ```ruby namespace :qt do resources :meditations, only: [:create, :update] do member do post :organize end collection do post :sync # 새로 추가 end end end ``` ### 2. Sync 액션 (`app/controllers/qt/meditations_controller.rb`) 기존 MeditationsController에 sync 액션 추가: ```ruby def sync results = [] sync_params[:meditations].each do |med_data| content = current_user.qt_sessions_contents.find_by(id: med_data[:qt_content_id]) next unless content meditation = current_user.user_meditations.find_or_initialize_by( qt_content_id: med_data[:qt_content_id], meditation_date: med_data[:meditation_date] ) # 충돌 해결: 서버에 이미 데이터가 있고 더 최신이면 서버 우선 if meditation.persisted? && meditation.updated_at > Time.parse(med_data[:created_at]) results << { qt_content_id: med_data[:qt_content_id], status: "conflict", server_data: meditation_json(meditation) } next end meditation.assign_attributes( personal_meditation: med_data[:personal_meditation], action_plan: med_data[:action_plan], prayer_topic: med_data[:prayer_topic], mood_after: med_data[:mood_after], is_tongtok_completed: med_data[:is_tongtok_completed] ) if meditation.save results << { qt_content_id: med_data[:qt_content_id], status: "synced", id: meditation.id } else results << { qt_content_id: med_data[:qt_content_id], status: "error", errors: meditation.errors.full_messages } end end render json: { results: results } end ``` ### 3. Strong Parameters ```ruby def sync_params params.permit(meditations: [:qt_content_id, :meditation_date, :personal_meditation, :action_plan, :prayer_topic, :mood_after, :is_tongtok_completed, :created_at]) end ``` ### 4. 테스트 (`test/controllers/qt/meditations_controller_test.rb`) 기존 테스트 파일에 sync 테스트 추가: - POST /qt/meditations/sync 인증 필요 - POST /qt/meditations/sync 새 묵상 생성 (synced) - POST /qt/meditations/sync 기존 묵상 업데이트 (synced) - POST /qt/meditations/sync 충돌 시 서버 우선 (conflict) - POST /qt/meditations/sync 잘못된 qt_content_id 무시 - POST /qt/meditations/sync 빈 배열 처리 - POST /qt/meditations/sync 여러 건 배치 처리 ### 5. qt_sessions_contents 헬퍼 sync에서 qt_content_id 유효성 검사를 위해 현재 사용자가 접근 가능한 콘텐츠 확인 필요. 기존 QtController에 이미 세션→콘텐츠 관계가 있으므로 참고: - User → qt_participants → qt_sessions → qt_theme → qt_contents ## 파일 목록 (이 파일들만 수정/생성) - `config/routes.rb` (수정 - sync 라우트 추가) - `app/controllers/qt/meditations_controller.rb` (수정 - sync 액션 + sync_params 추가) - `test/controllers/qt/meditations_controller_test.rb` (수정 - sync 테스트 추가) ## 주의사항 - UUID PK: ApplicationRecord의 set_uuid 패턴 사용 - SQLite: parallelize(workers: 1) 필수 - 기존 create/update/organize 액션 절대 수정 금지 - 기존 테스트 전체 통과 확인 필수 (bin/rails test) - JSON 응답 (render json:) 사용 - Turbo Stream 아님 - 충돌 해결: 서버 데이터가 더 최신이면 서버 우선, 그렇지 않으면 클라이언트 데이터 적용 - content의 접근 권한 검증: 사용자가 참여 중인 세션의 콘텐츠만 동기화 허용