👋 개요

지난 개발일지 이후, WAIA 프로젝트의 핵심 기능을 확장하고 사용자 경험을 개선하는 작업을 진행했습니다. 특히 서비스 팔로워 수 트래킹, 개인정보 처리방침 변경 감지 크롤러 구현, 그리고 대시보드 UI 개선에 중점을 두었습니다.


📝 오늘 한 일

  • 팔로워 수 트래킹 기능 구현:
    • Firestore services 컬렉션에 followerCount 필드 추가.
    • 사용자 팔로우/언팔로우 시 followerCount 자동 증감 로직 (handleFollow 함수 내 트랜잭션 사용) 구현.
    • displayServices 함수에 팔로워 수 표시 기능 추가.
    • initializeFollowerCounts() 헬퍼 함수를 통해 기존 서비스의 팔로워 수 초기화 기능 제공.
  • 개인정보 처리방침 변경 감지 크롤러 구현 (로컬 스크립트 방식):
    • Firestore services 컬렉션에 policyUrllastHash 필드 추가.
    • updateServicesWithUrls() 헬퍼 함수를 통해 서비스별 policyUrl 업데이트 및 lastHash 초기화 기능 제공.
    • Firebase Blaze 요금제 이슈로 인해 Cloud Function 대신 로컬 Node.js 스크립 (crawl.js) 방식으로 구현.
    • firebase-admin, axios, crypto 라이브러리를 사용하여 URL 콘텐츠 가져오기, 해시 계산, 변경 감지 및 Firestore 업데이트 로직 구현.
    • waia-service-account-key.jsonnode_modules 폴더를 .gitignore에 추가하여 보안 및 Git 관리 효율성 증대.
  • 대시보드 ‘원문 보기’ 버튼 추가:
    • ‘내 대시보드’의 각 서비스 항목에 ‘원문 보기’ 버튼 추가.
    • 버튼 클릭 시 해당 서비스의 policyUrl을 새 브라우저 탭에서 열도록 구현 (CORS 및 보안 문제로 인한 인앱 표시 대신).
  • UI/UX 개선:
    • 대시보드 서비스 항목 레이아웃 조정: 이름(왼쪽), 상태(중앙), 버튼(오른쪽)으로 균형 있게 배치.
    • 상태 텍스트 및 ‘원문 보기’ 버튼 색상 동적 변경: ‘정상’ 상태는 녹색, 그 외 모든 상태(주의 필요, 크롤링 실패, 확인전)는 주황색으로 표시.

✨ 주요 작업 내용

1. 개인정보 처리방침 크롤러 (로컬 스크립트)

Firebase Cloud Functions 배포 시 Blaze 요금제 요구사항으로 인해, 초기 계획을 변경하여 로컬에서 실행 가능한 Node.js 크롤링 스크립트(crawl.js)를 구현했습니다. 이 스크립트는 Firebase Admin SDK를 사용하여 Firestore에 직접 접근하며, axios로 웹 페이지 내용을 가져오고 crypto 모듈로 해시를 계산하여 변경 여부를 판단합니다.

핵심 코드 (crawl.js):

const admin = require('firebase-admin');
const axios = require('axios');
const crypto = require('crypto');

// 서비스 계정 키를 통해 Firebase Admin SDK 초기화
const serviceAccount = require('./waia-service-account-key.json');
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
const db = admin.firestore();

async function runCrawl() {
  // Firestore에서 서비스 목록 가져오기
  const servicesRef = db.collection('services');
  const snapshot = await servicesRef.get();

  for (const doc of snapshot.docs) {
    const service = doc.data();
    const serviceId = doc.id;

    if (!service.policyUrl) {
      console.log(`[SKIP] ${service.name}: policyUrl이 없습니다.`);
      continue;
    }

    try {
      // policyUrl에서 HTML 내용 가져오기
      const response = await axios.get(service.policyUrl, {
        headers: { 'User-Agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' }
      });
      const html = response.data;
      const currentHash = crypto.createHash('sha256').update(html).digest('hex');

      // 해시 비교 및 상태 업데이트
      if (service.lastHash && service.lastHash !== currentHash) {
        console.log(`  -> [!!] 변경 감지! ${service.name} 상태를 '주의 필요'로 업데이트합니다.`);
        await servicesRef.doc(serviceId).update({
          status: '주의 필요',
          lastHash: currentHash,
          lastUpdated: admin.firestore.FieldValue.serverTimestamp()
        });
      } else {
        console.log(`  -> [OK] ${service.name} 변경 없음. 해시값만 업데이트합니다.`);
        await servicesRef.doc(serviceId).update({
          lastHash: currentHash,
          lastUpdated: admin.firestore.FieldValue.serverTimestamp()
        });
      }
    } catch (error) {
      console.error(`  -> [ERROR] ${service.name} 크롤링 실패:`, error.message);
      await servicesRef.doc(serviceId).update({ status: '크롤링 실패' });
    }
  }
  console.log('\n크롤링 작업이 모두 완료되었습니다.');
}

