From be5c34e4f76b01294e49e8e95f209b4f9ff0f42f Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Sun, 24 May 2026 04:51:21 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20mod=C3=A8les=20SQLAlchemy=20pour=20todo?= =?UTF-8?q?s,=20shopping=20et=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/models/notes.py | 40 +++++++++++++++++ backend/app/models/shopping.py | 81 ++++++++++++++++++++++++++++++++++ backend/app/models/todos.py | 26 +++++++++++ 3 files changed, 147 insertions(+) create mode 100644 backend/app/models/notes.py create mode 100644 backend/app/models/shopping.py create mode 100644 backend/app/models/todos.py diff --git a/backend/app/models/notes.py b/backend/app/models/notes.py new file mode 100644 index 0000000..4681ba7 --- /dev/null +++ b/backend/app/models/notes.py @@ -0,0 +1,40 @@ +import uuid +from datetime import datetime +from decimal import Decimal +from sqlalchemy import String, Text, TIMESTAMP, Numeric, ForeignKey, text +from sqlalchemy.dialects.postgresql import UUID, ARRAY, JSONB +from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.core.database import Base + + +class NoteItem(Base): + __tablename__ = "items" + __table_args__ = {"schema": "notes"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + title: Mapped[str | None] = mapped_column(String(255)) + content: Mapped[str] = mapped_column(Text, nullable=False) + category: Mapped[str | None] = mapped_column(String(50)) + tags: Mapped[list[str]] = mapped_column(ARRAY(String(50)), server_default=text("'{}'::varchar[]")) + gps_lat: Mapped[Decimal | None] = mapped_column(Numeric(10, 7)) + gps_lon: Mapped[Decimal | None] = mapped_column(Numeric(10, 7)) + metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) + + attachments: Mapped[list["NoteAttachment"]] = relationship("NoteAttachment", back_populates="note", cascade="all, delete-orphan") + + +class NoteAttachment(Base): + __tablename__ = "attachments" + __table_args__ = {"schema": "notes"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + note_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("notes.items.id", ondelete="CASCADE"), nullable=False) + file_path: Mapped[str | None] = mapped_column(String(255)) + thumbnail_path: Mapped[str | None] = mapped_column(String(255)) + file_type: Mapped[str | None] = mapped_column(String(20)) + original_name: Mapped[str | None] = mapped_column(String(255)) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + + note: Mapped["NoteItem"] = relationship("NoteItem", back_populates="attachments") diff --git a/backend/app/models/shopping.py b/backend/app/models/shopping.py new file mode 100644 index 0000000..9e4f173 --- /dev/null +++ b/backend/app/models/shopping.py @@ -0,0 +1,81 @@ +import uuid +from datetime import datetime, date +from decimal import Decimal +from sqlalchemy import String, Text, Integer, TIMESTAMP, Date, Numeric, Boolean, ForeignKey, text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.core.database import Base + + +class Product(Base): + __tablename__ = "products" + __table_args__ = {"schema": "shopping"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(150), nullable=False) + brand: Mapped[str | None] = mapped_column(String(100)) + category: Mapped[str | None] = mapped_column(String(50)) + image_path: Mapped[str | None] = mapped_column(String(255)) + thumbnail_path: Mapped[str | None] = mapped_column(String(255)) + default_unit: Mapped[str | None] = mapped_column(String(20)) + barcode: Mapped[str | None] = mapped_column(String(50)) + frequency_score: Mapped[int] = mapped_column(Integer, server_default=text("0")) + owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + + +class Store(Base): + __tablename__ = "stores" + __table_args__ = {"schema": "shopping"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(100), nullable=False) + location: Mapped[str | None] = mapped_column(Text) + owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) + + +class PriceHistory(Base): + __tablename__ = "price_history" + __table_args__ = {"schema": "shopping"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + product_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.products.id", ondelete="CASCADE"), nullable=False) + store_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.stores.id", ondelete="SET NULL")) + price: Mapped[Decimal] = mapped_column(Numeric(8, 2), nullable=False) + unit: Mapped[str | None] = mapped_column(String(20)) + quantity: Mapped[Decimal | None] = mapped_column(Numeric(8, 3)) + source: Mapped[str] = mapped_column(String(20), server_default="manual") + recorded_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + + +class ShoppingList(Base): + __tablename__ = "lists" + __table_args__ = {"schema": "shopping"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name: Mapped[str | None] = mapped_column(String(100)) + store_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.stores.id", ondelete="SET NULL")) + week_date: Mapped[date | None] = mapped_column(Date) + status: Mapped[str] = mapped_column(String(20), server_default="draft") + owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True)) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + + items: Mapped[list["ListItem"]] = relationship("ListItem", back_populates="shopping_list", cascade="all, delete-orphan") + + +class ListItem(Base): + __tablename__ = "list_items" + __table_args__ = {"schema": "shopping"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + list_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.lists.id", ondelete="CASCADE"), nullable=False) + product_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.products.id", ondelete="SET NULL")) + custom_name: Mapped[str | None] = mapped_column(String(150)) + quantity: Mapped[Decimal | None] = mapped_column(Numeric(8, 3)) + unit: Mapped[str | None] = mapped_column(String(20)) + is_checked: Mapped[bool] = mapped_column(Boolean, server_default=text("false")) + price_recorded: Mapped[Decimal | None] = mapped_column(Numeric(8, 2)) + carried_over: Mapped[bool] = mapped_column(Boolean, server_default=text("false")) + sort_order: Mapped[int | None] = mapped_column(Integer) + + shopping_list: Mapped["ShoppingList"] = relationship("ShoppingList", back_populates="items") diff --git a/backend/app/models/todos.py b/backend/app/models/todos.py new file mode 100644 index 0000000..1887d1e --- /dev/null +++ b/backend/app/models/todos.py @@ -0,0 +1,26 @@ +import uuid +from datetime import datetime +from sqlalchemy import String, Text, Integer, TIMESTAMP, text +from sqlalchemy.dialects.postgresql import UUID, ARRAY +from sqlalchemy.orm import Mapped, mapped_column +from app.core.database import Base + + +class TodoItem(Base): + __tablename__ = "items" + __table_args__ = {"schema": "todos"} + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + title: Mapped[str] = mapped_column(String(255), nullable=False) + body: Mapped[str | None] = mapped_column(Text) + url: Mapped[str | None] = mapped_column(Text) + domain: Mapped[str | None] = mapped_column(String(50)) + category: Mapped[str | None] = mapped_column(String(50)) + tags: Mapped[list[str]] = mapped_column(ARRAY(String(50)), server_default=text("'{}'::varchar[]")) + status: Mapped[str] = mapped_column(String(20), server_default="pending") + priority: Mapped[str] = mapped_column(String(10), server_default="medium") + due_date: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True)) + postponed_count: Mapped[int] = mapped_column(Integer, server_default=text("0")) + created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()")) + updated_at: Mapped[datetime | None] = mapped_column(TIMESTAMP(timezone=True)) + owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))