Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions apps/pre-processing-service/app/api/endpoints/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from ...model.schemas import *
from app.service.blog.tistory_blog_post_service import TistoryBlogPostService
from app.service.blog.naver_blog_post_service import NaverBlogPostService
from ...service.blog.blogger_blog_post_service import BloggerBlogPostService
from ...service.blog.blogger_blog_post_adapter import (
BloggerBlogPostAdapter,
) # ์ˆ˜์ •๋œ import

router = APIRouter()

Expand Down Expand Up @@ -62,7 +64,7 @@ async def publish(request: RequestBlogPublish):
return ResponseBlogPublish(status="success", metadata=result)

elif request.tag == "blogger":
blogger_service = BloggerBlogPostService()
blogger_service = BloggerBlogPostAdapter() # ์ˆ˜์ •: Adapter ์‚ฌ์šฉ
result = blogger_service.post_content(
title=request.post_title,
content=request.post_content,
Expand Down
8 changes: 5 additions & 3 deletions apps/pre-processing-service/app/api/endpoints/keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
router = APIRouter()


@router.get("/")
async def root():
return {"message": "keyword API"}


@router.post(
"/search", response_model=ResponseNaverSearch, summary="๋„ค์ด๋ฒ„ ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰"
)
Expand All @@ -15,9 +20,6 @@ async def search(request: RequestNaverSearch):

์š”์ฒญ ์˜ˆ์‹œ:
{
"job_id": 1,
"schedule_id": 1,
"schedule_his_id": 1,
"tag": "naver",
"category": "50000000",
"start_date": "2025-09-01",
Expand Down
45 changes: 23 additions & 22 deletions apps/pre-processing-service/app/api/endpoints/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ...service.crawl_service import CrawlService
from ...service.search_service import SearchService
from ...service.match_service import MatchService
from ...service.similarity_service import SimilarityService


# from ...service.similarity_service import SimilarityService
Expand Down Expand Up @@ -54,33 +55,33 @@ async def match(request: RequestSadaguMatch):
raise HTTPException(status_code=500, detail=str(e))


# @router.post(
# "/similarity", response_model=ResponseSadaguSimilarity, summary="์ƒํ’ˆ ์œ ์‚ฌ๋„ ๋ถ„์„"
# )
# async def similarity(request: RequestSadaguSimilarity):
# """
# ๋งค์นญ๋œ ์ƒํ’ˆ๋“ค ์ค‘ ํ‚ค์›Œ๋“œ์™€์˜ ์œ ์‚ฌ๋„๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์ตœ์ ์˜ ์ƒํ’ˆ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
# """
# try:
# similarity_service = SimilarityService()
# result = similarity_service.select_product_by_similarity(request)
#
# if not result:
# raise CustomException(
# 500, "์œ ์‚ฌ๋„ ๋ถ„์„์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", "SIMILARITY_FAILED"
# )
#
# return result
# except InvalidItemDataException as e:
# raise HTTPException(status_code=e.status_code, detail=e.detail)
# except Exception as e:
# raise HTTPException(status_code=500, detail=str(e))
@router.post(
"/similarity", response_model=ResponseSadaguSimilarity, summary="์ƒํ’ˆ ์œ ์‚ฌ๋„ ๋ถ„์„"
)
async def similarity(request: RequestSadaguSimilarity):
"""
๋งค์นญ๋œ ์ƒํ’ˆ๋“ค ์ค‘ ํ‚ค์›Œ๋“œ์™€์˜ ์œ ์‚ฌ๋„๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์ตœ์ ์˜ ์ƒํ’ˆ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
"""
try:
similarity_service = SimilarityService()
result = similarity_service.select_product_by_similarity(request)

if not result:
raise CustomException(
500, "์œ ์‚ฌ๋„ ๋ถ„์„์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", "SIMILARITY_FAILED"
)

return result
except InvalidItemDataException as e:
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@router.post(
"/crawl", response_model=ResponseSadaguCrawl, summary="์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด ํฌ๋กค๋ง"
)
async def crawl(request: Request, body: RequestSadaguCrawl):
async def crawl(body: RequestSadaguCrawl):
"""
์ƒํ’ˆ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ํฌ๋กค๋งํ•˜์—ฌ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.
"""
Expand Down
13 changes: 4 additions & 9 deletions apps/pre-processing-service/app/api/endpoints/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,14 @@ def with_meta(data: Mapping[str, Any], meta: Mapping[str, Any]) -> Dict[str, Any

@router.get("/tester", response_model=None)
async def processing_tester():
meta = {
"job_id": 1,
"schedule_id": 1,
"schedule_his_id": 1, # โœ… ํƒ€์ดํฌ ์ˆ˜์ •
}
request_dict = {
"tag": "naver",
"category": "50000000",
"start_date": "2025-09-01",
"end_date": "2025-09-02",
}
# ๋„ค์ด๋ฒ„ ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰
naver_request = RequestNaverSearch(**with_meta(meta, request_dict))
naver_request = RequestNaverSearch(**with_meta(request_dict))
response_data = await keyword_search(naver_request)
keyword = response_data.get("keyword")
loguru.logger.info(keyword)
Expand All @@ -84,21 +79,21 @@ async def processing_tester():
}

# ์‹ธ๋‹ค๊ตฌ ์ƒํ’ˆ ๊ฒ€์ƒ‰
sadagu_request = RequestSadaguSearch(**with_meta(meta, keyword))
sadagu_request = RequestSadaguSearch(**with_meta(keyword))
search_service = SearchService()
keyword_result = await search_service.search_products(sadagu_request)
loguru.logger.info(keyword_result)

# ์‹ธ๋‹ค๊ตฌ ์ƒํ’ˆ ๋งค์น˜
keyword["search_results"] = keyword_result.get("search_results")
keyword_match_request = RequestSadaguMatch(**with_meta(meta, keyword))
keyword_match_request = RequestSadaguMatch(**with_meta(keyword))
match_service = MatchService()
keyword_match_response = match_service.match_products(keyword_match_request)
loguru.logger.info(keyword_match_response)

# ์‹ธ๋‹ค๊ตฌ ์ƒํ’ˆ ์œ ์‚ฌ๋„ ๋ถ„์„
keyword["matched_products"] = keyword_match_response.get("matched_products")
keyword_similarity_request = RequestSadaguSimilarity(**with_meta(meta, keyword))
keyword_similarity_request = RequestSadaguSimilarity(**with_meta(keyword))
# similarity_service = SimilarityService()
# keyword_similarity_response = similarity_service.select_product_by_similarity(
# keyword_similarity_request
Expand Down
18 changes: 0 additions & 18 deletions apps/pre-processing-service/app/model/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,11 @@

# ๊ธฐ๋ณธ ์š”์ฒญ
class RequestBase(BaseModel):
# job_id: int = Field(
# ..., title="์ž‘์—… ID", description="ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ž‘์—…์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
# schedule_id: int = Field(
# ..., title="์Šค์ผ€์ค„ ID", description="์˜ˆ์•ฝ๋œ ์Šค์ผ€์ค„์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
# schedule_his_id: Optional[int] = Field(
# None, title="์Šค์ผ€์ค„ ํžˆ์Šคํ† ๋ฆฌ ID", description="์Šค์ผ€์ค„ ์‹คํ–‰ ์ด๋ ฅ์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
pass


# ๊ธฐ๋ณธ ์‘๋‹ต
class ResponseBase(BaseModel):
# job_id: int = Field(
# ..., title="์ž‘์—… ID", description="ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ž‘์—…์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
# schedule_id: int = Field(
# ..., title="์Šค์ผ€์ค„ ID", description="์˜ˆ์•ฝ๋œ ์Šค์ผ€์ค„์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
# schedule_his_id: Optional[int] = Field(
# None, title="์Šค์ผ€์ค„ ํžˆ์Šคํ† ๋ฆฌ ID", description="์Šค์ผ€์ค„ ์‹คํ–‰ ์ด๋ ฅ์˜ ๊ณ ์œ  ์‹๋ณ„์ž"
# )
status: str = Field(..., title="์ƒํƒœ", description="์š”์ฒญ ์ฒ˜๋ฆฌ ์ƒํƒœ")
pass

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Optional
from typing import Dict

from app.utils.crawling_util import CrawlingUtil
from app.errors.BlogPostingException import *
Expand All @@ -11,51 +11,39 @@ class BaseBlogPostService(ABC):
๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ… ์„œ๋น„์Šค ์ถ”์ƒ ํด๋ž˜์Šค
"""

def __init__(self, config_file="blog_config.json"):
"""๊ณตํ†ต ์ดˆ๊ธฐํ™” ๋กœ์ง"""
# Selenium ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ์ดˆ๊ธฐํ™”
if self._requires_webdriver():
def __init__(self, use_webdriver=True):
"""
๊ณตํ†ต ์ดˆ๊ธฐํ™” ๋กœ์ง
:param use_webdriver: ์›น๋“œ๋ผ์ด๋ฒ„ ์‚ฌ์šฉ ์—ฌ๋ถ€ (API ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ False)
"""
self.use_webdriver = use_webdriver

if self.use_webdriver:
try:
self.crawling_service = CrawlingUtil()
# ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ…์šฉ ์„ค์ •์œผ๋กœ ์ดˆ๊ธฐํ™”
self.crawling_service = CrawlingUtil(
headless=False, # ๋„ค์ด๋ฒ„ ํƒ์ง€ ์šฐํšŒ๋ฅผ ์œ„ํ•ด headless ๋น„ํ™œ์„ฑํ™”
for_blog_posting=True,
)
self.web_driver = self.crawling_service.get_driver()
self.wait_driver = self.crawling_service.get_wait()
except Exception:
raise WebDriverConnectionException()
else:
# API ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ WebDriver๊ฐ€ ํ•„์š” ์—†์Œ
self.crawling_service = None
self.web_driver = None
self.wait_driver = None

# API ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ์ดˆ๊ธฐํ™”
self.config_file = config_file
self.config = {}
self.current_upload_account = None

# API ๊ด€๋ จ ์†์„ฑ๋“ค (์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์„œ๋น„์Šค์—์„œ๋Š” None์œผ๋กœ ์œ ์ง€)
self.blogger_service = None
self.blog_id = None
self.scopes = None

self._load_config()

def _requires_webdriver(self) -> bool:
"""
์„œ๋ธŒํด๋ž˜์Šค์—์„œ WebDriver๊ฐ€ ํ•„์š”ํ•œ์ง€ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜
๊ธฐ๋ณธ๊ฐ’์€ True (Selenium ๊ธฐ๋ฐ˜), API ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค์—์„œ๋Š” False๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œ
"""
return True

@abstractmethod
def _load_config(self) -> None:
"""ํ”Œ๋žซํผ๋ณ„ ์„ค์ • ๋กœ๋“œ"""
pass

@abstractmethod
def _login(self) -> None:
"""
ํ”Œ๋žซํผ๋ณ„ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ (API ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ ์ธ์ฆ์œผ๋กœ ๋Œ€์ฒด)
๊ธฐ๋ณธ ๊ตฌํ˜„์€ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Œ (API ์„œ๋น„์Šค์šฉ)
"""
"""ํ”Œ๋žซํผ๋ณ„ ๋กœ๊ทธ์ธ ๊ตฌํ˜„"""
pass

@abstractmethod
Expand Down Expand Up @@ -83,6 +71,14 @@ def _validate_content(
:param content: ํฌ์ŠคํŠธ ๋‚ด์šฉ
:param tags: ํฌ์ŠคํŠธ ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ
"""
# if not title or not title.strip():
# raise BlogContentValidationException("title", "์ œ๋ชฉ์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค")
#
# if not content or not content.strip():
# raise BlogContentValidationException("content", "๋‚ด์šฉ์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค")
#
# if tags is None:
# raise BlogContentValidationException("tags", "ํƒœ๊ทธ๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค")
pass

def post_content(self, title: str, content: str, tags: List[str] = None) -> Dict:
Expand All @@ -96,7 +92,7 @@ def post_content(self, title: str, content: str, tags: List[str] = None) -> Dict
# 1. ์ฝ˜ํ…์ธ  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
self._validate_content(title, content, tags)

# 2. ๋กœ๊ทธ์ธ (Selenium ๊ธฐ๋ฐ˜) ๋˜๋Š” ์ธ์ฆ (API ๊ธฐ๋ฐ˜)
# 2. ๋กœ๊ทธ์ธ
self._login()

# 3. ํฌ์ŠคํŠธ ์ž‘์„ฑ ๋ฐ ๋ฐœํ–‰
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from typing import Dict, List, Optional
from app.service.blog.base_blog_post_service import BaseBlogPostService
from app.service.blog.blogger_blog_post_service import BloggerApiService
from app.errors.BlogPostingException import *


class BloggerBlogPostAdapter(BaseBlogPostService):
"""
BaseBlogPostService์™€ ํ˜ธํ™˜๋˜๋„๋ก BloggerApiService๋ฅผ ๊ฐ์‹ผ ์–ด๋Œ‘ํ„ฐ
ํ˜„์žฌ BaseBlogPostService ์ธํ„ฐํŽ˜์ด์Šค์™€ ํ˜ธํ™˜
"""

def __init__(self, config_file="blog_config.json"):
# API ์ „์šฉ ์„œ๋น„์Šค (Adaptee) ๋จผ์ € ์ดˆ๊ธฐํ™”
self.api_service = BloggerApiService(config_file=config_file)

try:
# ๋ถ€๋ชจ ํด๋ž˜์Šค์˜ ์›น๋“œ๋ผ์ด๋ฒ„ ์ดˆ๊ธฐํ™”๋ฅผ ์‹œ๋„ํ•˜์ง€๋งŒ, ์‹คํŒจํ•ด๋„ ๋ฌด์‹œ
# ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ถ€๋ชจ์˜ ๋‹ค๋ฅธ ์ดˆ๊ธฐํ™” ๋กœ์ง์€ ์‹คํ–‰๋จ
super().__init__()
except Exception:
# ์›น๋“œ๋ผ์ด๋ฒ„ ์ดˆ๊ธฐํ™” ์‹คํŒจ ์‹œ API ์„œ๋น„์Šค์šฉ์œผ๋กœ ์†์„ฑ ์„ค์ •
self.crawling_service = None
self.web_driver = None
self.wait_driver = None
# ์„ค์ • ๋กœ๋“œ๋Š” ์ง์ ‘ ํ˜ธ์ถœ
self._load_config()

def _load_config(self) -> None:
"""
BloggerApiService ๋‚ด๋ถ€์—์„œ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋˜๋ฏ€๋กœ ๋ณ„๋„ ๊ตฌํ˜„ ๋ถˆํ•„์š”
"""
# API ์„œ๋น„์Šค์˜ ์„ค์ •์ด ์ด๋ฏธ ๋กœ๋“œ๋˜์—ˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ์ž‘์—… ์—†์Œ
pass

def _login(self) -> None:
"""
Selenium ๋กœ๊ทธ์ธ๊ณผ ๋‹ฌ๋ฆฌ, OAuth ์ธ์ฆ์œผ๋กœ ๋Œ€์ฒด
"""
try:
self.api_service.authenticate_with_google_oauth()
except Exception as e:
raise BlogLoginException("Blogger", f"OAuth ์ธ์ฆ ์‹คํŒจ: {str(e)}")

def _write_content(self, title: str, content: str, tags: List[str] = None) -> None:
"""
API๋ฅผ ํ†ตํ•œ ํฌ์ŠคํŠธ ์ž‘์„ฑ
"""
try:
result = self.api_service.create_post_via_api(title, content, labels=tags)
# ๊ฒฐ๊ณผ ๋กœ๊น…
print(f"ํฌ์ŠคํŠธ ์ƒ์„ฑ ์™„๋ฃŒ: {result.get('published_url', 'URL ์—†์Œ')}")
except Exception as e:
raise BlogPostPublishException("Blogger", f"ํฌ์ŠคํŠธ ์ž‘์„ฑ ์‹คํŒจ: {str(e)}")

def _get_platform_name(self) -> str:
"""ํ”Œ๋žซํผ ์ด๋ฆ„ ๋ฐ˜ํ™˜"""
return "Blogger"

def _validate_content(
self, title: str, content: str, tags: Optional[List[str]] = None
) -> None:
"""
API ์ „์šฉ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ˜ธ์ถœ
"""
try:
# Optional์„ List๋กœ ๋ณ€ํ™˜ (None์ธ ๊ฒฝ์šฐ ๋นˆ ๋ฆฌ์ŠคํŠธ)
tags_list = tags if tags is not None else []
self.api_service.validate_api_content(title, content, labels=tags_list)
except Exception as e:
# BloggerApiService์˜ ์˜ˆ์™ธ๋ฅผ BaseBlogPostService ํ˜ธํ™˜ ์˜ˆ์™ธ๋กœ ๋ณ€ํ™˜
if "title" in str(e).lower():
raise BlogContentValidationException("title", str(e))
elif "content" in str(e).lower():
raise BlogContentValidationException("content", str(e))
else:
raise BlogContentValidationException("general", str(e))

def __del__(self):
"""
API ์„œ๋น„์Šค์ด๋ฏ€๋กœ ์›น๋“œ๋ผ์ด๋ฒ„ ์ •๋ฆฌ๊ฐ€ ๋ถˆํ•„์š”
"""
# ์›น๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ •๋ฆฌํ•  ๊ฒƒ์ด ์—†์Œ
pass
Loading
Loading