"""Domain entity for Blog Post. This module defines the Post aggregate root entity that encapsulates all business logic related to blog posts including publishing, content management, and tag operations. """ from dataclasses import dataclass, field from typing import Any from app.domain.entities.base import BaseEntity from app.domain.value_objects.content import Content from app.domain.value_objects.slug import Slug from app.domain.value_objects.title import Title @dataclass(kw_only=True) class Post(BaseEntity): """Blog post domain entity. Represents a blog post with title, content, slug, and metadata. Encapsulates business logic for post lifecycle management. Attributes: title: Post title value object with validation. content: Post content value object with validation. slug: URL-friendly identifier generated from title. author_id: Identifier of the post author. published: Publication status flag. tags: List of tags associated with the post. Example: >>> post = Post.create( ... title_str="My First Post", ... content_str="This is the content...", ... author_id="user-123", ... tags=["python", "fastapi"] ... ) >>> post.publish() """ title: Title content: Content slug: Slug author_id: str published: bool = False tags: list[str] = field(default_factory=list) def publish(self) -> None: """Publish the post. Sets the published flag to True and updates the timestamp. """ self.published = True self.touch() def unpublish(self) -> None: """Unpublish the post. Sets the published flag to False and updates the timestamp. """ self.published = False self.touch() def update_content(self, content: Content) -> None: """Update post content. Args: content: New content value object. """ self.content = content self.touch() def update_title(self, title: Title) -> None: """Update post title and regenerate slug. Args: title: New title value object. """ self.title = title self.slug = Slug.from_title(title.value) self.touch() def add_tag(self, tag: str) -> None: """Add a tag to the post. Args: tag: Tag string to add. Only adds if not already present. """ if tag not in self.tags: self.tags.append(tag) self.touch() def remove_tag(self, tag: str) -> None: """Remove a tag from the post. Args: tag: Tag string to remove. Only removes if present. """ if tag in self.tags: self.tags.remove(tag) self.touch() def to_dict(self) -> dict[str, Any]: """Convert entity to dictionary. Returns: Dictionary representation with all post attributes. """ return { "id": str(self.id), "title": self.title.value, "content": self.content.value, "slug": self.slug.value, "author_id": self.author_id, "published": self.published, "tags": self.tags.copy(), "created_at": self.created_at.isoformat(), "updated_at": self.updated_at.isoformat(), } @classmethod def create( cls, title_str: str, content_str: str, author_id: str, tags: list[str] | None = None, ) -> "Post": """Factory method to create a new post. Args: title_str: Title string for the post. content_str: Content string for the post. author_id: Identifier of the post author. tags: Optional list of tags. Returns: New Post instance with validated value objects. """ title = Title(title_str) content = Content(content_str) slug = Slug.from_title(title_str) return cls( title=title, content=content, slug=slug, author_id=author_id, tags=tags or [], )