콘텐츠로 이동

📝 AI 기반 수강신청 시스템 통합 PRD (v1.0)

수강신청 시스템 개발을 따라해 보면서 최신 AI 기술을 적용한 개발 방식을 이해할 수 있도록 도와주는 프로젝트입니다.

1. 개발 가이드

1.1 개발 환경

  • Node.js: 22 이상
  • Python: 3.10 이상
  • Firebase CLI: 최신 버전

1.2 개발 방법

  1. 로컬 환경 및 GitHub 연동
  2. Firebase 콘솔 설정
  3. Firebase CLI 준비
  4. 로컬 프로젝트 초기화
  5. PRD(docs/prd.md) 기반 개발 세팅
  6. MVP 개발 및 배포
  7. PRD 전체 내용 개발 및 배포

1.3 프로젝트 아키텍처

graph LR
    User((사용자)) --> Hosting[Firebase Hosting]
    Hosting -- "URL 라우팅 (/api/**)" --> Rewrites{firebase.json Rewrites}
    Rewrites --> Functions[Cloud Functions<br/>FastAPI Handler]
    Functions --> Firestore[(Cloud Firestore)]
    Functions --> Gemini[Gemini API<br/>AI Agent]
    Gemini --> MCP[MCP Tools]

    subgraph "Firebase 요금제: Blaze (권장)"
        Hosting
        Rewrites
        Functions
        Firestore
    end

2. 개발 단계별 상세 가이드

2.1 로컬 환경 및 GitHub 연동 (Step 1)

  1. VS Code에서 course-registration 폴더 생성 및 열기.
  2. README.md, docs/prd.md 파일 생성 및 내용 작성.
  3. GitHub 저장소 생성 후 초기 push (main 브랜치).

2.2 Firebase 콘솔 설정 (Step 2)

  1. Firebase Console 프로젝트 추가.
  2. 프로젝트 ID 확인 (예: course-registration-89cf5).
  3. 요금제 업그레이드: 좌측 하단 SparkBlaze 선택 (Cloud Functions 실행 필수 조건).
  4. Firestore Database 생성: 좌측 메뉴 빌드 > Firestore Database 클릭 후 '데이터베이스 만들기' 클릭 (리전: asia-northeast3 권장).

2.3 Firebase CLI 준비 (Step 3)

npm install -g firebase-tools
firebase login

2.4 로컬 프로젝트 초기화 및 상세 설정 (Step 4)

2.4.1 Firebase 초기화

  1. 터미널에서 firebase init 실행.
  2. 다음 세 가지 기능 선택: Firestore, Functions, Hosting.
  3. Existing Project 선택 후 2.2에서 만든 프로젝트 연결.
  4. Firestore 설정:
  5. What file should be used for Firestore Rules? → Enter (기본값: firestore.rules)
  6. What file should be used for Firestore indexes? → Enter (기본값: firestore.indexes.json)
  7. Functions 설정:
  8. Language: Python 선택.
  9. Do you want to install dependencies with pip now? → Yes
  10. Hosting 설정:
  11. Public directory: frontend/dist.
  12. Configure as a single-page app? → Yes.
  13. Set up automatic builds and deploys with GitHub? → Yes.
  14. GitHub Actions 설정 (위에서 Yes 선택 시 자동 진행):
  15. For which GitHub repository? → YOUR_USERNAME/course-registration (본인 저장소 입력)
  16. Set up the workflow to run a build script before every deploy? → Yes
  17. What script should be run before every deploy? → npm ci && npm run build
  18. Set up automatic deployment to your site's live channel when a PR is merged? → Yes
  19. What is the name of the GitHub branch associated with your site's live channel? → main

2.4.2 초기 폴더 세팅 (Frontend & Backend)

  1. Frontend: Vite + React + TypeScript 템플릿으로 생성
  2. Backend: Firebase Functions이 자동 생성한 functions/ 폴더 사용
npm create vite@latest frontend -- --template react-ts

⚠️ 주의: Target directory "frontend" is not empty 경고가 나오면, "Remove existing files and continue"를 선택하세요. firebase init이 생성한 임시 파일(index.html 등)이므로 삭제해도 안전합니다.

2.4.3 GitHub Actions Workflow 경로 수정

firebase init 시 자동 생성된 GitHub Actions workflow 파일들은 루트 디렉토리에서 npm 명령을 실행하도록 되어 있습니다. 하지만 우리 프로젝트는 frontend/ 디렉토리에 React 앱이 있으므로, workflow 파일을 수정해야 합니다.

.github/workflows/firebase-hosting-merge.yml:

# 수정 전
- run: npm ci && npm run build

# 수정 후
- run: cd frontend && npm ci && npm run build

.github/workflows/firebase-hosting-pull-request.yml:

# 수정 전
- run: npm ci && npm run build

# 수정 후
- run: cd frontend && npm ci && npm run build

💡 이 수정을 하지 않으면 GitHub Actions 배포 시 package-lock.json not found 에러가 발생합니다.

2.4.4 firebase.json 설정

firebase.json에서 public 경로를 frontend/dist로 변경하고, /api로 들어오는 요청은 백엔드(fastapi_handler)가 처리하고 나머지는 프론트엔드 SPA /index.html로 라우팅을 위해 rewrites 설정을 추가합니다:

