You've already forked RekomenciBackend
Merge branch 'main' of gitlab.prodcontest.com:team-39/backend
* 'main' of gitlab.prodcontest.com:team-39/backend: feat(): update resume contracts
This commit is contained in:
@@ -23,8 +23,6 @@ class KeySkillsDataGateway:
|
|||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def add_skills(self, name: list[str]) -> None:
|
async def add_skills(self, name: list[str]) -> None:
|
||||||
insert_statement = insert(key_skills_table).values(
|
insert_statement = insert(key_skills_table).values([{"name": _} for _ in name])
|
||||||
[{"name": _} for _ in name]
|
|
||||||
)
|
|
||||||
with contextlib.suppress(IntegrityError):
|
with contextlib.suppress(IntegrityError):
|
||||||
await self._session.execute(insert_statement)
|
await self._session.execute(insert_statement)
|
||||||
|
|||||||
@@ -4,9 +4,29 @@ from typing import override
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from template_project.adapters.data_gateways.tables import resume_prediction_table, resume_table
|
from template_project.adapters.data_gateways.tables import (
|
||||||
from template_project.application.resume.data_gateway import ResumeDataGateway, ResumePredictionDataGateway
|
resume_education_table,
|
||||||
from template_project.application.resume.entity import Resume, ResumeEmbeddingId, ResumeId, ResumePrediction
|
resume_experience_table,
|
||||||
|
resume_prediction_table,
|
||||||
|
resume_project_table,
|
||||||
|
resume_table,
|
||||||
|
)
|
||||||
|
from template_project.application.resume.data_gateway import (
|
||||||
|
ResumeDataGateway,
|
||||||
|
ResumeEducationDataGateway,
|
||||||
|
ResumeExperienceDataGateway,
|
||||||
|
ResumePredictionDataGateway,
|
||||||
|
ResumeProjectDataGateway,
|
||||||
|
)
|
||||||
|
from template_project.application.resume.entity import (
|
||||||
|
Resume,
|
||||||
|
ResumeEducation,
|
||||||
|
ResumeEmbeddingId,
|
||||||
|
ResumeExperience,
|
||||||
|
ResumeId,
|
||||||
|
ResumePrediction,
|
||||||
|
ResumeProject,
|
||||||
|
)
|
||||||
from template_project.application.resume.errors import ResumeNotFoundError
|
from template_project.application.resume.errors import ResumeNotFoundError
|
||||||
from template_project.application.user.entity import UserId
|
from template_project.application.user.entity import UserId
|
||||||
|
|
||||||
@@ -29,12 +49,7 @@ class DefaultResumeDataGateway(ResumeDataGateway):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
async def list_by_user_id(self, user_id: UserId, limit: int, offset: int) -> Sequence[Resume]:
|
async def list_by_user_id(self, user_id: UserId, limit: int, offset: int) -> Sequence[Resume]:
|
||||||
statement = (
|
statement = select(Resume).where(resume_table.c.user_id == user_id).limit(limit).offset(offset)
|
||||||
select(Resume)
|
|
||||||
.where(resume_table.c.user_id == user_id)
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset)
|
|
||||||
)
|
|
||||||
result = await self._session.execute(statement)
|
result = await self._session.execute(statement)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
@@ -73,3 +88,36 @@ class DefaultResumePredictionDataGateway(ResumePredictionDataGateway):
|
|||||||
statement = select(ResumePrediction).where(resume_prediction_table.c.resume_id == resume_id)
|
statement = select(ResumePrediction).where(resume_prediction_table.c.resume_id == resume_id)
|
||||||
result = await self._session.execute(statement)
|
result = await self._session.execute(statement)
|
||||||
return result.scalar()
|
return result.scalar()
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultResumeExperienceDataGateway(ResumeExperienceDataGateway):
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeExperience]:
|
||||||
|
statement = select(ResumeExperience).where(resume_experience_table.c.resume_id == resume_id)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultResumeEducationDataGateway(ResumeEducationDataGateway):
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeEducation]:
|
||||||
|
statement = select(ResumeEducation).where(resume_education_table.c.resume_id == resume_id)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultResumeProjectDataGateway(ResumeProjectDataGateway):
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@override
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeProject]:
|
||||||
|
statement = select(ResumeProject).where(resume_project_table.c.resume_id == resume_id)
|
||||||
|
result = await self._session.execute(statement)
|
||||||
|
return result.scalars().all()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from typing import Any, Final, override
|
|||||||
|
|
||||||
from pgvector.sqlalchemy import Vector
|
from pgvector.sqlalchemy import Vector
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
ARRAY,
|
|
||||||
Boolean,
|
Boolean,
|
||||||
Column,
|
Column,
|
||||||
DateTime,
|
DateTime,
|
||||||
@@ -23,8 +22,16 @@ from sqlalchemy.orm import registry
|
|||||||
|
|
||||||
from template_project.application.access_token.entity import AccessToken
|
from template_project.application.access_token.entity import AccessToken
|
||||||
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
|
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
|
||||||
|
from template_project.application.common.enums import EducationGrade
|
||||||
from template_project.application.notification_device.entity import NotificationDevice
|
from template_project.application.notification_device.entity import NotificationDevice
|
||||||
from template_project.application.resume.entity import Resume, ResumeEmbedding, ResumePrediction
|
from template_project.application.resume.entity import (
|
||||||
|
Resume,
|
||||||
|
ResumeEducation,
|
||||||
|
ResumeEmbedding,
|
||||||
|
ResumeExperience,
|
||||||
|
ResumePrediction,
|
||||||
|
ResumeProject,
|
||||||
|
)
|
||||||
from template_project.application.user.entity import User
|
from template_project.application.user.entity import User
|
||||||
from template_project.application.user.profile.entity import Profile
|
from template_project.application.user.profile.entity import Profile
|
||||||
|
|
||||||
@@ -127,6 +134,7 @@ resume_table: Final = Table(
|
|||||||
Column("created_at", DateTime(timezone=True), nullable=False),
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
||||||
Column("position", String, nullable=False),
|
Column("position", String, nullable=False),
|
||||||
|
Column("location", String, nullable=False),
|
||||||
Column("about_me", String, nullable=False),
|
Column("about_me", String, nullable=False),
|
||||||
Column("key_skills", StringArrayType(), nullable=False, server_default=text("'[]'::jsonb")),
|
Column("key_skills", StringArrayType(), nullable=False, server_default=text("'[]'::jsonb")),
|
||||||
Column("experience_type", String, nullable=False),
|
Column("experience_type", String, nullable=False),
|
||||||
@@ -158,7 +166,43 @@ key_skills_table: Final = Table(
|
|||||||
"key_skills",
|
"key_skills",
|
||||||
meta_data,
|
meta_data,
|
||||||
Column("id", Integer, autoincrement=True, primary_key=True),
|
Column("id", Integer, autoincrement=True, primary_key=True),
|
||||||
Column("name", String, nullable=False, unique=True)
|
Column("name", String, nullable=False, unique=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
resume_experience_table: Final = Table(
|
||||||
|
"resume_experience",
|
||||||
|
meta_data,
|
||||||
|
Column("id", UUID, primary_key=True),
|
||||||
|
Column("deleted_at", DateTime(timezone=True)),
|
||||||
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
|
Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
|
||||||
|
Column("place", String, nullable=False),
|
||||||
|
Column("description", String, nullable=False),
|
||||||
|
Column("months_duration", Integer, nullable=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
resume_education_table: Final = Table(
|
||||||
|
"resume_education",
|
||||||
|
meta_data,
|
||||||
|
Column("id", UUID, primary_key=True),
|
||||||
|
Column("deleted_at", DateTime(timezone=True)),
|
||||||
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
|
Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
|
||||||
|
Column("place", String, nullable=False),
|
||||||
|
Column("grade", Enum(EducationGrade, name="education_grade"), nullable=False),
|
||||||
|
Column("specialization", String, nullable=False),
|
||||||
|
Column("description", String, nullable=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
resume_project_table: Final = Table(
|
||||||
|
"resume_project",
|
||||||
|
meta_data,
|
||||||
|
Column("id", UUID, primary_key=True),
|
||||||
|
Column("deleted_at", DateTime(timezone=True)),
|
||||||
|
Column("created_at", DateTime(timezone=True), nullable=False),
|
||||||
|
Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
|
||||||
|
Column("name", String, nullable=False),
|
||||||
|
Column("description", String, nullable=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -182,3 +226,6 @@ mapper_registry.map_imperatively(
|
|||||||
"recommended_skills": resume_prediction_table.c.recommended_skills,
|
"recommended_skills": resume_prediction_table.c.recommended_skills,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
mapper_registry.map_imperatively(ResumeExperience, resume_experience_table)
|
||||||
|
mapper_registry.map_imperatively(ResumeEducation, resume_education_table)
|
||||||
|
mapper_registry.map_imperatively(ResumeProject, resume_project_table)
|
||||||
|
|||||||
@@ -6,3 +6,14 @@ class ExperienceType(StrEnum):
|
|||||||
BETWEEN_1_AND_3 = "between1And3"
|
BETWEEN_1_AND_3 = "between1And3"
|
||||||
BETWEEN_3_AND_6 = "between3And6"
|
BETWEEN_3_AND_6 = "between3And6"
|
||||||
MORE_THAN_6 = "moreThan6"
|
MORE_THAN_6 = "moreThan6"
|
||||||
|
|
||||||
|
|
||||||
|
class EducationGrade(StrEnum):
|
||||||
|
BASIC_GENERAL_EDUCATION = "basic_general_education"
|
||||||
|
SECONDARY_GENERAL_EDUCATION = "secondary_general_education"
|
||||||
|
SECONDARY_PROFESSIONAL_EDUCATION = "secondary_professional_education"
|
||||||
|
BACHELOR = "bachelor"
|
||||||
|
SPECIALIST = "specialist"
|
||||||
|
MASTER = "master"
|
||||||
|
POSTGRADUATE_STUDIES = "postgraduate_studies"
|
||||||
|
OTHER = "other"
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ from typing import Protocol
|
|||||||
|
|
||||||
from template_project.application.resume.entity import (
|
from template_project.application.resume.entity import (
|
||||||
Resume,
|
Resume,
|
||||||
|
ResumeEducation,
|
||||||
ResumeEmbeddingId,
|
ResumeEmbeddingId,
|
||||||
|
ResumeExperience,
|
||||||
ResumeId,
|
ResumeId,
|
||||||
ResumePrediction,
|
ResumePrediction,
|
||||||
|
ResumeProject,
|
||||||
)
|
)
|
||||||
from template_project.application.user.entity import UserId
|
from template_project.application.user.entity import UserId
|
||||||
|
|
||||||
@@ -37,3 +40,21 @@ class ResumePredictionDataGateway(Protocol):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def load_by_resume_id(self, resume_id: ResumeId) -> ResumePrediction | None:
|
async def load_by_resume_id(self, resume_id: ResumeId) -> ResumePrediction | None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ResumeExperienceDataGateway(Protocol):
|
||||||
|
@abstractmethod
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeExperience]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ResumeEducationDataGateway(Protocol):
|
||||||
|
@abstractmethod
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeEducation]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ResumeProjectDataGateway(Protocol):
|
||||||
|
@abstractmethod
|
||||||
|
async def load_by_resume_id(self, resume_id: ResumeId) -> Sequence[ResumeProject]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -6,19 +6,22 @@ from uuid import UUID
|
|||||||
from uuid_utils.compat import uuid7
|
from uuid_utils.compat import uuid7
|
||||||
|
|
||||||
from template_project.application.common.entity import Entity, to_entity
|
from template_project.application.common.entity import Entity, to_entity
|
||||||
from template_project.application.common.enums import ExperienceType
|
from template_project.application.common.enums import EducationGrade, ExperienceType
|
||||||
from template_project.application.user.entity import UserId
|
from template_project.application.user.entity import UserId
|
||||||
|
|
||||||
ResumeId = NewType("ResumeId", UUID)
|
ResumeId = NewType("ResumeId", UUID)
|
||||||
ResumeEmbeddingId = NewType("ResumeEmbeddingId", UUID)
|
ResumeEmbeddingId = NewType("ResumeEmbeddingId", UUID)
|
||||||
ResumePredictionId = NewType("ResumePredictionId", UUID)
|
ResumePredictionId = NewType("ResumePredictionId", UUID)
|
||||||
|
ResumeExperienceId = NewType("ResumeExperienceId", UUID)
|
||||||
|
ResumeEducationId = NewType("ResumeEducationId", UUID)
|
||||||
|
ResumeProjectId = NewType("ResumeProjectId", UUID)
|
||||||
|
|
||||||
|
|
||||||
@to_entity
|
@to_entity
|
||||||
class Resume(Entity[ResumeId]):
|
class Resume(Entity[ResumeId]):
|
||||||
user_id: UserId
|
user_id: UserId
|
||||||
position: str
|
position: str
|
||||||
# location: str
|
location: str
|
||||||
about_me: str
|
about_me: str
|
||||||
key_skills: list[str]
|
key_skills: list[str]
|
||||||
experience_type: ExperienceType
|
experience_type: ExperienceType
|
||||||
@@ -30,6 +33,7 @@ class Resume(Entity[ResumeId]):
|
|||||||
cls,
|
cls,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
position: str,
|
position: str,
|
||||||
|
location: str,
|
||||||
about_me: str,
|
about_me: str,
|
||||||
key_skills: list[str],
|
key_skills: list[str],
|
||||||
experience_type: ExperienceType,
|
experience_type: ExperienceType,
|
||||||
@@ -41,6 +45,7 @@ class Resume(Entity[ResumeId]):
|
|||||||
created_at=datetime.now(tz=UTC),
|
created_at=datetime.now(tz=UTC),
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
position=position,
|
position=position,
|
||||||
|
location=location,
|
||||||
about_me=about_me,
|
about_me=about_me,
|
||||||
key_skills=key_skills,
|
key_skills=key_skills,
|
||||||
experience_type=experience_type,
|
experience_type=experience_type,
|
||||||
@@ -74,4 +79,78 @@ class ResumePrediction(Entity[ResumePredictionId]):
|
|||||||
from_salary: Decimal
|
from_salary: Decimal
|
||||||
to_salary: Decimal
|
to_salary: Decimal
|
||||||
recommended_skills: list[str]
|
recommended_skills: list[str]
|
||||||
# common_recommended: str # TODO
|
|
||||||
|
|
||||||
|
@to_entity
|
||||||
|
class ResumeExperience(Entity[ResumeExperienceId]):
|
||||||
|
resume_id: ResumeId
|
||||||
|
place: str
|
||||||
|
description: str
|
||||||
|
months_duration: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(
|
||||||
|
cls,
|
||||||
|
resume_id: ResumeId,
|
||||||
|
place: str,
|
||||||
|
description: str,
|
||||||
|
months_duration: int,
|
||||||
|
) -> Self:
|
||||||
|
return cls(
|
||||||
|
id=ResumeExperienceId(uuid7()),
|
||||||
|
created_at=datetime.now(tz=UTC),
|
||||||
|
resume_id=resume_id,
|
||||||
|
place=place,
|
||||||
|
description=description,
|
||||||
|
months_duration=months_duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@to_entity
|
||||||
|
class ResumeEducation(Entity[ResumeEducationId]):
|
||||||
|
resume_id: ResumeId
|
||||||
|
place: str
|
||||||
|
grade: EducationGrade
|
||||||
|
specialization: str
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(
|
||||||
|
cls,
|
||||||
|
resume_id: ResumeId,
|
||||||
|
place: str,
|
||||||
|
grade: EducationGrade,
|
||||||
|
specialization: str,
|
||||||
|
description: str | None = None,
|
||||||
|
) -> Self:
|
||||||
|
return cls(
|
||||||
|
id=ResumeEducationId(uuid7()),
|
||||||
|
created_at=datetime.now(tz=UTC),
|
||||||
|
resume_id=resume_id,
|
||||||
|
place=place,
|
||||||
|
grade=grade,
|
||||||
|
specialization=specialization,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@to_entity
|
||||||
|
class ResumeProject(Entity[ResumeProjectId]):
|
||||||
|
resume_id: ResumeId
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(
|
||||||
|
cls,
|
||||||
|
resume_id: ResumeId,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
) -> Self:
|
||||||
|
return cls(
|
||||||
|
id=ResumeProjectId(uuid7()),
|
||||||
|
created_at=datetime.now(tz=UTC),
|
||||||
|
resume_id=resume_id,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,8 +1,36 @@
|
|||||||
from template_project.application.common.enums import ExperienceType
|
from template_project.application.common.data_structure import to_data_structure
|
||||||
|
from template_project.application.common.enums import EducationGrade, ExperienceType
|
||||||
from template_project.application.common.identity_provider import IdentityProvider
|
from template_project.application.common.identity_provider import IdentityProvider
|
||||||
from template_project.application.common.interactor import to_interactor
|
from template_project.application.common.interactor import to_interactor
|
||||||
from template_project.application.common.unit_of_work import UnitOfWork
|
from template_project.application.common.unit_of_work import UnitOfWork
|
||||||
from template_project.application.resume.entity import Resume, ResumeId
|
from template_project.application.resume.entity import (
|
||||||
|
Resume,
|
||||||
|
ResumeEducation,
|
||||||
|
ResumeExperience,
|
||||||
|
ResumeId,
|
||||||
|
ResumeProject,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ExperienceInput:
|
||||||
|
place: str
|
||||||
|
description: str
|
||||||
|
months_duration: int
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class EducationInput:
|
||||||
|
place: str
|
||||||
|
grade: EducationGrade
|
||||||
|
specialization: str
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ProjectInput:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
@to_interactor
|
@to_interactor
|
||||||
@@ -13,15 +41,20 @@ class AddResumeInteractor:
|
|||||||
async def execute(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
position: str,
|
position: str,
|
||||||
|
location: str,
|
||||||
about_me: str,
|
about_me: str,
|
||||||
key_skills: list[str],
|
key_skills: list[str],
|
||||||
experience_type: ExperienceType,
|
experience_type: ExperienceType,
|
||||||
|
experience: list[ExperienceInput] | None = None,
|
||||||
|
education: list[EducationInput] | None = None,
|
||||||
|
projects: list[ProjectInput] | None = None,
|
||||||
) -> ResumeId:
|
) -> ResumeId:
|
||||||
user = await self.identity_provider.get_current_user()
|
user = await self.identity_provider.get_current_user()
|
||||||
|
|
||||||
resume = Resume.factory(
|
resume = Resume.factory(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
position=position,
|
position=position,
|
||||||
|
location=location,
|
||||||
about_me=about_me,
|
about_me=about_me,
|
||||||
key_skills=key_skills,
|
key_skills=key_skills,
|
||||||
experience_type=experience_type,
|
experience_type=experience_type,
|
||||||
@@ -29,9 +62,38 @@ class AddResumeInteractor:
|
|||||||
up_resume_id=None,
|
up_resume_id=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: тут надо сделать запуск фоновой таски для вычитывания подходящей вакансии
|
|
||||||
|
|
||||||
await self.unit_of_work.add(resume)
|
await self.unit_of_work.add(resume)
|
||||||
|
|
||||||
|
if experience:
|
||||||
|
for exp in experience:
|
||||||
|
resume_experience = ResumeExperience.factory(
|
||||||
|
resume_id=resume.id,
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_experience)
|
||||||
|
|
||||||
|
if education:
|
||||||
|
for edu in education:
|
||||||
|
resume_education = ResumeEducation.factory(
|
||||||
|
resume_id=resume.id,
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_education)
|
||||||
|
|
||||||
|
if projects:
|
||||||
|
for proj in projects:
|
||||||
|
resume_project = ResumeProject.factory(
|
||||||
|
resume_id=resume.id,
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_project)
|
||||||
|
|
||||||
await self.unit_of_work.commit()
|
await self.unit_of_work.commit()
|
||||||
|
|
||||||
return resume.id
|
return resume.id
|
||||||
|
|||||||
@@ -1,20 +1,77 @@
|
|||||||
from template_project.application.common.data_structure import to_data_structure
|
from template_project.application.common.data_structure import to_data_structure
|
||||||
from template_project.application.common.enums import ExperienceType
|
from template_project.application.common.enums import EducationGrade, ExperienceType
|
||||||
from template_project.application.common.identity_provider import IdentityProvider
|
from template_project.application.common.identity_provider import IdentityProvider
|
||||||
from template_project.application.common.interactor import to_interactor
|
from template_project.application.common.interactor import to_interactor
|
||||||
from template_project.application.common.unit_of_work import UnitOfWork
|
from template_project.application.common.unit_of_work import UnitOfWork
|
||||||
from template_project.application.resume.data_gateway import ResumeDataGateway
|
from template_project.application.resume.data_gateway import (
|
||||||
from template_project.application.resume.entity import Resume, ResumeId
|
ResumeDataGateway,
|
||||||
|
ResumeEducationDataGateway,
|
||||||
|
ResumeExperienceDataGateway,
|
||||||
|
ResumeProjectDataGateway,
|
||||||
|
)
|
||||||
|
from template_project.application.resume.entity import (
|
||||||
|
Resume,
|
||||||
|
ResumeEducation,
|
||||||
|
ResumeExperience,
|
||||||
|
ResumeId,
|
||||||
|
ResumeProject,
|
||||||
|
)
|
||||||
from template_project.application.resume.errors import ResumeDoesBelongUserError
|
from template_project.application.resume.errors import ResumeDoesBelongUserError
|
||||||
|
|
||||||
|
|
||||||
@to_data_structure
|
@to_data_structure
|
||||||
class _Response:
|
class ExperienceInput:
|
||||||
|
place: str
|
||||||
|
description: str
|
||||||
|
months_duration: int
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class EducationInput:
|
||||||
|
place: str
|
||||||
|
grade: EducationGrade
|
||||||
|
specialization: str
|
||||||
|
description: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ProjectInput:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ExperienceItemResponse:
|
||||||
|
place: str
|
||||||
|
description: str
|
||||||
|
months_duration: int
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class EducationItemResponse:
|
||||||
|
place: str
|
||||||
|
grade: EducationGrade
|
||||||
|
specialization: str
|
||||||
|
description: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ProjectItemResponse:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class EditResumeResponse:
|
||||||
id: ResumeId
|
id: ResumeId
|
||||||
position: str
|
position: str
|
||||||
|
location: str
|
||||||
about_me: str
|
about_me: str
|
||||||
key_skills: list[str]
|
key_skills: list[str]
|
||||||
experience_type: ExperienceType
|
experience_type: ExperienceType
|
||||||
|
experience: list[ExperienceItemResponse]
|
||||||
|
education: list[EducationItemResponse]
|
||||||
|
projects: list[ProjectItemResponse]
|
||||||
|
|
||||||
|
|
||||||
@to_interactor
|
@to_interactor
|
||||||
@@ -22,21 +79,29 @@ class EditResumeInteractor:
|
|||||||
unit_of_work: UnitOfWork
|
unit_of_work: UnitOfWork
|
||||||
identity_provider: IdentityProvider
|
identity_provider: IdentityProvider
|
||||||
resume_data_gateway: ResumeDataGateway
|
resume_data_gateway: ResumeDataGateway
|
||||||
|
resume_experience_data_gateway: ResumeExperienceDataGateway
|
||||||
|
resume_education_data_gateway: ResumeEducationDataGateway
|
||||||
|
resume_project_data_gateway: ResumeProjectDataGateway
|
||||||
|
|
||||||
async def execute(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
resume_id: ResumeId,
|
resume_id: ResumeId,
|
||||||
position: str | None,
|
position: str | None,
|
||||||
|
location: str | None,
|
||||||
about_me: str | None,
|
about_me: str | None,
|
||||||
key_skills: list[str] | None,
|
key_skills: list[str] | None,
|
||||||
experience_type: ExperienceType | None,
|
experience_type: ExperienceType | None,
|
||||||
) -> _Response:
|
experience: list[ExperienceInput] | None = None,
|
||||||
|
education: list[EducationInput] | None = None,
|
||||||
|
projects: list[ProjectInput] | None = None,
|
||||||
|
) -> EditResumeResponse:
|
||||||
user = await self.identity_provider.get_current_user()
|
user = await self.identity_provider.get_current_user()
|
||||||
old_resume = await self.resume_data_gateway.load(resume_id)
|
old_resume = await self.resume_data_gateway.load(resume_id)
|
||||||
if old_resume.user_id != user.id:
|
if old_resume.user_id != user.id:
|
||||||
raise ResumeDoesBelongUserError
|
raise ResumeDoesBelongUserError
|
||||||
|
|
||||||
new_position = position if position is not None else old_resume.position
|
new_position = position if position is not None else old_resume.position
|
||||||
|
new_location = location if location is not None else old_resume.location
|
||||||
new_about_me = about_me if about_me is not None else old_resume.about_me
|
new_about_me = about_me if about_me is not None else old_resume.about_me
|
||||||
new_key_skills = key_skills if key_skills is not None else old_resume.key_skills
|
new_key_skills = key_skills if key_skills is not None else old_resume.key_skills
|
||||||
new_experience_type = experience_type if experience_type is not None else old_resume.experience_type
|
new_experience_type = experience_type if experience_type is not None else old_resume.experience_type
|
||||||
@@ -44,6 +109,7 @@ class EditResumeInteractor:
|
|||||||
new_resume = Resume.factory(
|
new_resume = Resume.factory(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
position=new_position,
|
position=new_position,
|
||||||
|
location=new_location,
|
||||||
about_me=new_about_me,
|
about_me=new_about_me,
|
||||||
key_skills=new_key_skills,
|
key_skills=new_key_skills,
|
||||||
experience_type=new_experience_type,
|
experience_type=new_experience_type,
|
||||||
@@ -53,15 +119,74 @@ class EditResumeInteractor:
|
|||||||
|
|
||||||
await self.unit_of_work.add(new_resume)
|
await self.unit_of_work.add(new_resume)
|
||||||
|
|
||||||
|
if experience is not None:
|
||||||
|
for exp in experience:
|
||||||
|
resume_experience = ResumeExperience.factory(
|
||||||
|
resume_id=new_resume.id,
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_experience)
|
||||||
|
|
||||||
|
if education is not None:
|
||||||
|
for edu in education:
|
||||||
|
resume_education = ResumeEducation.factory(
|
||||||
|
resume_id=new_resume.id,
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_education)
|
||||||
|
|
||||||
|
if projects is not None:
|
||||||
|
for proj in projects:
|
||||||
|
resume_project = ResumeProject.factory(
|
||||||
|
resume_id=new_resume.id,
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
await self.unit_of_work.add(resume_project)
|
||||||
|
|
||||||
if old_resume.up_resume_id is None:
|
if old_resume.up_resume_id is None:
|
||||||
old_resume.up_resume_id = new_resume.id
|
old_resume.up_resume_id = new_resume.id
|
||||||
|
|
||||||
await self.unit_of_work.commit()
|
await self.unit_of_work.commit()
|
||||||
|
|
||||||
return _Response(
|
new_experiences = await self.resume_experience_data_gateway.load_by_resume_id(new_resume.id)
|
||||||
|
new_educations = await self.resume_education_data_gateway.load_by_resume_id(new_resume.id)
|
||||||
|
new_projects = await self.resume_project_data_gateway.load_by_resume_id(new_resume.id)
|
||||||
|
|
||||||
|
return EditResumeResponse(
|
||||||
id=new_resume.id,
|
id=new_resume.id,
|
||||||
position=new_resume.position,
|
position=new_resume.position,
|
||||||
|
location=new_resume.location,
|
||||||
about_me=new_resume.about_me,
|
about_me=new_resume.about_me,
|
||||||
key_skills=new_resume.key_skills,
|
key_skills=new_resume.key_skills,
|
||||||
experience_type=new_resume.experience_type,
|
experience_type=new_resume.experience_type,
|
||||||
|
experience=[
|
||||||
|
ExperienceItemResponse(
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
for exp in new_experiences
|
||||||
|
],
|
||||||
|
education=[
|
||||||
|
EducationItemResponse(
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
for edu in new_educations
|
||||||
|
],
|
||||||
|
projects=[
|
||||||
|
ProjectItemResponse(
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
for proj in new_projects
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from template_project.application.common.data_structure import to_data_structure
|
from template_project.application.common.data_structure import to_data_structure
|
||||||
from template_project.application.common.enums import ExperienceType
|
from template_project.application.common.enums import EducationGrade, ExperienceType
|
||||||
from template_project.application.common.identity_provider import IdentityProvider
|
from template_project.application.common.identity_provider import IdentityProvider
|
||||||
from template_project.application.common.interactor import to_interactor
|
from template_project.application.common.interactor import to_interactor
|
||||||
from template_project.application.resume.data_gateway import ResumeDataGateway, ResumePredictionDataGateway
|
from template_project.application.resume.data_gateway import (
|
||||||
|
ResumeDataGateway,
|
||||||
|
ResumeEducationDataGateway,
|
||||||
|
ResumeExperienceDataGateway,
|
||||||
|
ResumePredictionDataGateway,
|
||||||
|
ResumeProjectDataGateway,
|
||||||
|
)
|
||||||
from template_project.application.resume.entity import ResumeId
|
from template_project.application.resume.entity import ResumeId
|
||||||
from template_project.application.resume.errors import ResumeDoesBelongUserError
|
from template_project.application.resume.errors import ResumeDoesBelongUserError
|
||||||
|
|
||||||
@@ -17,12 +23,37 @@ class ResumePredictionResponse:
|
|||||||
|
|
||||||
|
|
||||||
@to_data_structure
|
@to_data_structure
|
||||||
class _Response:
|
class ExperienceItemResponse:
|
||||||
|
place: str
|
||||||
|
description: str
|
||||||
|
months_duration: int
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class EducationItemResponse:
|
||||||
|
place: str
|
||||||
|
grade: EducationGrade
|
||||||
|
specialization: str
|
||||||
|
description: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class ProjectItemResponse:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
@to_data_structure
|
||||||
|
class GetResumeResponse:
|
||||||
id: ResumeId
|
id: ResumeId
|
||||||
position: str
|
position: str
|
||||||
|
location: str
|
||||||
about_me: str
|
about_me: str
|
||||||
key_skills: list[str]
|
key_skills: list[str]
|
||||||
experience_type: ExperienceType
|
experience_type: ExperienceType
|
||||||
|
experience: list[ExperienceItemResponse]
|
||||||
|
education: list[EducationItemResponse]
|
||||||
|
projects: list[ProjectItemResponse]
|
||||||
prediction: ResumePredictionResponse | None
|
prediction: ResumePredictionResponse | None
|
||||||
|
|
||||||
|
|
||||||
@@ -31,11 +62,14 @@ class GetResumeInteractor:
|
|||||||
identity_provider: IdentityProvider
|
identity_provider: IdentityProvider
|
||||||
resume_data_gateway: ResumeDataGateway
|
resume_data_gateway: ResumeDataGateway
|
||||||
resume_prediction_data_gateway: ResumePredictionDataGateway
|
resume_prediction_data_gateway: ResumePredictionDataGateway
|
||||||
|
resume_experience_data_gateway: ResumeExperienceDataGateway
|
||||||
|
resume_education_data_gateway: ResumeEducationDataGateway
|
||||||
|
resume_project_data_gateway: ResumeProjectDataGateway
|
||||||
|
|
||||||
async def execute(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
resume_id: ResumeId,
|
resume_id: ResumeId,
|
||||||
) -> _Response:
|
) -> GetResumeResponse:
|
||||||
user = await self.identity_provider.get_current_user()
|
user = await self.identity_provider.get_current_user()
|
||||||
|
|
||||||
resume = await self.resume_data_gateway.load(resume_id)
|
resume = await self.resume_data_gateway.load(resume_id)
|
||||||
@@ -53,12 +87,41 @@ class GetResumeInteractor:
|
|||||||
else:
|
else:
|
||||||
prediction = None
|
prediction = None
|
||||||
|
|
||||||
return _Response(
|
experiences = await self.resume_experience_data_gateway.load_by_resume_id(resume.id)
|
||||||
|
educations = await self.resume_education_data_gateway.load_by_resume_id(resume.id)
|
||||||
|
projects = await self.resume_project_data_gateway.load_by_resume_id(resume.id)
|
||||||
|
|
||||||
|
return GetResumeResponse(
|
||||||
id=resume.id,
|
id=resume.id,
|
||||||
position=resume.position,
|
position=resume.position,
|
||||||
|
location=resume.location,
|
||||||
about_me=resume.about_me,
|
about_me=resume.about_me,
|
||||||
key_skills=resume.key_skills,
|
key_skills=resume.key_skills,
|
||||||
experience_type=resume.experience_type,
|
experience_type=resume.experience_type,
|
||||||
|
experience=[
|
||||||
|
ExperienceItemResponse(
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
for exp in experiences
|
||||||
|
],
|
||||||
|
education=[
|
||||||
|
EducationItemResponse(
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
for edu in educations
|
||||||
|
],
|
||||||
|
projects=[
|
||||||
|
ProjectItemResponse(
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
for proj in projects
|
||||||
|
],
|
||||||
prediction=prediction,
|
prediction=prediction,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,6 +130,7 @@ class GetResumeInteractor:
|
|||||||
class ResumeListItemResponse:
|
class ResumeListItemResponse:
|
||||||
id: ResumeId
|
id: ResumeId
|
||||||
position: str
|
position: str
|
||||||
|
location: str
|
||||||
about_me: str
|
about_me: str
|
||||||
key_skills: list[str]
|
key_skills: list[str]
|
||||||
experience_type: ExperienceType
|
experience_type: ExperienceType
|
||||||
@@ -86,6 +150,7 @@ class GetResumeListInteractor:
|
|||||||
ResumeListItemResponse(
|
ResumeListItemResponse(
|
||||||
id=r.id,
|
id=r.id,
|
||||||
position=r.position,
|
position=r.position,
|
||||||
|
location=r.location,
|
||||||
about_me=r.about_me,
|
about_me=r.about_me,
|
||||||
key_skills=r.key_skills,
|
key_skills=r.key_skills,
|
||||||
experience_type=r.experience_type,
|
experience_type=r.experience_type,
|
||||||
@@ -112,6 +177,7 @@ class GetResumeHistoryInteractor:
|
|||||||
ResumeListItemResponse(
|
ResumeListItemResponse(
|
||||||
id=r.id,
|
id=r.id,
|
||||||
position=r.position,
|
position=r.position,
|
||||||
|
location=r.location,
|
||||||
about_me=r.about_me,
|
about_me=r.about_me,
|
||||||
key_skills=r.key_skills,
|
key_skills=r.key_skills,
|
||||||
experience_type=r.experience_type,
|
experience_type=r.experience_type,
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ class PredictSalaryResponse:
|
|||||||
class PredictSalaryInteractor:
|
class PredictSalaryInteractor:
|
||||||
async def execute(self, request: PredictSalaryRequest) -> PredictSalaryResponse:
|
async def execute(self, request: PredictSalaryRequest) -> PredictSalaryResponse:
|
||||||
return PredictSalaryResponse(
|
return PredictSalaryResponse(
|
||||||
salary_from=Decimal("50000"),
|
salary_from=Decimal(50000),
|
||||||
salary_to=Decimal("80000"),
|
salary_to=Decimal(80000),
|
||||||
recommended_skills=["python", "django", "postgresql"],
|
recommended_skills=["python", "django", "postgresql"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ class InteractorProvider(Provider):
|
|||||||
interactors = provide_all(
|
interactors = provide_all(
|
||||||
PredictSalaryInteractor,
|
PredictSalaryInteractor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ from decimal import Decimal
|
|||||||
|
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
from dishka.integrations.fastapi import DishkaRoute
|
from dishka.integrations.fastapi import DishkaRoute
|
||||||
from fastapi import APIRouter, status
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from template_project.application.resume.entity import ResumeId
|
from template_project.application.resume.entity import ResumeId
|
||||||
from template_project.ml.interactors.predict_salary import (
|
from template_project.ml.interactors.predict_salary import (
|
||||||
PredictSalaryInteractor,
|
PredictSalaryInteractor,
|
||||||
PredictSalaryRequest,
|
PredictSalaryRequest,
|
||||||
PredictSalaryResponse,
|
|
||||||
VacancyInput,
|
VacancyInput,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,9 +42,7 @@ class PredictSalaryRequestModel(BaseModel):
|
|||||||
key_skills: list[str] = Field(
|
key_skills: list[str] = Field(
|
||||||
min_length=1, description="List of key skills from resume", examples=[["Python", "FastAPI", "PostgreSQL"]]
|
min_length=1, description="List of key skills from resume", examples=[["Python", "FastAPI", "PostgreSQL"]]
|
||||||
)
|
)
|
||||||
vacancies: list[VacancyInputModel] = Field(
|
vacancies: list[VacancyInputModel] = Field(min_length=1, description="List of relevant vacancies", examples=[[]])
|
||||||
min_length=1, description="List of relevant vacancies", examples=[[]]
|
|
||||||
)
|
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
@@ -120,4 +117,3 @@ async def predict_salary(
|
|||||||
salary_to=response.salary_to,
|
salary_to=response.salary_to,
|
||||||
recommended_skills=response.recommended_skills,
|
recommended_skills=response.recommended_skills,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ from template_project.adapters.data_gateways.auth_identity import DefaultAuthIde
|
|||||||
from template_project.adapters.data_gateways.key_skills import KeySkillsDataGateway
|
from template_project.adapters.data_gateways.key_skills import KeySkillsDataGateway
|
||||||
from template_project.adapters.data_gateways.notification_device import DefaultNotificationDeviceDataGateway
|
from template_project.adapters.data_gateways.notification_device import DefaultNotificationDeviceDataGateway
|
||||||
from template_project.adapters.data_gateways.profile import DefaultProfileDataGateway
|
from template_project.adapters.data_gateways.profile import DefaultProfileDataGateway
|
||||||
from template_project.adapters.data_gateways.resume import DefaultResumeDataGateway, DefaultResumePredictionDataGateway
|
from template_project.adapters.data_gateways.resume import (
|
||||||
|
DefaultResumeDataGateway,
|
||||||
|
DefaultResumeEducationDataGateway,
|
||||||
|
DefaultResumeExperienceDataGateway,
|
||||||
|
DefaultResumePredictionDataGateway,
|
||||||
|
DefaultResumeProjectDataGateway,
|
||||||
|
)
|
||||||
from template_project.adapters.data_gateways.user import DefaultUserDataGateway
|
from template_project.adapters.data_gateways.user import DefaultUserDataGateway
|
||||||
from template_project.adapters.unit_of_work import DefaultUnitOfWork
|
from template_project.adapters.unit_of_work import DefaultUnitOfWork
|
||||||
|
|
||||||
@@ -23,4 +29,7 @@ class DataGatewayProvider(Provider):
|
|||||||
WithParents[DefaultNotificationDeviceDataGateway],
|
WithParents[DefaultNotificationDeviceDataGateway],
|
||||||
WithParents[DefaultResumeDataGateway],
|
WithParents[DefaultResumeDataGateway],
|
||||||
WithParents[DefaultResumePredictionDataGateway],
|
WithParents[DefaultResumePredictionDataGateway],
|
||||||
|
WithParents[DefaultResumeExperienceDataGateway],
|
||||||
|
WithParents[DefaultResumeEducationDataGateway],
|
||||||
|
WithParents[DefaultResumeProjectDataGateway],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,11 +8,21 @@ from fastapi import APIRouter, Depends, HTTPException, Query
|
|||||||
from fastapi.security import HTTPBearer
|
from fastapi.security import HTTPBearer
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from template_project.application.common.enums import ExperienceType
|
from template_project.application.common.enums import EducationGrade, ExperienceType
|
||||||
from template_project.application.resume.entity import ResumeId
|
from template_project.application.resume.entity import ResumeId
|
||||||
from template_project.application.resume.errors import ResumeDoesBelongUserError, ResumeNotFoundError
|
from template_project.application.resume.errors import ResumeDoesBelongUserError, ResumeNotFoundError
|
||||||
from template_project.application.resume.interactors.add import AddResumeInteractor
|
from template_project.application.resume.interactors.add import (
|
||||||
from template_project.application.resume.interactors.edit import EditResumeInteractor
|
AddResumeInteractor,
|
||||||
|
EducationInput,
|
||||||
|
ExperienceInput,
|
||||||
|
ProjectInput,
|
||||||
|
)
|
||||||
|
from template_project.application.resume.interactors.edit import (
|
||||||
|
EditResumeInteractor,
|
||||||
|
EducationInput as EditEducationInput,
|
||||||
|
ExperienceInput as EditExperienceInput,
|
||||||
|
ProjectInput as EditProjectInput,
|
||||||
|
)
|
||||||
from template_project.application.resume.interactors.get import (
|
from template_project.application.resume.interactors.get import (
|
||||||
GetResumeHistoryInteractor,
|
GetResumeHistoryInteractor,
|
||||||
GetResumeInteractor,
|
GetResumeInteractor,
|
||||||
@@ -23,18 +33,81 @@ security = HTTPBearer()
|
|||||||
router = APIRouter(route_class=DishkaRoute, tags=["Resume"], dependencies=[Depends(security)])
|
router = APIRouter(route_class=DishkaRoute, tags=["Resume"], dependencies=[Depends(security)])
|
||||||
|
|
||||||
|
|
||||||
|
class ExperienceItem(BaseModel):
|
||||||
|
place: str = Field(min_length=1, max_length=200, description="Company or organization name", examples=["T-bank"])
|
||||||
|
description: str = Field(
|
||||||
|
min_length=1, max_length=2000, description="Job description", examples=["some description lorem ipsum"]
|
||||||
|
)
|
||||||
|
months_duration: int = Field(ge=1, description="Duration in months", examples=[12])
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"json_schema_extra": {
|
||||||
|
"example": {
|
||||||
|
"place": "T-bank",
|
||||||
|
"description": "some description lorem ipsum",
|
||||||
|
"months_duration": 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EducationItem(BaseModel):
|
||||||
|
place: str = Field(
|
||||||
|
min_length=1, max_length=200, description="Educational institution name", examples=["Central university"]
|
||||||
|
)
|
||||||
|
grade: EducationGrade = Field(description="Education grade", examples=[EducationGrade.BACHELOR])
|
||||||
|
specialization: str = Field(min_length=1, max_length=200, description="Field of study", examples=["IT guy"])
|
||||||
|
description: str | None = Field(
|
||||||
|
None,
|
||||||
|
max_length=2000,
|
||||||
|
description="Additional description (optional)",
|
||||||
|
examples=["optional field, if user want add something"],
|
||||||
|
)
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"json_schema_extra": {
|
||||||
|
"example": {
|
||||||
|
"place": "Central university",
|
||||||
|
"grade": "bachelor",
|
||||||
|
"specialization": "IT guy",
|
||||||
|
"description": "optional field, if user want add something",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectItem(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=200, description="Project name", examples=["Rekomenci fluon"])
|
||||||
|
description: str = Field(
|
||||||
|
min_length=1, max_length=2000, description="Project description", examples=["fucking shit"]
|
||||||
|
)
|
||||||
|
|
||||||
|
model_config = {
|
||||||
|
"json_schema_extra": {
|
||||||
|
"example": {
|
||||||
|
"name": "Rekomenci fluon",
|
||||||
|
"description": "fucking shit",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CreateResumeRequest(BaseModel):
|
class CreateResumeRequest(BaseModel):
|
||||||
position: str = Field(min_length=1, max_length=200, description="Job position", examples=["Python Developer"])
|
position: str = Field(min_length=1, max_length=200, description="Job position", examples=["Python Developer"])
|
||||||
about_me: str = Field(
|
about_me: str = Field(
|
||||||
min_length=1,
|
min_length=1,
|
||||||
max_length=2000,
|
max_length=2000,
|
||||||
description="About me section",
|
description="About me section",
|
||||||
examples=["Experienced Python developer"],
|
examples=["Experienced Python developer with 5 years of experience"],
|
||||||
)
|
)
|
||||||
key_skills: list[str] = Field(
|
key_skills: list[str] = Field(
|
||||||
min_length=1, description="List of key skills", examples=[["Python", "FastAPI", "PostgreSQL"]]
|
min_length=1, description="List of key skills", examples=[["Python", "FastAPI", "PostgreSQL"]]
|
||||||
)
|
)
|
||||||
experience_type: ExperienceType = Field(description="Experience type", examples=[ExperienceType.BETWEEN_3_AND_6])
|
experience_type: ExperienceType = Field(description="Experience type", examples=[ExperienceType.BETWEEN_3_AND_6])
|
||||||
|
location: str = Field(min_length=1, max_length=100, description="Location", examples=["Moscow"])
|
||||||
|
experience: list[ExperienceItem] = Field(default_factory=list, description="Work experience list")
|
||||||
|
education: list[EducationItem] = Field(default_factory=list, description="Education list")
|
||||||
|
projects: list[ProjectItem] = Field(default_factory=list, description="Projects list")
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
@@ -43,6 +116,28 @@ class CreateResumeRequest(BaseModel):
|
|||||||
"about_me": "Experienced Python developer with 5 years of experience",
|
"about_me": "Experienced Python developer with 5 years of experience",
|
||||||
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
||||||
"experience_type": "between3And6",
|
"experience_type": "between3And6",
|
||||||
|
"location": "Moscow",
|
||||||
|
"experience": [
|
||||||
|
{
|
||||||
|
"place": "T-bank",
|
||||||
|
"description": "some description lorem ipsum",
|
||||||
|
"months_duration": 12,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"education": [
|
||||||
|
{
|
||||||
|
"place": "Central university",
|
||||||
|
"grade": "bachelor",
|
||||||
|
"specialization": "IT guy",
|
||||||
|
"description": "optional field, if user want add something",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "Rekomenci fluon",
|
||||||
|
"description": "fucking shit",
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,18 +168,44 @@ class SalaryPrediction(BaseModel):
|
|||||||
class ResumeResponse(BaseModel):
|
class ResumeResponse(BaseModel):
|
||||||
id: ResumeId = Field(description="Resume ID")
|
id: ResumeId = Field(description="Resume ID")
|
||||||
position: str = Field(description="Job position")
|
position: str = Field(description="Job position")
|
||||||
|
location: str = Field(description="Location")
|
||||||
about_me: str = Field(description="About me section")
|
about_me: str = Field(description="About me section")
|
||||||
key_skills: list[str] = Field(description="List of key skills")
|
key_skills: list[str] = Field(description="List of key skills")
|
||||||
experience_type: ExperienceType = Field(description="Experience type")
|
experience_type: ExperienceType = Field(description="Experience type")
|
||||||
|
experience: list[ExperienceItem] = Field(description="Work experience list")
|
||||||
|
education: list[EducationItem] = Field(description="Education list")
|
||||||
|
projects: list[ProjectItem] = Field(description="Projects list")
|
||||||
prediction: SalaryPrediction | None = Field(None, description="Salary prediction (can be null)")
|
prediction: SalaryPrediction | None = Field(None, description="Salary prediction (can be null)")
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
"example": {
|
"example": {
|
||||||
"position": "Python Developer",
|
"position": "Python Developer",
|
||||||
|
"location": "Moscow",
|
||||||
"about_me": "Experienced Python developer with 5 years of experience",
|
"about_me": "Experienced Python developer with 5 years of experience",
|
||||||
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
||||||
"experience_type": "between3And6",
|
"experience_type": "between3And6",
|
||||||
|
"experience": [
|
||||||
|
{
|
||||||
|
"place": "T-bank",
|
||||||
|
"description": "some description lorem ipsum",
|
||||||
|
"months_duration": 12,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"education": [
|
||||||
|
{
|
||||||
|
"place": "Central university",
|
||||||
|
"grade": "bachelor",
|
||||||
|
"specialization": "IT guy",
|
||||||
|
"description": "optional field, if user want add something",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "Rekomenci fluon",
|
||||||
|
"description": "fucking shit",
|
||||||
|
}
|
||||||
|
],
|
||||||
"prediction": {
|
"prediction": {
|
||||||
"from_salary": "100000",
|
"from_salary": "100000",
|
||||||
"to_salary": "150000",
|
"to_salary": "150000",
|
||||||
@@ -98,6 +219,7 @@ class ResumeResponse(BaseModel):
|
|||||||
class ResumeListItem(BaseModel):
|
class ResumeListItem(BaseModel):
|
||||||
id: ResumeId = Field(description="Resume ID")
|
id: ResumeId = Field(description="Resume ID")
|
||||||
position: str = Field(description="Job position")
|
position: str = Field(description="Job position")
|
||||||
|
location: str = Field(description="Location")
|
||||||
about_me: str = Field(description="About me section")
|
about_me: str = Field(description="About me section")
|
||||||
key_skills: list[str] = Field(description="List of key skills")
|
key_skills: list[str] = Field(description="List of key skills")
|
||||||
experience_type: ExperienceType = Field(description="Experience type")
|
experience_type: ExperienceType = Field(description="Experience type")
|
||||||
@@ -106,6 +228,7 @@ class ResumeListItem(BaseModel):
|
|||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
"example": {
|
"example": {
|
||||||
"position": "Python Developer",
|
"position": "Python Developer",
|
||||||
|
"location": "Moscow",
|
||||||
"about_me": "Experienced Python developer with 5 years of experience",
|
"about_me": "Experienced Python developer with 5 years of experience",
|
||||||
"key_skills": ["Python", "FastAPI", "PostgreSQL"],
|
"key_skills": ["Python", "FastAPI", "PostgreSQL"],
|
||||||
"experience_type": "between3And6",
|
"experience_type": "between3And6",
|
||||||
@@ -175,11 +298,39 @@ async def create_resume(
|
|||||||
request: CreateResumeRequest,
|
request: CreateResumeRequest,
|
||||||
interactor: FromDishka[AddResumeInteractor],
|
interactor: FromDishka[AddResumeInteractor],
|
||||||
) -> CreateResumeResponse:
|
) -> CreateResumeResponse:
|
||||||
|
experience = (
|
||||||
|
[
|
||||||
|
ExperienceInput(place=exp.place, description=exp.description, months_duration=exp.months_duration)
|
||||||
|
for exp in request.experience
|
||||||
|
]
|
||||||
|
if request.experience
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
education = (
|
||||||
|
[
|
||||||
|
EducationInput(
|
||||||
|
place=edu.place, grade=edu.grade, specialization=edu.specialization, description=edu.description
|
||||||
|
)
|
||||||
|
for edu in request.education
|
||||||
|
]
|
||||||
|
if request.education
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
projects = (
|
||||||
|
[ProjectInput(name=proj.name, description=proj.description) for proj in request.projects]
|
||||||
|
if request.projects
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
interactor_response = await interactor.execute(
|
interactor_response = await interactor.execute(
|
||||||
position=request.position,
|
position=request.position,
|
||||||
|
location=request.location,
|
||||||
about_me=request.about_me,
|
about_me=request.about_me,
|
||||||
key_skills=request.key_skills,
|
key_skills=request.key_skills,
|
||||||
experience_type=request.experience_type,
|
experience_type=request.experience_type,
|
||||||
|
experience=experience,
|
||||||
|
education=education,
|
||||||
|
projects=projects,
|
||||||
)
|
)
|
||||||
return CreateResumeResponse(
|
return CreateResumeResponse(
|
||||||
resume_id=interactor_response,
|
resume_id=interactor_response,
|
||||||
@@ -206,6 +357,7 @@ async def get_resume_list(
|
|||||||
ResumeListItem(
|
ResumeListItem(
|
||||||
id=r.id,
|
id=r.id,
|
||||||
position=r.position,
|
position=r.position,
|
||||||
|
location=r.location,
|
||||||
about_me=r.about_me,
|
about_me=r.about_me,
|
||||||
key_skills=r.key_skills,
|
key_skills=r.key_skills,
|
||||||
experience_type=r.experience_type,
|
experience_type=r.experience_type,
|
||||||
@@ -246,9 +398,34 @@ async def get_resume(
|
|||||||
return ResumeResponse(
|
return ResumeResponse(
|
||||||
id=interactor_response.id,
|
id=interactor_response.id,
|
||||||
position=interactor_response.position,
|
position=interactor_response.position,
|
||||||
|
location=interactor_response.location,
|
||||||
about_me=interactor_response.about_me,
|
about_me=interactor_response.about_me,
|
||||||
key_skills=interactor_response.key_skills,
|
key_skills=interactor_response.key_skills,
|
||||||
experience_type=interactor_response.experience_type,
|
experience_type=interactor_response.experience_type,
|
||||||
|
experience=[
|
||||||
|
ExperienceItem(
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
for exp in interactor_response.experience
|
||||||
|
],
|
||||||
|
education=[
|
||||||
|
EducationItem(
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
for edu in interactor_response.education
|
||||||
|
],
|
||||||
|
projects=[
|
||||||
|
ProjectItem(
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
for proj in interactor_response.projects
|
||||||
|
],
|
||||||
prediction=SalaryPrediction(
|
prediction=SalaryPrediction(
|
||||||
from_salary=interactor_response.prediction.from_salary,
|
from_salary=interactor_response.prediction.from_salary,
|
||||||
to_salary=interactor_response.prediction.to_salary,
|
to_salary=interactor_response.prediction.to_salary,
|
||||||
@@ -263,6 +440,7 @@ class PatchResumeRequest(BaseModel):
|
|||||||
position: str | None = Field(
|
position: str | None = Field(
|
||||||
None, min_length=1, max_length=200, description="Job position", examples=["Python Developer"]
|
None, min_length=1, max_length=200, description="Job position", examples=["Python Developer"]
|
||||||
)
|
)
|
||||||
|
location: str | None = Field(None, min_length=1, max_length=100, description="Location", examples=["Moscow"])
|
||||||
about_me: str | None = Field(
|
about_me: str | None = Field(
|
||||||
None, min_length=1, max_length=2000, description="About me section", examples=["Experienced Python developer"]
|
None, min_length=1, max_length=2000, description="About me section", examples=["Experienced Python developer"]
|
||||||
)
|
)
|
||||||
@@ -272,14 +450,39 @@ class PatchResumeRequest(BaseModel):
|
|||||||
experience_type: ExperienceType | None = Field(
|
experience_type: ExperienceType | None = Field(
|
||||||
None, description="Experience type", examples=[ExperienceType.BETWEEN_3_AND_6]
|
None, description="Experience type", examples=[ExperienceType.BETWEEN_3_AND_6]
|
||||||
)
|
)
|
||||||
|
experience: list[ExperienceItem] | None = Field(None, description="Work experience list")
|
||||||
|
education: list[EducationItem] | None = Field(None, description="Education list")
|
||||||
|
projects: list[ProjectItem] | None = Field(None, description="Projects list")
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
"example": {
|
"example": {
|
||||||
"position": "Python Developer",
|
"position": "Python Developer",
|
||||||
|
"location": "Moscow",
|
||||||
"about_me": "Experienced Python developer with 5 years of experience",
|
"about_me": "Experienced Python developer with 5 years of experience",
|
||||||
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
||||||
"experience_type": "between3And6",
|
"experience_type": "between3And6",
|
||||||
|
"experience": [
|
||||||
|
{
|
||||||
|
"place": "T-bank",
|
||||||
|
"description": "some description lorem ipsum",
|
||||||
|
"months_duration": 12,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"education": [
|
||||||
|
{
|
||||||
|
"place": "Central university",
|
||||||
|
"grade": "bachelor",
|
||||||
|
"specialization": "IT guy",
|
||||||
|
"description": "optional field, if user want add something",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "Rekomenci fluon",
|
||||||
|
"description": "fucking shit",
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,17 +491,43 @@ class PatchResumeRequest(BaseModel):
|
|||||||
class PatchResumeResponse(BaseModel):
|
class PatchResumeResponse(BaseModel):
|
||||||
id: ResumeId = Field(description="Resume ID")
|
id: ResumeId = Field(description="Resume ID")
|
||||||
position: str = Field(description="Job position")
|
position: str = Field(description="Job position")
|
||||||
|
location: str = Field(description="Location")
|
||||||
about_me: str = Field(description="About me section")
|
about_me: str = Field(description="About me section")
|
||||||
key_skills: list[str] = Field(description="List of key skills")
|
key_skills: list[str] = Field(description="List of key skills")
|
||||||
experience_type: ExperienceType = Field(description="Experience type")
|
experience_type: ExperienceType = Field(description="Experience type")
|
||||||
|
experience: list[ExperienceItem] = Field(description="Work experience list")
|
||||||
|
education: list[EducationItem] = Field(description="Education list")
|
||||||
|
projects: list[ProjectItem] = Field(description="Projects list")
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"json_schema_extra": {
|
"json_schema_extra": {
|
||||||
"example": {
|
"example": {
|
||||||
"position": "Python Developer",
|
"position": "Python Developer",
|
||||||
|
"location": "Moscow",
|
||||||
"about_me": "Experienced Python developer with 5 years of experience",
|
"about_me": "Experienced Python developer with 5 years of experience",
|
||||||
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
"key_skills": ["Python", "FastAPI", "PostgreSQL", "Docker"],
|
||||||
"experience_type": "between3And6",
|
"experience_type": "between3And6",
|
||||||
|
"experience": [
|
||||||
|
{
|
||||||
|
"place": "T-bank",
|
||||||
|
"description": "some description lorem ipsum",
|
||||||
|
"months_duration": 12,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"education": [
|
||||||
|
{
|
||||||
|
"place": "Central university",
|
||||||
|
"grade": "bachelor",
|
||||||
|
"specialization": "IT guy",
|
||||||
|
"description": "optional field, if user want add something",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "Rekomenci fluon",
|
||||||
|
"description": "fucking shit",
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,6 +566,7 @@ async def get_resume_history(
|
|||||||
ResumeListItem(
|
ResumeListItem(
|
||||||
id=r.id,
|
id=r.id,
|
||||||
position=r.position,
|
position=r.position,
|
||||||
|
location=r.location,
|
||||||
about_me=r.about_me,
|
about_me=r.about_me,
|
||||||
key_skills=r.key_skills,
|
key_skills=r.key_skills,
|
||||||
experience_type=r.experience_type,
|
experience_type=r.experience_type,
|
||||||
@@ -363,12 +593,40 @@ async def patch_resume(
|
|||||||
interactor: FromDishka[EditResumeInteractor],
|
interactor: FromDishka[EditResumeInteractor],
|
||||||
) -> PatchResumeResponse:
|
) -> PatchResumeResponse:
|
||||||
try:
|
try:
|
||||||
|
experience = (
|
||||||
|
[
|
||||||
|
EditExperienceInput(place=exp.place, description=exp.description, months_duration=exp.months_duration)
|
||||||
|
for exp in request.experience
|
||||||
|
]
|
||||||
|
if request.experience is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
education = (
|
||||||
|
[
|
||||||
|
EditEducationInput(
|
||||||
|
place=edu.place, grade=edu.grade, specialization=edu.specialization, description=edu.description
|
||||||
|
)
|
||||||
|
for edu in request.education
|
||||||
|
]
|
||||||
|
if request.education is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
projects = (
|
||||||
|
[EditProjectInput(name=proj.name, description=proj.description) for proj in request.projects]
|
||||||
|
if request.projects is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
interactor_response = await interactor.execute(
|
interactor_response = await interactor.execute(
|
||||||
resume_id=resume_id,
|
resume_id=resume_id,
|
||||||
position=request.position,
|
position=request.position,
|
||||||
|
location=request.location,
|
||||||
about_me=request.about_me,
|
about_me=request.about_me,
|
||||||
key_skills=request.key_skills,
|
key_skills=request.key_skills,
|
||||||
experience_type=request.experience_type,
|
experience_type=request.experience_type,
|
||||||
|
experience=experience,
|
||||||
|
education=education,
|
||||||
|
projects=projects,
|
||||||
)
|
)
|
||||||
except ResumeDoesBelongUserError as error:
|
except ResumeDoesBelongUserError as error:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -384,7 +642,32 @@ async def patch_resume(
|
|||||||
return PatchResumeResponse(
|
return PatchResumeResponse(
|
||||||
id=interactor_response.id,
|
id=interactor_response.id,
|
||||||
position=interactor_response.position,
|
position=interactor_response.position,
|
||||||
|
location=interactor_response.location,
|
||||||
about_me=interactor_response.about_me,
|
about_me=interactor_response.about_me,
|
||||||
key_skills=interactor_response.key_skills,
|
key_skills=interactor_response.key_skills,
|
||||||
experience_type=interactor_response.experience_type,
|
experience_type=interactor_response.experience_type,
|
||||||
|
experience=[
|
||||||
|
ExperienceItem(
|
||||||
|
place=exp.place,
|
||||||
|
description=exp.description,
|
||||||
|
months_duration=exp.months_duration,
|
||||||
|
)
|
||||||
|
for exp in interactor_response.experience
|
||||||
|
],
|
||||||
|
education=[
|
||||||
|
EducationItem(
|
||||||
|
place=edu.place,
|
||||||
|
grade=edu.grade,
|
||||||
|
specialization=edu.specialization,
|
||||||
|
description=edu.description,
|
||||||
|
)
|
||||||
|
for edu in interactor_response.education
|
||||||
|
],
|
||||||
|
projects=[
|
||||||
|
ProjectItem(
|
||||||
|
name=proj.name,
|
||||||
|
description=proj.description,
|
||||||
|
)
|
||||||
|
for proj in interactor_response.projects
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ async def test_success_add_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
assert is_success_response(response)
|
assert is_success_response(response)
|
||||||
assert response.json() == IsDict(
|
assert response.json() == IsDict(
|
||||||
@@ -54,6 +55,7 @@ async def test_unauthorized_add_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
assert is_unauthorized_response(response)
|
assert is_unauthorized_response(response)
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ async def test_success_get_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await test_api_gateway.get_resume(
|
response = await test_api_gateway.get_resume(
|
||||||
@@ -84,9 +87,13 @@ async def test_success_get_resume(
|
|||||||
assert is_success_response(response)
|
assert is_success_response(response)
|
||||||
assert response.json() == IsPartialDict(
|
assert response.json() == IsPartialDict(
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
about_me="About me",
|
about_me="About me",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
|
experience=[],
|
||||||
|
education=[],
|
||||||
|
projects=[],
|
||||||
prediction=None,
|
prediction=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,6 +115,7 @@ async def test_unauthorized_get_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await test_api_gateway.get_resume(
|
response = await test_api_gateway.get_resume(
|
||||||
@@ -152,6 +160,7 @@ async def test_success_edit_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
resume_id = response.json()["resume_id"]
|
resume_id = response.json()["resume_id"]
|
||||||
response = await test_api_gateway.edit_resume(
|
response = await test_api_gateway.edit_resume(
|
||||||
@@ -161,13 +170,18 @@ async def test_success_edit_resume(
|
|||||||
experience_type="between1And3",
|
experience_type="between1And3",
|
||||||
key_skills=["i love python"],
|
key_skills=["i love python"],
|
||||||
position="Updated Position",
|
position="Updated Position",
|
||||||
|
location="St. Petersburg",
|
||||||
)
|
)
|
||||||
assert is_success_response(response)
|
assert is_success_response(response)
|
||||||
assert response.json() == IsPartialDict(
|
assert response.json() == IsPartialDict(
|
||||||
position="Updated Position",
|
position="Updated Position",
|
||||||
|
location="St. Petersburg",
|
||||||
about_me="Updated about me",
|
about_me="Updated about me",
|
||||||
key_skills=["i love python"],
|
key_skills=["i love python"],
|
||||||
experience_type="between1And3",
|
experience_type="between1And3",
|
||||||
|
experience=[],
|
||||||
|
education=[],
|
||||||
|
projects=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -188,6 +202,7 @@ async def test_unauthorized_edit_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
resume_id = response.json()["resume_id"]
|
resume_id = response.json()["resume_id"]
|
||||||
response = await test_api_gateway.edit_resume(
|
response = await test_api_gateway.edit_resume(
|
||||||
@@ -239,6 +254,7 @@ async def test_forbidden_edit_resume(
|
|||||||
experience_type="noExperience",
|
experience_type="noExperience",
|
||||||
key_skills=["i love lisp", "i love rust"],
|
key_skills=["i love lisp", "i love rust"],
|
||||||
position="Position",
|
position="Position",
|
||||||
|
location="Moscow",
|
||||||
)
|
)
|
||||||
|
|
||||||
response_other_sign_up = await test_api_gateway.sign_up_email("f" + unique_email, DEFAULT_PASSWORD)
|
response_other_sign_up = await test_api_gateway.sign_up_email("f" + unique_email, DEFAULT_PASSWORD)
|
||||||
|
|||||||
@@ -58,18 +58,30 @@ class TestApiGateway:
|
|||||||
self,
|
self,
|
||||||
access_token: str,
|
access_token: str,
|
||||||
position: str,
|
position: str,
|
||||||
|
location: str,
|
||||||
about_me: str,
|
about_me: str,
|
||||||
key_skills: list[str],
|
key_skills: list[str],
|
||||||
experience_type: str,
|
experience_type: str,
|
||||||
|
experience: list[dict[str, Any]] | None = None,
|
||||||
|
education: list[dict[str, Any]] | None = None,
|
||||||
|
projects: list[dict[str, Any]] | None = None,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
|
json_data: dict[str, Any] = {
|
||||||
|
"position": position,
|
||||||
|
"location": location,
|
||||||
|
"about_me": about_me,
|
||||||
|
"key_skills": key_skills,
|
||||||
|
"experience_type": experience_type,
|
||||||
|
}
|
||||||
|
if experience is not None:
|
||||||
|
json_data["experience"] = experience
|
||||||
|
if education is not None:
|
||||||
|
json_data["education"] = education
|
||||||
|
if projects is not None:
|
||||||
|
json_data["projects"] = projects
|
||||||
return await self._client.post(
|
return await self._client.post(
|
||||||
"/resume",
|
"/resume",
|
||||||
json={
|
json=json_data,
|
||||||
"position": position,
|
|
||||||
"about_me": about_me,
|
|
||||||
"key_skills": key_skills,
|
|
||||||
"experience_type": experience_type,
|
|
||||||
},
|
|
||||||
headers=make_auth_headers(access_token),
|
headers=make_auth_headers(access_token),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,22 +96,34 @@ class TestApiGateway:
|
|||||||
access_token: str,
|
access_token: str,
|
||||||
resume_id: str,
|
resume_id: str,
|
||||||
position: str | None = None,
|
position: str | None = None,
|
||||||
|
location: str | None = None,
|
||||||
about_me: str | None = None,
|
about_me: str | None = None,
|
||||||
key_skills: list[str] | None = None,
|
key_skills: list[str] | None = None,
|
||||||
experience_type: str | None = None,
|
experience_type: str | None = None,
|
||||||
|
experience: list[dict[str, Any]] | None = None,
|
||||||
|
education: list[dict[str, Any]] | None = None,
|
||||||
|
projects: list[dict[str, Any]] | None = None,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
|
json_data: dict[str, Any] = {}
|
||||||
|
if position is not None:
|
||||||
|
json_data["position"] = position
|
||||||
|
if location is not None:
|
||||||
|
json_data["location"] = location
|
||||||
|
if about_me is not None:
|
||||||
|
json_data["about_me"] = about_me
|
||||||
|
if key_skills is not None:
|
||||||
|
json_data["key_skills"] = key_skills
|
||||||
|
if experience_type is not None:
|
||||||
|
json_data["experience_type"] = experience_type
|
||||||
|
if experience is not None:
|
||||||
|
json_data["experience"] = experience
|
||||||
|
if education is not None:
|
||||||
|
json_data["education"] = education
|
||||||
|
if projects is not None:
|
||||||
|
json_data["projects"] = projects
|
||||||
return await self._client.patch(
|
return await self._client.patch(
|
||||||
f"/resume/{resume_id}",
|
f"/resume/{resume_id}",
|
||||||
json={
|
json=json_data,
|
||||||
key: value
|
|
||||||
for key, value in {
|
|
||||||
"position": position,
|
|
||||||
"about_me": about_me,
|
|
||||||
"key_skills": key_skills,
|
|
||||||
"experience_type": experience_type,
|
|
||||||
}.items()
|
|
||||||
if value is not None
|
|
||||||
},
|
|
||||||
headers=make_auth_headers(access_token),
|
headers=make_auth_headers(access_token),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user