from datetime import datetime from typing import List, Optional from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy import ( Integer, String, Text, Boolean, DateTime, ForeignKey, Column, func, ) from db.base import Base # whatever your Base is # from .author import Author # make sure imports resolve # from ..app.blog.calendars.model import Calendar class Tag(Base): __tablename__ = "tags" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False) slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[Optional[str]] = mapped_column(Text()) visibility: Mapped[str] = mapped_column(String(32), default="public", nullable=False) feature_image: Mapped[Optional[str]] = mapped_column(Text()) meta_title: Mapped[Optional[str]] = mapped_column(String(300)) meta_description: Mapped[Optional[str]] = mapped_column(Text()) created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) # NEW: posts relationship is now direct Post objects via PostTag posts: Mapped[List["Post"]] = relationship( "Post", secondary="post_tags", primaryjoin="Tag.id==post_tags.c.tag_id", secondaryjoin="Post.id==post_tags.c.post_id", back_populates="tags", order_by="PostTag.sort_order", ) class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False) uuid: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False) title: Mapped[str] = mapped_column(String(500), nullable=False) html: Mapped[Optional[str]] = mapped_column(Text()) plaintext: Mapped[Optional[str]] = mapped_column(Text()) mobiledoc: Mapped[Optional[str]] = mapped_column(Text()) lexical: Mapped[Optional[str]] = mapped_column(Text()) feature_image: Mapped[Optional[str]] = mapped_column(Text()) feature_image_alt: Mapped[Optional[str]] = mapped_column(Text()) feature_image_caption: Mapped[Optional[str]] = mapped_column(Text()) excerpt: Mapped[Optional[str]] = mapped_column(Text()) custom_excerpt: Mapped[Optional[str]] = mapped_column(Text()) visibility: Mapped[str] = mapped_column(String(32), default="public", nullable=False) status: Mapped[str] = mapped_column(String(32), default="draft", nullable=False) featured: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False) is_page: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False) email_only: Mapped[bool] = mapped_column(Boolean(), default=False, nullable=False) canonical_url: Mapped[Optional[str]] = mapped_column(Text()) meta_title: Mapped[Optional[str]] = mapped_column(String(500)) meta_description: Mapped[Optional[str]] = mapped_column(Text()) og_image: Mapped[Optional[str]] = mapped_column(Text()) og_title: Mapped[Optional[str]] = mapped_column(String(500)) og_description: Mapped[Optional[str]] = mapped_column(Text()) twitter_image: Mapped[Optional[str]] = mapped_column(Text()) twitter_title: Mapped[Optional[str]] = mapped_column(String(500)) twitter_description: Mapped[Optional[str]] = mapped_column(Text()) custom_template: Mapped[Optional[str]] = mapped_column(String(191)) reading_time: Mapped[Optional[int]] = mapped_column(Integer()) comment_id: Mapped[Optional[str]] = mapped_column(String(191)) published_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) user_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("users.id", ondelete="SET NULL"), index=True ) publish_requested: Mapped[bool] = mapped_column(Boolean(), default=False, server_default="false", nullable=False) primary_author_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("authors.id", ondelete="SET NULL") ) primary_tag_id: Mapped[Optional[int]] = mapped_column( Integer, ForeignKey("tags.id", ondelete="SET NULL") ) primary_author: Mapped[Optional["Author"]] = relationship( "Author", foreign_keys=[primary_author_id] ) primary_tag: Mapped[Optional[Tag]] = relationship( "Tag", foreign_keys=[primary_tag_id] ) user: Mapped[Optional["User"]] = relationship( "User", foreign_keys=[user_id] ) # AUTHORS RELATIONSHIP (many-to-many via post_authors) authors: Mapped[List["Author"]] = relationship( "Author", secondary="post_authors", primaryjoin="Post.id==post_authors.c.post_id", secondaryjoin="Author.id==post_authors.c.author_id", back_populates="posts", order_by="PostAuthor.sort_order", ) # TAGS RELATIONSHIP (many-to-many via post_tags) tags: Mapped[List[Tag]] = relationship( "Tag", secondary="post_tags", primaryjoin="Post.id==post_tags.c.post_id", secondaryjoin="Tag.id==post_tags.c.tag_id", back_populates="posts", order_by="PostTag.sort_order", ) calendars:Mapped[List["Calendar"]] = relationship( "Calendar", back_populates="post", cascade="all, delete-orphan", passive_deletes=True, order_by="Calendar.name", ) likes: Mapped[List["PostLike"]] = relationship( "PostLike", back_populates="post", cascade="all, delete-orphan", passive_deletes=True, ) calendar_entries: Mapped[List["CalendarEntryPost"]] = relationship( "CalendarEntryPost", back_populates="post", cascade="all, delete-orphan", passive_deletes=True, ) menu_items: Mapped[List["MenuItem"]] = relationship( "MenuItem", back_populates="post", cascade="all, delete-orphan", passive_deletes=True, order_by="MenuItem.sort_order", ) class Author(Base): __tablename__ = "authors" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) ghost_id: Mapped[str] = mapped_column(String(64), index=True, unique=True, nullable=False) slug: Mapped[str] = mapped_column(String(191), index=True, nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) email: Mapped[Optional[str]] = mapped_column(String(255)) profile_image: Mapped[Optional[str]] = mapped_column(Text()) cover_image: Mapped[Optional[str]] = mapped_column(Text()) bio: Mapped[Optional[str]] = mapped_column(Text()) website: Mapped[Optional[str]] = mapped_column(Text()) location: Mapped[Optional[str]] = mapped_column(Text()) facebook: Mapped[Optional[str]] = mapped_column(Text()) twitter: Mapped[Optional[str]] = mapped_column(Text()) created_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) # backref to posts via post_authors posts: Mapped[List[Post]] = relationship( "Post", secondary="post_authors", primaryjoin="Author.id==post_authors.c.author_id", secondaryjoin="Post.id==post_authors.c.post_id", back_populates="authors", order_by="PostAuthor.sort_order", ) class PostAuthor(Base): __tablename__ = "post_authors" post_id: Mapped[int] = mapped_column( ForeignKey("posts.id", ondelete="CASCADE"), primary_key=True, ) author_id: Mapped[int] = mapped_column( ForeignKey("authors.id", ondelete="CASCADE"), primary_key=True, ) sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) class PostTag(Base): __tablename__ = "post_tags" post_id: Mapped[int] = mapped_column( ForeignKey("posts.id", ondelete="CASCADE"), primary_key=True, ) tag_id: Mapped[int] = mapped_column( ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True, ) sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False) class PostLike(Base): __tablename__ = "post_likes" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True)) post: Mapped["Post"] = relationship("Post", back_populates="likes", foreign_keys=[post_id]) user = relationship("User", back_populates="liked_posts")