Merge branch 'main' into scraper-poc
commit
bcd2c0a96f
|
|
@ -102,7 +102,7 @@ def _extract_region_from_address(road_address: str | None) -> str:
|
||||||
"model": ErrorResponse,
|
"model": ErrorResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tags=["crawling"],
|
tags=["Crawling"],
|
||||||
)
|
)
|
||||||
async def crawling(request_body: CrawlingRequest):
|
async def crawling(request_body: CrawlingRequest):
|
||||||
"""네이버 지도 장소 크롤링"""
|
"""네이버 지도 장소 크롤링"""
|
||||||
|
|
@ -379,7 +379,7 @@ print(response.json())
|
||||||
200: {"description": "이미지 업로드 성공"},
|
200: {"description": "이미지 업로드 성공"},
|
||||||
400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse},
|
400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse},
|
||||||
},
|
},
|
||||||
tags=["Image"],
|
tags=["Image-Server"],
|
||||||
)
|
)
|
||||||
async def upload_images(
|
async def upload_images(
|
||||||
images_json: Optional[str] = Form(
|
images_json: Optional[str] = Form(
|
||||||
|
|
@ -597,7 +597,7 @@ curl -X POST "http://localhost:8000/image/upload/blob" \\
|
||||||
200: {"description": "이미지 업로드 성공"},
|
200: {"description": "이미지 업로드 성공"},
|
||||||
400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse},
|
400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse},
|
||||||
},
|
},
|
||||||
tags=["image"],
|
tags=["Image-Blob"],
|
||||||
)
|
)
|
||||||
async def upload_images_blob(
|
async def upload_images_blob(
|
||||||
images_json: Optional[str] = Form(
|
images_json: Optional[str] = Form(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from app.user.dependencies import get_current_user
|
||||||
from app.user.models import User
|
from app.user.models import User
|
||||||
from app.user.schemas.user_schema import (
|
from app.user.schemas.user_schema import (
|
||||||
AccessTokenResponse,
|
AccessTokenResponse,
|
||||||
|
KakaoCodeRequest,
|
||||||
KakaoLoginResponse,
|
KakaoLoginResponse,
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
RefreshTokenRequest,
|
RefreshTokenRequest,
|
||||||
|
|
@ -78,6 +79,54 @@ async def kakao_callback(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/kakao/verify",
|
||||||
|
response_model=LoginResponse,
|
||||||
|
summary="카카오 인가 코드 검증 및 토큰 발급",
|
||||||
|
description="""
|
||||||
|
프론트엔드에서 카카오 로그인 후 받은 인가 코드를 검증하고 JWT 토큰을 발급합니다.
|
||||||
|
|
||||||
|
## 사용 시나리오
|
||||||
|
1. 프론트엔드가 카카오 로그인 완료 후 인가 코드(code)를 받음
|
||||||
|
2. 프론트엔드가 이 엔드포인트에 code를 POST로 전달
|
||||||
|
3. 서버가 카카오 서버에 code 검증 및 사용자 정보 조회
|
||||||
|
4. JWT 토큰 발급 및 사용자 정보 반환
|
||||||
|
|
||||||
|
## 응답
|
||||||
|
- 신규 사용자인 경우 `user.is_new_user`가 `true`로 반환됩니다.
|
||||||
|
- `redirect_url`은 로그인 후 이동할 프론트엔드 URL입니다.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
async def kakao_verify(
|
||||||
|
request: Request,
|
||||||
|
body: KakaoCodeRequest,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
user_agent: Optional[str] = Header(None, alias="User-Agent"),
|
||||||
|
) -> LoginResponse:
|
||||||
|
"""
|
||||||
|
카카오 인가 코드 검증 및 토큰 발급
|
||||||
|
|
||||||
|
프론트엔드가 카카오 콜백에서 받은 인가 코드를 전달하면
|
||||||
|
카카오 서버에서 검증 후 JWT 토큰을 발급합니다.
|
||||||
|
|
||||||
|
신규 사용자인 경우 자동으로 회원가입이 처리됩니다.
|
||||||
|
"""
|
||||||
|
# 클라이언트 IP 추출
|
||||||
|
ip_address = request.client.host if request.client else None
|
||||||
|
|
||||||
|
# X-Forwarded-For 헤더 확인 (프록시/로드밸런서 뒤에 있는 경우)
|
||||||
|
forwarded_for = request.headers.get("X-Forwarded-For")
|
||||||
|
if forwarded_for:
|
||||||
|
ip_address = forwarded_for.split(",")[0].strip()
|
||||||
|
|
||||||
|
return await auth_service.kakao_login(
|
||||||
|
code=body.code,
|
||||||
|
session=session,
|
||||||
|
user_agent=user_agent,
|
||||||
|
ip_address=ip_address,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/refresh",
|
"/refresh",
|
||||||
response_model=AccessTokenResponse,
|
response_model=AccessTokenResponse,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from app.user.schemas.user_schema import (
|
from app.user.schemas.user_schema import (
|
||||||
AccessTokenResponse,
|
AccessTokenResponse,
|
||||||
KakaoCallbackRequest,
|
KakaoCodeRequest,
|
||||||
KakaoLoginResponse,
|
KakaoLoginResponse,
|
||||||
KakaoTokenResponse,
|
KakaoTokenResponse,
|
||||||
KakaoUserInfo,
|
KakaoUserInfo,
|
||||||
|
|
@ -13,7 +13,7 @@ from app.user.schemas.user_schema import (
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AccessTokenResponse",
|
"AccessTokenResponse",
|
||||||
"KakaoCallbackRequest",
|
"KakaoCodeRequest",
|
||||||
"KakaoLoginResponse",
|
"KakaoLoginResponse",
|
||||||
"KakaoTokenResponse",
|
"KakaoTokenResponse",
|
||||||
"KakaoUserInfo",
|
"KakaoUserInfo",
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ class KakaoLoginResponse(BaseModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class KakaoCallbackRequest(BaseModel):
|
class KakaoCodeRequest(BaseModel):
|
||||||
"""카카오 콜백 요청 (인가 코드)"""
|
"""카카오 인가 코드 검증 요청 (프론트엔드에서 전달)"""
|
||||||
|
|
||||||
code: str = Field(..., min_length=1, description="카카오 인가 코드")
|
code: str = Field(..., min_length=1, description="카카오 인가 코드")
|
||||||
|
|
||||||
|
|
@ -163,6 +163,7 @@ class LoginResponse(BaseModel):
|
||||||
token_type: str = Field(default="Bearer", description="토큰 타입")
|
token_type: str = Field(default="Bearer", description="토큰 타입")
|
||||||
expires_in: int = Field(..., description="액세스 토큰 만료 시간 (초)")
|
expires_in: int = Field(..., description="액세스 토큰 만료 시간 (초)")
|
||||||
user: UserBriefResponse = Field(..., description="사용자 정보")
|
user: UserBriefResponse = Field(..., description="사용자 정보")
|
||||||
|
redirect_url: str = Field(..., description="로그인 후 리다이렉트할 프론트엔드 URL")
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
|
|
@ -177,7 +178,8 @@ class LoginResponse(BaseModel):
|
||||||
"email": "user@kakao.com",
|
"email": "user@kakao.com",
|
||||||
"profile_image_url": "https://k.kakaocdn.net/dn/.../profile.jpg",
|
"profile_image_url": "https://k.kakaocdn.net/dn/.../profile.jpg",
|
||||||
"is_new_user": False
|
"is_new_user": False
|
||||||
}
|
},
|
||||||
|
"redirect_url": "http://localhost:3000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ class AuthService:
|
||||||
profile_image_url=user.profile_image_url,
|
profile_image_url=user.profile_image_url,
|
||||||
is_new_user=is_new_user,
|
is_new_user=is_new_user,
|
||||||
),
|
),
|
||||||
|
redirect_url="http://localhost:3000",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def refresh_tokens(
|
async def refresh_tokens(
|
||||||
|
|
|
||||||
6
main.py
6
main.py
|
|
@ -43,12 +43,12 @@ tags_metadata = [
|
||||||
"description": "홈 화면 및 프로젝트 관리 API",
|
"description": "홈 화면 및 프로젝트 관리 API",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "crawling",
|
"name": "Crawling",
|
||||||
"description": "네이버 지도 크롤링 API - 장소 정보 및 이미지 수집",
|
"description": "네이버 지도 크롤링 API - 장소 정보 및 이미지 수집",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "image",
|
"name": "Image-Blob",
|
||||||
"description": "이미지 업로드 API - 로컬 서버 또는 Azure Blob Storage",
|
"description": "이미지 업로드 API - Azure Blob Storage",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Lyric",
|
"name": "Lyric",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue