Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8861fcb
Chore: Add dnspython 및 pymongo 패키지 추가
Coldot Mar 21, 2025
f883712
Feat: MongoDB 클라이언트 설정
Coldot Mar 21, 2025
1c6fd82
Chore: 프로젝트 구조 설정
Coldot Mar 21, 2025
27eb839
Refactor: Move to `src/main/auth`
Coldot Mar 21, 2025
8473b62
Chore: .gitignore 파일에 .doc/ 및 .idea/ 추가
Coldot Mar 21, 2025
994af44
Fix: MongoDB 클라이언트 URI를 SRV 형식으로 변경
Coldot Mar 21, 2025
bd41400
Feat: 요청 유효성 검사 예외 처리 핸들러 추가
Coldot Mar 21, 2025
e3e683f
Feat: 카테고리 추천 큐 클래스 추가 및 SQS 메시지 전송 기능 구현
Coldot Mar 21, 2025
dfdca27
Feat: 카테고리 추천 요청을 위한 리포지토리 클래스 추가
Coldot Mar 21, 2025
9525c46
Feat: 카테고리 추천 모델 클래스 추가
Coldot Mar 21, 2025
6d81417
Feat: 카테고리 추천 서비스 클래스 추가 및 추천 요청 생성, 상태 조회, 결과 업데이트 기능 구현
Coldot Mar 21, 2025
fbe93e2
Feat: AI 내부 API 라우터 추가 및 카테고리 추천 결과 업데이트 엔드포인트 구현
Coldot Mar 21, 2025
efe0f61
Feat: AI 공개 API 라우터 추가 및 카테고리 추천 요청 및 상태 조회 엔드포인트 구현
Coldot Mar 21, 2025
655cec7
Feat: AI 관련 의존성 추가 및 라우터 통합
Coldot Mar 21, 2025
7db4223
Fix: 카테고리 추천 결과 업데이트 엔드포인트의 경로 수정
Coldot Mar 21, 2025
f7fc560
Test: AI 테스트를 위한 초기화 파일 추가
Coldot Mar 21, 2025
973bc26
Test: 카테고리 추천 큐 클래스의 SQS 메시지 전송 기능에 대한 단위 테스트 추가
Coldot Mar 21, 2025
6fc069a
Test: 카테고리 추천 리포지토리에 대한 단위 테스트 추가
Coldot Mar 21, 2025
c025880
Test: 카테고리 추천 모델에 대한 단위 테스트 추가
Coldot Mar 21, 2025
ed2327f
Test: 카테고리 추천 서비스에 대한 단위 테스트 추가
Coldot Mar 21, 2025
0f63c39
Test: AI 내부 API 라우터의 카테고리 추천 결과 업데이트 기능에 대한 단위 테스트 추가
Coldot Mar 21, 2025
d24529d
Test: AI 공개 API 라우터의 카테고리 추천 요청 및 상태 조회 기능에 대한 단위 테스트 추가
Coldot Mar 21, 2025
bf9abc6
Refactor: MongoDB 연결 설정을 위한 환경 변수를 MONGODB_URL로 단일화
Coldot Mar 21, 2025
b542630
Refactor: 환경 변수에 TEST_MONGODB_URL 추가 및 Docker 실행 시 MONGODB_URL 설정
Coldot Mar 21, 2025
d5059e3
Refactor: 환경 변수에 MONGODB_URL 추가: AWS Secrets Manager에서 MongoDB URL을 가…
Coldot Mar 21, 2025
cce65ea
Fix: 환경 변수에 AWS_REGION 추가
Coldot Mar 21, 2025
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
4 changes: 4 additions & 0 deletions .aws/ecs-task-definition.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"mountPoints": [],
"volumesFrom": [],
"secrets": [
{
"name": "MONGODB_URL",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-2:864981757354:secret:xrpedia/credentials-UAy9x0:xrpedia-mongodb-url::"
}
],
"ulimits": [],
"logConfiguration": {
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/cd-ecs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ env:
ECS_TASK_DEFINITION: ${{ vars.ECS_TASK_DEFINITION }}
CONTAINER_NAME: ${{ vars.CONTAINER_NAME }}
TEST_IMAGE_NAME: xrpedia-ai-proxy-test
TEST_MONGODB_URL: ${{ secrets.TEST_MONGODB_URL }}
permissions:
contents: read
id-token: write
Expand Down Expand Up @@ -49,6 +50,8 @@ jobs:
id: run-test
run: |
docker run --rm \
-e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \
-e AWS_REGION=${{ env.AWS_REGION }} \
${{ env.TEST_IMAGE_NAME }}

- name: Build, tag, and push image to Amazon ECR
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci-pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:

env:
TEST_IMAGE_NAME: xrpedia-ai-proxy-test
TEST_MONGODB_URL: ${{ secrets.TEST_MONGODB_URL }}
AWS_REGION: ${{ secrets.AWS_REGION }}

jobs:
build:
Expand All @@ -22,4 +24,6 @@ jobs:
id: run-test
run: |
docker run --rm \
-e AWS_REGION=${{ env.AWS_REGION }} \
-e MONGODB_URL=${{ env.TEST_MONGODB_URL }} \
${{ env.TEST_IMAGE_NAME }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,6 @@ cython_debug/
# PyPI configuration file
.pypirc

.DS_Store
.DS_Store
.doc/
.idea/
103 changes: 102 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ boto3 = "^1.37.16"
botocore = "^1.37.16"
dotenv = "^0.9.9"
httpx = "^0.28.1"
pymongo = "^4.11.3"

[build-system]
requires = ["poetry-core"]
Expand Down
17 changes: 16 additions & 1 deletion src/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from fastapi import FastAPI
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from dotenv import load_dotenv

from src.router import router
Expand All @@ -26,4 +28,17 @@
allow_headers=["*"],
)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={
"status": 400,
"message": "잘못된 요청입니다.",
"detail": "명세에 맞지 않은 요청입니다."
}
)


