сделал регистрацию, изменил модели данных

This commit is contained in:
Андрей Дувакин 2025-11-27 13:38:12 +05:00
parent 7d5cbdf431
commit 408325a277
16 changed files with 427 additions and 143 deletions

View File

@ -0,0 +1,38 @@
from fastapi import APIRouter, Depends, Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.session import get_db
from app.domain.entities.users import UserRead, UserRegister, UserCreate
from app.infrastructure.dependencies import require_admin
from app.infrastructure.register_service import RegisterService
register_router = APIRouter()
@register_router.post(
'/register/',
response_model=UserRead,
summary='User registration',
description='Performs user registration in the system',
)
async def register_user(
user_data: UserRegister,
db: AsyncSession = Depends(get_db)
):
register_service = RegisterService(db)
return await register_service.user_register(user_data)
@register_router.post(
'/create/',
response_model=UserRead,
summary='User creation',
description='Performs user creation in the system',
)
async def create_user(
user_data: UserCreate,
user=Depends(require_admin),
db: AsyncSession = Depends(get_db)
):
register_service = RegisterService(db)
return await register_service.create_user(user_data)

View File

@ -0,0 +1,196 @@
"""0001 инициализация
Revision ID: 6241a16321b4
Revises:
Create Date: 2025-11-27 13:33:22.506743
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '6241a16321b4'
down_revision: Union[str, Sequence[str], None] = None
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('courses',
sa.Column('title', sa.String(length=250), nullable=False),
sa.Column('description', sa.String(length=1000), nullable=True),
sa.Column('photo_filename', sa.String(), nullable=False),
sa.Column('photo_path', sa.String(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('roles',
sa.Column('title', sa.String(length=150), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('title'),
schema='public'
)
op.create_table('statuses',
sa.Column('title', sa.String(length=250), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('title'),
schema='public'
)
op.create_table('users',
sa.Column('first_name', sa.String(length=250), nullable=False),
sa.Column('last_name', sa.String(length=250), nullable=False),
sa.Column('patronymic', sa.String(length=250), nullable=True),
sa.Column('login', sa.String(length=250), nullable=False),
sa.Column('password_hash', sa.String(), nullable=False),
sa.Column('email', sa.String(length=250), nullable=True),
sa.Column('birthdate', sa.Date(), nullable=False),
sa.Column('reg_date', sa.Date(), nullable=False),
sa.Column('last_visit', sa.DateTime(), nullable=True),
sa.Column('photo_filename', sa.String(length=250), nullable=True),
sa.Column('photo_path', sa.String(), nullable=True),
sa.Column('status_id', sa.Integer(), nullable=False),
sa.Column('role_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['role_id'], ['public.roles.id'], ),
sa.ForeignKeyConstraint(['status_id'], ['public.statuses.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('login'),
schema='public'
)
op.create_table('course_teachers',
sa.Column('course_id', sa.Integer(), nullable=False),
sa.Column('teacher_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['public.courses.id'], ),
sa.ForeignKeyConstraint(['teacher_id'], ['public.users.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('enrollments',
sa.Column('enrollment_date', sa.DateTime(), nullable=False),
sa.Column('course_id', sa.Integer(), nullable=False),
sa.Column('student_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['public.courses.id'], ),
sa.ForeignKeyConstraint(['student_id'], ['public.users.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('lessons',
sa.Column('title', sa.String(length=250), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('text', sa.String(), nullable=True),
sa.Column('number', sa.Integer(), nullable=False),
sa.Column('course_id', sa.Integer(), nullable=False),
sa.Column('creator_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['public.courses.id'], ),
sa.ForeignKeyConstraint(['creator_id'], ['public.users.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('tasks',
sa.Column('title', sa.String(length=250), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('text', sa.String(), nullable=True),
sa.Column('number', sa.Integer(), nullable=False),
sa.Column('course_id', sa.Integer(), nullable=False),
sa.Column('creator_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['course_id'], ['public.courses.id'], ),
sa.ForeignKeyConstraint(['creator_id'], ['public.users.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('lesson_files',
sa.Column('lesson_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(), nullable=False),
sa.Column('file_path', sa.String(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['lesson_id'], ['public.lessons.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('solutions',
sa.Column('answer_text', sa.String(), nullable=True),
sa.Column('assessment_text', sa.String(length=50), nullable=True),
sa.Column('assessment_autor_id', sa.Integer(), nullable=True),
sa.Column('task_id', sa.Integer(), nullable=False),
sa.Column('student_id', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['assessment_autor_id'], ['public.users.id'], ),
sa.ForeignKeyConstraint(['student_id'], ['public.users.id'], ),
sa.ForeignKeyConstraint(['task_id'], ['public.tasks.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('task_files',
sa.Column('task_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(), nullable=False),
sa.Column('file_path', sa.String(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['task_id'], ['public.tasks.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
op.create_table('solution_files',
sa.Column('solution_id', sa.Integer(), nullable=False),
sa.Column('filename', sa.String(), nullable=False),
sa.Column('file_path', sa.String(), nullable=False),
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['solution_id'], ['public.solutions.id'], ),
sa.PrimaryKeyConstraint('id'),
schema='public'
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('solution_files', schema='public')
op.drop_table('task_files', schema='public')
op.drop_table('solutions', schema='public')
op.drop_table('lesson_files', schema='public')
op.drop_table('tasks', schema='public')
op.drop_table('lessons', schema='public')
op.drop_table('enrollments', schema='public')
op.drop_table('course_teachers', schema='public')
op.drop_table('users', schema='public')
op.drop_table('statuses', schema='public')
op.drop_table('roles', schema='public')
op.drop_table('courses', schema='public')
# ### end Alembic commands ###

View File

@ -1,32 +0,0 @@
"""0001 инициализация
Revision ID: 7a6554b361e8
Revises:
Create Date: 2025-11-26 19:52:23.751193
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '7a6554b361e8'
down_revision: Union[str, Sequence[str], None] = None
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! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@ -4,7 +4,7 @@ from typing import Optional
from pydantic import BaseModel, EmailStr, Field
class UserCreate(BaseModel):
class UserRegister(BaseModel):
first_name: str = Field(max_length=250)
last_name: str = Field(max_length=250)
patronymic: Optional[str] = Field(default=None, max_length=250)
@ -12,11 +12,15 @@ class UserCreate(BaseModel):
email: Optional[EmailStr] = None
birthdate: date
password: str = Field(min_length=8)
role_id: Optional[int] = Field(default=None)
repeat_password: str = Field(min_length=8)
class UserCreate(UserRegister):
role_id: int = Field()
class UserRead(BaseModel):
id: int
first_name: str
last_name: str
patronymic: Optional[str]
@ -28,3 +32,4 @@ class UserRead(BaseModel):
class Config:
orm_mode = True
from_attributes = True

