import uuid from datetime import datetime from sqlalchemy import String, Text, DateTime, ForeignKey, Table, Column, Integer, Boolean, Enum as SAEnum from sqlalchemy.orm import mapped_column, Mapped, relationship from sqlalchemy.dialects.postgresql import UUID import enum from .database import Base class ArticleStatus(str, enum.Enum): draft = "draft" published = "published" article_tag = Table( "article_tag", Base.metadata, Column("article_id", UUID(as_uuid=True), ForeignKey("articles.id", ondelete="CASCADE")), Column("tag_id", UUID(as_uuid=True), ForeignKey("tags.id", ondelete="CASCADE")), ) class Category(Base): __tablename__ = "categories" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name: Mapped[str] = mapped_column(String(100), unique=True) slug: Mapped[str] = mapped_column(String(100), unique=True, index=True) articles: Mapped[list["Article"]] = relationship(back_populates="category", lazy="noload") class Tag(Base): __tablename__ = "tags" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name: Mapped[str] = mapped_column(String(100), unique=True) slug: Mapped[str] = mapped_column(String(100), unique=True, index=True) class Article(Base): __tablename__ = "articles" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) title: Mapped[str] = mapped_column(String(500)) slug: Mapped[str] = mapped_column(String(500), unique=True, index=True) content: Mapped[str] = mapped_column(Text, default="") excerpt: Mapped[str] = mapped_column(String(1000), default="") cover_url: Mapped[str | None] = mapped_column(String(1000), nullable=True) font_family: Mapped[str] = mapped_column(String(100), default="Merriweather") source_url: Mapped[str | None] = mapped_column(String(1000), nullable=True) status: Mapped[ArticleStatus] = mapped_column(SAEnum(ArticleStatus), default=ArticleStatus.draft) view_count: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) published_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) category_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("categories.id", ondelete="SET NULL"), nullable=True ) category: Mapped["Category | None"] = relationship(back_populates="articles", lazy="selectin") tags: Mapped[list["Tag"]] = relationship(secondary=article_tag, lazy="selectin") class Admin(Base): __tablename__ = "admins" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) username: Mapped[str] = mapped_column(String(100), unique=True) password_hash: Mapped[str] = mapped_column(String(300)) class Media(Base): __tablename__ = "media" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) filename: Mapped[str] = mapped_column(String(500)) url: Mapped[str] = mapped_column(String(1000)) media_type: Mapped[str] = mapped_column(String(20)) # image | video size_bytes: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) article_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("articles.id", ondelete="SET NULL"), nullable=True ) class VkSource(Base): __tablename__ = "vk_sources" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) group_id: Mapped[str] = mapped_column(String(50), unique=True) # числовой ID без минуса group_name: Mapped[str] = mapped_column(String(200)) # станет названием категории enabled: Mapped[bool] = mapped_column(Boolean, default=True) last_run: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)