init commit

This commit is contained in:
2025-08-17 11:44:54 +09:00
commit 5592014530
59 changed files with 3175 additions and 0 deletions

0
app/models/__init__.py Normal file
View File

15
app/models/audit.py Normal file
View File

@@ -0,0 +1,15 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from sqlalchemy import ForeignKey, String, JSON, func, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from app.db.session import Base
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"))
action: Mapped[str] = mapped_column(String(64))
payload: Mapped[Optional[dict]] = mapped_column(JSON)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

17
app/models/bot.py Normal file
View File

@@ -0,0 +1,17 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import ForeignKey, String, func, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
class Bot(Base):
__tablename__ = "bots"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
name: Mapped[str] = mapped_column(String(64))
username: Mapped[str | None] = mapped_column(String(64))
token_enc: Mapped[str] = mapped_column(String(512))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
owner = relationship("User")

26
app/models/channel.py Normal file
View File

@@ -0,0 +1,26 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import ForeignKey, String, BigInteger, Boolean, UniqueConstraint, func, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.session import Base
class Channel(Base):
__tablename__ = "channels"
__table_args__ = (UniqueConstraint("owner_id", "chat_id", name="uq_owner_chat"),)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
chat_id: Mapped[int] = mapped_column(BigInteger, index=True)
title: Mapped[str | None] = mapped_column(String(128))
username: Mapped[str | None] = mapped_column(String(64))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
owner = relationship("User")
class BotChannel(Base):
__tablename__ = "bot_channels"
id: Mapped[int] = mapped_column(primary_key=True)
bot_id: Mapped[int] = mapped_column(ForeignKey("bots.id", ondelete="CASCADE"))
channel_id: Mapped[int] = mapped_column(ForeignKey("channels.id", ondelete="CASCADE"))
can_post: Mapped[bool] = mapped_column(Boolean, default=True)

20
app/models/keyboard.py Normal file
View File

@@ -0,0 +1,20 @@
from __future__ import annotations
from sqlalchemy import ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.session import Base
class Keyboard(Base):
__tablename__ = "keyboards"
id: Mapped[int] = mapped_column(primary_key=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
name: Mapped[str] = mapped_column(String(64))
class KeyboardButton(Base):
__tablename__ = "keyboard_buttons"
id: Mapped[int] = mapped_column(primary_key=True)
keyboard_id: Mapped[int] = mapped_column(ForeignKey("keyboards.id", ondelete="CASCADE"))
text: Mapped[str] = mapped_column(String(128))
url: Mapped[str] = mapped_column(String(512))
order_index: Mapped[int] = mapped_column(Integer, default=0)

52
app/models/post.py Normal file
View File

@@ -0,0 +1,52 @@
from __future__ import annotations
import enum
from datetime import datetime
from typing import Optional
from sqlalchemy import ForeignKey, String, Enum, DateTime, func
from sqlalchemy.orm import Mapped, mapped_column
from app.db.session import Base
class PostType(str, enum.Enum):
text = "text"
photo = "photo"
video = "video"
animation = "animation"
class PostStatus(str, enum.Enum):
draft = "draft"
scheduled = "scheduled"
sent = "sent"
failed = "failed"
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
bot_id: Mapped[Optional[int]] = mapped_column(ForeignKey("bots.id", ondelete="SET NULL"), nullable=True)
channel_id: Mapped[int] = mapped_column(ForeignKey("channels.id", ondelete="CASCADE"))
type: Mapped[PostType] = mapped_column(Enum(PostType))
text: Mapped[Optional[str]] = mapped_column(String(4096))
media_file_id: Mapped[Optional[str]] = mapped_column(String(512))
parse_mode: Mapped[Optional[str]] = mapped_column(String(16))
keyboard_id: Mapped[Optional[int]] = mapped_column(ForeignKey("keyboards.id", ondelete="SET NULL"))
status: Mapped[PostStatus] = mapped_column(Enum(PostStatus), default=PostStatus.draft)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
sent_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
class ScheduleStatus(str, enum.Enum):
pending = "pending"
done = "done"
cancelled = "cancelled"
class Schedule(Base):
__tablename__ = "schedules"
id: Mapped[int] = mapped_column(primary_key=True)
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"))
due_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
timezone: Mapped[str] = mapped_column(String(64))
celery_task_id: Mapped[Optional[str]] = mapped_column(String(128))
status: Mapped[ScheduleStatus] = mapped_column(Enum(ScheduleStatus), default=ScheduleStatus.pending)

33
app/models/templates.py Normal file
View File

@@ -0,0 +1,33 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from sqlalchemy import DateTime, Enum, ForeignKey, String, UniqueConstraint, func, Text, JSON, Boolean
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.sqltypes import Enum as EnumType # Явный импорт Enum для типизации
from app.db.session import Base
from app.models.post import PostType
from enum import Enum as PyEnum
class TemplateVisibility(str, PyEnum):
private = "private"
org = "org"
public = "public"
class Template(Base):
__tablename__ = "templates"
__table_args__ = (
UniqueConstraint("owner_id", "name", name="uq_template_owner_name"),
)
id: Mapped[int] = mapped_column(primary_key=True)
owner_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
name: Mapped[str] = mapped_column(String(64))
title: Mapped[Optional[str]] = mapped_column(String(128))
type: Mapped[PostType] = mapped_column(EnumType(PostType), default=PostType.text)
content: Mapped[str] = mapped_column(Text)
keyboard_tpl: Mapped[Optional[list[dict]]] = mapped_column(JSON, nullable=True)
parse_mode: Mapped[Optional[str]] = mapped_column(String(16), default="HTML")
visibility: Mapped[TemplateVisibility] = mapped_column(EnumType(TemplateVisibility), default=TemplateVisibility.private)
is_archived: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

17
app/models/user.py Normal file
View File

@@ -0,0 +1,17 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import BigInteger, String, func, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from app.db.session import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
tg_user_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True)
username: Mapped[str | None] = mapped_column(String(64))
role: Mapped[str] = mapped_column(String(16), default="user")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
def __repr__(self):
return f"<User(id={self.id}, tg_user_id={self.tg_user_id}, username={self.username}, role={self.role}, created_at={self.created_at})>"