View File

@ -8,3 +8,16 @@ metadata_obj = MetaData(schema=Settings().db_schema)
class Base(DeclarativeBase):
metadata = metadata_obj
from app.domain.models.course_teachers import CourseTeacher
from app.domain.models.courses import Course
from app.domain.models.enrollments import Enrollment
from app.domain.models.lesson_files import LessonFile
from app.domain.models.lessons import Lesson
from app.domain.models.roles import Role
from app.domain.models.solution_files import SolutionFile
from app.domain.models.solutions import Solution
from app.domain.models.statuses import Status
from app.domain.models.task_files import TaskFile
from app.domain.models.tasks import Task
from app.domain.models.users import User

View File

@ -9,9 +9,9 @@ from app.domain.models import Base
class RootTable(Base):
__abstract__ = True
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime] = mapped_column(default=func.now(), onupdate=func.now())
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now(), server_default=func.now())
class PhotoAbstract(RootTable):

View File

@ -10,7 +10,7 @@ class Course(PhotoAbstract):
__tablename__ = 'courses'
title: Mapped[str] = mapped_column(String(250), nullable=False)
description: Mapped[str] = mapped_column(String(1000))
description: Mapped[str] = mapped_column(String(1000), nullable=True)
teachers: Mapped[List['CourseTeacher']] = relationship('CourseTeacher', back_populates='course')
enrollments: Mapped[List['Enrollment']] = relationship('Enrollment', back_populates='course')

