diff --git a/api/app/controllers/auth_routes.py b/api/app/controllers/auth_routes.py new file mode 100644 index 0000000..4ea1eeb --- /dev/null +++ b/api/app/controllers/auth_routes.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, Depends, HTTPException +from fastapi.openapi.models import Response +from sqlalchemy.ext.asyncio import AsyncSession +from starlette import status + +from app.database.session import get_db +from app.domain.entities.auth import AuthEntity +from app.infrastructure.auth_service import AuthService + +router = APIRouter() + + +@router.post('/login/') +async def auth_user(response: Response, user_data: AuthEntity, db: AsyncSession = Depends(get_db)): + auth_service = AuthService(db) + check = await auth_service.authenticate_user(login=user_data.login, password=user_data.password) + + if check is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail='Неверный логин или пароль', + ) + + access_token = auth_service.create_access_token({"sub": str(check.id)}) + response.set_cookie(key="users_access_token", value=access_token, httponly=True) + + return {'access_token': access_token, 'refresh_token': None} diff --git a/api/app/database/migrations/env.py b/api/app/database/migrations/env.py index bc931a6..48f397e 100644 --- a/api/app/database/migrations/env.py +++ b/api/app/database/migrations/env.py @@ -1,10 +1,9 @@ from logging.config import fileConfig -import sqlalchemy from alembic import context from sqlalchemy import pool -from sqlalchemy.ext.asyncio import AsyncEngine +from sqlalchemy.ext.asyncio import create_async_engine from app.domain.models import Base from app.settings import settings @@ -21,9 +20,8 @@ def get_url(): async def run_migrations_online(): - connectable = AsyncEngine( - sqlalchemy.create_engine(get_url(), poolclass=pool.NullPool, future=True) - ) + connectable = create_async_engine(get_url(), poolclass=pool.NullPool, future=True) + async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) diff --git a/api/app/database/migrations/versions/dde27a957087_initial_migration.py b/api/app/database/migrations/versions/dde27a957087_initial_migration.py deleted file mode 100644 index 19cb011..0000000 --- a/api/app/database/migrations/versions/dde27a957087_initial_migration.py +++ /dev/null @@ -1,30 +0,0 @@ -"""initial migration - -Revision ID: dde27a957087 -Revises: -Create Date: 2025-02-04 13:46:46.958839 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'dde27a957087' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/api/app/database/migrations/versions/f8f3f414b162_initial_migrationa.py b/api/app/database/migrations/versions/f8f3f414b162_initial_migrationa.py new file mode 100644 index 0000000..0f95cf0 --- /dev/null +++ b/api/app/database/migrations/versions/f8f3f414b162_initial_migrationa.py @@ -0,0 +1,177 @@ +"""Initial migrationA + +Revision ID: f8f3f414b162 +Revises: +Create Date: 2025-02-06 20:16:16.961555 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'f8f3f414b162' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('appointment_types', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.VARCHAR(length=150), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + op.create_table('lenses_types', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.VARCHAR(length=150), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + op.create_table('mailing_delivery_methods', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.VARCHAR(length=200), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('patients', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.VARCHAR(length=200), nullable=False), + sa.Column('last_name', sa.VARCHAR(length=200), nullable=False), + sa.Column('patronymic', sa.VARCHAR(length=200), nullable=True), + sa.Column('birthday', sa.Date(), nullable=False), + sa.Column('address', sa.String(), nullable=True), + sa.Column('email', sa.VARCHAR(length=350), nullable=True), + sa.Column('phone', sa.VARCHAR(length=25), nullable=True), + sa.Column('diagnosis', sa.String(), nullable=True), + sa.Column('correction', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('roles', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.VARCHAR(length=150), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + op.create_table('sets', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.VARCHAR(length=150), nullable=False), + sa.Column('count', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('title') + ) + op.create_table('lens', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('tor', sa.Float(), nullable=False), + sa.Column('trial', sa.Float(), nullable=False), + sa.Column('esa', sa.Float(), nullable=False), + sa.Column('fvc', sa.Float(), nullable=False), + sa.Column('preset_refraction', sa.Float(), nullable=False), + sa.Column('diameter', sa.Float(), nullable=False), + sa.Column('periphery_toricity', sa.Float(), nullable=False), + sa.Column('side', sa.Enum('LEFT', 'RIGHT', name='sideenum'), nullable=False), + sa.Column('type_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['type_id'], ['lenses_types.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('users', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_name', sa.VARCHAR(length=200), nullable=False), + sa.Column('last_name', sa.VARCHAR(length=200), nullable=False), + sa.Column('patronymic', sa.VARCHAR(length=200), nullable=True), + sa.Column('login', sa.String(), nullable=False), + sa.Column('password', sa.String(), nullable=False), + sa.Column('role_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('login') + ) + op.create_table('appointments', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('results', sa.String(), nullable=True), + sa.Column('days_until_the_next_appointment', sa.Integer(), nullable=True), + sa.Column('patient_id', sa.Integer(), nullable=False), + sa.Column('doctor_id', sa.Integer(), nullable=False), + sa.Column('type_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['doctor_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['patient_id'], ['patients.id'], ), + sa.ForeignKeyConstraint(['type_id'], ['appointment_types.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('lens_issues', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('issue_date', sa.Date(), nullable=False), + sa.Column('patient_id', sa.Integer(), nullable=False), + sa.Column('doctor_id', sa.Integer(), nullable=False), + sa.Column('lens_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['doctor_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['lens_id'], ['lens.id'], ), + sa.ForeignKeyConstraint(['patient_id'], ['patients.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('mailing', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('text', sa.String(), nullable=False), + sa.Column('title', sa.String(), nullable=False), + sa.Column('datetime', sa.DateTime(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('set_lens', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('set_id', sa.Integer(), nullable=False), + sa.Column('lens_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['lens_id'], ['lens.id'], ), + sa.ForeignKeyConstraint(['set_id'], ['sets.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('lens_id') + ) + op.create_table('appointment_files', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('file_path', sa.String(), nullable=False), + sa.Column('file_title', sa.String(), nullable=False), + sa.Column('appointment_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['appointment_id'], ['appointments.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('mailing_options', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('option_id', sa.Integer(), nullable=False), + sa.Column('mailing_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['mailing_id'], ['mailing.id'], ), + sa.ForeignKeyConstraint(['option_id'], ['patients.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('recipients', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('patient_id', sa.Integer(), nullable=False), + sa.Column('mailing_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['mailing_id'], ['mailing.id'], ), + sa.ForeignKeyConstraint(['patient_id'], ['mailing_delivery_methods.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('recipients') + op.drop_table('mailing_options') + op.drop_table('appointment_files') + op.drop_table('set_lens') + op.drop_table('mailing') + op.drop_table('lens_issues') + op.drop_table('appointments') + op.drop_table('users') + op.drop_table('lens') + op.drop_table('sets') + op.drop_table('roles') + op.drop_table('patients') + op.drop_table('mailing_delivery_methods') + op.drop_table('lenses_types') + op.drop_table('appointment_types') + # ### end Alembic commands ### diff --git a/api/app/domain/entities/auth.py b/api/app/domain/entities/auth.py new file mode 100644 index 0000000..83d11f1 --- /dev/null +++ b/api/app/domain/entities/auth.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel, Field + + +class AuthEntity(BaseModel): + login: str = Field(...) + password: str = Field(..., min_length=5) diff --git a/api/app/domain/models/__init__.py b/api/app/domain/models/__init__.py index 7c2377a..5771f05 100644 --- a/api/app/domain/models/__init__.py +++ b/api/app/domain/models/__init__.py @@ -1,3 +1,19 @@ -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import declarative_base -Base = declarative_base() \ No newline at end of file +Base = declarative_base() + +from app.domain.models.appointment_files import AppointmentFile +from app.domain.models.appointments import Appointment +from app.domain.models.appointment_types import AppointmentType +from app.domain.models.lenses_types import LensesType +from app.domain.models.lens_issues import LensIssue +from app.domain.models.lens import Lens +from app.domain.models.mailing_delivery_methods import MailingDeliveryMethod +from app.domain.models.mailing_options import MailingOption +from app.domain.models.mailing import Mailing +from app.domain.models.patients import Patient +from app.domain.models.recipients import Recipient +from app.domain.models.roles import Role +from app.domain.models.set_lens import SetLens +from app.domain.models.sets import Set +from app.domain.models.users import User diff --git a/api/app/domain/models/lens_issues.py b/api/app/domain/models/lens_issues.py index 2f40564..8e57f2f 100644 --- a/api/app/domain/models/lens_issues.py +++ b/api/app/domain/models/lens_issues.py @@ -11,7 +11,7 @@ class LensIssue(Base): issue_date = Column(Date, nullable=False) patient_id = Column(Integer, ForeignKey('patients.id'), nullable=False) - doctor_id = Column(Integer, ForeignKey('user.id'), nullable=False) + doctor_id = Column(Integer, ForeignKey('users.id'), nullable=False) lens_id = Column(Integer, ForeignKey('lens.id'), nullable=False) patient = relationship('Patient', back_populates='lens_issues') diff --git a/api/app/domain/models/mailing_delivery_methods.py b/api/app/domain/models/mailing_delivery_methods.py index 3bd3e86..3fce573 100644 --- a/api/app/domain/models/mailing_delivery_methods.py +++ b/api/app/domain/models/mailing_delivery_methods.py @@ -5,7 +5,7 @@ from app.domain.models import Base class MailingDeliveryMethod(Base): - __tbalename__ = 'mailing_delivery_methods' + __tablename__ = 'mailing_delivery_methods' id = Column(Integer, primary_key=True, autoincrement=True) title = Column(VARCHAR(200), nullable=False) diff --git a/api/app/domain/models/recipients.py b/api/app/domain/models/recipients.py index 607ab04..c850eee 100644 --- a/api/app/domain/models/recipients.py +++ b/api/app/domain/models/recipients.py @@ -5,7 +5,7 @@ from app.domain.models import Base class Recipient(Base): - __tbalename__ = 'recipients' + __tablename__ = 'recipients' id = Column(Integer, primary_key=True, autoincrement=True) diff --git a/api/app/infrastructure/auth_service.py b/api/app/infrastructure/auth_service.py index 9ff5c60..f6ebdfc 100644 --- a/api/app/infrastructure/auth_service.py +++ b/api/app/infrastructure/auth_service.py @@ -1,14 +1,33 @@ import datetime from jose import jwt +from sqlalchemy.ext.asyncio import AsyncSession +from app.application.users_repository import UsersRepository from app.settings import get_auth_data -def create_access_token(data: dict) -> str: - to_encode = data.copy() - expire = datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30) - to_encode.update({"exp": expire}) - auth_data = get_auth_data() - encode_jwt = jwt.encode(to_encode, auth_data['secret_key'], algorithm=auth_data['algorithm']) - return encode_jwt +class AuthService: + def __init__(self, db: AsyncSession): + self.repository = UsersRepository(db) + + async def authenticate_user(self, login: str, password: str): + user = await self.repository.get_by_login(login) + + if not user: + return None + + if not user.check_password(password): + return None + + return user + + @staticmethod + def create_access_token(data: dict) -> str: + to_encode = data.copy() + expire = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=30) + to_encode.update({"exp": expire}) + auth_data = get_auth_data() + encode_jwt = jwt.encode(to_encode, auth_data['secret_key'], algorithm=auth_data['algorithm']) + + return encode_jwt diff --git a/api/app/main.py b/api/app/main.py index e69de29..0326539 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -0,0 +1,4 @@ +from app.run import start_app + +if __name__ == '__main__': + start_app() diff --git a/api/app/run.py b/api/app/run.py new file mode 100644 index 0000000..72730f2 --- /dev/null +++ b/api/app/run.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI +from starlette.middleware.cors import CORSMiddleware + + +def start_app(): + app = FastAPI() + + app.add_middleware( + CORSMiddleware, + allow_origins=['*'], + allow_credentials=True, + allow_methods=['GET', 'POST', 'PUT', 'DELETE'], + allow_headers=['*'], + )