Files
blog.pyaqa.ru/app/infrastructure/database/models.py
Sergey Vanyushkin 3cf6c94da2 feat: add like/unlike toggle on blog posts with per-user tracking
- PostLike domain entity (post_id, liked_by) with BaseEntity integration
- Post entity: add like_count field (default 0) and to_dict serialization
- PostRepository interface: add get_like, add_like, remove_like methods
- TogglePostLikeUseCase: toggle logic (like → unlike, unlike → like)
- PostResponseDTO/PostResponseSchema: add like_count field
- PostLikeORM model with FK to posts and cascade delete
- SQLAlchemyPostRepository: implement like query/add/remove with ORM mapping
- DI provider registration for TogglePostLikeUseCase
- API endpoint POST /api/v1/posts/{id}/like (auth required)
- Unit tests: PostLike entity, Post.like_count, TogglePostLikeUseCase (7 tests)
- API tests: POST /api/v1/posts/{id}/like (4 tests)
- Test model files: FEATURE_LIKES.md, TEST_MODEL.md updated
2026-05-10 18:24:09 +03:00

90 lines
3.2 KiB
Python

"""SQLAlchemy ORM models.
This module defines the database ORM models that map to database tables.
Models are used by repositories for data persistence.
"""
from datetime import UTC, datetime
from uuid import uuid4
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, declarative_base, mapped_column, relationship
Base = declarative_base()
class PostORM(Base): # type: ignore[valid-type,misc]
"""SQLAlchemy model for Blog Post.
Database table representation of blog posts.
Maps to the 'posts' table with all post attributes.
Attributes:
id: Primary key as UUID string.
title: Post title (max 200 chars).
content: Post content (text).
slug: URL-friendly unique identifier.
author_id: Author reference.
published: Publication status flag.
tags: JSON array of tags.
created_at: Creation timestamp.
updated_at: Last update timestamp.
Example:
>>> post = PostORM(title="Post", content="...", slug="post", author_id="user-1")
"""
__tablename__ = "posts"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
title: Mapped[str] = mapped_column(String(200), nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
slug: Mapped[str] = mapped_column(String(200), nullable=False, unique=True, index=True)
author_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
published: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True)
like_count: Mapped[int] = mapped_column(default=0, nullable=False)
likes: Mapped[list["PostLikeORM"]] = relationship(
back_populates="post", cascade="all, delete-orphan"
)
tags: Mapped[list[str]] = mapped_column(JSON, default=list)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(UTC),
nullable=False,
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(UTC),
onupdate=lambda: datetime.now(UTC),
nullable=False,
)
class PostLikeORM(Base): # type: ignore[valid-type,misc]
"""SQLAlchemy model for PostLike.
Database table representation of post likes.
Maps to the 'post_likes' table tracking which users/devices liked which posts.
Attributes:
id: Primary key as UUID string.
post_id: Foreign key to the liked post.
liked_by: User ID or device identifier.
created_at: Creation timestamp.
"""
__tablename__ = "post_likes"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
post_id: Mapped[str] = mapped_column(
String(36), ForeignKey("posts.id", ondelete="CASCADE"), nullable=False, index=True
)
liked_by: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(UTC),
nullable=False,
)
post: Mapped[PostORM] = relationship(back_populates="likes")