Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
814c23d
Chore: 프로젝트 구조 추가
Coldot Mar 22, 2025
40e4f3a
Feat: 파일 중복 체크를 위한 SQS 메시지 전송 기능 추가
Coldot Mar 22, 2025
b4e0a20
Feat: 파일 중복 체크를 위한 리포지토리 클래스 추가
Coldot Mar 22, 2025
c51f07a
Feat: 파일 중복 체크를 위한 요청 및 응답 모델 클래스 추가
Coldot Mar 22, 2025
6cbf016
Feat: 파일 중복 체크 서비스 클래스 추가 및 요청 처리 기능 구현
Coldot Mar 22, 2025
d9c877e
Feat: 파일 중복 검사 API 엔드포인트 추가 및 요청 처리 기능 구현
Coldot Mar 22, 2025
1f6e750
Feat: 파일 중복 검사 결과 업데이트 API 엔드포인트 추가
Coldot Mar 22, 2025
10f4469
Feat: 파일 중복 체크를 위한 리포지토리, 서비스 및 큐 생성 함수 추가
Coldot Mar 22, 2025
f25b40b
Feat: 파일 중복 체크 큐에 대한 단위 테스트 추가
Coldot Mar 22, 2025
be347f3
Feat: 파일 중복 체크 리포지토리에 대한 단위 테스트 추가
Coldot Mar 22, 2025
8575689
Feat: 파일 중복 체크 임베딩 업데이트에 대한 단위 테스트 추가
Coldot Mar 22, 2025
95ab859
Feat: 파일 중복 체크 API에 대한 단위 테스트 추가 및 기존 테스트 개선
Coldot Mar 22, 2025
579f15a
Feat: 파일 중복 체크 서비스에 대한 단위 테스트 추가
Coldot Mar 22, 2025
3b669c7
Feat: 파일 중복 체크 큐 클래스의 메시지 전송 메서드 개선 및 초기화 메서드 수정
Coldot Mar 22, 2025
5cfaffc
Feat: 파일 중복 체크 리포지토리의 메서드 개선 및 새로운 메서드 추가
Coldot Mar 22, 2025
626a1a5
Feat: 예외 처리 패키지 및 BadRequestException, FileNotFoundException 클래스 추가
Coldot Mar 22, 2025
622f0af
Feat: CategoryRecommendationResultRequest에 request_id 필드 추가
Coldot Mar 22, 2025
c81be91
Feat: 파일 중복 검사 모델 및 요청 클래스 추가 및 수정
Coldot Mar 22, 2025
1ebb9f5
Feat: 파일 중복 검사 API 및 카테고리 추천 결과 업데이트 엔드포인트 수정
Coldot Mar 22, 2025
8597e9f
파일 중복 검사 API에서 요청 처리 메서드 제거 및 주석 수정
Coldot Mar 22, 2025
a4dc03e
Feat: 파일 중복 검사 서비스의 SQS 메시지 발송 로직 개선 및 중복 검사 요청 처리 흐름 수정
Coldot Mar 22, 2025
1330f36
Chore: 파일 중복 검사 큐의 SQS URL을 요청 큐 URL로 변경
Coldot Mar 22, 2025
cf73aab
Feat: 파일 중복 검사 큐의 메시지 전송 메서드에 사용자 ID 및 요청 ID 매개변수 추가
Coldot Mar 22, 2025
5a3618a
로그 추가: 파일 중복 검사 서비스에 로깅 기능을 추가하여 중복 검사 요청 및 SQS 메시지 발송 상태를 기록함
Coldot Mar 22, 2025
d6bdee3
파일 중복 검사 API의 요청 처리 로직 간소화: 예외 처리 블록 제거 및 결과 반환 방식 수정
Coldot Mar 22, 2025
2596f96
Fix: 애플리케이션에 로깅 설정을 추가하여 정보 로그를 기록하도록 구성함
Coldot Mar 22, 2025
21b1847
Test: 파일 중복 검사 리포지토리의 mock 객체를 개선하고 find_one 결과를 설정함
Coldot Mar 22, 2025
13fa31e
Test: 카테고리 추천 및 파일 중복 검사 API의 요청 처리 로직 수정 및 새로운 테스트 케이스 추가
Coldot Mar 22, 2025
df2271b
Test: 파일 중복 검사 요청 관련 테스트 케이스 제거 및 불필요한 임포트 정리
Coldot Mar 22, 2025
33a8908
Test: 파일 중복 검사 요청 처리 로직 개선 및 기존 요청 처리 테스트 케이스 추가
Coldot Mar 22, 2025
8091e2e
Feat: 파일 중복 검사 큐의 메시지 전송 메서드에 S3 버킷 및 키 매개변수 추가 및 메시지 형식 JSON으로 변경
Coldot Mar 22, 2025
4aee93f
Refactor: 파일 중복 검사 리포지토리에서 데이터베이스 컬렉션 접근 방식을 개선하고 현재 시간을 반환하는 메서드 추가
Coldot Mar 22, 2025
2f9abb8
Fix: CategoryRecommendationResultRequest 클래스의 request_id 필드를 선택적(Opti…
Coldot Mar 22, 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
8 changes: 8 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from dotenv import load_dotenv
import logging

from src.router import router


load_dotenv()

# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)

origins = [
"https://d6jo3bhmz1u5k.cloudfront.net",
"https://localhost:5173",
Expand Down
27 changes: 27 additions & 0 deletions src/main/ai/data/FileDuplicateCheckQueue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json

class FileDuplicateCheckQueue:
def __init__(self, sqs_client, queue_url):
self.sqs_client = sqs_client
self.queue_url = queue_url

def send_message(self, request_id: str, user_id: str, s3_bucket: str, s3_key: str):
"""SQS 큐에 메시지를 전송합니다."""
message_body = {
'request_type': 'file_duplicate_check_embedding_file',
'request_id': request_id,
'user_id': user_id,
'payload': {
's3_bucket': s3_bucket,
's3_key': s3_key
}
}

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

return response
100 changes: 100 additions & 0 deletions src/main/ai/data/FileDuplicateCheckRepository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from bson import ObjectId
from datetime import datetime, timezone


class FileDuplicateCheckRepository:
def __init__(self, mongo_client):
self.mongo_client = mongo_client
self.db = mongo_client.get_database()
self.file_checks_collection = self.db.get_collection("file_duplicate_checks")
self.files_collection = self.db.get_collection("files")
self.file_embeddings_collection = self.db.get_collection("file_embeddings")

def get_current_time(self):
"""현재 시간을 UTC 기준으로 반환합니다."""
return datetime.now(timezone.utc)

def get_file_by_id(self, file_id: str):
"""파일 ID로 파일 정보를 조회합니다."""
try:
file_obj_id = ObjectId(file_id)
return self.files_collection.find_one({"_id": file_obj_id})
except Exception as e:
return None

def has_file_embedding(self, file_id: str) -> bool:
"""파일의 임베딩 존재 여부를 확인합니다."""
try:
file_obj_id = ObjectId(file_id)
result = self.file_embeddings_collection.find_one({"file_id": file_obj_id})
return result is not None
except Exception as e:
return False

def create_duplicate_check_request(self, file_id: str, user_id: str):
"""중복 검사 요청을 생성합니다."""
now = self.get_current_time()
document = {
"file_id": file_id,
"user_id": user_id,
"is_completed": False,
"is_duplicated": None,
"created_at": now
}

result = self.file_checks_collection.insert_one(document)

return self.file_checks_collection.find_one({"_id": result.inserted_id})

def get_duplicate_check_by_file_id(self, file_id: str, user_id: str):
"""파일 ID와 사용자 ID로 중복 검사 요청을 조회합니다."""
return self.file_checks_collection.find_one({
"file_id": file_id,
"user_id": user_id
})

def get_duplicate_check_by_id(self, request_id: str):
"""요청 ID로 중복 검사 요청을 조회합니다."""
try:
request_obj_id = ObjectId(request_id)
return self.file_checks_collection.find_one({"_id": request_obj_id})
except Exception as e:
return None

def update_file_duplicate_status(self, file_id: str, is_duplicated: bool):
"""파일의 중복 상태를 업데이트합니다."""
try:
file_obj_id = ObjectId(file_id)
result = self.files_collection.update_one(
{"_id": file_obj_id},
{"$set": {"is_duplicated": is_duplicated}}
)

if result.modified_count > 0:
return self.files_collection.find_one({"_id": file_obj_id})
return None
except Exception as e:
return None

def update_duplicate_check_result(self, request_id: str, is_duplicated: bool):
"""중복 검사 결과를 업데이트합니다."""
try:
now = self.get_current_time()
request_obj_id = ObjectId(request_id)

result = self.file_checks_collection.update_one(
{"_id": request_obj_id},
{
"$set": {
"is_completed": True,
"is_duplicated": is_duplicated,
"updated_at": now
}
}
)

if result.modified_count > 0:
return self.file_checks_collection.find_one({"_id": request_obj_id})
return None
except Exception as e:
return None
4 changes: 4 additions & 0 deletions src/main/ai/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from src.main.ai.data.CategoryRecommendationRepository import CategoryRecommendationRepository
from src.main.ai.data.CategoryRecommendationQueue import CategoryRecommendationQueue
from src.main.ai.data.FileDuplicateCheckRepository import FileDuplicateCheckRepository
from src.main.ai.data.FileDuplicateCheckQueue import FileDuplicateCheckQueue
26 changes: 26 additions & 0 deletions src/main/ai/di/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
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.ai.data.FileDuplicateCheckRepository import FileDuplicateCheckRepository
from src.main.ai.service.FileDuplicateCheckService import FileDuplicateCheckService
from src.main.ai.data.FileDuplicateCheckQueue import FileDuplicateCheckQueue
from src.main.config.mongodb import get_mongo_client

load_dotenv()
Expand All @@ -31,3 +34,26 @@ def get_category_recommendation_service():
repository = get_category_recommendation_repository()
queue = get_category_recommendation_queue()
return CategoryRecommendationService(repository, queue)


def get_file_duplicate_check_repository():
client = get_mongo_client()
return FileDuplicateCheckRepository(client)


def get_file_duplicate_check_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 FileDuplicateCheckQueue(sqs_client, queue_url)


def get_file_duplicate_check_service():
repository = get_file_duplicate_check_repository()
queue = get_file_duplicate_check_queue()
return FileDuplicateCheckService(repository, queue)
13 changes: 13 additions & 0 deletions src/main/ai/exception/BadRequestException.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from fastapi import HTTPException


class BadRequestException(HTTPException):
"""
요청이 잘못되었을 때 발생하는 예외
"""

def __init__(self, detail: str = "Bad request"):
super().__init__(
status_code=400,
detail=detail
)
13 changes: 13 additions & 0 deletions src/main/ai/exception/FileNotFoundException.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from fastapi import HTTPException


class FileNotFoundException(HTTPException):
"""
파일을 찾을 수 없을 때 발생하는 예외
"""

def __init__(self, file_id: str):
super().__init__(
status_code=404,
detail=f"File with ID '{file_id}' not found"
)
3 changes: 3 additions & 0 deletions src/main/ai/exception/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
예외 패키지
"""
1 change: 1 addition & 0 deletions src/main/ai/models/CategoryRecommendation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ class CategoryRecommendationStatusResponse(BaseModel):


class CategoryRecommendationResultRequest(BaseModel):
request_id: Optional[str] = None
predicted_category: str
34 changes: 34 additions & 0 deletions src/main/ai/models/FileDuplicateCheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime


class FileDuplicateCheckRequest(BaseModel):
"""파일 중복 검사 요청 모델"""
user_id: str
file_id: str


class FileDuplicateCheckResponse(BaseModel):
"""파일 중복 검사 응답 모델"""
request_id: str


class FileDuplicateCheckStatusResponse(BaseModel):
"""파일 중복 검사 상태 응답 모델"""
request_id: str
file_id: str
is_completed: bool
is_duplicated: Optional[bool] = None


class FileDuplicateCheckResultRequest(BaseModel):
"""파일 중복 검사 결과 요청 모델"""
request_id: str
is_duplicated: bool


class FileDuplicateCheckEmbeddingsRequest(BaseModel):
"""파일 임베딩 저장 요청 모델"""
file_id: str
embeddings: List[float]
3 changes: 2 additions & 1 deletion src/main/ai/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@

from src.main.ai.models.CategoryRecommendation import *
from src.main.ai.models.FileDuplicateCheck import *
48 changes: 42 additions & 6 deletions src/main/ai/router/AIInternalAPIRouter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import JSONResponse

from src.main.ai.di.dependencies import get_category_recommendation_service
from src.main.ai.di.dependencies import get_category_recommendation_service, get_file_duplicate_check_service
from src.main.ai.models.CategoryRecommendation import CategoryRecommendationResultRequest, CategoryRecommendationStatusResponse
from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService
from src.main.ai.models.FileDuplicateCheck import FileDuplicateCheckStatusResponse, FileDuplicateCheckEmbeddingsRequest, FileDuplicateCheckRequest, FileDuplicateCheckResponse, FileDuplicateCheckResultRequest
from src.main.ai.service.FileDuplicateCheckService import FileDuplicateCheckService


router = APIRouter(
Expand All @@ -12,13 +14,39 @@
)


@router.post("/category-recommendation-results/{request_id}", response_model=CategoryRecommendationStatusResponse)
@router.post("/category-recommendation-results", response_model=dict)
async def update_category_recommendation_result(
request_id: str,
request: CategoryRecommendationResultRequest,
service: CategoryRecommendationService = Depends(get_category_recommendation_service)
):
result = service.update_recommendation_result(request_id, request)
result = service.update_recommendation_result(request.request_id, request)

if not result:
return JSONResponse(
status_code=status.HTTP_404_NOT_FOUND,
content={
"detail": "요청을 찾을 수 없습니다. 존재하지 않는 ID입니다."
}
)

return {"success": True}


@router.post("/file-duplicate-checks", response_model=FileDuplicateCheckResponse)
async def create_file_duplicate_check(
request: FileDuplicateCheckRequest,
service: FileDuplicateCheckService = Depends(get_file_duplicate_check_service)
):
result = service.create_duplicate_check_request(request)
return result


@router.post("/file-duplicate-check-embeddings", response_model=dict)
async def update_file_duplicate_check_result(
request: FileDuplicateCheckResultRequest,
service: FileDuplicateCheckService = Depends(get_file_duplicate_check_service)
):
result = service.update_duplicate_check_result(request.request_id, request.is_duplicated)

if not result:
return JSONResponse(
Expand All @@ -29,5 +57,13 @@ async def update_category_recommendation_result(
"detail": "존재하지 않는 ID입니다."
}
)

return result

# 요청 정보 조회
check = service.repository.get_duplicate_check_by_id(request.request_id)

return {
"request_id": request.request_id,
"file_id": check["file_id"],
"is_completed": check["is_completed"],
"is_duplicated": check["is_duplicated"]
}
24 changes: 23 additions & 1 deletion src/main/ai/router/AIPublicAPIRouter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import uuid

from src.main.auth.dependencies import get_current_user
from src.main.ai.di.dependencies import get_category_recommendation_service
from src.main.ai.di.dependencies import get_category_recommendation_service, get_file_duplicate_check_service
from src.main.ai.models.CategoryRecommendation import CategoryRecommendationRequest, CategoryRecommendationResponse, CategoryRecommendationStatusResponse
from src.main.ai.service.CategoryRecommendationService import CategoryRecommendationService
from src.main.ai.models.FileDuplicateCheck import FileDuplicateCheckStatusResponse
from src.main.ai.service.FileDuplicateCheckService import FileDuplicateCheckService


router = APIRouter(
Expand Down Expand Up @@ -43,3 +45,23 @@ async def get_category_recommendation_status(
)

return result


@router.get("/file-duplicate-checks", response_model=FileDuplicateCheckStatusResponse)
async def get_file_duplicate_check_status(
file_id: str,
user_id: uuid.UUID = Depends(get_current_user),
service: FileDuplicateCheckService = Depends(get_file_duplicate_check_service)
):
"""
파일 중복 검사 결과 조회
"""
result = service.get_duplicate_check_status(file_id, str(user_id))

if not result:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="요청을 찾을 수 없습니다. 존재하지 않는 ID입니다."
)

return result
2 changes: 2 additions & 0 deletions src/main/ai/router/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from src.main.ai.router.AIPublicAPIRouter import router as public_router
from src.main.ai.router.AIInternalAPIRouter import router as internal_router
Loading