From 6d532b8beb7e37d5b1a2a64dacc204f8b2fc3eb5 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Fri, 6 Dec 2024 07:32:54 -0300 Subject: [PATCH 1/9] chore: test push to main --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 05015ec..b61773b 100644 --- a/README.md +++ b/README.md @@ -360,3 +360,4 @@ Os seguintes Status de Pedidos estão disponíveis: | `CONCLUIDO` | O Pedido foi concluído | | `CANCELADO` | O Pedido foi cancelado | + From 863f3f42e13fb87ec3867ca89f8b879ae1cb98e9 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Mon, 16 Dec 2024 23:27:55 -0300 Subject: [PATCH 2/9] docs: move db modelling to docs --- docs/db_model.png | Bin 0 -> 60464 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/db_model.png diff --git a/docs/db_model.png b/docs/db_model.png new file mode 100644 index 0000000000000000000000000000000000000000..5f2af04c40c58679683d045e6ce3497b1802420f GIT binary patch literal 60464 zcmdpeXIPV2*KU+Slp+=kO^jWTszC_7g9?g>B7!s#14@YkK}rA%qS71@AsUKwMd?La zK#*jlgx-aS83-*PhMEv^cChiz`<<_xbAFv%*Nh3tv+FANy4TwK;hED$0=#0p5C}xz z#Bu$z5Xc531i~e@aRc~^XVMb{_-CExStDIYHfiS=`0xi(=adcvk{89d?7$5^Z$cfv z=m~*ruI2oc$EB9S z)w&z_zql*xp6c11H6UQfBhb>*V)=GMk@mAK>o0OygqhtHyX#gngx*=Bse@3B)YwWJ z-9A)x$dt7$p{<}h@Thdw;fW_$vT$P0`H5#2&(eyrx^gVK*fVwVc8}MAfvvvmRX2j^ z{CK55KyhZZ`m%rY|NR%&1d#ZJ4+)bgcFV6#7gErJVOoAF!ktZzNJOp8Dy+fgd|LOQ zNU?Bd{4MH0ZUoy0TSDZ>!RjkLs~Bat?gKWFwYT4wz0U#jr!PM;d{^}P*oauS%@dXR zC_*FMks|=BuXZzQgy-L*Gc})eyvH^Rg&=r6-&_iKN10${?_hOc$jy;ii54SzJ7TIB zeG{xei`WEDF@pY(iQixsxkgmNXxQvbTJg%Qv$KewTc@qoxs4=Qfj}?E8#w6e-wxH? zS`OoeLcJ`{4C!ryWiEbGJZ>hg7U|CXtT%iuhu-OH4U_J6J3%yg^=VfLq3cMpXTkjI z0zWB#T-UE%FEo}N3a<$Xy0uVQgEG8*kEZyi&QQ26FWFY6P?8tfb|c{HHVM6R)Y=^p zw7Y*w%H(Y!kQ&f`rRo^_*|pIn|FtPMHKfpCN!Q~OLYE{Pb3Yrpy}k#>M`RR3uPhpe zrcp8j&LH-wJ{zBfYXl9L)^3ZBjek*(c}J-g2@05akzmwI`Qzv6(a!bzkR-hx;Nb>) zLeQO=pLB7GxD#BwMG@#PiwYlNjm1(Iw;T{KP?s>K&DtCpD-{lbD?J+zLzDEQq&1?F ziJN}5)W_L2A@xOLFR6+%rMkTRW8c0>=g4l96*|-7RI_|qWNyy!V&U zuWbCtL5RRha_^|m&tEE7>xICT2LnutDbM`ZHL9?(w~>A{QXL?+2iJ?m41dd7*C@q` z{QbqUm=x|}ksqdb_72~=Mp2gg@2>(tzgG8t>Hi3Mai}pr4ALkz#Q&pNMMD8Kq+Gq6 z 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

ClUZ68ewwE_cD?kCespwY8iA)1SnhXqfTx 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 y=y=qfN$RC5ra)kzt9&KyK Date: Tue, 17 Dec 2024 21:05:26 -0300 Subject: [PATCH 3/9] refactor: normalize product repository --- src/product/adapters/AuditMixin.py | 13 ++++++ src/product/adapters/postgresql_repository.py | 36 ++++++++++++++-- src/product/adapters/product_table.py | 41 +++++++++++++++---- 3 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 src/product/adapters/AuditMixin.py diff --git a/src/product/adapters/AuditMixin.py b/src/product/adapters/AuditMixin.py new file mode 100644 index 0000000..70a0931 --- /dev/null +++ b/src/product/adapters/AuditMixin.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.sql.functions import now + + +class AuditMixin: + created_at: Mapped[datetime] = mapped_column(default=now()) + updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) + + def __init__(self, created_at: datetime, updated_at: datetime): + self.created_at = created_at + self.updated_at = updated_at diff --git a/src/product/adapters/postgresql_repository.py b/src/product/adapters/postgresql_repository.py index 918ff10..b2bc23c 100644 --- a/src/product/adapters/postgresql_repository.py +++ b/src/product/adapters/postgresql_repository.py @@ -1,20 +1,32 @@ +import uuid from typing import Any, Sequence, Optional from sqlalchemy import select, Row, delete from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session -from src.product.adapters.product_table import ProductTable +from src.product.adapters.product_table import ProductTable, CategoryTable from src.product.ports.repository_interface import IProductRepository +PRODUCTS_COLS: tuple = ( + ProductTable.id, + ProductTable.name, + ProductTable.description, + CategoryTable.category, + ProductTable.image, + ProductTable.price, + ProductTable.stock, +) + class PostgreSqlProductRepository(IProductRepository): + def __init__(self, session: Session): super().__init__() self.session = session def get_all(self) -> Sequence[Row]: - stmt = select(ProductTable) + stmt = select(*PRODUCTS_COLS).join(ProductTable.category) results = self.session.execute(stmt).all() if not results: return [] @@ -22,14 +34,14 @@ def get_all(self) -> Sequence[Row]: return results def find_by_name(self, name: str) -> Optional[Row]: - stmt = select(ProductTable).where(ProductTable.name == name) + stmt = select(*PRODUCTS_COLS).join(ProductTable.category).where(ProductTable.name == name) result = self.session.execute(stmt).first() if not result: return return result[0] def filter_by_id(self, product_id: str) -> Optional[Row]: - stmt = select(ProductTable).where(ProductTable.id == product_id) + stmt = select(*PRODUCTS_COLS).join(ProductTable.category).where(ProductTable.id == product_id) result = self.session.execute(stmt).first() if not result: @@ -38,14 +50,30 @@ def filter_by_id(self, product_id: str) -> Optional[Row]: return result[0] def insert_update(self, values: dict[str, Any]): + category = self.create_or_get_category(values['category']) + + values['category_id'] = category.id stmt = insert(ProductTable).values(**values) stmt = stmt.on_conflict_do_update( index_elements=[ProductTable.id], set_={key: values[key] for key in values if key != 'id'}, ) self.session.execute(stmt) + self.session.commit() + + def create_or_get_category(self, category: str): + category = self.session.query(CategoryTable).filter_by(category=category).first() + if not category: + category = CategoryTable( + id=str(uuid.uuid4()), + category=category + ) + self.session.add(category) + self.session.commit() + return category def delete(self, product_id: str): stmt = delete(ProductTable).where(ProductTable.id == product_id) self.session.execute(stmt) + self.session.commit() diff --git a/src/product/adapters/product_table.py b/src/product/adapters/product_table.py index 266277b..9d13e90 100644 --- a/src/product/adapters/product_table.py +++ b/src/product/adapters/product_table.py @@ -1,15 +1,40 @@ -from datetime import datetime from typing import Any, Optional -from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase -from sqlalchemy.sql.functions import now +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship + +from src.product.adapters.AuditMixin import AuditMixin class Base(DeclarativeBase): pass -class ProductTable(Base): +class CategoryTable(Base, AuditMixin): + __tablename__ = "categories" + + id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) + category: Mapped[str] = mapped_column(nullable=False, unique=True) + + product: Mapped[list["ProductTable"]] = relationship(back_populates="category", + cascade="all, delete", + passive_deletes=True) + + def __init__(self, **kwargs: Any): + super().__init__(**kwargs) + self.id = kwargs.get('id') + self.category = kwargs.get('category') + self.created_at = kwargs.get('created_at') + self.updated_at = kwargs.get('updated_at') + + def __repr__(self): + return (f"CategoryTable(id={self.id!r}, " + f"category={self.category!r}, " + f"created_at={self.created_at!r}, " + f"updated_at={self.updated_at!r})") + + +class ProductTable(Base, AuditMixin): __tablename__ = "products" id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) @@ -17,10 +42,10 @@ class ProductTable(Base): description: Mapped[str] price: Mapped[float] stock: Mapped[int] - category: Mapped[str] image: Mapped[Optional[str]] - created_at: Mapped[datetime] = mapped_column(default=now()) - updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) + + category_id: Mapped[str] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE")) + category: Mapped["CategoryTable"] = relationship(back_populates="product") def __init__(self, **kwargs: Any): super().__init__(**kwargs) @@ -31,6 +56,7 @@ def __init__(self, **kwargs: Any): self.stock = kwargs.get('stock') self.category = kwargs.get('category') self.image = kwargs.get('image') + self.category_id = kwargs.get('category_id') self.created_at = kwargs.get('created_at') self.updated_at = kwargs.get('updated_at') @@ -42,5 +68,6 @@ def __repr__(self): f"stock={self.stock!r}, " f"category={self.category!r}, " f"image={self.image!r}, " + f"category_id={self.category_id!r}, " f"created_at={self.created_at!r}, " f"updated_at={self.updated_at!r})") From 6c61a396c978c238f24db4636ca4e7b55a8eca9d Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 21:07:48 -0300 Subject: [PATCH 4/9] refactor: export audit fields to mixin class --- src/cart/adapters/AuditMixin.py | 13 +++++++++++++ src/cart/adapters/order_table.py | 6 +++--- src/client/adapters/AuditMixin.py | 13 +++++++++++++ src/client/adapters/client_table.py | 9 +++------ 4 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 src/cart/adapters/AuditMixin.py create mode 100644 src/client/adapters/AuditMixin.py diff --git a/src/cart/adapters/AuditMixin.py b/src/cart/adapters/AuditMixin.py new file mode 100644 index 0000000..70a0931 --- /dev/null +++ b/src/cart/adapters/AuditMixin.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.sql.functions import now + + +class AuditMixin: + created_at: Mapped[datetime] = mapped_column(default=now()) + updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) + + def __init__(self, created_at: datetime, updated_at: datetime): + self.created_at = created_at + self.updated_at = updated_at diff --git a/src/cart/adapters/order_table.py b/src/cart/adapters/order_table.py index 02d64cd..e9bcf8c 100644 --- a/src/cart/adapters/order_table.py +++ b/src/cart/adapters/order_table.py @@ -4,12 +4,14 @@ from sqlalchemy.orm import relationship, Mapped, mapped_column, DeclarativeBase from sqlalchemy.sql.functions import now +from src.cart.adapters.AuditMixin import AuditMixin + class Base(DeclarativeBase): pass -class OrderTable(Base): +class OrderTable(Base, AuditMixin): __tablename__ = "orders" id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False, index=True) @@ -18,8 +20,6 @@ class OrderTable(Base): payment_condition: Mapped[str] total: Mapped[float] products: Mapped["OrderProductTable"] = relationship("OrderProductTable", back_populates="order") - created_at: Mapped[datetime] = mapped_column(default=now()) - updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/src/client/adapters/AuditMixin.py b/src/client/adapters/AuditMixin.py new file mode 100644 index 0000000..70a0931 --- /dev/null +++ b/src/client/adapters/AuditMixin.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.sql.functions import now + + +class AuditMixin: + created_at: Mapped[datetime] = mapped_column(default=now()) + updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) + + def __init__(self, created_at: datetime, updated_at: datetime): + self.created_at = created_at + self.updated_at = updated_at diff --git a/src/client/adapters/client_table.py b/src/client/adapters/client_table.py index 0b1b70b..7025f08 100644 --- a/src/client/adapters/client_table.py +++ b/src/client/adapters/client_table.py @@ -1,14 +1,13 @@ -from datetime import datetime - from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase -from sqlalchemy.sql.functions import now + +from src.client.adapters.AuditMixin import AuditMixin class Base(DeclarativeBase): pass -class ClientTable(Base): +class ClientTable(Base, AuditMixin): __tablename__ = "clients" id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) @@ -17,8 +16,6 @@ class ClientTable(Base): cpf: Mapped[str] email: Mapped[str] password: Mapped[str] - created_at: Mapped[datetime] = mapped_column(default=now()) - updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) def __init__(self, **kwargs): super().__init__(**kwargs) From 5f9d3b4548b4bd28f52089d7fb6596a0de895b97 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 21:45:24 -0300 Subject: [PATCH 5/9] refactor: normalize client repository --- src/client/adapters/client_table.py | 44 +++++++++--- src/client/adapters/postgresql_repository.py | 74 ++++++++++++-------- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/src/client/adapters/client_table.py b/src/client/adapters/client_table.py index 7025f08..081db21 100644 --- a/src/client/adapters/client_table.py +++ b/src/client/adapters/client_table.py @@ -1,4 +1,5 @@ -from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship from src.client.adapters.AuditMixin import AuditMixin @@ -7,15 +8,43 @@ class Base(DeclarativeBase): pass -class ClientTable(Base, AuditMixin): - __tablename__ = "clients" +class CredentialTable(Base, AuditMixin): + __tablename__ = "credentials" + + id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) + email: Mapped[str] + password: Mapped[str] + + profile: Mapped["ProfileTable"] = relationship(back_populates="credential", + cascade="all, delete", + passive_deletes=True) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.id = kwargs.get('id') + self.email = kwargs.get('email') + self.password = kwargs.get('password') + self.created_at = kwargs.get('created_at') + self.updated_at = kwargs.get('updated_at') + + def __repr__(self): + return (f"CredentialTable(id={self.id}, " + f"email={self.email}, " + f"password={self.password}, " + f"created_at={self.created_at}, " + f"updated_at={self.updated_at})") + + +class ProfileTable(Base, AuditMixin): + __tablename__ = "profiles" id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) first_name: Mapped[str] last_name: Mapped[str] cpf: Mapped[str] - email: Mapped[str] - password: Mapped[str] + + credential_id: Mapped[str] = mapped_column(ForeignKey("credentials.id", ondelete="CASCADE")) + credential: Mapped["CredentialTable"] = relationship(back_populates="profile") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -23,16 +52,13 @@ def __init__(self, **kwargs): self.first_name = kwargs.get('first_name') self.last_name = kwargs.get('last_name') self.cpf = kwargs.get('cpf') - self.email = kwargs.get('email') - self.password = kwargs.get('password') self.created_at = kwargs.get('created_at') self.updated_at = kwargs.get('updated_at') def __repr__(self): - return (f"ClientTable(id={self.id}, " + return (f"ProfileTable(id={self.id}, " f"first_name={self.first_name}, " f"last_name={self.last_name}, " f"cpf={self.cpf}, " - f"email={self.email}, " f"created_at={self.created_at}, " f"updated_at={self.updated_at})") diff --git a/src/client/adapters/postgresql_repository.py b/src/client/adapters/postgresql_repository.py index 0d3fb3c..52a42a6 100644 --- a/src/client/adapters/postgresql_repository.py +++ b/src/client/adapters/postgresql_repository.py @@ -1,12 +1,22 @@ +import uuid from typing import Dict, Sequence, Optional from sqlalchemy import select, Row, delete from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session -from src.client.adapters.client_table import ClientTable +from src.client.adapters.client_table import ProfileTable, CredentialTable from src.client.ports.repository_interface import IClientRepository +PROFILE_COLS: tuple = ( + ProfileTable.id, + ProfileTable.first_name, + ProfileTable.last_name, + ProfileTable.cpf, + CredentialTable.email, + CredentialTable.password, +) + class PostgreSqlClientRepository(IClientRepository): def __init__(self, session: Session): @@ -14,12 +24,7 @@ def __init__(self, session: Session): self.session = session def get_users(self) -> Optional[Sequence[Row[tuple]]]: - stmt = select(ClientTable.id, - ClientTable.first_name, - ClientTable.last_name, - ClientTable.cpf, - ClientTable.email, - ClientTable.password) + stmt = select(*PROFILE_COLS).join(ProfileTable.credential) results = self.session.execute(stmt).all() if not results: return [] @@ -27,12 +32,7 @@ def get_users(self) -> Optional[Sequence[Row[tuple]]]: return results def get_user_by_cpf(self, cpf: str) -> Optional[Row]: - stmt = select(ClientTable.id, - ClientTable.first_name, - ClientTable.last_name, - ClientTable.cpf, - ClientTable.email, - ClientTable.password).where(ClientTable.cpf == cpf) + stmt = select(*PROFILE_COLS).join(ProfileTable.credential).where(ProfileTable.cpf == cpf) results = self.session.execute(stmt).first() if not results: return @@ -40,12 +40,7 @@ def get_user_by_cpf(self, cpf: str) -> Optional[Row]: return results def get_user_by_email(self, email: str) -> Optional[Row]: - stmt = select(ClientTable.id, - ClientTable.first_name, - ClientTable.last_name, - ClientTable.cpf, - ClientTable.email, - ClientTable.password).where(ClientTable.email == email) + stmt = select(*PROFILE_COLS).join(ProfileTable.credential).where(CredentialTable.email == email) results = self.session.execute(stmt).first() if not results: return @@ -53,28 +48,45 @@ def get_user_by_email(self, email: str) -> Optional[Row]: return results def create_user(self, user: Dict): - stmt = insert(ClientTable).values(**user) - self.session.execute(stmt) + credential = self.save_credential(email=user['email'], password=user['password']) + + profile = ProfileTable(credential_id=credential.id, **user) + self.session.add(profile) + self.session.commit() + + def save_credential(self, email: str, password: str): + credential = CredentialTable(id=str(uuid.uuid4()), email=email, password=password) + self.session.add(credential) + self.session.commit() + return credential def update_user(self, user: Dict): - stmt = insert(ClientTable).values(**user) + profile_data = {key: user[key] for key in user if key not in ['email', 'password']} + stmt = insert(ProfileTable).values(**profile_data) stmt = stmt.on_conflict_do_update( - index_elements=[ClientTable.id], - set_={key: user[key] for key in user if key != 'id'} + index_elements=[ProfileTable.id], + set_=profile_data ) self.session.execute(stmt) + credential_data = {key: user[key] for key in user if key in ['email', 'password']} + if credential_data: + stmt = insert(CredentialTable).values(id=user['credential_id'], **credential_data) + stmt = stmt.on_conflict_do_update( + index_elements=[CredentialTable.id], + set_=credential_data + ) + self.session.execute(stmt) + + self.session.commit() + def delete_user(self, user_id: str): - stmt = delete(ClientTable).where(ClientTable.id == user_id) + stmt = delete(ProfileTable).where(ProfileTable.id == user_id) self.session.execute(stmt) + self.session.commit() def get_user_by_id(self, user_id: str) -> Optional[Row]: - stmt = select(ClientTable.id, - ClientTable.first_name, - ClientTable.last_name, - ClientTable.cpf, - ClientTable.email, - ClientTable.password).where(ClientTable.id == user_id) + stmt = select(*PROFILE_COLS).join(ProfileTable.credential).where(ProfileTable.id == user_id) results = self.session.execute(stmt).first() if not results: return From 54d51cbd8ef3baea3680d986cb96646f78b1b9f9 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 22:35:40 -0300 Subject: [PATCH 6/9] refactor: normalize cart repository --- src/cart/adapters/order_table.py | 73 ++++++++++++++++++---- src/cart/adapters/postgresql_repository.py | 53 ++++++++++++---- 2 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/cart/adapters/order_table.py b/src/cart/adapters/order_table.py index e9bcf8c..3ccbb92 100644 --- a/src/cart/adapters/order_table.py +++ b/src/cart/adapters/order_table.py @@ -1,8 +1,5 @@ -from datetime import datetime - -from sqlalchemy import String, ForeignKey +from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column, DeclarativeBase -from sqlalchemy.sql.functions import now from src.cart.adapters.AuditMixin import AuditMixin @@ -11,15 +8,67 @@ class Base(DeclarativeBase): pass +class StatusTable(Base, AuditMixin): + __tablename__ = "status" + + id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) + status: Mapped[str] = mapped_column(nullable=False, unique=True) + + orders: Mapped[list["OrderTable"]] = relationship(back_populates="status", + cascade="all, delete", + passive_deletes=True) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.id = kwargs.get('id') + self.status = kwargs.get('status') + self.created_at = kwargs.get('created_at') + self.updated_at = kwargs.get('updated_at') + + def __repr__(self): + return (f"StatusTable(id={self.id}, " + f"status={self.status}, " + f"created_at={self.created_at}, " + f"updated_at={self.updated_at})") + + +class PaymentConditionTable(Base, AuditMixin): + __tablename__ = "payment_conditions" + + id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) + description: Mapped[str] + + order: Mapped["OrderTable"] = relationship(back_populates="payment_condition", + cascade="all, delete", + passive_deletes=True) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.id = kwargs.get('id') + self.description = kwargs.get('description') + self.created_at = kwargs.get('created_at') + self.updated_at = kwargs.get('updated_at') + + def __repr__(self): + return (f"PaymentConditionTable(id={self.id}, " + f"description={self.description}, " + f"created_at={self.created_at}, " + f"updated_at={self.updated_at})") + + class OrderTable(Base, AuditMixin): __tablename__ = "orders" id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False, index=True) user_id: Mapped[str] - status: Mapped[int] - payment_condition: Mapped[str] + status_id: Mapped[str] = mapped_column(ForeignKey("status.id", ondelete="CASCADE")) + status: Mapped["StatusTable"] = relationship(back_populates="orders") + payment_condition_id: Mapped[str] = mapped_column(ForeignKey("payment_conditions.id", ondelete="CASCADE")) + payment_condition: Mapped["PaymentConditionTable"] = relationship(back_populates="order") total: Mapped[float] - products: Mapped["OrderProductTable"] = relationship("OrderProductTable", back_populates="order") + products: Mapped[list["OrderProductTable"]] = relationship(back_populates="order", + cascade="all, delete", + passive_deletes=True) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -39,18 +88,16 @@ def __repr__(self): f"updated_at={self.updated_at})") -class OrderProductTable(Base): +class OrderProductTable(Base, AuditMixin): __tablename__ = "order_products" - id: Mapped[str] = mapped_column(String(255), primary_key=True, nullable=False, autoincrement=False) + id: Mapped[str] = mapped_column(primary_key=True, nullable=False, autoincrement=False) product_id: Mapped[str] quantity: Mapped[int] observation: Mapped[str] - created_at: Mapped[datetime] = mapped_column(default=now()) - updated_at: Mapped[datetime] = mapped_column(default=now(), onupdate=now()) - order_id: Mapped[str] = mapped_column(ForeignKey("orders.id")) - order = relationship("OrderTable", back_populates="products") + order_id: Mapped[str] = mapped_column(ForeignKey("orders.id", ondelete="CASCADE")) + order: Mapped["OrderTable"] = relationship(back_populates="products") def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/src/cart/adapters/postgresql_repository.py b/src/cart/adapters/postgresql_repository.py index dbb5a49..e6f945b 100644 --- a/src/cart/adapters/postgresql_repository.py +++ b/src/cart/adapters/postgresql_repository.py @@ -1,12 +1,21 @@ +import uuid from typing import Any, Sequence, Optional from sqlalchemy import select, delete, Row from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session -from src.cart.adapters.order_table import OrderTable, OrderProductTable +from src.cart.adapters.order_table import OrderTable, OrderProductTable, StatusTable, PaymentConditionTable from src.cart.ports.repository_interface import IRepository +ORDER_COLS: tuple = ( + OrderTable.id.label('order_id'), + OrderTable.user_id, + StatusTable.status, + PaymentConditionTable.description.label('payment_condition'), + OrderTable.created_at, +) + class PostgreSqlRepository(IRepository): @@ -17,12 +26,10 @@ def __init__(self, session: Session): def get_all(self) -> Optional[Sequence[Row]]: stmt = ( select( - OrderTable.id.label('order_id'), - OrderTable.user_id, - OrderTable.status, - OrderTable.payment_condition, - OrderTable.created_at + *ORDER_COLS ) + .join(StatusTable, OrderTable.status_id == StatusTable.id) + .join(PaymentConditionTable, OrderTable.payment_condition_id == PaymentConditionTable.id) .order_by(OrderTable.status, OrderTable.created_at) ) @@ -36,13 +43,11 @@ def get_all(self) -> Optional[Sequence[Row]]: def filter_by_id(self, order_id: str) -> Optional[Row]: stmt = ( select( - OrderTable.id, - OrderTable.user_id, - OrderTable.status, - OrderTable.payment_condition, - OrderTable.created_at, - OrderTable.updated_at + *ORDER_COLS ) + .join(StatusTable, OrderTable.status_id == StatusTable.id) + .join(PaymentConditionTable, OrderTable.payment_condition_id == PaymentConditionTable.id) + .order_by(OrderTable.status, OrderTable.created_at) .where(OrderTable.id == order_id) ) @@ -71,6 +76,11 @@ def _upsert_order(self, values: dict[str, Any]): """Inserção ou atualização de um pedido.""" order_data = {key: values[key] for key in values if key != "products"} + status = self.create_or_get_status(values) + payment_condition = self.create_or_get_payment_condition(values) + + order_data["status_id"] = status.id + order_data["payment_condition_id"] = payment_condition.id stmt = insert(OrderTable).values(order_data) stmt = stmt.on_conflict_do_update( index_elements=[OrderTable.id], @@ -78,6 +88,23 @@ def _upsert_order(self, values: dict[str, Any]): ) self.session.execute(stmt) + def create_or_get_payment_condition(self, values): + payment_condition = (self.session.query(PaymentConditionTable) + .filter_by(description=values["payment_condition"]).first()) + if not payment_condition: + payment_condition = PaymentConditionTable(id=str(uuid.uuid4()), description=values["payment_condition"]) + self.session.add(payment_condition) + self.session.commit() + return payment_condition + + def create_or_get_status(self, values): + status = self.session.query(StatusTable).filter_by(status=values["status"]).first() + if not status: + status = StatusTable(id=str(uuid.uuid4()), status=values["status"]) + self.session.add(status) + self.session.commit() + return status + def _upsert_order_products(self, order_id: str, products: list[dict[str, Any]]): """Inserção ou atualização dos produtos relacionados a um pedido.""" for product in products: @@ -91,6 +118,8 @@ def _upsert_order_products(self, order_id: str, products: list[dict[str, Any]]): ) self.session.execute(stmt) + self.session.commit() + def delete(self, order_id: str): stmt = delete(OrderTable).where(OrderTable.id == order_id) self.session.execute(stmt) From ff3f6dd9bd712324d35519f57b5421de785f41c9 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 23:04:17 -0300 Subject: [PATCH 7/9] fix: insert category --- src/product/adapters/postgres_gateway.py | 4 ++-- src/product/adapters/postgresql_repository.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/product/adapters/postgres_gateway.py b/src/product/adapters/postgres_gateway.py index d902cd5..4efd829 100644 --- a/src/product/adapters/postgres_gateway.py +++ b/src/product/adapters/postgres_gateway.py @@ -16,7 +16,7 @@ def __init__(self, uow: IProductUnitOfWork): def get_products(self) -> list[Product]: with self.uow: products = self.uow.repository.get_all() - return [self.build_product_entity(p[0]) for p in products] + return [self.build_product_entity(p) for p in products] def get_product_by_id(self, product_id: str) -> Optional[Product]: with self.uow: @@ -48,7 +48,7 @@ def create_update_product(self, product: Product) -> Product: 'name': product.name, 'category': product.category, 'description': product.description, - 'image': product.image, + 'image': product.image, 'stock': product.stock, 'price': product.price}) self.uow.commit() diff --git a/src/product/adapters/postgresql_repository.py b/src/product/adapters/postgresql_repository.py index b2bc23c..31c7f3e 100644 --- a/src/product/adapters/postgresql_repository.py +++ b/src/product/adapters/postgresql_repository.py @@ -38,7 +38,7 @@ def find_by_name(self, name: str) -> Optional[Row]: result = self.session.execute(stmt).first() if not result: return - return result[0] + return result def filter_by_id(self, product_id: str) -> Optional[Row]: stmt = select(*PRODUCTS_COLS).join(ProductTable.category).where(ProductTable.id == product_id) @@ -47,12 +47,13 @@ def filter_by_id(self, product_id: str) -> Optional[Row]: if not result: return - return result[0] + return result def insert_update(self, values: dict[str, Any]): category = self.create_or_get_category(values['category']) values['category_id'] = category.id + del values['category'] stmt = insert(ProductTable).values(**values) stmt = stmt.on_conflict_do_update( index_elements=[ProductTable.id], @@ -62,15 +63,15 @@ def insert_update(self, values: dict[str, Any]): self.session.commit() def create_or_get_category(self, category: str): - category = self.session.query(CategoryTable).filter_by(category=category).first() - if not category: - category = CategoryTable( + _category = self.session.query(CategoryTable).filter_by(category=category).first() + if not _category: + _category = CategoryTable( id=str(uuid.uuid4()), category=category ) - self.session.add(category) + self.session.add(_category) self.session.commit() - return category + return _category def delete(self, product_id: str): stmt = delete(ProductTable).where(ProductTable.id == product_id) From c6f4e5e13baf0f4702ad07a53c5668b6c9032cd8 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 23:22:05 -0300 Subject: [PATCH 8/9] fix: update data --- src/client/adapters/postgresql_repository.py | 39 +++++++++++--------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/client/adapters/postgresql_repository.py b/src/client/adapters/postgresql_repository.py index 52a42a6..490af93 100644 --- a/src/client/adapters/postgresql_repository.py +++ b/src/client/adapters/postgresql_repository.py @@ -48,38 +48,43 @@ def get_user_by_email(self, email: str) -> Optional[Row]: return results def create_user(self, user: Dict): - credential = self.save_credential(email=user['email'], password=user['password']) - + credential = self.create_or_get_credential(user_id=user['id'], email=user['email'], password=user['password']) + del user['email'] + del user['password'] profile = ProfileTable(credential_id=credential.id, **user) self.session.add(profile) self.session.commit() - def save_credential(self, email: str, password: str): - credential = CredentialTable(id=str(uuid.uuid4()), email=email, password=password) - self.session.add(credential) - self.session.commit() + def create_or_get_credential(self, user_id: str, email: str, password: str): + credential = self.session.query(CredentialTable).join(CredentialTable.profile).filter( + ProfileTable.id == user_id).first() + if not credential: + credential = CredentialTable(id=str(uuid.uuid4()), email=email, password=password) + self.session.add(credential) + self.session.commit() return credential def update_user(self, user: Dict): - profile_data = {key: user[key] for key in user if key not in ['email', 'password']} - stmt = insert(ProfileTable).values(**profile_data) - stmt = stmt.on_conflict_do_update( - index_elements=[ProfileTable.id], - set_=profile_data - ) - self.session.execute(stmt) - - credential_data = {key: user[key] for key in user if key in ['email', 'password']} + credential_data = self.create_or_get_credential(user_id=user['id'], email=user['email'], + password=user['password']) if credential_data: - stmt = insert(CredentialTable).values(id=user['credential_id'], **credential_data) + stmt = insert(CredentialTable).values(id=credential_data.id, email=user['email'], password=user['password']) stmt = stmt.on_conflict_do_update( index_elements=[CredentialTable.id], - set_=credential_data + set_=dict(email=user['email'], password=user['password']) ) self.session.execute(stmt) self.session.commit() + profile_data = {key: user[key] for key in user if key not in ['email', 'password']} + stmt = insert(ProfileTable).values(credential_id=credential_data.id, **profile_data) + stmt = stmt.on_conflict_do_update( + index_elements=[ProfileTable.id], + set_=profile_data + ) + self.session.execute(stmt) + def delete_user(self, user_id: str): stmt = delete(ProfileTable).where(ProfileTable.id == user_id) self.session.execute(stmt) From 3e2336fb00db765d3e0d74a63e60d1f59762b235 Mon Sep 17 00:00:00 2001 From: Rodrigo dos Santos Felix Date: Tue, 17 Dec 2024 23:57:42 -0300 Subject: [PATCH 9/9] fix: get orders --- src/cart/adapters/order_table.py | 1 + src/cart/adapters/postgres_gateway.py | 8 +++++--- src/cart/adapters/postgresql_repository.py | 5 ++++- src/cart/adapters/pydantic_presenter.py | 3 +-- src/cart/domain/entities/order.py | 3 +++ src/cart/use_cases/create_cart.py | 10 ++++++---- src/cart/use_cases/update_order_status.py | 3 ++- 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/cart/adapters/order_table.py b/src/cart/adapters/order_table.py index 3ccbb92..ac40c82 100644 --- a/src/cart/adapters/order_table.py +++ b/src/cart/adapters/order_table.py @@ -1,3 +1,4 @@ + from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column, DeclarativeBase diff --git a/src/cart/adapters/postgres_gateway.py b/src/cart/adapters/postgres_gateway.py index ae52b6b..27281cc 100644 --- a/src/cart/adapters/postgres_gateway.py +++ b/src/cart/adapters/postgres_gateway.py @@ -3,6 +3,7 @@ from sqlalchemy import Row from src.cart.domain.entities.order import Order +from src.cart.domain.enums.order_status import OrderStatus from src.cart.domain.enums.paymentConditions import PaymentConditions from src.cart.ports.cart_gateway import ICartGateway from src.cart.ports.unit_of_work_interface import ICartUnitOfWork @@ -27,7 +28,8 @@ def get_order_products(self, order_id) -> list[dict]: def get_orders(self) -> list[Order]: with self.uow: orders: Optional[list[Row]] = self.uow.repository.get_all() - return [self.build_order_entity(o) for o in orders] + orders_entity = [self.build_order_entity(o) for o in orders] + return orders_entity def get_order_by_id(self, order_id: str) -> Optional[Order]: with self.uow: @@ -42,7 +44,7 @@ def create_update_order(self, order: Order) -> Order: self.uow.repository.insert_update({ 'id': order.id, 'user_id': order.user, - 'status': order.order_status.value, + 'status': order.order_status.name, 'payment_condition': condition.name, 'total': order.total_order, 'products': [{'id': p.id, @@ -68,5 +70,5 @@ def build_order_entity(order): return Order(_id=order.id, user=order.user_id, order_datetime=order.created_at, - order_status=order.status, + order_status=OrderStatus[order.status], payment_condition=payment_condition.value) diff --git a/src/cart/adapters/postgresql_repository.py b/src/cart/adapters/postgresql_repository.py index e6f945b..7437548 100644 --- a/src/cart/adapters/postgresql_repository.py +++ b/src/cart/adapters/postgresql_repository.py @@ -9,7 +9,7 @@ from src.cart.ports.repository_interface import IRepository ORDER_COLS: tuple = ( - OrderTable.id.label('order_id'), + OrderTable.id, OrderTable.user_id, StatusTable.status, PaymentConditionTable.description.label('payment_condition'), @@ -81,6 +81,9 @@ def _upsert_order(self, values: dict[str, Any]): order_data["status_id"] = status.id order_data["payment_condition_id"] = payment_condition.id + + del order_data["status"] + del order_data["payment_condition"] stmt = insert(OrderTable).values(order_data) stmt = stmt.on_conflict_do_update( index_elements=[OrderTable.id], diff --git a/src/cart/adapters/pydantic_presenter.py b/src/cart/adapters/pydantic_presenter.py index cc4c68c..f15a7f1 100644 --- a/src/cart/adapters/pydantic_presenter.py +++ b/src/cart/adapters/pydantic_presenter.py @@ -1,7 +1,6 @@ from src.api.presentation.shared.dtos.order_response_dto import OrderResponseDto, OrderProductResponseDto from src.cart.domain.entities.order import Order -from src.cart.domain.enums.order_status import OrderStatus from src.cart.ports.cart_presenter import ICartPresenter @@ -20,7 +19,7 @@ def formater(order): return OrderResponseDto(id=order.id, user=order.user, total_order=order.total_order, - order_status=OrderStatus(order.order_status), + order_status=order.order_status.name, payment_condition=order.payment_condition, products=[OrderProductResponseDto( product=p.product.id, diff --git a/src/cart/domain/entities/order.py b/src/cart/domain/entities/order.py index 7c9722b..0f10c62 100644 --- a/src/cart/domain/entities/order.py +++ b/src/cart/domain/entities/order.py @@ -30,6 +30,9 @@ def __hash__(self): def __eq__(self, other): return self.id == other.id + def __gt__(self, other): + return self.order_status.value > other.order_status.value + @property def id(self): return self._id diff --git a/src/cart/use_cases/create_cart.py b/src/cart/use_cases/create_cart.py index 697acad..280c438 100644 --- a/src/cart/use_cases/create_cart.py +++ b/src/cart/use_cases/create_cart.py @@ -6,7 +6,7 @@ from src.cart.domain.enums.order_status import OrderStatus from src.cart.exceptions import (ClientError, ProductNotFoundError, - OrderExistsError) + OrderExistsError, OrderNotFoundError) from src.cart.ports.cart_gateway import ICartGateway from src.cart.use_cases.get_order_by_id import GetOrderByIdUseCase from src.client.ports.user_gateway import IUserGateway @@ -36,9 +36,11 @@ def execute(self, request_data: Dict): for product in products_required: order.add_product(product) - - if self.get_order_by_id.execute(order.id): - raise OrderExistsError(order=order.id) + try: + if self.get_order_by_id.execute(order.id): + raise OrderExistsError(order=order.id) + except OrderNotFoundError: + pass return self.cart_gateway.create_update_order(order) diff --git a/src/cart/use_cases/update_order_status.py b/src/cart/use_cases/update_order_status.py index a90eff2..13c8ff6 100644 --- a/src/cart/use_cases/update_order_status.py +++ b/src/cart/use_cases/update_order_status.py @@ -1,3 +1,4 @@ +from src.cart.domain.enums.order_status import OrderStatus from src.cart.domain.validators.order_validator import OrderValidator from src.cart.exceptions import OrderNotFoundError from src.cart.ports.cart_gateway import ICartGateway @@ -13,6 +14,6 @@ def execute(self, order_id: str, new_status: str): order = self.get_order_by_id.execute(order_id) if not order: raise OrderNotFoundError(order=order_id) - order.order_status = new_status + order.order_status = OrderStatus[new_status] OrderValidator.validate(order) return self.gateway.create_update_order(order)