{
  "hosting": {
    "public": "frontend/dist",
    "rewrites": [
      {
        "source": "/api/**",
        "function": "fastapi_handler",
        "region": "asia-northeast3"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

firebase.json (Functions 및 Hosting 설정) Firebase 프로젝트의 핵심 설정 파일입니다. 백엔드(Functions)의 predeploy는 배포 전 로컬에서 함수 메타데이터를 추출하기 위해 반드시 필요합니다.

{
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "ignore": ["venv", ".git", "firebase-debug.log", "*.local"],
      "runtime": "python313",
      "predeploy": [
        "cd functions && python3.13 -m venv venv && . venv/bin/activate && pip install -r requirements.txt"
      ]
    }
  ],
  "hosting": {
    "public": "frontend/dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "predeploy": ["npm --prefix frontend run build"],
    "rewrites": [
      {
        "source": "/api/**",
        "function": "fastapi_handler",
        "region": "asia-northeast3"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

2.4.5 Git 및 브랜치 전략 설정

.gitignore 파일이 root 폴더를 비롯하여 frontend, functions 폴더에 모두 생성되어 있을 것입니다. AI를 이용하여 .gitignore 파일 내용을 보강하도록 합니다. 루트 폴더에 .gitignore를 생성하여 민감한 정보를 반드시 차단합니다 (.env 등).

AI 프롬프트 예시:

.gitignore 파일들을 보강해줘. functions/ 폴더에는 Python 백엔드가 있고, frontend/ 폴더에는 React 앱이 있어. 루트, functions/, frontend/ 각각의 .gitignore에 추가해야 할 표준적인 무시 파일 목록을 알려줘. (예: .env, node_modules, venv, pycache, .DS_Store 등)

이후 git 업데이트를 진행합니다.

# 1. 변경사항 커밋 및 Main 브랜치 Push
git add .
git commit -m "Setup: Firebase Init & Project Structure"
git push origin main

# 2. Develop 브랜치 생성 및 이동
git checkout -b develop
git push -u origin develop

💡 이후 모든 개발은 develop 브랜치에서 진행하며, 배포 시점에 main 브랜치로 Merge합니다. main 브랜치에 코드가 푸시되면 Firebase Hosting에 자동 배포됩니다.


2.5 MVP 개발 및 배포 (Step 5)

🎯 목표: Google 로그인이 동작하는 기본 웹페이지 배포

2.5.1 Backend (FastAPI 통합)

FastAPI를 Firebase Functions v2와 통합하여 /api/health 엔드포인트를 제공합니다.

구현 방식: ASGI 어댑터를 수동으로 구현하여 FastAPI의 모든 기능을 사용할 수 있습니다.

📌 참고: Firebase Functions v2에서 FastAPI를 통합하는 방법은 Reddit 커뮤니티 토론을 참고했습니다. 기본적으로 Firebase Functions v2 SDK는 ASGI 앱을 직접 지원하지 않으므로, 수동 ASGI 어댑터를 구현하여 FastAPI를 실행합니다.

# functions/main.py

from firebase_functions import https_fn, options
import firebase_admin
from firebase_admin import initialize_app
from fastapi import FastAPI
import asyncio
import json

# Firebase Admin 초기화 (중복 방지)
if not firebase_admin._apps:
    initialize_app()

# 전역 지역 설정 (서울)
options.set_global_options(region="asia-northeast3")

# FastAPI 앱 생성
app = FastAPI()

@app.get("/api/health")
def health_check():
    return {"status": "ok"}

@https_fn.on_request()
def fastapi_handler(req: https_fn.Request) -> https_fn.Response:
    """
    수동 ASGI 어댑터 구현
    Firebase Functions v2에서 FastAPI를 실행하기 위해 ASGI 프로토콜을 직접 구현
    """
    try:
        # ASGI scope 구성
        asgi_request = {
            "type": "http",
            "method": req.method,
            "path": req.path,
            "headers": [(k.lower().encode(), v.encode()) for k, v in req.headers.items()],
            "query_string": req.query_string or b"",
        }

        # ASGI receive 함수
        async def receive():
            return {"type": "http.request", "body": req.get_data() or b"", "more_body": False}

        # 응답 데이터 수집
        response_body, response_headers, response_status = [], [], 200

        # ASGI send 함수
        async def send(message):
            nonlocal response_body, response_headers, response_status
            if message["type"] == "http.response.start":
                response_status = message.get("status", 200)
                response_headers = message.get("headers", [])
            elif message["type"] == "http.response.body":
                response_body.append(message.get("body", b""))

        # FastAPI를 asyncio로 실행
        async def run_asgi():
            await app(asgi_request, receive, send)

        asyncio.run(run_asgi())

        # 응답 조합
        full_body = b"".join(response_body)
        headers_dict = {
            k.decode() if isinstance(k, bytes) else k: v.decode() if isinstance(v, bytes) else v
            for k, v in response_headers
        }

        return https_fn.Response(response=full_body, status=response_status, headers=headers_dict)

    except Exception as e:
        return https_fn.Response(
            response=json.dumps({"error": f"Internal Server Error: {str(e)}"}),
            status=500,
            headers={"Content-Type": "application/json"},
        )

주요 포인트:

  • Firebase Functions v2는 ASGI 앱을 직접 지원하지 않음
  • ASGI 프로토콜(scope, receive, send)을 수동으로 구현
  • asyncio.run()으로 FastAPI의 비동기 핸들러를 실행
  • 모든 FastAPI 기능(라우팅, validation, async/await) 사용 가능

2.5.2 Frontend (Google 로그인 + 백엔드 통신)

  • Firebase Console > 빌드 > Authentication에서 시작하기 클릭.
  • Sign-in method 탭에서 Google 선택 후 사용 설정 스위치 ON 및 저장.
  • 설정 > 승인된 도메인 탭으로 이동.

    • 기본적으로 localhost가 추가되어 있지만, 127.0.0.1도 "도메인 추가" 버튼을 눌러 추가해야 합니다. (로컬 에뮬레이터 테스트 시 필수)
  • frontend/src/firebase.js: Firebase SDK 설정

  • Firebase Console > 프로젝트 개요 페이지에서 웹 (웹) 아이콘 클릭.
  • 앱 닉네임 입력(예: course-registration-web) 후 앱 등록.
  • Firebase 호스팅 설정 체크박스는 체크하지 않습니다 (이미 CLI로 설정함).
  • Firebase SDK 추가에 나오는 대로 npm install firebase 실행. 실행 전 반드시 frontend 폴더로 이동(cd frontend).
  • SDK 설정 및 구성에 나오는 코드 전체(const firebaseConfig = ..., initializeApp(firebaseConfig) 등 포함)를 복사.

AI 프롬프트 예시 (설정 파일 생성):

내가 복사한 Firebase SDK 설정 코드를 줄게.

// (복사한 내용 전체 붙여넣기)
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// ...
const firebaseConfig = {
  apiKey: "AIzaSy...",
  authDomain: "...",
  // ...
};
const app = initializeApp(firebaseConfig);

이 내용과 동일한 내용으로 frontend/src/firebase.ts 파일을 만들어줘. 대신 frontend/.env 파일을 만들어서 환경 변수를 불러와야해. (Vite 환경 변수 규칙인 VITE_ 접두사 사용) 또한, 로컬 환경(localhost)에서 실행될 때는 자동으로 Firebase Emulator(Auth: 9099, Firestore: 8080)에 연결되도록 코드를 추가해줘.

결과 예시:

  1. frontend/.env
VITE_FIREBASE_API_KEY=AIzaSy...
VITE_FIREBASE_AUTH_DOMAIN=course-registration-79d64.firebaseapp.com
...
  1. frontend/src/firebase.ts
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";

const firebaseConfig = {
  apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
  // ...
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);

// 로컬 개발 환경(localhost)에서는 자동으로 에뮬레이터에 연결합니다.
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
  console.log("🔧 Using Firebase Emulators");
  connectAuthEmulator(auth, "http://127.0.0.1:9099");
  connectFirestoreEmulator(db, "127.0.0.1", 8080);
}

export default app;

AI 프롬프트 예시 (MVP 개발):

frontend 폴더에 React 기반의 MVP 코드를 작성해줘.

기본 레이아웃을 잡고, Firebase Auth Google 로그인 기능을 구현하고, 로그인 여부에 따라 'Google 로그인 버튼' 또는 '환영 메시지'가 보이도록 해줘. 추가로 백엔드 통신을 테스트할 수 있는 버튼도 만들어줘. 이 버튼을 클릭하면 /api/health 엔드포인트를 호출하고, 응답({"status":"ok"})을 alert로 표시해줘. Tailwind CSS를 사용하여 깔끔한 스타일을 적용해줘.

2.5.3 MVP 동작 확인 (Firebase Emulator)

Firebase Emulator를 사용하면 프론트엔드와 백엔드를 동시에 실행하여 통합 테스트할 수 있습니다.

1. Firebase 에뮬레이터 설정 확인

firebase.json에 에뮬레이터 포트가 설정되어 있는지 확인합니다. Firebase 기본 포트를 사용하므로 생략 가능하지만, 프론트엔드(vite.config.ts)와의 포트 고정 및 충돌 방지를 위해 설정을 명시하는 것을 권장합니다:

{
  "emulators": {
    "auth": { "port": 9099 },
    "functions": { "port": 5001 },
    "firestore": { "port": 8080 },
    "hosting": { "port": 5002 },
    "ui": { "enabled": true }
  }
}
2. Firebase Emulator 실행
# 루트 폴더에서 실행
firebase emulators:start

에뮬레이터가 실행되면:

  • Emulator UI: http://127.0.0.1:4000 (Functions, Firestore, Authentication, Hosting 등) 접속하여 확인
2. 통합 테스트
  1. Emulator UI: Hosting Emulator 접속
  2. Google 로그인 테스트: 로그인 버튼 클릭 시 나타나는 "Auth Emulator IDP Login Widget" 팝업에서 "Add new account"를 클릭하여 테스트 계정을 생성합니다.
  3. Firestore 사용자 생성 확인:
  4. Emulator UI -> Firestore 탭으로 이동
  5. users 컬렉션이 생성되었는지, 내 UID 문서가 추가되었는지 확인
  6. role 필드가 student로 되어 있는지 확인
  7. 백엔드 통신 확인: 프론트엔드에서 "백엔드 통신 확인" 버튼 클릭 시 {"status":"ok"} 응답 확인

2.5.4 체크리스트: 작동하지 않는다면?

Backend 관련

[!IMPORTANT] Lessons Learned: Firestore SDK 선택 Firebase Functions 환경에서는 google-cloud-firestore 대신 firebase-adminfirestore 클라이언트를 사용하는 것이 배포 시 권한 문제를 방지하는 지름길입니다. 로컬에서는 둘 다 잘 작동할 수 있지만, 실제 서버(Production)에서는 firebase-admin을 통한 일관된 인증이 필수적입니다.

  • Database '(default)' not found: Firebase 콘솔의 Firestore Database 메뉴에서 데이터베이스가 생성되어 있는지 확인하세요. firebase.json 설정만으로는 데이터베이스가 자동 생성되지 않습니다.
  • Dependencies 확인: functions/requirements.txt에 다음 패키지들이 포함되어 있는지 확인
    firebase-functions>=0.5.0
    firebase-admin>=6.5.0
    fastapi
    uvicorn
    
  • Python 버전: Python 3.10 이상 필수 (3.13 권장)
  • 가상 환경 재생성: 문제가 지속되면 functions/venv 삭제 후 재설치
    cd functions
    rm -rf venv __pycache__
    python3 -m venv venv
    source venv/bin/activate  # Windows: venv\Scripts\activate
    pip install -r requirements.txt
    
  • Firebase Admin 초기화 에러: main.py에서 if not firebase_admin._apps: 체크 확인
Frontend 관련
  • Firebase SDK 설정: frontend/src/firebase.tsfirebaseConfig 값이 Firebase Console의 프로젝트 설정과 일치하는지 확인
  • Authentication 도메인: Firebase Console > Authentication > Settings > Authorized domains에 127.0.0.1, localhost 뿐만 아니라 배포된 호스팅 도메인(*.web.app, *.firebaseapp.com)이 추가되어 있는지 확인
  • GitHub Secrets 값 확인: 설정한 Secrets 값에 오타가 없는지 확인하세요 (예: courseourse로 입력하는 등 사소한 오타 주의). 특히 VITE_FIREBASE_AUTH_DOMAINVITE_FIREBASE_PROJECT_ID 값이 실제 프로젝트 정보와 콤마 하나까지 일치하는지 확인하세요.
  • 400: redirect_uri_mismatch 에러: 위 Authorized domains 설정이 되어 있음에도 에러가 난다면, Google Cloud 콘솔에서 직접 확인이 필요합니다.
  • 백엔드 배포 에러 (Build failed with status: FAILURE): Cloud Functions v2(Gen 2) 배포 시 권한 부족으로 실패하는 경우입니다.
  • 'Cloud 빌드 서비스 계정' (Cloud Build Service Account) 역할 부여를 확인
  • 설정 후 다시 firebase deploy --only functions를 실행합니다. (로그에 asia-northeast3이 보이면 정상)
  • OAuth 2.0 클라이언트 ID 섹션에서 Web client 선택
  • 승인된 JavaScript 원본https://PROJECT_ID.web.app 추가
  • 승인된 리디렉션 URIhttps://PROJECT_ID.web.app/__/auth/handler 추가 (매우 중요!)
  • 설정 후 반영까지 약 5분 정도 소요될 수 있습니다.
Emulator 관련
  • 포트 충돌: 다른 프로세스가 포트를 사용 중이면 에뮬레이터가 실행되지 않음
  • Functions: 5001, Hosting: 5002, Firestore: 8080, Auth: 9099
  • 충돌 시 firebase.jsonemulators 섹션에서 포트 변경
  • 로그 확인: 에뮬레이터 터미널 출력에서 에러 메시지 확인
  • Failed to load function → Python 문법 오류 또는 import 에러
  • 504 Gateway Timeoutmain.py의 ASGI 어댑터 구현 확인

2.5.5 배포 및 지속적 배포 (CD) - 완전 자동화

이제 프론트엔드와 백엔드 모두 GitHub Actions를 통해 자동으로 배포됩니다.

Google Cloud IAM 설정 (배포 전 필수!)

GitHub Actions가 자동으로 배포하려면 서비스 계정에 적절한 권한이 부여되어야 합니다.

⚠️ 참고: [ID]-compute@... 계정은 Cloud Functions를 처음 배포한 후에 IAM 페이지에 나타납니다. 첫 배포 후 권한을 추가하세요.

  1. Google Cloud Console > IAM 접속
  2. course-registration 프로젝트 선택
  3. 엑세스 권한 부여 버튼을 누르고 주 구성원 검색으로 github, compute, firebase 키워드로 검색

1️⃣ GitHub Actions 서비스 계정 (github-action-...)

  • Cloud 함수 관리자 (Cloud Functions Admin) - Firebase Functions V2 함수 배포 필수
  • 서비스 계정 사용자 (Service Account User) - Firebase Functions V2 함수 배포 필수
  • 서비스 사용량 소비자 (Service Usage Consumer) - Firebase Functions V2 함수 배포 필수
  • Artifact Registry 관리자 (Artifact Registry Administrator) - Firebase Functions V2 함수 배포 필수
  • Cloud Build 편집자 (Cloud Build Editor) - Firebase Functions V2 함수 빌드 필수
  • Firebase 규칙 관리자 (Firebase Rules Admin) - Firestore 규칙 배포용
  • Cloud Datastore 인덱스 관리자 (Cloud Datastore Index Admin) - Firestore 인덱스 배포용
  • 보안 비밀 관리자 뷰어 (Secret Manager Viewer) - Secret 존재 확인용
  • 보안 비밀 관리자 보안 비밀 접근자 (Secret Manager Secret Accessor) - 배포 시 검증용
  • Cloud Run 뷰어 (Cloud Run Viewer) - Cloud Run 서비스 확인용

2️⃣ Default Compute Service Account ([ID]-compute@...) (첫 배포 후 생성)

  • Cloud Datastore 사용자 (Cloud Datastore User) - Firestore 접근용
  • 보안 비밀 관리자 보안 비밀 접근자 (Secret Manager Secret Accessor) - 런타임에 API 키 접근용
  • Cloud 빌드 서비스 계정 (Cloud Build Service Account) - Cloud Build가 V2 함수를 빌드

3️⃣ Firebase 서비스 에이전트 (firebase-adminsdk-...)

  • 보안 비밀 관리자 관리 (Secret Manager Admin) - Secret 생성 및 관리 권한
  • 서비스 계정 사용자 (Service Account User) - 서비스 계정 대행 권한
  • 서비스 계정 토큰 생성자 (Service Account Token Creator) - 토큰 기반 인증용
  • Firebase 인증 관리자 (Firebase Authentication Admin) - 인증 관리 권한
  • Firebase Admin SDK 관리자 서비스 에이전트 - Admin SDK 연동 필수 권한
GitHub Secrets 설정 (배포 필수!)

GitHub Actions 배포 시 Firebase Config를 안전하게 주입하기 위해 GitHub Repository Secrets를 설정해야 합니다.

설정 경로: GitHub Repository → SettingsSecrets and variablesActions

"New repository secret" 버튼을 눌러 다음 6개의 secret을 추가하세요:

Secret Name Value (.env 파일 참고)
VITE_FIREBASE_API_KEY Firebase Console에서 확인한 API Key
VITE_FIREBASE_AUTH_DOMAIN PROJECT_ID.firebaseapp.com
VITE_FIREBASE_PROJECT_ID Firebase 프로젝트 ID
VITE_FIREBASE_STORAGE_BUCKET PROJECT_ID.firebasestorage.app
VITE_FIREBASE_MESSAGING_SENDER_ID Firebase Console에서 확인
VITE_FIREBASE_APP_ID Firebase Console에서 확인

💡 왜 필요한가요?

  • frontend/.env 파일은 .gitignore에 포함되어 GitHub에 업로드되지 않습니다
  • GitHub Actions 빌드 시 환경 변수가 없으면 invalid-api-key 에러 발생
  • Secrets를 사용하면 민감한 정보를 안전하게 관리하면서 빌드에 주입 가능

ℹ️ 참고: Firebase Config의 모든 값(API Key, Project ID 등)은 클라이언트 측 공개 식별자이며, 공개되어도 안전합니다. 실제 보안은 Firebase Security Rules, Authentication, 도메인 제한으로 관리됩니다. (Firebase 공식 문서)

자동 배포 흐름
  1. 로컬에서 develop 브랜치 작업 후 push
  2. developmain 브랜치로 Pull Request 생성 및 Merge
  3. GitHub Actions가 다음 순서로 작업을 자동 수행합니다:
  4. 백엔드/데이터베이스: functions 빌드 및 Cloud Functions & Firestore 배포
  5. 프론트엔드: frontend 빌드 및 Firebase Hosting 배포

아래 수정 사항들을 모두 반영한 후 main 브랜치로 PR을 생성하여 Merge하면 자동 배포가 실행됩니다.

🔧 Workflow 파일 수정 방법

.github/workflows/firebase-hosting-merge.yml 파일을 열어 steps 섹션에 다음 내용을 추가합니다:

# 1. Python 설치 (백엔드 빌드용)
- name: Set up Python 3.13
  uses: actions/setup-python@v5
  with:
    python-version: "3.13"

# 2. 구글 클라우드 인증 및 백엔드/Firestore 배포
- name: Authenticate to Google Cloud
  uses: google-github-actions/auth@v2
  with:
    credentials_json: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_COURSE_REGISTRATION_79D64 }}

- name: Deploy Cloud Functions and Firestore
  run: npx firebase-tools deploy --only functions,firestore --project course-registration-79d64

[!IMPORTANT] GEMINI_API_KEY 설정 (Secret Manager)

  1. 터미널에서 아래 명령어를 실행하여 API 키를 등록하세요:
    firebase functions:secrets:set GEMINI_API_KEY
    
  2. functions/main.pyfastapi_handlersecrets=["GEMINI_API_KEY"]가 추가되어 있는지 확인하세요.
  3. 이 방식은 GitHub Secrets보다 안전하며 프로덕션 환경에서 권장되는 방식입니다.

💡 왜 백엔드 배포는 Merge 시에만 수행되나요?

  • 동기화: 검증이 완료된 코드만 main 브랜치를 통해 실제 서버에 반영하여 프론트엔드와 백엔드의 버전을 일치시킵니다.

ℹ️ 참고: 본 프로젝트는 서울(asia-northeast3) 지역을 사용합니다.

배포 후 프론트엔드에서 백엔드를 호출하려면 allUsers에게 Cloud Run 호출자 권한을 부여해야 합니다.

  • 1차 관문 (IAM): allUsers 권한은 "누구나 서버 문 앞까지 올 수 있다"는 뜻입니다. 웹 앱 특성상 익명의 브라우저 요청을 받아야 하므로 필수입니다.
  • 2차 관문 (Application): 실제 보안은 코드 내부에서 이루어집니다. Firebase Auth 토큰을 검증하여, 로그인된 유효한 사용자만 기능을 사용할 수 있도록 철저히 보호합니다.

[설정 방법]

  1. Google Cloud Console > Cloud Run 접속
  2. fastapi-handler 서비스 클릭 → 보안(Security) 탭 이동
  3. 인증(Authentication) 섹션 확인
  4. 🔘 공개 액세스 허용 (Allow unauthenticated invocations) 선택
  5. 저장 (Save) 클릭
수동 배포 (필요한 경우)

자동 배포와 별개로, 특정 시점에 강제로 배포하고 싶다면 로컬에서 실행할 수 있습니다:

firebase deploy --only functions  # 백엔드만
firebase deploy --only hosting    # 프론트엔드만
보안 및 비용 관리 (Security & Cost) - 필수 설정

배포 후 무단 사용 방지 및 비용 절감을 위해 functions/main.py에 다음 안전장치를 적용합니다.

1. 비용 폭탄 방지 (Resource Limits) options.set_global_options를 사용하여 공격이나 오류로 인한 과다 청구를 원천 차단합니다.

# functions/main.py 전역 설정
options.set_global_options(
    region="asia-northeast3",
    max_instances=10,       # 최대 10개 인스턴스 제한 (DDoS 방어)
    memory=options.MemoryOption.MB_256, # 최소 메모리 사용
    timeout_sec=30,         # 30초 초과 시 강제 종료
)

2. 무단 호출 방지 (CORS) 허용된 프론트엔드 도메인에서 오는 요청만 받도록 CORSMiddleware를 설정합니다.

# functions/main.py 미들웨어 추가
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:5173",  # 로컬 개발
        "http://127.0.0.1:5002",  # Firebase Hosting Emulator
        "http://localhost:5002",
        "https://PROJECT_ID.web.app", # Firebase Hosting 도메인
        "https://PROJECT_ID.firebaseapp.com",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

이제 배포 준비가 완료되었습니다. main 브랜치로 PR을 생성하여 Merge하면 자동 배포가 실행됩니다.


2.6 PRD 전체 내용 개발 및 배포 (Step 6)

🎯 목적: PRD의 모든 기능 구현 및 안정적인 배포 프로세스 확립

2.6.1 개발 워크플로우 (Development Workflow) 🚦

모든 개발은 안정성을 위해 다음 4단계 프로세스를 엄격히 준수합니다.

  1. 기능 개발 (Feature): feature/기능명 브랜치 생성 후 작업
  2. 로컬 테스트 (Local): Firebase 에뮬레이터에서 충분히 검증
  3. 통합 테스트 (Develop): develop 브랜치 병합 후 스테이징(Staging) 테스트
  4. 운영 배포 (Main): 검증 완료 후 main 병합 및 운영 환경(Production) 배포

2.6.2 로컬 테스트 가이드 (Firebase Emulator) 🛠️

파이썬 스크립트나 Admin SDK를 사용하여 로컬 에뮬레이터에 연결하려면 다음 두 가지 환경변수 설정이 필수입니다.

  1. FIRESTORE_EMULATOR_HOST="127.0.0.1:8080": 실제 클라우드 DB가 아닌 로컬 가상 DB(에뮬레이터)를 바라보게 합니다.
  2. GCLOUD_PROJECT="YOUR_PROJECT_ID": 에뮬레이터 환경에서 인증(Auth) 절차 없이 프로젝트를 식별하기 위한 임시 신분증 역할을 합니다. (예: course-registration-79d64)
  3. 에뮬레이터는 프론트엔드 별도 빌드(npm run build)가 필요하므로, 먼저 프론트엔드 폴더에서 npm run build를 실행해줍니다.
  4. 프론트엔드 개발 진행중에는 vite 개발 서버(npm run dev)로 우선 테스트하고, 빌드 이후에 Firebase Hosting을 통해 빌드 테스트를 진행합니다.
💡 주요 활용 상황
상황 설명
DB 초기 데이터 삽입 seed.py 등을 실행하여 로컬 DB에 테스트용 데이터를 넣을 때
백엔드 로직 단독 테스트 특정 비즈니스 로직(수강신청 등)을 터미널에서 직접 실행하며 디버깅할 때
단위 테스트 (Unit Test) pytest 등을 실행하여 빠르고 안전하게 로직을 검증할 때
🚀 설정 방법 (터미널)
  • Mac / Linux:
    export FIRESTORE_EMULATOR_HOST="127.0.0.1:8080"
    export GCLOUD_PROJECT="course-registration-79d64"
    
  • Windows (PS):
    $env:FIRESTORE_EMULATOR_HOST="127.0.0.1:8080"
    $env:GCLOUD_PROJECT="course-registration-79d64"
    

[!CAUTION] 데이터 오염 주의: 환경 변수 설정 없이 스크립트를 실행하면 실제 운영(Production) DB에 데이터가 들어갈 수 있습니다. 에뮬레이터 UI(localhost:4000)에 데이터가 보이지 않는다면 설정을 다시 확인하세요.


2.6.3 백엔드 테스트 전략 & 로드맵

안정적인 백엔드 개발을 위해 기능 구현 전 테스트 코드를 먼저 작성하는 TDD 방식을 권장합니다. 모든 테스트는 functions/tests/ 디렉토리에 구성되어 있습니다.

📂 테스트 디렉토리 구조
└── functions/tests/     # ✅ 테스트 코드 모음
    ├── conftest.py      # 설정 및 공유 Fixture (에뮬레이터 설정, 자동 시딩 등)
    ├── test_api.py      # 엔드포인트(API) 테스트 (requests 활용)
    └── test_logic.py    # 순수 비즈니스 로직 테스트 (DAL 직접 호출)
🛠️ TDD 개발 워크플로우
  1. 테스트 코드를 먼저 작성: 요구사항에 맞춰 test_logic.pytest_api.py를 먼저 업데이트합니다.
  2. 테스트 실행 (실패 확인): pytest tests/ 명령으로 테스트가 실패하는 것을 확인합니다.
  3. 코드 구현: dal.pymain.py 등을 수정하여 실제 로직을 구현합니다.
  4. 테스트 재실행 (통과 확인): 구현 결과가 테스트를 통과하는지 확인합니다.
🚀 테스트 실행 명령어
cd functions
./venv/bin/pytest tests/
🎯 테스트 원칙 (Philosophy)

모든 백엔드 테스트는 [1. 초기화(Initialize) → 2. 시딩(Seed) → 3. 테스트(Test)]의 3단계 구성을 따릅니다.

  1. 초기화 (Initialize): 에뮬레이터 환경에서 이전 테스터의 잔류 데이터를 배제하고 깨끗한 상태로 시작합니다.
  2. 시딩 (Seed): seed.py 등을 실행하여 테스트에 필요한 기초 데이터를 주입합니다. (AI가 작성했든 수동으로 작성했든 이 과정은 필수입니다.)
  3. 테스트 (Test): 준비된 환경에서 실제 비즈니스 로직(Happy/Sad Path)을 검증합니다.
  4. 멱등성 (Idempotency): 위 과정을 통해 테스트는 언제 어디서 실행해도 같은 결과를 내야 합니다.
  5. 환경 분리: 반드시 Firebase 에뮬레이터 환경에서 실행하여 운영 데이터 오염을 방지합니다.
🤖 AI에게 테스트 요청하는 방법 (Design First)

무작정 테스트 코드를 작성해달라고 하면 AI도 헷갈려합니다. 먼저 prd.md에 정의된 기능 명세를 바탕으로 함수의 뼈대(Scaffolding)를 만들고, 그 다음에 테스트를 요청하세요.

  1. Step 1. 기능 명세 기반 함수 껍데기 생성

    "prd.md를 참고해서 [기능명]에 필요한 백엔드 함수와 데이터 입출력 양식만 먼저 만들어줘. (함수 내용은 pass로 비워둬도 좋아)"

  2. Step 2. 테스트 코드 생성

    "방금 만든 함수들을 검증할 수 있는 pytest 코드를 작성해줘."

통합 프롬프트 예시

prd.md의 [기능명] 명세를 검증하고 싶어. 다음 단계로 개발해줘.

  1. 먼저 functions/main.py에 필요한 빈 함수(Empty Function)와 입출력 모델을 정의해줘.
  2. 그 다음, 이 함수들이 정상 작동하는지 검증하는 pytest 코드를 tests/ 폴더에 작성해줘.
  3. 테스트 실행 전 seed.py를 활용해 DB를 초기화하는 Fixture도 잊지 마.
🤖 AI에게 백엔드 개발 요청 시 팁 (Backend Prompting Tips)

실제 배포 환경(Production)에서의 권한 오류를 방지하기 위해 다음 문구를 프롬프트에 추가하는 것이 좋습니다:

"Firebase Functions 환경이므로, 모든 DB 작업은 google-cloud-firestore 대신 firebase-adminfirestore 클라이언트를 사용해서 일관성 있게 구현해줘."

이 한 문장으로 로컬과 서버 환경 사이의 인증 방식 차이로 인한 500 에러를 사전에 차단할 수 있습니다.

🚀 테스트 실행 예시 (test_api.py)

현재 구현된 test_api.py는 위 원칙을 충실히 따르고 있습니다.

# functions 디렉토리에서 실행
pytest test_api.py
  • 검증 항목: 강의 조회, 수강 신청 트랜잭션(원자성), 중복 방지, 잔여 인원 실시간 계산 등.
Backend 확장 (진행 로드맵)
  • Step 1: Database Design & Setup
  • Firestore 모델(User, Course, Enrollment) 정의
  • 초기 강의 데이터 생성 스크립트 작성 (DB Seeding)
  • Step 2: Firestore Integration
  • functions/main.py에 Firestore 클라이언트 연결
  • 데이터 접근 계층(DAL) 구현
  • Step 3: Core Business Logic (CRUD)
  • 강의 조회/필터링, 수강신청(트랜잭션), 내 강의 조회 API 구현
  • Step 4: MCP Tool Implementation
  • 비즈니스 로직을 AI가 쓸 수 있는 도구(Tool)로 포장
  • Step 5: Agent Development
  • Gemini 연동 및 대화형 에이전트 엔드포인트(/api/chat) 구현

2.6.4 프론트엔드 테스트 전략 & 로드맵 & 관리자 설정

프론트엔드는 QA 체크리스트를 만들어서 AI가 테스트 코드를 작성하도록 유도한다.

기능 검증 체크리스트 (QA Checklist) ✅

개발이 완료된 후 다음 항목들을 순서대로 확인해주세요.

  1. 로그인 및 사용자 생성

    • [ ] Google 로그인 버튼을 클릭하여 로그인이 정상적으로 되는가?
    • [ ] (에뮬레이터) Auth Emulator IDP Login Widget이 뜨고 계정 생성이 가능한가?
    • [ ] Firestore users 컬렉션에 내 UID로 된 문서가 생성되었는가? (기본 role: "student")
  2. 권한 분리 확인 (학생)

    • [ ] 로그인 직후 상단 네비게이션 바에 '관리자' 메뉴가 보이지 않는가?
    • [ ] 주소창에 직접 /admin을 입력했을 때 /student 페이지로 리다이렉트 되는가?
  3. 관리자 승격 및 접속

    • [ ] seed.py --admin 명령어가 에러 없이 실행되었는가?
    • [ ] Firestore에서 해당 유저의 role 필드가 "admin"으로 변경되었는가?
    • [ ] 새로고침 후 상단 네비게이션 바에 '관리자' 메뉴가 나타나는가?
    • [ ] '관리자' 메뉴 클릭 시 Admin Dashboard로 정상 진입하는가?
  4. 관리자 기능 검증

    • [ ] (관리자 계정) 대시보드에 현재 개설된 과목 목록이 표시되는가?
    • [ ] 과목 생성: '과목 추가' 버튼을 눌러 새 과목(과목ID, 과목명, 교수명, 정원)을 추가할 수 있는가?
    • [ ] 과목 생성시 과목ID는 중복되지 않아야 한다.: 과목ID가 중복되면 에러 메시지를 반환하고 생성을 중단해야 한다.
    • [ ] 과목 수정: '수정' 버튼을 눌러 과목명이나 정원을 변경하면 리스트에 반영되는가?
    • [ ] 과목 삭제: 목록에서 과목을 삭제하면 리스트에서 사라지는가?
    • [ ] (Firestore) courses 컬렉션에 데이터가 실제로 추가/삭제되었는가?
  5. 관리자 화면에 유저 관리 및 통계 검증

    • [ ] 유저 목록: 가입된 모든 유저의 목록이 표시되는가? (현재 미구현 가능성 있음)
    • [ ] 관리자 승격: 특정 유저에게 UI에서 직접 '관리자' 권한을 부여할 수 있는가?
    • [ ] 통계 정확성: 상단 대시보드의 '총 수강생', '등록률' 등이 실제 데이터와 일치하는가?
  6. 학생 채팅 및 수강신청 검증

    • [ ] (학생 계정) /student 페이지에 접속하여 채팅창이 뜨는가?
    • [ ] 채팅 테스트: "수강신청 가능한 과목 알려줘"라고 입력하면 Agent가 과목 답변을 주는가?
    • [ ] 수강신청 명령: "Python 과목 신청해줘"라고 입력하면 신청이 완료되는가?
    • [ ] 상태 반영: 신청 후 하단 '내 수강 목록' 테이블에 과목이 즉시 추가되는가?
    • [ ] 중복 신청 방지: 이미 신청한 과목을 다시 신청했을 때, Agent가 "이미 신청된 과목입니다"라고 안내하는가?

💡 개발 팁: AI와 함께하는 TDD 개발

위 체크리스트를 기반으로 더욱 견고한 코드를 작성하고 싶다면, AI에게 다음과 같이 요청해보세요.

"넌 지금부터 시니어 프론트엔드 개발자야. 위 [QA 체크리스트]의 모든 항목을 검증할 수 있는 Vitest + React Testing Library 테스트 코드를 먼저 작성해줘. (Mocking이 필요한 API 호출은 vi.fn()으로 처리하고, 로딩/에러 상태와 접근성(ARIA)도 고려해줘.)

그 다음, 작성된 테스트를 100% 통과하는 완성도 높은 React 컴포넌트를 TypescriptTailwind CSS로 구현해줘."

Frontend 확장 (진행 로드맵)

백엔드 API와 연동하여 실제 사용자가 경험할 UI/UX를 개발합니다.

  • Step 1: Project Structure & Routing
  • react-router-dom 설치 및 라우팅 설정 (/, /login, /student, /admin)
  • 공통 레이아웃 (Layout.tsx, Navbar.tsx) 및 페이지 컴포넌트 스케폴딩
  • Step 2: Authentication Integration
  • Firebase Auth 연동 및 useAuth 커스텀 훅 구현 (구글 로그인)
  • Protected Route 구현 (로그인 안 된 사용자 접근 제한)
  • Step 3: Student Course Registration UI (Chat)
  • ChatInterface 컴포넌트 구현 (메시지 목록, 입력창)
  • /api/chat 엔드포인트 연동 및 에이전트 응답 렌더링
  • 내 수강신청 현황 시각화 (CourseTable)
  • Step 4: Admin Dashboard
  • 강의 목록 조회 및 관리자용 테이블 구현
  • 강의 생성/수정/삭제 (CRUD) 모달 구현
  • Step 5: Voice Interface (STT/TTS)
  • Web Speech API를 활용한 음성 인식(STT) 및 음성 합성(TTS) 기능 추가
  • "마이크 버튼"으로 대화하기 기능 구현
  • Step 6: Final Deployment
  • 통합 테스트 및 Firebase Hosting 배포 (admin-dashboard 포함)
프론트엔드 사용자 계정 중 관리자 권한 설정 (Admin Setup) 👑

프론트엔드 테스트는 관리자 권한 사용자와 일반 학생 사용자로 나뉜다. 초기 관리자 계정은 보안상 자동으로 생성되지 않으며, 백엔드 스크립트를 통해 수동으로 승격시켜야 합니다.

  1. Frontend에서 먼저 로그인하여 users 컬렉션에 문서를 생성합니다.
  2. Firebase Auth 또는 Firestore에서 본인의 UID를 확인합니다.
  3. 백엔드 스크립트 실행:
# 로컬 에뮬레이터 환경 (functions 폴더에서 실행)
# 사전에 환경변수 설정 필수 (export FIRESTORE_EMULATOR_HOST="127.0.0.1:8080")

# 방법 1: UID 사용 (기존 방식)
python seed.py --admin "YOUR_USER_UID"

# 방법 2: 이메일 사용 (추천, @가 포함되면 자동으로 이메일로 검색)
python seed.py --admin "admin@example.com"

실행 후 에뮬레이터 또는 배포된 사이트에서 새로고침하면 관리자 메뉴 접근이 가능해집니다.

이제 모든 QA 체크리스트를 기반으로 프론트엔드 개발을 진행하면 됩니다. 그리고, QA 체크리스트는 계속 업데이트하면서 개발을 진행하면 됩니다.


3. Firebase 비용 가이드

💡 결론: 개인 학습 및 소규모 테스트용으로는 거의 0원에 가깝게 운영 가능합니다.

3.1 요금제 선택

  • Spark (무료): 기본 테스트 가능, 단 Cloud Functions 사용 불가
  • Blaze (종량제): 카드 등록 필요, 하지만 무료 할당량이 매우 넉넉

3.2 주요 서비스별 무료 할당량

서비스 무료 할당량 초과 시 비용
Authentication 무제한 무료 -
Firestore 읽기 5만 회/일, 쓰기 2만 회/일 10만 회당 ~$0.06
Cloud Functions 호출 200만 회/월 100만 회당 ~$0.40
Hosting 저장 10GB, 전송 360MB/일 GB당 ~$0.15

3.3 비용 주의 사항

  • Gemini API: Firebase와 별개로 비용 발생 가능 (대량 호출 시)
  • Firestore 무한 루프: 코드 오작동으로 읽기/쓰기가 폭증할 수 있음 → 항상 limit() 사용
  • 외부 API 호출: 네트워크 전송료(Egress) 소량 발생 가능

3.4 비용 폭탄 방지 팁

  1. 예산 알림 설정: Google Cloud Console → 예산 및 알림 → 월 $1 초과 시 메일 알림
  2. 함수 메모리 최적화: 배포 시 최소 사양(256MB)으로 설정
  3. Firestore 인덱스 관리: 필요한 인덱스만 생성