add skills endpoints

This commit is contained in:
ivankirpichnikov
2025-11-22 04:26:06 +03:00
parent 690faecfeb
commit fc52561154
8 changed files with 161 additions and 1 deletions
@@ -0,0 +1,27 @@
from collections.abc import Sequence
from sqlalchemy import insert, select
from sqlalchemy.ext.asyncio import AsyncSession
from template_project.adapters.data_gateways.tables import key_skills_table
class KeySkillsDataGateway:
def __init__(self, session: AsyncSession) -> None:
self._session = session
async def query(self, query: str) -> Sequence[str]:
statement = (
select(key_skills_table.c.name)
.where(key_skills_table.c.name.ilike(f"{query}%"))
.order_by(key_skills_table.c.name)
.limit(30)
)
result = await self._session.execute(statement)
return result.scalars().all()
async def add_skills(self, name: list[str]) -> None:
insert_statement = insert(key_skills_table).values(
[{"name": _} for _ in name]
)
await self._session.execute(insert_statement)
@@ -8,6 +8,7 @@ from sqlalchemy import (
DateTime,
Enum,
ForeignKey,
Integer,
MetaData,
Numeric,
String,
@@ -118,6 +119,12 @@ resume_prediction_table: Final = Table(
Column("to_salary", Numeric, nullable=False),
Column("recommended_skills", ARRAY(String, as_tuple=True), nullable=False),
)
key_skills_table: Final = Table(
"key_skills",
meta_data,
Column("id", Integer, autoincrement=True, primary_key=True),
Column("name", String, nullable=False, unique=True)
)
mapper_registry.map_imperatively(User, user_table)
@@ -0,0 +1,37 @@
"""add key skills
Revision ID: 2ebcb2592cab
Revises: 5b7a1ca1f06b
Create Date: 2025-11-22 03:59:55.147083
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2ebcb2592cab'
down_revision: Union[str, Sequence[str], None] = '5b7a1ca1f06b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('key_skills',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('key_skills')
# ### end Alembic commands ###
+2 -1
View File
@@ -21,7 +21,7 @@ from prometheus_fastapi_instrumentator import Instrumentator
from template_project.application.auth_identity.errors import AuthError
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, resume, storage
from template_project.web_api.routes import auth, healthcheck, key_skills, notification, profile, resume, storage
LOG_CONFIG: Final = {
"version": 1,
@@ -82,6 +82,7 @@ def make_asgi_application(
app.include_router(auth.router)
app.include_router(healthcheck.router)
app.include_router(profile.router)
app.include_router(key_skills.router)
app.include_router(notification.router)
app.include_router(storage.router)
app.include_router(resume.router)
@@ -2,6 +2,7 @@ from dishka import BaseScope, Provider, Scope, WithParents, provide, provide_all
from template_project.adapters.data_gateways.access_token import DefaultAccessTokenDataGateway
from template_project.adapters.data_gateways.auth_identity import DefaultAuthIdentityDataGateway
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.profile import DefaultProfileDataGateway
from template_project.adapters.data_gateways.resume import DefaultResumeDataGateway, DefaultResumePredictionDataGateway
@@ -14,6 +15,7 @@ class DataGatewayProvider(Provider):
unit_of_work = provide(WithParents[DefaultUnitOfWork])
data_gateways = provide_all(
KeySkillsDataGateway,
WithParents[DefaultUserDataGateway],
WithParents[DefaultAccessTokenDataGateway],
WithParents[DefaultAuthIdentityDataGateway],
@@ -0,0 +1,42 @@
from collections.abc import Sequence
from typing import Annotated
from dishka import FromDishka
from dishka.integrations.fastapi import DishkaRoute
from fastapi import APIRouter, Query
from pydantic import BaseModel, Field
from template_project.adapters.data_gateways.key_skills import KeySkillsDataGateway
from template_project.application.common.unit_of_work import UnitOfWork
router = APIRouter(
route_class=DishkaRoute,
tags=["Key Skills"],
)
class KeySkill(BaseModel):
name: str = Field(description="The name of the key skill")
@router.get("/key_skills")
async def query_key_skills(
query: Annotated[str, Query()],
data_gateway: FromDishka[KeySkillsDataGateway],
) -> Sequence[KeySkill]:
skills = await data_gateway.query(query)
return [KeySkill(name=skill) for skill in skills]
class AddKeySkillsRequest(BaseModel):
key_skills: list[str]
@router.post("/key_skills")
async def add_key_skill(
request: AddKeySkillsRequest,
unit_of_work: FromDishka[UnitOfWork],
data_gateway: FromDishka[KeySkillsDataGateway],
) -> None:
await data_gateway.add_skills(request.key_skills)
await unit_of_work.commit()