сделал регистрацию, изменил модели данных
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
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(BaseModel):
|
class UserRegister(BaseModel):
|
||||||
first_name: str = Field(max_length=250)
|
first_name: str = Field(max_length=250)
|
||||||
last_name: str = Field(max_length=250)
|
last_name: str = Field(max_length=250)
|
||||||
patronymic: Optional[str] = Field(default=None, max_length=250)
|
patronymic: Optional[str] = Field(default=None, max_length=250)
|
||||||
@ -12,11 +12,15 @@ class UserCreate(BaseModel):
|
|||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
birthdate: date
|
birthdate: date
|
||||||
password: str = Field(min_length=8)
|
password: str = Field(min_length=8)
|
||||||
role_id: Optional[int] = Field(default=None)
|
|
||||||
repeat_password: str = Field(min_length=8)
|
repeat_password: str = Field(min_length=8)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(UserRegister):
|
||||||
|
role_id: int = Field()
|
||||||
|
|
||||||
|
|
||||||
class UserRead(BaseModel):
|
class UserRead(BaseModel):
|
||||||
|
id: int
|
||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
patronymic: Optional[str]
|
patronymic: Optional[str]
|
||||||
@ -28,3 +32,4 @@ class UserRead(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
from_attributes = True
|
||||||
|
|||||||
@ -8,3 +8,16 @@ metadata_obj = MetaData(schema=Settings().db_schema)
|
|||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
metadata = metadata_obj
|
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):
|
class RootTable(Base):
|
||||||
__abstract__ = True
|
__abstract__ = True
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(default=func.now())
|
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
|
||||||
updated_at: Mapped[datetime] = mapped_column(default=func.now(), onupdate=func.now())
|
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now(), server_default=func.now())
|
||||||
|
|
||||||
|
|
||||||
class PhotoAbstract(RootTable):
|
class PhotoAbstract(RootTable):
|
||||||
|
|||||||
@ -10,7 +10,7 @@ class Course(PhotoAbstract):
|
|||||||
__tablename__ = 'courses'
|
__tablename__ = 'courses'
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(250), nullable=False)
|
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')
|
teachers: Mapped[List['CourseTeacher']] = relationship('CourseTeacher', back_populates='course')
|
||||||
enrollments: Mapped[List['Enrollment']] = relationship('Enrollment', back_populates='course')
|
enrollments: Mapped[List['Enrollment']] = relationship('Enrollment', back_populates='course')
|
||||||
|
|||||||
@ -10,8 +10,8 @@ class Lesson(RootTable):
|
|||||||
__tablename__ = 'lessons'
|
__tablename__ = 'lessons'
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(250), nullable=False)
|
title: Mapped[str] = mapped_column(String(250), nullable=False)
|
||||||
description: Mapped[str] = mapped_column()
|
description: Mapped[str] = mapped_column(nullable=True)
|
||||||
text: Mapped[str] = mapped_column()
|
text: Mapped[str] = mapped_column(nullable=True)
|
||||||
number: Mapped[int] = mapped_column(nullable=False)
|
number: Mapped[int] = mapped_column(nullable=False)
|
||||||
|
|
||||||
course_id: Mapped[int] = mapped_column(ForeignKey('courses.id'), 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')
|
course: Mapped['Course'] = relationship('Course', back_populates='lessons')
|
||||||
creator: Mapped['User'] = relationship('User', back_populates='created_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):
|
class Solution(RootTable):
|
||||||
__tablename__ = 'solutions'
|
__tablename__ = 'solutions'
|
||||||
|
|
||||||
answer_text: Mapped[str] = mapped_column()
|
answer_text: Mapped[str] = mapped_column(nullable=True)
|
||||||
assessment_text: Mapped[str] = mapped_column(String(50))
|
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)
|
task_id: Mapped[int] = mapped_column(ForeignKey('tasks.id'), nullable=False)
|
||||||
student_id: Mapped[int] = mapped_column(ForeignKey('users.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)
|
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'
|
__tablename__ = 'tasks'
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(250), nullable=False)
|
title: Mapped[str] = mapped_column(String(250), nullable=False)
|
||||||
description: Mapped[str] = mapped_column()
|
description: Mapped[str] = mapped_column(nullable=True)
|
||||||
text: Mapped[str] = mapped_column()
|
text: Mapped[str] = mapped_column(nullable=True)
|
||||||
number: Mapped[int] = mapped_column(nullable=False)
|
number: Mapped[int] = mapped_column(nullable=False)
|
||||||
|
|
||||||
course_id: Mapped[int] = mapped_column(ForeignKey('courses.id'), 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')
|
course: Mapped['Course'] = relationship('Course', back_populates='tasks')
|
||||||
creator: Mapped['User'] = relationship('User', back_populates='created_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)
|
first_name: Mapped[str] = mapped_column(String(250), nullable=False)
|
||||||
last_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)
|
login: Mapped[str] = mapped_column(String(250), nullable=False, unique=True)
|
||||||
password_hash: Mapped[str] = mapped_column(nullable=False)
|
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)
|
birthdate: Mapped[date] = mapped_column(nullable=False)
|
||||||
reg_date: Mapped[date] = mapped_column(nullable=False, default=func.now())
|
reg_date: Mapped[date] = mapped_column(nullable=False, default=func.now())
|
||||||
last_visit: Mapped[datetime] = mapped_column()
|
last_visit: Mapped[datetime] = mapped_column(nullable=True)
|
||||||
photo_filename: Mapped[str] = mapped_column(String(250))
|
photo_filename: Mapped[str] = mapped_column(String(250), nullable=True)
|
||||||
photo_path: Mapped[str] = mapped_column()
|
photo_path: Mapped[str] = mapped_column(nullable=True)
|
||||||
|
|
||||||
status_id: Mapped[int] = mapped_column(ForeignKey('statuses.id'), nullable=False)
|
status_id: Mapped[int] = mapped_column(ForeignKey('statuses.id'), nullable=False)
|
||||||
role_id: Mapped[int] = mapped_column(ForeignKey('roles.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')
|
enrollments: Mapped[List['Enrollment']] = relationship('Enrollment', back_populates='student')
|
||||||
created_lessons: Mapped[List['Lesson']] = relationship('Lesson', back_populates='creator')
|
created_lessons: Mapped[List['Lesson']] = relationship('Lesson', back_populates='creator')
|
||||||
created_tasks: Mapped[List['Task']] = relationship('Task', 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(
|
assessments: Mapped[List['Solution']] = relationship(
|
||||||
'Solution',
|
'Solution',
|
||||||
back_populates='assessment_autor',
|
back_populates='assessment_autor',
|
||||||
foreign_keys=['assessment_autor_id'],
|
foreign_keys=[Solution.assessment_autor_id],
|
||||||
)
|
)
|
||||||
my_solutions: Mapped[List['Solution']] = relationship(
|
my_solutions: Mapped[List['Solution']] = relationship(
|
||||||
'Solution',
|
'Solution',
|
||||||
back_populates='student',
|
back_populates='student',
|
||||||
foreign_keys=['student_id'],
|
foreign_keys=[Solution.student_id],
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_password(self, password):
|
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.roles_repository import RolesRepository
|
||||||
from app.application.statuses_repository import StatusesRepository
|
from app.application.statuses_repository import StatusesRepository
|
||||||
from app.application.users_repository import UsersRepository
|
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.domain.models.users import User
|
||||||
from app.settings import Settings
|
from app.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
class UsersService:
|
class UsersService:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.users_repository = UsersRepository(db)
|
pass
|
||||||
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
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from fastapi import FastAPI
|
|||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from app.controllers.auth_router import auth_router
|
from app.controllers.auth_router import auth_router
|
||||||
|
from app.controllers.register_router import register_router
|
||||||
from app.settings import Settings
|
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(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
|
return api_app
|
||||||
|
|
||||||
|
|||||||
@ -5,4 +5,5 @@ asyncpg==0.31.0
|
|||||||
greenlet==3.2.4
|
greenlet==3.2.4
|
||||||
werkzeug==3.1.3
|
werkzeug==3.1.3
|
||||||
pyjwt==2.9.0
|
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