app.include_router(router)
Empty file added src/main/ai/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions src/main/ai/data/CategoryRecommendationQueue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import os
import json
import boto3
from dotenv import load_dotenv

load_dotenv()


class CategoryRecommendationQueue:
def __init__(self, sqs_client: boto3.client, queue_url: str):
self.sqs = sqs_client
self.queue_url = queue_url

def send_message(self, request_id: str, title: str, user_id: str):
try:
message_body = {
'request_type': 'category_recommendation',
'request_id': request_id,
'user_id': str(user_id),
'payload': {
'title': title
}
}

response = self.sqs.send_message(
QueueUrl=self.queue_url,
MessageGroupId=str(user_id),
MessageDeduplicationId=str(request_id),
MessageBody=json.dumps(message_body)
)

return response
except Exception as e:
print(f"Error sending message to SQS: {e}")
raise
56 changes: 56 additions & 0 deletions src/main/ai/data/CategoryRecommendationRepository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import Optional
from pymongo.collection import Collection
from pymongo import MongoClient
from bson import ObjectId
from datetime import datetime, timezone


class CategoryRecommendationRepository:
def __init__(self, client: MongoClient):
self.db = client.get_database()
self.collection: Collection = self.db.get_collection('category_recommendations')

def create_recommendation_request(self, title: str, user_id: str) -> dict:
document = {
"title": title,
"user_id": user_id,
"is_completed": False,
"created_at": self.get_current_time()
}
result = self.collection.insert_one(document)
document["_id"] = result.inserted_id
return document

def get_recommendation_by_id(self, request_id: str, user_id: str) -> Optional[dict]:
try:
object_id = ObjectId(request_id)
return self.collection.find_one({
"_id": object_id,
"user_id": user_id
})
except:
return None

def update_recommendation_result(self, request_id: str, predicted_category: str) -> Optional[dict]:
try:
object_id = ObjectId(request_id)
result = self.collection.update_one(
{"_id": object_id},
{
"$set": {
"is_completed": True,
"predicted_category": predicted_category,
"updated_at": self.get_current_time()
}
}
)

if result.modified_count == 0:
return None

return self.collection.find_one({"_id": object_id})
except:
return None

def get_current_time(self):
return datetime.now(timezone.utc)
Empty file added src/main/ai/data/__init__.py
Empty file.
Empty file added src/main/ai/di/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions src/main/ai/di/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import boto3
from dotenv import load_dotenv

from src.main.ai.data.CategoryRecommendationRepository import CategoryRecommendationRepository
from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService
from src.main.ai.data.CategoryRecommendationQueue import CategoryRecommendationQueue
from src.main.config.mongodb import get_mongo_client

load_dotenv()


def get_category_recommendation_repository():
client = get_mongo_client()
return CategoryRecommendationRepository(client)


def get_category_recommendation_queue():
if os.getenv('ENV') == 'local':
aws_profile = os.getenv('AWS_PROFILE', 'default')
session = boto3.Session(profile_name=aws_profile)
sqs_client = session.client('sqs')
else:
sqs_client = boto3.client('sqs', region_name=os.getenv('AWS_REGION'))

queue_url = os.getenv('SQS_REQUEST_QUEUE_URL')
return CategoryRecommendationQueue(sqs_client, queue_url)


def get_category_recommendation_service():
repository = get_category_recommendation_repository()
queue = get_category_recommendation_queue()
return CategoryRecommendationService(repository, queue)
21 changes: 21 additions & 0 deletions src/main/ai/models/CategoryRecommendation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pydantic import BaseModel, Field
import uuid
from typing import Optional


class CategoryRecommendationRequest(BaseModel):
title: str


class CategoryRecommendationResponse(BaseModel):
request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))


class CategoryRecommendationStatusResponse(BaseModel):
request_id: str
is_completed: bool
predicted_category: Optional[str] = None


class CategoryRecommendationResultRequest(BaseModel):
predicted_category: str
1 change: 1 addition & 0 deletions src/main/ai/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading