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
126 changes: 84 additions & 42 deletions app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
save_policy_bookmarks,
)

from app.services.job_service import recommend_jobs
from app.models.jobSchemas import JobRecommendation

router = APIRouter()

Expand All @@ -53,6 +55,44 @@ async def ping():
return {"message": "pong"}


@router.get(
"/api/job/recommend",
response_model=list[JobRecommendation],
summary="사용자 맞춤 구직 추천",
description="user_id에 기반하여 서울시 구인공고 중 사용자 조건에 맞는 구직 정보를 추천하고, 각 항목에 대해 GPT 기반 설명을 포함하여 반환합니다.",
response_description="추천된 구직 리스트",
responses={
200: {
"description": "구직 추천 응답 예시",
"content": {
"application/json": {
"example": [
{
"jo_reqst_no": "H954202505090864",
"jo_regist_no": "K120122505090018",
"company_name": "늘섬김 재가복지센터",
"job_title": "제기동 90세 4등급 10:30-13:30 주5일 할머니 모실 요양보호사 모십니다.",
"description": "우대사항: 고령자 이동 지원, 목욕 등 신체 활동 지원, 가사 및 일상 생활 지원, 정서 지원에 능한 요양보호사\n\n직무 특징: 90세의 4등급 할머니를 주 5일, 오전 10시 30분부터 오후 1시 30분까지 돌봐야 합니다. 실내에서는 이동식 보행기를 이용하며, 외출 시에는 휠체어를 이용합니다. 인지력이 좋아 본인 의사를 잘 전달하며, 딸이 주말에 필요한 물품을 챙겨줍니다. 시급은 10,030원이며 기초 수당이 별도로 제공됩니다.",
"deadline": "마감일 (2025-07-08)",
"location": "서울 동대문구.",
"pay": "시급 / 10030원 ",
"registration_date": "2025-05-09",
"time": "(근무시간) (오전) 10시 30분 ~ (오후) 1시 30분",
}
]
}
},
}
},
)
def recommend_job_for_user(
user_id: int,
db: Session = Depends(get_db),
token_data=Depends(verify_jwt),
):
return recommend_jobs(user_id, db)


@router.post(
"/api/reemployment/analyze",
response_model=ReemploymentResponse,
Expand All @@ -79,6 +119,7 @@ async def reemployment_analysis_endpoint(
request: ReemploymentRequest = Body(
example={"question": "50대, 광업, 남성 재취업 가능성이 궁금해"}
),
token_data=Depends(verify_jwt),
):
question = request.question
result = get_final_reemployment_analysis(question)
Expand Down Expand Up @@ -131,12 +172,49 @@ def education_search(
"category": "디지털기초역량/사무행정실무/전문기술자격증/서비스 직무교육 중 버튼 선택 1"
}
),
token_data=Depends(verify_jwt),
):
xml_data = fetch_education_data()
filtered_results = parse_education_xml(xml_data, request.category)
return {"results": filtered_results}


@router.post(
"/api/education/bookmark",
summary="교육 정보 북마크",
description="사용자가 선택한 교육 정보를 데이터베이스에 북마크로 저장합니다.",
response_description="북마크 저장 결과 메시지",
responses={
200: {
"description": "북마크 성공 예시",
"content": {
"application/json": {"example": {"message": "교육 정보 북마크 성공."}}
},
}
},
)
def bookmark_education(
data: EducationBookmarkRequest = Body(
example={
"user_id": 1,
"bookmarks": [
{
"title": "교육1",
"url": "https://example.com/edu1",
},
{
"title": "교육2",
"url": "https://example.com/edu2",
},
],
}
),
db: Session = Depends(get_db),
token_data=Depends(verify_jwt),
):
return save_bookmarked_education(data, db)


@router.post(
"/api/policy/recommend",
response_model=PolicyRecommendResponse,
Expand Down Expand Up @@ -174,6 +252,7 @@ async def policy_recommend(
"category": "디지털기초역량/사무행정실무/전문기술자격증/서비스 직무교육 중 버튼 선택 1"
}
),
token_data=Depends(verify_jwt),
):
policies = recommend_policy_by_category(req.category)

Expand All @@ -182,42 +261,6 @@ async def policy_recommend(
)


@router.post(
"/api/education/bookmark",
summary="교육 정보 북마크",
description="사용자가 선택한 교육 정보를 데이터베이스에 북마크로 저장합니다.",
response_description="북마크 저장 결과 메시지",
responses={
200: {
"description": "북마크 성공 예시",
"content": {
"application/json": {"example": {"message": "교육 정보 북마크 성공."}}
},
}
},
)
def bookmark_education(
data: EducationBookmarkRequest = Body(
example={
"user_id": 1,
"bookmarks": [
{
"title": "교육1",
"url": "https://example.com/edu1",
},
{
"title": "교육2",
"url": "https://example.com/edu2",
},
],
}
),
db: Session = Depends(get_db),
token_data=Depends(verify_jwt),
):
return save_bookmarked_education(data, db)


@router.post(
"/api/policy/bookmark",
summary="복지 정보 북마크",
Expand Down Expand Up @@ -264,7 +307,7 @@ def bookmark_policy(
summary="자기소개서 세션 시작",
description="입사할 회사명과 직무를 입력받아 자기소개서 작성을 위한 세션을 초기화하고 첫 번째 질문을 반환.",
)
def init(data: ResumeInitRequest):
def init(data: ResumeInitRequest, token_data=Depends(verify_jwt)):
"""
입력:
- company: 지원 회사명
Expand All @@ -285,7 +328,7 @@ def init(data: ResumeInitRequest):
summary="사용자 입력에 대한 AI 응답 생성",
description="현재 질문에 대한 사용자의 답변을 받아 AI가 해당 항목의 자기소개서 문장을 생성. 이후 다음 질문 항목도 함께 반환.",
)
def answer(data: ResumeAnswerRequest):
def answer(data: ResumeAnswerRequest, token_data=Depends(verify_jwt)):
"""
입력:
- session_id: 기존 생성된 세션 ID
Expand All @@ -308,7 +351,7 @@ def answer(data: ResumeAnswerRequest):
description="해당 세션 ID에 대해 지금까지 작성된 모든 자기소개서 항목과 내용을 반환.",
response_model=ResumeResult,
)
def result(session_id: str):
def result(session_id: str, token_data=Depends(verify_jwt)):
"""
입력:
- session_id: 자기소개서 작성 세션 ID
Expand Down Expand Up @@ -406,7 +449,6 @@ def save_resume(
},
)
def get_user_resumes(
userId: int,
db: Session = Depends(get_db),
token_data=Depends(verify_jwt)):
userId: int, db: Session = Depends(get_db), token_data=Depends(verify_jwt)
):
return get_resumes_by_user_id(db, userId)
2 changes: 1 addition & 1 deletion app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Settings(BaseSettings):
JWT_SECRET_KEY_BASE64: str
JWT_ALGORITHM: str = "HS256"

# Base64 디코딩된 JWT secret 키
# Base64 디코딩된 JWT secret 키
@property
def jwt_secret_bytes(self) -> bytes:
return base64.b64decode(self.JWT_SECRET_KEY_BASE64)
Expand Down
4 changes: 2 additions & 2 deletions app/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

# BaseEntity에 해당하는 공통 필드 믹스인
class BaseEntity:
createdAt = Column(DateTime, default=func.now(), nullable=False)
updatedAt = Column(
created_at = Column(DateTime, default=func.now(), nullable=False)
updated_at = Column(
DateTime, default=func.now(), onupdate=func.now(), nullable=False
)

Expand Down
7 changes: 7 additions & 0 deletions app/db_models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .user import User
from .refresh_token import RefreshToken
from .resume import Resume
from .education_info import EducationInfo
from .policy_info import PolicyInfo
from .bookmark import Bookmark
from .authentication_code import AuthenticationCode
3 changes: 1 addition & 2 deletions app/db_models/authentication_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ class CodeStatus(str, enum.Enum):

# AuthenticationCode 모델
class AuthenticationCode(Base):
__tablename__ = "AuthenticationCode"
__tablename__ = "authentication_code"

id = Column(Integer, primary_key=True, index=True)

email = Column(String(50), nullable=False)
code = Column(String, unique=True, nullable=False)
isVerified = Column(Boolean, nullable=False)
Expand Down
4 changes: 2 additions & 2 deletions app/db_models/base_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


class TimestampMixin:
createdAt = Column(DateTime, default=func.now(), nullable=False)
updatedAt = Column(
created_at = Column(DateTime, default=func.now(), nullable=False)
updated_at = Column(
DateTime, default=func.now(), onupdate=func.now(), nullable=False
)
29 changes: 15 additions & 14 deletions app/db_models/bookmark.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy import Column, Integer, String, ForeignKey, BigInteger
from sqlalchemy.orm import relationship
from app.core.db import Base
from app.db_models.base_entity import TimestampMixin


class Bookmark(Base):
__tablename__ = "Bookmark"
class Bookmark(Base, TimestampMixin):
__tablename__ = "bookmark"

id = Column(Integer, primary_key=True, index=True)

user_id = Column(Integer, ForeignKey("User.id", ondelete="CASCADE"), nullable=False)
id = Column(BigInteger, primary_key=True, index=True, autoincrement=True)
user_id = Column(BigInteger, ForeignKey("user.id"), nullable=False)
user = relationship("User", back_populates="bookmarks")

jobId = Column(Integer, nullable=True)
companyName = Column(String, nullable=True)
jobTitle = Column(String, nullable=True)
pay = Column(String, nullable=True)
time = Column(String, nullable=True)
location = Column(String, nullable=True)
deadline = Column(String, nullable=True)
registrationDate = Column(String, nullable=True)
job_id = Column(BigInteger, nullable=True)
company_name = Column(String(255), nullable=True)
job_title = Column(String(255), nullable=True)
pay = Column(String(255), nullable=True)
time = Column(String(255), nullable=True)
location = Column(String(255), nullable=True)
deadline = Column(String(255), nullable=True)
registration_date = Column(String(255), nullable=True)
detail_url = Column(String(255), nullable=True)
16 changes: 9 additions & 7 deletions app/db_models/education_info.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from sqlalchemy import Column, Integer, String, Text, ForeignKey
from sqlalchemy import Column, BigInteger, String, Text, ForeignKey
from sqlalchemy.orm import relationship
from app.core.db import Base
from app.db_models.base_entity import TimestampMixin


class EducationInfo(Base):
__tablename__ = "EducationInfo"
class EducationInfo(Base, TimestampMixin):
__tablename__ = "education_info"

id = Column(Integer, primary_key=True, index=True)

user_id = Column(Integer, ForeignKey("User.id", ondelete="CASCADE"), nullable=False)
user = relationship("User", back_populates="educationInfos")
id = Column(BigInteger, primary_key=True, index=True, autoincrement=True)
user_id = Column(
BigInteger, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
user = relationship("User", back_populates="education_infos")

title = Column(String(50), nullable=False)
url = Column(Text, nullable=True)
18 changes: 10 additions & 8 deletions app/db_models/policy_info.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from sqlalchemy import Column, Integer, String, Text, ForeignKey
from sqlalchemy import Column, BigInteger, String, Text, ForeignKey
from sqlalchemy.orm import relationship
from app.core.db import Base
from app.db_models.base_entity import TimestampMixin


class PolicyInfo(Base):
__tablename__ = "PolicyInfo"
class PolicyInfo(Base, TimestampMixin):
__tablename__ = "policy_info"

id = Column(Integer, primary_key=True, index=True)
id = Column(BigInteger, primary_key=True, index=True, autoincrement=True)
user_id = Column(
BigInteger, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
user = relationship("User", back_populates="policy_infos")

user_id = Column(Integer, ForeignKey("User.id", ondelete="CASCADE"), nullable=False)
user = relationship("User", back_populates="policyInfos")

category = Column(String(50), nullable=False)
title = Column(String(50), nullable=False)
category = Column(String(255), nullable=False)
description = Column(Text, nullable=True)
url = Column(Text, nullable=True)
22 changes: 12 additions & 10 deletions app/db_models/refresh_token.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy import Column, BigInteger, String, DateTime
from sqlalchemy.orm import relationship
from app.core.db import Base
from datetime import datetime
from app.db_models.base_entity import TimestampMixin


class RefreshToken(Base):
__tablename__ = "RefreshToken"
class RefreshToken(Base, TimestampMixin):
__tablename__ = "refresh_token"

id = Column(Integer, primary_key=True, index=True)
id = Column(BigInteger, primary_key=True, index=True, autoincrement=True)
refresh_token = Column(String(512), nullable=False)
expiry_date = Column(DateTime, nullable=False)

refreshToken = Column(String(512), nullable=False)

expiryDate = Column(DateTime, nullable=False)

user = relationship("User", back_populates="refreshToken", uselist=False)
user = relationship(
"User",
back_populates="refresh_token",
uselist=False,
)
Loading
Loading