View File

@ -10,8 +10,8 @@ class Lesson(RootTable):
__tablename__ = 'lessons'
title: Mapped[str] = mapped_column(String(250), nullable=False)
description: Mapped[str] = mapped_column()
text: Mapped[str] = mapped_column()
description: Mapped[str] = mapped_column(nullable=True)
text: Mapped[str] = mapped_column(nullable=True)
number: Mapped[int] = mapped_column(nullable=False)
course_id: Mapped[int] = mapped_column(ForeignKey('courses.id'), nullable=False)
@ -20,4 +20,4 @@ class Lesson(RootTable):
course: Mapped['Course'] = relationship('Course', back_populates='lessons')
creator: Mapped['User'] = relationship('User', back_populates='created_lessons')
files: Mapped[List['LessonFile']] = relationship('LessonFile', back_populates='lessons')
files: Mapped[List['LessonFile']] = relationship('LessonFile', back_populates='lesson')

View File

@ -9,10 +9,10 @@ from app.domain.models.base import RootTable
class Solution(RootTable):
__tablename__ = 'solutions'
answer_text: Mapped[str] = mapped_column()
assessment_text: Mapped[str] = mapped_column(String(50))
answer_text: Mapped[str] = mapped_column(nullable=True)
assessment_text: Mapped[str] = mapped_column(String(50), nullable=True)
assessment_autor_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
assessment_autor_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=True)
task_id: Mapped[int] = mapped_column(ForeignKey('tasks.id'), nullable=False)
student_id: Mapped[int] = mapped_column(ForeignKey('users.id'), nullable=False)

View File

@ -11,4 +11,4 @@ class Status(RootTable):
title: Mapped[str] = mapped_column(String(250), nullable=False, unique=True)
users = Mapped[List['User']] = relationship('User', back_populates='status')
users: Mapped[List['User']] = relationship('User', back_populates='status')

View File

@ -10,8 +10,8 @@ class Task(RootTable):
__tablename__ = 'tasks'
title: Mapped[str] = mapped_column(String(250), nullable=False)
description: Mapped[str] = mapped_column()
text: Mapped[str] = mapped_column()
description: Mapped[str] = mapped_column(nullable=True)
text: Mapped[str] = mapped_column(nullable=True)
number: Mapped[int] = mapped_column(nullable=False)
course_id: Mapped[int] = mapped_column(ForeignKey('courses.id'), nullable=False)
@ -20,4 +20,4 @@ class Task(RootTable):
course: Mapped['Course'] = relationship('Course', back_populates='tasks')
creator: Mapped['User'] = relationship('User', back_populates='created_tasks')
files: Mapped[List['TaskFile']] = relationship('TaskFile', back_populates='lessons')
files: Mapped[List['TaskFile']] = relationship('TaskFile', back_populates='task')

View File

