сделал регистрацию, изменил модели данных
This commit is contained in:
parent
7d5cbdf431
commit
408325a277
38
api/app/controllers/register_router.py
Normal file
38
api/app/controllers/register_router.py
Normal 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)
|
||||
@ -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 ###
|
||||
@ -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 ###
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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):
|
||||
|
||||
144
api/app/infrastructure/register_service.py
Normal file
144
api/app/infrastructure/register_service.py
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -5,4 +5,5 @@ asyncpg==0.31.0
|
||||
greenlet==3.2.4
|
||||
werkzeug==3.1.3
|
||||
pyjwt==2.9.0
|
||||
fastapi==0.115.0
|
||||
fastapi==0.115.0
|
||||
pydantic[email]==2.11.4
|
||||
Loading…
x
Reference in New Issue
Block a user