runCrawl();

2. 대시보드 UI 개선 및 ‘원문 보기’ 버튼

사용자 대시보드의 가독성을 높이고 기능을 추가했습니다. 각 서비스 항목은 이름, 상태, ‘원문 보기’ 버튼으로 구성되며, Flexbox를 활용하여 세 요소가 균형 있게 정렬되도록 했습니다. 상태에 따라 색상이 동적으로 변경되어 시각적인 피드백을 강화했습니다.

핵심 코드 (app.js - displayDashboard 함수 내):

// ... (생략) ...
const statusClass = service.status === '정상' ? 'ok' : 'warning';
const buttonClass = service.status === '정상' ? 'policy-btn-ok' : 'policy-btn-warning';

item.innerHTML = `
  <span class="name">${service.name}</span>
  <span class="status ${statusClass}">${service.status}</span>
  <div class="service-actions">
    ${service.policyUrl ? `<a href="${service.policyUrl}" target="_blank" class="policy-link-btn ${buttonClass}">원문 보기</a>` : ''}
  </div>
`;
// ... (생략) ...

겪었던 문제 및 해결 과정

  • Firebase Cloud Functions 배포 문제:
    • 문제: Cloud Functions 배포 시 Blaze 요금제(종량제)로의 업그레이드가 필수적이라는 오류 발생. 이는 Cloud Build API 사용 정책 때문이었음.
    • 해결: Firebase 서버리스 환경 대신, 사용자의 로컬 환경에서 직접 실행 가능한 Node.js 크롤링 스크립트(crawl.js) 방식으로 전환. 이를 위해 Firebase Admin SDK를 사용하고 서비스 계정 키를 통한 인증 방식을 도입.
  • firebase init 과정 중 파일 덮어쓰기 문제:
    • 문제: firebase init 실행 시 기존에 생성해 둔 functions/package.jsonfunctions/index.js 파일 덮어쓰기 여부 질문 발생.
    • 해결: 사용자에게 ‘덮어쓰지 않음(N)’을 명확히 안내하여 기존 작업물 보존.
  • node_modules 및 서비스 계정 키 Git 관리:
    • 문제: node_modules 폴더와 민감한 waia-service-account-key.json 파일이 Git 저장소에 포함될 위험.
    • 해결: .gitignore 파일에 node_modules/waia-service-account-key.json을 추가하여 Git 추적에서 제외.
  • 개인정보 처리방침 인앱 표시 보안 문제:
    • 문제: 사용자가 요청한 ‘개인정보 처리방침 전문 인앱 표시’ 기능이 CORS 및 XSS 보안 취약점을 야기할 수 있음.
    • 해결: 보안 위험을 설명하고, 대신 새 탭에서 원문 페이지를 여는 표준적이고 안전한 방식으로 구현.

💡 새롭게 배운 점

  • Firebase Cloud Functions 배포 정책: Node.js 런타임의 Cloud Functions 배포는 Blaze 요금제와 Cloud Build API에 의존한다는 점을 명확히 인지.
  • 로컬 Node.js 스크립트 활용: 서버리스 환경이 아닌 로컬 환경에서 Firebase Admin SDK를 사용하여 데이터베이스와 상호작용하는 방법 및 그 장단점 (무료 사용, 수동 실행, 보안 관리) 체득.
  • 웹 보안 (CORS, XSS): 클라이언트 측에서 외부 콘텐츠를 직접 가져오거나 렌더링할 때 발생하는 보안 위험의 중요성 재확인 및 안전한 대안 제시의 필요성.
  • Git .gitignore의 중요성: 민감 정보 및 불필요한 빌드 아티팩트(예: node_modules)를 효과적으로 관리하는 방법.

🚀 다음 계획

  • 이메일 알림 기능: 서비스 상태가 ‘주의 필요’로 변경될 경우 사용자에게 이메일 알림을 보내는 기능 (Blaze 요금제 업그레이드 시 Cloud Functions로 구현 가능).
  • UI/UX 추가 개선: ‘마지막 확인 시각’ 표시 등 사용자에게 유용한 정보 추가.
  • 서비스 추가/관리 UI: 관리자가 웹 앱 내에서 직접 서비스를 추가하거나 수정할 수 있는 기능.