diff --git a/src/api/__init__.py b/src/api/__init__.py index 71ab889..c1d42e5 100644 --- a/src/api/__init__.py +++ b/src/api/__init__.py @@ -6,4 +6,3 @@ from src.core.settings import settings router = APIRouter(prefix=settings.api.prefix) router.include_router(router=v1_router) - diff --git a/src/api/v1/auth.py b/src/api/v1/auth.py index 1f65364..5b3b59b 100644 --- a/src/api/v1/auth.py +++ b/src/api/v1/auth.py @@ -5,9 +5,10 @@ from src.schemas.users import UserRequest from src.core.settings import settings from src.services.auth import AuthService -router = APIRouter(prefix=settings.api.v1.auth, tags=['Auth']) +router = APIRouter(prefix=settings.api.v1.auth, tags=["Auth"]) -@router.post(path='/signup') +@router.post(path="/signup") async def registration(session: sessionDep, credential: UserRequest): - await AuthService(session).registration(credential) + auth = await AuthService(session).registration(credential) + return auth diff --git a/src/core/interfaces.py b/src/core/interfaces.py index fcf1383..ea648e6 100644 --- a/src/core/interfaces.py +++ b/src/core/interfaces.py @@ -6,11 +6,8 @@ from src.repository.users import UsersRepo class IUnitOfWork(Protocol): user: UsersRepo - async def __aenter__(self) -> "IUnitOfWork": - ... + async def __aenter__(self) -> "IUnitOfWork": ... - async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: - ... + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... - async def commit(self) -> None: - ... \ No newline at end of file + async def commit(self) -> None: ... diff --git a/src/core/settings.py b/src/core/settings.py index 8113a1c..3150467 100644 --- a/src/core/settings.py +++ b/src/core/settings.py @@ -24,10 +24,9 @@ class DbSettings(BaseModel): class AccessToken(BaseSettings): - model_config = SettingsConfigDict(env_file='.env', - env_file_encoding='utf-8', - env_prefix="ACCESS_TOKEN_" - ) + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", env_prefix="ACCESS_TOKEN_" + ) expire_minutes: int = 15 secret_key: str algorithm: str = "HS256" @@ -40,4 +39,4 @@ class Settings(BaseSettings): settings = Settings() -print(settings.db.url) \ No newline at end of file +print(settings.db.url) diff --git a/src/migrations/env.py b/src/migrations/env.py index 50c31c0..ff19274 100644 --- a/src/migrations/env.py +++ b/src/migrations/env.py @@ -6,7 +6,7 @@ from sqlalchemy import pool from alembic import context from src.db.database import Base -from src.models import * # noqa +from src.models import * # noqa # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -68,9 +68,7 @@ def run_migrations_online() -> None: ) with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) + context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations() diff --git a/src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py b/src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py index 6006157..e62e52e 100644 --- a/src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py +++ b/src/migrations/versions/2025_07_06_0002-a2fdd0ec4a96_init.py @@ -1,10 +1,11 @@ """init Revision ID: a2fdd0ec4a96 -Revises: +Revises: Create Date: 2025-07-06 00:02:09.254907 """ + from typing import Sequence, Union from alembic import op @@ -12,7 +13,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = 'a2fdd0ec4a96' +revision: str = "a2fdd0ec4a96" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,34 +22,61 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.create_table('users', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('username', sa.String(length=30), nullable=False), - sa.Column('hashed_password', sa.String(length=255), nullable=False), - sa.Column('email', sa.String(length=255), nullable=True), - sa.Column('telegram_id', sa.BigInteger(), nullable=True), - sa.Column('avatar_path', sa.String(length=255), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('is_superuser', sa.Boolean(), nullable=False), - sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(length=30), nullable=False), + sa.Column("hashed_password", sa.String(length=255), nullable=False), + sa.Column("email", sa.String(length=255), nullable=True), + sa.Column("telegram_id", sa.BigInteger(), nullable=True), + sa.Column("avatar_path", sa.String(length=255), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("is_superuser", sa.Boolean(), nullable=False), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), ) - op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True) - op.create_table('tasks', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=100), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('due_date', sa.Date(), nullable=True), - sa.Column("status", sa.Enum("open", "closed", "in_progress", "todo", name="status_enum"), nullable=False), - sa.CheckConstraint("status IN ('open', 'closed', 'in_progress', 'todo')", name="ck_status_enum"), - sa.Column("priority", sa.Enum("low", "medium", "high", "critical", name="priority_enum"), nullable=False), - sa.CheckConstraint("priority in ('low', 'medium', 'high', 'critical')", name="ck_priority_enum"), - sa.Column('time_spent', sa.Integer(), nullable=False), - sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True) + op.create_table( + "tasks", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=100), nullable=False), + sa.Column("description", sa.Text(), nullable=True), + sa.Column("due_date", sa.Date(), nullable=True), + sa.Column( + "status", + sa.Enum("open", "closed", "in_progress", "todo", name="status_enum"), + nullable=False, + ), + sa.CheckConstraint( + "status IN ('open', 'closed', 'in_progress', 'todo')", name="ck_status_enum" + ), + sa.Column( + "priority", + sa.Enum("low", "medium", "high", "critical", name="priority_enum"), + nullable=False, + ), + sa.CheckConstraint( + "priority in ('low', 'medium', 'high', 'critical')", name="ck_priority_enum" + ), + sa.Column("time_spent", sa.Integer(), nullable=False), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("(CURRENT_TIMESTAMP)"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), ) # ### end Alembic commands ### @@ -56,7 +84,7 @@ def upgrade() -> None: def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('tasks') - op.drop_index(op.f('ix_users_username'), table_name='users') - op.drop_table('users') + op.drop_table("tasks") + op.drop_index(op.f("ix_users_username"), table_name="users") + op.drop_table("users") # ### end Alembic commands ### diff --git a/src/models/tasks.py b/src/models/tasks.py index fb685f2..b000446 100644 --- a/src/models/tasks.py +++ b/src/models/tasks.py @@ -14,7 +14,6 @@ priority_enum = Enum("low", "medium", "high", "critical", name="priority_enum") class TasksORM(Base): - __tablename__ = "tasks" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) diff --git a/src/models/users.py b/src/models/users.py index cac4ab6..9958677 100644 --- a/src/models/users.py +++ b/src/models/users.py @@ -16,7 +16,9 @@ class UsersORM(Base): String(30), nullable=False, unique=True, index=True ) hashed_password: Mapped[str] = mapped_column(String(255), nullable=False) - email: Mapped[Optional[str]] = mapped_column(String(255), unique=True, nullable=True) + email: Mapped[Optional[str]] = mapped_column( + String(255), unique=True, nullable=True + ) telegram_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True) avatar_path: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) diff --git a/src/repository/base.py b/src/repository/base.py index bbe5155..cd1d2cb 100644 --- a/src/repository/base.py +++ b/src/repository/base.py @@ -11,7 +11,7 @@ class BaseRepo: self.session = session async def create_one(self, data: BaseModel): - print(self.session) statement = insert(self.model).values(data.model_dump()).returning(self.model) result = await self.session.execute(statement) - return result + obj = result.scalar_one() + return obj diff --git a/src/schemas/users.py b/src/schemas/users.py index 268b692..48e7c38 100644 --- a/src/schemas/users.py +++ b/src/schemas/users.py @@ -6,11 +6,12 @@ from src.schemas.validators import ensure_password class User(BaseModel): + id: int email: EmailStr | None username: str is_active: bool is_superuser: bool - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True, extra="ignore") class UserWithHashedPass(User): @@ -21,3 +22,11 @@ class UserRequest(BaseModel): username: str email: EmailStr | None = None password: Annotated[str, BeforeValidator(ensure_password)] + + +class UserAdd(BaseModel): + email: EmailStr | None + username: str + is_active: bool + is_superuser: bool + hashed_password: str diff --git a/src/services/auth.py b/src/services/auth.py index e3fd842..5241296 100644 --- a/src/services/auth.py +++ b/src/services/auth.py @@ -1,19 +1,19 @@ -from src.schemas.users import UserWithHashedPass, UserRequest +from src.schemas.users import UserRequest, User, UserAdd from src.services.base import BaseService from src.utils.auth_manager import AuthManger class AuthService(BaseService): - - async def registration(self, cred: UserRequest): + async def registration(self, cred: UserRequest) -> User: hashed_pass = AuthManger.get_password_hash(cred.password) - user_to_insert = UserWithHashedPass( + user_to_insert = UserAdd( username=cred.username, email=cred.email, hashed_password=hashed_pass, is_active=True, - is_superuser=False + is_superuser=False, ) result = await self.session.user.create_one(user_to_insert) + print(result) await self.session.commit() - return result + return User.model_validate(result) diff --git a/src/utils/auth_manager.py b/src/utils/auth_manager.py index 3883214..45fe5c2 100644 --- a/src/utils/auth_manager.py +++ b/src/utils/auth_manager.py @@ -23,7 +23,13 @@ class AuthManger: if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: - expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token.expire_minutes) + expire = datetime.now(timezone.utc) + timedelta( + minutes=settings.access_token.expire_minutes + ) to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, settings.access_token.secret_key, algorithm=settings.access_token.algorithm) + encoded_jwt = jwt.encode( + to_encode, + settings.access_token.secret_key, + algorithm=settings.access_token.algorithm, + ) return encoded_jwt