@ -14,15 +14,15 @@ class User(PhotoAbstract):
first_name: Mapped[str] = mapped_column(String(250), nullable=False)
last_name: Mapped[str] = mapped_column(String(250), nullable=False)
patronymic: Mapped[str] = mapped_column(String(250))
patronymic: Mapped[str] = mapped_column(String(250), nullable=True)
login: Mapped[str] = mapped_column(String(250), nullable=False, unique=True)
password_hash: Mapped[str] = mapped_column(nullable=False)
email: Mapped[str] = mapped_column(String(250), unique=True)
email: Mapped[str] = mapped_column(String(250), unique=True, nullable=True)
birthdate: Mapped[date] = mapped_column(nullable=False)
reg_date: Mapped[date] = mapped_column(nullable=False, default=func.now())
last_visit: Mapped[datetime] = mapped_column()
photo_filename: Mapped[str] = mapped_column(String(250))
photo_path: Mapped[str] = mapped_column()
last_visit: Mapped[datetime] = mapped_column(nullable=True)
photo_filename: Mapped[str] = mapped_column(String(250), nullable=True)
photo_path: Mapped[str] = mapped_column(nullable=True)
status_id: Mapped[int] = mapped_column(ForeignKey('statuses.id'), nullable=False)
role_id: Mapped[int] = mapped_column(ForeignKey('roles.id'), nullable=False)
@ -34,15 +34,17 @@ class User(PhotoAbstract):
enrollments: Mapped[List['Enrollment']] = relationship('Enrollment', back_populates='student')
created_lessons: Mapped[List['Lesson']] = relationship('Lesson', back_populates='creator')
created_tasks: Mapped[List['Task']] = relationship('Task', back_populates='creator')
from app.domain.models.solutions import Solution
assessments: Mapped[List['Solution']] = relationship(
'Solution',
back_populates='assessment_autor',
foreign_keys=['assessment_autor_id'],
foreign_keys=[Solution.assessment_autor_id],
)
my_solutions: Mapped[List['Solution']] = relationship(
'Solution',
back_populates='student',
foreign_keys=['student_id'],
foreign_keys=[Solution.student_id],
)
def check_password(self, password):

View File

@ -0,0 +1,144 @@
import re
from typing import Optional
from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.application.roles_repository import RolesRepository
from app.application.statuses_repository import StatusesRepository
from app.application.users_repository import UsersRepository
from app.core.constants import UserRoles
from app.domain.entities.users import UserRead, UserCreate, UserRegister
from app.domain.models.users import User
from app.settings import Settings
class RegisterService:
def __init__(self, db: AsyncSession):
self.users_repository = UsersRepository(db)
self.roles_repository = RolesRepository(db)
self.statuses_repository = StatusesRepository(db)
self.settings = Settings()
async def create_user(self, create_user_entity: UserCreate) -> Optional[UserRead]:
user = await self.users_repository.get_by_login(create_user_entity.login)
if user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пользователь с таким логином уже существует',
)
if create_user_entity.password != create_user_entity.repeat_password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароли не совпадают',
)
if not self.is_strong_password(create_user_entity.repeat_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароль слишком слабый. Пароль должен содержать не менее 8 символов, включая хотя бы одну букву и одну цифру и один специальный символ.'
)
default_status = await self.statuses_repository.get_by_title(self.settings.default_status)
if default_status is None:
raise HTTPException(
status_code=status.HTTP_424_FAILED_DEPENDENCY,
detail='Статус по умолчанию не найден',
)
user = User(
first_name=create_user_entity.first_name,
last_name=create_user_entity.last_name,
patronymic=create_user_entity.patronymic,
login=create_user_entity.login,
email=create_user_entity.email,
birthdate=create_user_entity.birthdate,
status_id=default_status.id,
)
role = await self.roles_repository.get_by_id(create_user_entity.role_id)
if role is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Роль с таким ID не найдена',
)
user.role_id = role.id
user.set_password(create_user_entity.password)
user = await self.users_repository.create(user)
return UserRead.model_validate(user)
async def user_register(self, register_user_entity: UserRegister) -> UserRead:
user = await self.users_repository.get_by_login(register_user_entity.login)
if user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пользователь с таким логином уже существует',
)
if register_user_entity.password != register_user_entity.repeat_password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароли не совпадают',
)
if not self.is_strong_password(register_user_entity.repeat_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароль слишком слабый. Пароль должен содержать не менее 8 символов, включая хотя бы одну букву и одну цифру и один специальный символ.'
)
default_status = await self.statuses_repository.get_by_title(self.settings.default_status)
if default_status is None:
raise HTTPException(
status_code=status.HTTP_424_FAILED_DEPENDENCY,
detail='Статус по умолчанию не найден',
)
user = User(
first_name=register_user_entity.first_name,
last_name=register_user_entity.last_name,
patronymic=register_user_entity.patronymic,
login=register_user_entity.login,
email=register_user_entity.email,
birthdate=register_user_entity.birthdate,
status_id=default_status.id,
)
role = await self.roles_repository.get_by_title(self.settings.default_role_name)
if role is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Роль по умолчанию не найдена',
)
user.role_id = role.id
user.set_password(register_user_entity.password)
user = await self.users_repository.create(user)
return UserRead.model_validate(user)
@staticmethod
def is_strong_password(password: str) -> bool:
if len(password) < 8:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'\d', password):
return False
if not re.search(r'[!@#$%^&*(),.?\':{}|<>]', password):
return False
return True

