feat(): openapi docs for resume routes

This commit is contained in:
gitgernit
2025-11-21 20:02:39 +03:00
parent 5dc0ceea34
commit fe0433bf62
2 changed files with 226 additions and 1 deletions
+2 -1
View File
@@ -19,7 +19,7 @@ from prometheus_fastapi_instrumentator import Instrumentator
from template_project.web_api.configuration import Configuration, load_configuration
from template_project.web_api.ioc.make import make_ioc
from template_project.web_api.routes import auth, healthcheck, notification, profile, storage
from template_project.web_api.routes import auth, healthcheck, notification, profile, resume, storage
LOG_CONFIG: Final = {
"version": 1,
@@ -74,6 +74,7 @@ def make_asgi_application(
app.include_router(profile.router)
app.include_router(notification.router)
app.include_router(storage.router)
app.include_router(resume.router)
Instrumentator().instrument(app).expose(app)
setup_dishka(container=ioc, app=app)
@@ -0,0 +1,224 @@
from decimal import Decimal
from enum import StrEnum
from dishka.integrations.fastapi import DishkaRoute
from fastapi import APIRouter, Depends, Query
from fastapi.security import HTTPBearer
from pydantic import BaseModel, Field
security = HTTPBearer()
router = APIRouter(route_class=DishkaRoute, tags=["Resume"], dependencies=[Depends(security)])
class ExperienceTypeEnum(StrEnum):
NO_EXPERIENCE = "no_experience"
BETWEEN_1_AND_3 = "between_1_and_3"
BETWEEN_3_AND_6 = "between_3_and_6"
MORE_THAN_6 = "more_than_6"
class CreateResumeRequest(BaseModel):
position: str = Field(..., min_length=1, max_length=200, description="Job position", examples=["Python Developer"])
about_me: str = Field(
..., min_length=1, max_length=2000, description="About me section", examples=["Experienced Python developer"]
)
key_skills: list[str] = Field(
..., min_length=1, description="List of key skills", examples=[["Python", "FastAPI", "PostgreSQL"]]
)
experience_type: ExperienceTypeEnum = Field(
..., description="Experience type", examples=[ExperienceTypeEnum.BETWEEN_3_AND_6]
)
model_config = {
"json_schema_extra": {
"example": {
"position": "Python Developer",
"about_me": "Experienced Python developer with 5 years of experience",
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
"experience_type": "between_3_and_6",
}
}
}
class CreateResumeResponse(BaseModel):
resume_id: str = Field(..., description="Created resume ID")
model_config = {"json_schema_extra": {"example": {"resume_id": "01234567-89ab-cdef-0123-456789abcdef"}}}
class SalaryPrediction(BaseModel):
from_salary: Decimal = Field(..., description="Minimum predicted salary", examples=[Decimal(100000)])
to_salary: Decimal = Field(..., description="Maximum predicted salary", examples=[Decimal(150000)])
recommended_skills: list[str] = Field(
..., description="Recommended skills to add", examples=[["Kubernetes", "Redis"]]
)
model_config = {
"json_schema_extra": {
"example": {
"from_salary": "100000",
"to_salary": "150000",
"recommended_skills": ["Kubernetes", "Redis"],
}
}
}
class ResumeResponse(BaseModel):
position: str = Field(..., description="Job position")
about_me: str = Field(..., description="About me section")
key_skills: list[str] = Field(..., description="List of key skills")
experience_type: ExperienceTypeEnum = Field(..., description="Experience type")
prediction: SalaryPrediction | None = Field(None, description="Salary prediction (can be null)")
model_config = {
"json_schema_extra": {
"example": {
"position": "Python Developer",
"about_me": "Experienced Python developer with 5 years of experience",
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
"experience_type": "between_3_and_6",
"prediction": {
"from_salary": "100000",
"to_salary": "150000",
"recommended_skills": ["Kubernetes", "Redis"],
},
}
}
}
class ResumeListItem(BaseModel):
position: str = Field(..., description="Job position")
about_me: str = Field(..., description="About me section")
key_skills: list[str] = Field(..., description="List of key skills")
experience_type: ExperienceTypeEnum = Field(..., description="Experience type")
model_config = {
"json_schema_extra": {
"example": {
"position": "Python Developer",
"about_me": "Experienced Python developer with 5 years of experience",
"key_skills": ["Python", "FastAPI", "PostgreSQL"],
"experience_type": "between_3_and_6",
}
}
}
class GetResumeListRequest(BaseModel):
limit: int = Field(..., ge=1, le=100, description="Number of resumes to return", examples=[10])
offset: int = Field(..., ge=0, description="Number of resumes to skip", examples=[0])
class GetResumeListResponse(BaseModel):
resumes: list[ResumeListItem] = Field(..., description="List of resumes")
model_config = {
"json_schema_extra": {
"example": {
"resumes": [
{
"position": "Python Developer",
"about_me": "Experienced Python developer",
"key_skills": ["Python", "FastAPI"],
"experience_type": "between_3_and_6",
}
]
}
}
}
class GetResumeHistoryRequest(BaseModel):
limit: int = Field(..., ge=1, le=100, description="Number of resumes to return", examples=[10])
offset: int = Field(..., ge=0, description="Number of resumes to skip", examples=[0])
class GetResumeHistoryResponse(BaseModel):
resumes: list[ResumeListItem] = Field(..., description="List of resume history items")
model_config = {
"json_schema_extra": {
"example": {
"resumes": [
{
"position": "Python Developer",
"about_me": "Experienced Python developer",
"key_skills": ["Python", "FastAPI"],
"experience_type": "between_3_and_6",
}
]
}
}
}
@router.post(
"/resume",
summary="Create resume",
description="Create a new resume with position, about me, key skills and experience type",
responses={
200: {"description": "Resume created successfully", "model": CreateResumeResponse},
401: {"description": "Unauthorized - invalid or missing token"},
},
)
async def create_resume(
request: CreateResumeRequest,
) -> CreateResumeResponse:
# TODO: Implement resume creation
raise NotImplementedError
@router.get(
"/resume/{resume_id}",
summary="Get resume",
description="Get resume by ID with optional salary prediction",
responses={
200: {"description": "Resume retrieved successfully", "model": ResumeResponse},
401: {"description": "Unauthorized - invalid or missing token"},
404: {"description": "Resume not found"},
},
)
async def get_resume(
resume_id: str = Field(..., description="Resume ID"),
) -> ResumeResponse:
# TODO: Implement resume retrieval
raise NotImplementedError
@router.get(
"/resume/list",
summary="Get resume list",
description="Get paginated list of resumes",
responses={
200: {"description": "Resume list retrieved successfully", "model": GetResumeListResponse},
401: {"description": "Unauthorized - invalid or missing token"},
},
)
async def get_resume_list(
limit: int = Query(..., ge=1, le=100, description="Number of resumes to return", examples=[10]),
offset: int = Query(..., ge=0, description="Number of resumes to skip", examples=[0]),
) -> GetResumeListResponse:
# TODO: Implement resume list retrieval
raise NotImplementedError
@router.get(
"/resume/{resume_id}/history",
summary="Get resume history",
description="Get paginated history of resume changes",
responses={
200: {"description": "Resume history retrieved successfully", "model": GetResumeHistoryResponse},
401: {"description": "Unauthorized - invalid or missing token"},
404: {"description": "Resume not found"},
},
)
async def get_resume_history(
resume_id: str = Field(..., description="Resume ID"),
limit: int = Query(..., ge=1, le=100, description="Number of history items to return", examples=[10]),
offset: int = Query(..., ge=0, description="Number of history items to skip", examples=[0]),
) -> GetResumeHistoryResponse:
# TODO: Implement resume history retrieval
raise NotImplementedError