Skip to content

面接の情報を保存するDBをFirestoreに変更した #174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/.env.sample
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
GOOGLE_API_KEY=
GOOGLE_MODEL_NAME=

FIRESTORE_EMULATOR_HOST=
GCP_PROJECT_ID=
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Dockerコンテナが削除される度にVS Codeの拡張機能をインスト
ホストの環境でFastAPIを起動し、RedisのみをDockerで起動する。

```sh
docker compose up redis -d
docker compose up firestore-emulator redis -d

source .venv/bin/activate
task start
Expand Down
8 changes: 4 additions & 4 deletions backend/app/api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from app.domain.repositories.interview_repository import InterviewRepository
from app.domain.repositories.source_code_repository import SourceCodeRepository
from app.infrastructure.llm_clients.google.llm_client import GoogleLLMClient
from app.infrastructure.repositories.firestore.interview_repository import (
FirestoreInterviewRepository,
)
from app.infrastructure.repositories.local.source_code_repository import (
LocalSourceCodeRepository,
)
from app.infrastructure.repositories.redis.interview_repository import (
RedisInterviewRepository,
)
from app.usecase.usecases.get_feedback_usecase import GetFeedbackUseCase
from app.usecase.usecases.get_interview_result_usecase import GetInterviewResultUseCase
from app.usecase.usecases.get_question_usecase import GetQuestionUseCase
Expand All @@ -18,7 +18,7 @@


def get_interview_repository() -> InterviewRepository:
return RedisInterviewRepository()
return FirestoreInterviewRepository()


def get_source_code_repository() -> SourceCodeRepository:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import os

from app.domain.entities.interview_question import InterviewQuestion
from app.domain.repositories.interview_repository import InterviewRepository
from google.cloud import firestore


class FirestoreInterviewRepository(InterviewRepository):
def __init__(self) -> None:
"""コンストラクタ"""
self.db = firestore.Client(project=os.getenv("GCP_PROJECT_ID"))
self.interviews_collection = self.db.collection("interviews")

def create_interview(self, questions: list[InterviewQuestion]) -> None:
"""面接を作成する

Args:
questions (list[InterviewQuestion]): 質問のリスト
"""
# Firestoreのバッチ書き込みを使用して効率的にデータを保存
batch = self.db.batch()

for question in questions:
# 面接IDをドキュメントIDとして使用
# 質問をサブコレクションとして保存
interview_doc = self.interviews_collection.document(question.interview_id)
question_doc = interview_doc.collection("questions").document(
question.question_id
)

# 質問データを辞書形式で保存
question_data = question.to_dict()
batch.set(question_doc, question_data)

# バッチ書き込みを実行
batch.commit()

def get_question(
self,
interview_id: str,
question_id: str,
) -> InterviewQuestion | None:
"""質問を取得する

Args:
interview_id (str): 面接ID
question_id (str): 質問ID

Returns:
InterviewQuestion | None: 質問
"""
try:
# 特定の質問ドキュメントを取得
question_doc = (
self.interviews_collection.document(interview_id)
.collection("questions")
.document(question_id)
.get()
)

if not question_doc.exists:
return None

# ドキュメントデータを辞書形式で取得
question_data = question_doc.to_dict()
if question_data is None:
return None

return InterviewQuestion.from_dict(question_data)

except Exception:
return None

def update_question(
self,
interview_id: str,
question_id: str,
question: InterviewQuestion,
) -> InterviewQuestion:
"""質問の情報を更新する

Args:
interview_id (str): 面接ID
question_id (str): 質問ID
question (InterviewQuestion): 更新後の質問

Returns:
InterviewQuestion: 更新後の質問
"""
# 質問ドキュメントを更新
question_doc = (
self.interviews_collection.document(interview_id)
.collection("questions")
.document(question_id)
)

# 質問データを辞書形式で更新
question_data = question.to_dict()
question_doc.set(question_data)

return question

def get_all_questions(
self,
interview_id: str,
) -> list[InterviewQuestion]:
"""面接の質問をすべて取得する

Args:
interview_id (str): 面接ID

Returns:
list[InterviewQuestion]: 面接の質問のリスト
"""
try:
# 面接の全質問ドキュメントを取得
questions_docs = (
self.interviews_collection.document(interview_id)
.collection("questions")
.stream()
)

questions = []
for doc in questions_docs:
question_data = doc.to_dict()
if question_data is not None:
questions.append(InterviewQuestion.from_dict(question_data))

return questions

except Exception:
return []
9 changes: 9 additions & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ services:
- REDIS_PORT=6379
depends_on:
- redis
- firestore-emulator
networks:
- app-network

Expand All @@ -21,6 +22,14 @@ services:
networks:
- app-network

firestore-emulator:
image: gcr.io/google.com/cloudsdktool/cloud-sdk:emulators
ports:
- "8080:8080"
command: gcloud beta emulators firestore start --host-port=0.0.0.0:8080
networks:
- app-network

networks:
app-network:
driver: bridge
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.12",
"google-cloud-firestore>=2.21.0",
"google-genai>=1.17.0",
"gunicorn>=23.0.0",
"python-dotenv>=1.1.0",
Expand Down
Loading
Loading