Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ COPY pyproject.toml pyproject.toml

RUN pip install poetry
RUN poetry config virtualenvs.create false
RUN poetry install --no-dev
RUN poetry install --no-root

COPY . /app

Expand Down
245 changes: 245 additions & 0 deletions migrations/versions/d9f8524aadcd_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
"""empty message

Revision ID: d9f8524aadcd
Revises: 799909f2b79d
Create Date: 2025-09-18 16:07:12.322371

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = 'd9f8524aadcd'
down_revision: Union[str, None] = '799909f2b79d'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('company',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('phone_number', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('is_admin', sa.Boolean(), nullable=False),
sa.Column('is_staff', sa.Boolean(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('first_name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('last_name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.add_column('additional_products', sa.Column('primary_id', sa.Integer(), nullable=False))
op.create_foreign_key(None, 'additional_products', 'products', ['primary_id'], ['id'])
op.drop_column('additional_products', 'id')
op.alter_column('categories', 'title',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('categories', 'description',
existing_type=sa.TEXT(),
type_=sqlmodel.sql.sqltypes.AutoString(),
existing_nullable=True)
op.alter_column('categories', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=False)
op.drop_index(op.f('ix_categories_id'), table_name='categories')
op.alter_column('product_categories', 'product_id',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('product_categories', 'category_id',
existing_type=sa.INTEGER(),
nullable=False)
op.drop_index(op.f('ix_product_categories_id'), table_name='product_categories')
op.alter_column('product_discounts', 'product_id',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('product_discounts', 'discount_amount',
existing_type=sa.NUMERIC(),
type_=sa.Float(),
existing_nullable=True)
op.alter_column('product_discounts', 'valid_from',
existing_type=postgresql.TIMESTAMP(),
nullable=False)
op.alter_column('product_discounts', 'valid_to',
existing_type=postgresql.TIMESTAMP(),
nullable=False)
op.drop_index(op.f('ix_product_discounts_id'), table_name='product_discounts')
op.alter_column('product_images', 'product_id',
existing_type=sa.INTEGER(),
nullable=False)
op.drop_index(op.f('ix_product_images_id'), table_name='product_images')
op.alter_column('products', 'title',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('products', 'description',
existing_type=sa.TEXT(),
type_=sqlmodel.sql.sqltypes.AutoString(),
existing_nullable=True)
op.alter_column('products', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=False)
op.drop_index(op.f('ix_products_id'), table_name='products')
op.add_column('recommended_products', sa.Column('primary_id', sa.Integer(), nullable=False))
op.create_foreign_key(None, 'recommended_products', 'products', ['primary_id'], ['id'])
op.drop_column('recommended_products', 'id')
op.alter_column('stock_records', 'product_id',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('stock_records', 'price',
existing_type=sa.NUMERIC(),
type_=sa.Float(),
nullable=False)
op.alter_column('stock_records', 'quantity',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('stock_records', 'date_created',
existing_type=postgresql.TIMESTAMP(),
nullable=False)
op.drop_index(op.f('ix_stock_records_id'), table_name='stock_records')
op.alter_column('user_addresses', 'city',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('user_addresses', 'street',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('user_addresses', 'house',
existing_type=sa.VARCHAR(),
nullable=False)
op.drop_index(op.f('ix_user_addresses_id'), table_name='user_addresses')
op.alter_column('users', 'email',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('users', 'phone_number',
existing_type=sa.VARCHAR(),
nullable=False)
op.alter_column('users', 'is_admin',
existing_type=sa.BOOLEAN(),
nullable=False)
op.alter_column('users', 'is_staff',
existing_type=sa.BOOLEAN(),
nullable=False)
op.alter_column('users', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=False)
op.alter_column('users', 'date_joined',
existing_type=postgresql.TIMESTAMP(),
nullable=False)
op.alter_column('users', 'is_temporary',
existing_type=sa.BOOLEAN(),
nullable=False)
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_phone_number'), table_name='users')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_index(op.f('ix_users_phone_number'), 'users', ['phone_number'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.alter_column('users', 'is_temporary',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('users', 'date_joined',
existing_type=postgresql.TIMESTAMP(),
nullable=True)
op.alter_column('users', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('users', 'is_staff',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('users', 'is_admin',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('users', 'phone_number',
existing_type=sa.VARCHAR(),
nullable=True)
op.alter_column('users', 'email',
existing_type=sa.VARCHAR(),
nullable=True)
op.create_index(op.f('ix_user_addresses_id'), 'user_addresses', ['id'], unique=False)
op.alter_column('user_addresses', 'house',
existing_type=sa.VARCHAR(),
nullable=True)
op.alter_column('user_addresses', 'street',
existing_type=sa.VARCHAR(),
nullable=True)
op.alter_column('user_addresses', 'city',
existing_type=sa.VARCHAR(),
nullable=True)
op.create_index(op.f('ix_stock_records_id'), 'stock_records', ['id'], unique=False)
op.alter_column('stock_records', 'date_created',
existing_type=postgresql.TIMESTAMP(),
nullable=True)
op.alter_column('stock_records', 'quantity',
existing_type=sa.INTEGER(),
nullable=True)
op.alter_column('stock_records', 'price',
existing_type=sa.Float(),
type_=sa.NUMERIC(),
nullable=True)
op.alter_column('stock_records', 'product_id',
existing_type=sa.INTEGER(),
nullable=True)
op.add_column('recommended_products', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False))
op.drop_constraint(None, 'recommended_products', type_='foreignkey')
op.drop_column('recommended_products', 'primary_id')
op.create_index(op.f('ix_products_id'), 'products', ['id'], unique=False)
op.alter_column('products', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('products', 'description',
existing_type=sqlmodel.sql.sqltypes.AutoString(),
type_=sa.TEXT(),
existing_nullable=True)
op.alter_column('products', 'title',
existing_type=sa.VARCHAR(),
nullable=True)
op.create_index(op.f('ix_product_images_id'), 'product_images', ['id'], unique=False)
op.alter_column('product_images', 'product_id',
existing_type=sa.INTEGER(),
nullable=True)
op.create_index(op.f('ix_product_discounts_id'), 'product_discounts', ['id'], unique=False)
op.alter_column('product_discounts', 'valid_to',
existing_type=postgresql.TIMESTAMP(),
nullable=True)
op.alter_column('product_discounts', 'valid_from',
existing_type=postgresql.TIMESTAMP(),
nullable=True)
op.alter_column('product_discounts', 'discount_amount',
existing_type=sa.Float(),
type_=sa.NUMERIC(),
existing_nullable=True)
op.alter_column('product_discounts', 'product_id',
existing_type=sa.INTEGER(),
nullable=True)
op.create_index(op.f('ix_product_categories_id'), 'product_categories', ['id'], unique=False)
op.alter_column('product_categories', 'category_id',
existing_type=sa.INTEGER(),
nullable=True)
op.alter_column('product_categories', 'product_id',
existing_type=sa.INTEGER(),
nullable=True)
op.create_index(op.f('ix_categories_id'), 'categories', ['id'], unique=False)
op.alter_column('categories', 'is_active',
existing_type=sa.BOOLEAN(),
nullable=True)
op.alter_column('categories', 'description',
existing_type=sqlmodel.sql.sqltypes.AutoString(),
type_=sa.TEXT(),
existing_nullable=True)
op.alter_column('categories', 'title',
existing_type=sa.VARCHAR(),
nullable=True)
op.add_column('additional_products', sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False))
op.drop_constraint(None, 'additional_products', type_='foreignkey')
op.drop_column('additional_products', 'primary_id')
op.drop_table('company')
# ### end Alembic commands ###
41 changes: 41 additions & 0 deletions src/catalogue/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@
)


class AdditionalProducts(SQLModel, table=True):
__tablename__ = 'additional_products'

primary_id: int = Field(foreign_key="products.id", primary_key=True)
additional_id: int = Field(foreign_key="products.id", primary_key=True)



class RecommendedProducts(SQLModel, table=True):
__tablename__ = 'recommended_products'

primary_id: int = Field(foreign_key="products.id", primary_key=True)
recommended_id: int = Field(foreign_key="products.id", primary_key=True)



class Product(SQLModel, table=True):
__tablename__ = 'products'

Expand All @@ -25,6 +41,27 @@ class Product(SQLModel, table=True):
stock_records: List["StockRecord"] = Relationship(back_populates="product")
discounts: List["ProductDiscount"] = Relationship(back_populates="product")

primary_id: List["Product"] = Relationship(back_populates="additional_id",
link_model=AdditionalProducts,
sa_relationship_kwargs={"primaryjoin": "AdditionalProducts.primary_id==Product.id",
"secondaryjoin": "AdditionalProducts.additional_id==Product.id"})
additional_id: List["Product"] = Relationship(back_populates="primary_id",
link_model=AdditionalProducts,
sa_relationship_kwargs={"primaryjoin": "AdditionalProducts.additional_id==Product.id",
"secondaryjoin": "AdditionalProducts.primary_id==Product.id"})


recommended_primary_id: List["Product"] = Relationship(back_populates="recommended_id",
link_model=RecommendedProducts,
sa_relationship_kwargs={"primaryjoin": "RecommendedProducts.primary_id==Product.id",
"secondaryjoin": "RecommendedProducts.recommended_id==Product.id"})
recommended_id: List["Product"] = Relationship(back_populates="recommended_primary_id",
link_model=RecommendedProducts,
sa_relationship_kwargs={"primaryjoin": "RecommendedProducts.recommended_id==Product.id",
"secondaryjoin": "RecommendedProducts.primary_id==Product.id"})



class ProductCategory(SQLModel, table=True):
__tablename__ = 'product_categories'

Expand Down Expand Up @@ -90,3 +127,7 @@ class ProductDiscount(SQLModel, table=True):
valid_to: datetime

product: Product = Relationship(back_populates="discounts")




20 changes: 19 additions & 1 deletion src/catalogue/repository.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from src.catalogue.models.database import Product
from src.catalogue.models.database import Product, AdditionalProducts, RecommendedProducts
from src.common.databases.postgres import get_session
from src.common.repository.sqlalchemy import BaseSQLAlchemyRepository

Expand All @@ -13,3 +13,21 @@ def __init__(self, session: AsyncSession):

def get_product_repository(session: AsyncSession = Depends(get_session)) -> ProductRepository:
return ProductRepository(session=session)


class AdditionalProductRepository(BaseSQLAlchemyRepository[AdditionalProducts]):
def __init__(self, session: AsyncSession):
super().__init__(model=AdditionalProducts, session=session)


def get_additional_product_repository(session: AsyncSession = Depends(get_session)) -> AdditionalProductRepository:
return AdditionalProductRepository(session=session)


class RecommendedProductRepository(BaseSQLAlchemyRepository[RecommendedProducts]):
def __init__(self, session: AsyncSession):
super().__init__(model=RecommendedProducts, session=session)


def get_recommended_product_repository(session: AsyncSession = Depends(get_session)) -> RecommendedProductRepository:
return RecommendedProductRepository(session=session)
24 changes: 22 additions & 2 deletions src/catalogue/services.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from fastapi import Depends

from src.catalogue.models.database import Product
from src.catalogue.models.database import Product, RecommendedProducts, AdditionalProducts
from src.catalogue.repository import (
ProductRepository,
get_product_repository,
get_product_repository, AdditionalProductRepository, RecommendedProductRepository,
get_additional_product_repository, get_recommended_product_repository,
)
from src.common.service import BaseService

Expand All @@ -15,3 +16,22 @@ def __init__(self, repository: ProductRepository):

def get_product_service(repo: ProductRepository = Depends(get_product_repository)) -> ProductService:
return ProductService(repository=repo)



class AdditionalProductService(BaseService[AdditionalProducts]):
def __init__(self, repository: AdditionalProductRepository):
super().__init__(repository)


def get_additional_product_service(repo: AdditionalProductRepository = Depends(get_additional_product_repository)) -> AdditionalProductService:
return AdditionalProductService(repository=repo)


class RecommendedProductService(BaseService[RecommendedProducts]):
def __init__(self, repository: RecommendedProductRepository):
super().__init__(repository)


def get_recommended_product_service(repo: RecommendedProductRepository = Depends(get_recommended_product_repository)) -> RecommendedProductService:
return RecommendedProductService(repository=repo)
Loading