View File

@ -7,96 +7,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.application.roles_repository import RolesRepository
from app.application.statuses_repository import StatusesRepository
from app.application.users_repository import UsersRepository
from app.domain.entities.users import UserCreate, UserRead
from app.domain.entities.users import UserRegister, UserRead
from app.domain.models.users import User
from app.settings import Settings
class UsersService:
def __init__(self, db: AsyncSession):
self.users_repository = UsersRepository(db)
self.roles_repository = RolesRepository(db)
self.statuses_repository = StatusesRepository(db)
self.settings = Settings()
async def create_user(self, create_user_entity: UserCreate) -> Optional[UserRead]:
user = await self.users_repository.get_by_login(create_user_entity.login)
if user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пользователь с таким логином уже существует',
)
if create_user_entity.password != create_user_entity.repeat_password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароли не совпадают',
)
if not self.is_strong_password(create_user_entity.repeat_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Пароль слишком слабый. Пароль должен содержать не менее 8 символов, включая хотя бы одну букву и одну цифру и один специальный символ.'
)
default_status = await self.statuses_repository.get_by_title(self.settings.default_status)
if default_status is None:
raise HTTPException(
status_code=status.HTTP_424_FAILED_DEPENDENCY,
detail='Статус по умолчанию не найден',
)
user = User(
first_name=create_user_entity.first_name,
last_name=create_user_entity.last_name,
patronymic=create_user_entity.patronymic,
login=create_user_entity.login,
email=create_user_entity.email,
birthdate=create_user_entity.birthdate,
status_id=default_status.status_id,
)
if create_user_entity.role_id is None:
default_role = await self.roles_repository.get_by_title(self.settings.default_role_name)
if default_role is None:
raise HTTPException(
status_code=status.HTTP_424_FAILED_DEPENDENCY,
detail='Роль по умолчанию не найдена',
)
user.role_id = default_role.id
else:
role = await self.roles_repository.get_by_id(create_user_entity.role_id)
if role is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Роль с таким ID не найдена',
)
user.role_id = role.id
user.set_password(create_user_entity.password)
user = await self.users_repository.create(user)
return UserRead.model_validate(user)
@staticmethod
def is_strong_password(password: str) -> bool:
if len(password) < 8:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'\d', password):
return False
if not re.search(r'[!@#$%^&*(),.?\':{}|<>]', password):
return False
return True
pass

View File

@ -2,6 +2,7 @@ from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from app.controllers.auth_router import auth_router
from app.controllers.register_router import register_router
from app.settings import Settings
@ -18,6 +19,7 @@ def start_app():
)
api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth'])
api_app.include_router(register_router, prefix=f'{settings.prefix}/register', tags=['register'])
return api_app

View File

@ -6,3 +6,4 @@ greenlet==3.2.4
werkzeug==3.1.3
pyjwt==2.9.0
fastapi==0.115.0
pydantic[email]==2.11.4