Initial release
Build and Push Docker Images / build (push) Successful in 17s

This commit is contained in:
Stardream
2026-06-02 10:58:24 +10:00
commit eace48732d
88 changed files with 11140 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
from .base import Base
from .user import User
from .managed_chat import ManagedChat
from .lottery import Lottery, LotteryStatus
from .prize import Prize
from .participant import Participant
from .winner import Winner
from .passphrase_verification import PassphraseVerification
__all__ = ["Base", "User", "ManagedChat", "Lottery", "LotteryStatus", "Prize", "Participant", "Winner", "PassphraseVerification"]
+14
View File
@@ -0,0 +1,14 @@
from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase
NAMING_CONVENTION = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
class Base(DeclarativeBase):
metadata = MetaData(naming_convention=NAMING_CONVENTION)
+63
View File
@@ -0,0 +1,63 @@
import enum
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, JSON, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
if TYPE_CHECKING:
from .prize import Prize
from .participant import Participant
from .winner import Winner
class LotteryStatus(str, enum.Enum):
DRAFT = "draft"
ACTIVE = "active"
DRAWING = "drawing"
FINISHED = "finished"
CANCELLED = "cancelled"
class Lottery(Base):
__tablename__ = "lotteries"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
creator_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id"))
title: Mapped[str] = mapped_column(String(255))
description: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[LotteryStatus] = mapped_column(String(20), default=LotteryStatus.DRAFT)
target_chat_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
target_chat_title: Mapped[str | None] = mapped_column(String(255), nullable=True)
require_membership: Mapped[bool] = mapped_column(Boolean, default=False)
required_chat_ids: Mapped[list | None] = mapped_column(JSON, nullable=True)
required_chat_titles: Mapped[list | None] = mapped_column(JSON, nullable=True)
required_chat_types: Mapped[list | None] = mapped_column(JSON, nullable=True)
required_chat_usernames: Mapped[list | None] = mapped_column(JSON, nullable=True)
announcement_targets: Mapped[list | None] = mapped_column(JSON, nullable=True)
max_participants: Mapped[int | None] = mapped_column(Integer, nullable=True)
announcement_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
contact_info: Mapped[str | None] = mapped_column(Text, nullable=True)
draw_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
auto_draw_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
allow_multiple_wins: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
invite_link_chat_ids: Mapped[list] = mapped_column(JSON, default=list, server_default="'[]'")
creator_timezone: Mapped[str | None] = mapped_column(String(64), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
drawn_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
draw_trigger: Mapped[str | None] = mapped_column(String(20), nullable=True)
required_chat_passphrases: Mapped[list | None] = mapped_column(JSON, nullable=True)
prizes: Mapped[list["Prize"]] = relationship(
"Prize", back_populates="lottery", order_by="Prize.sort_order", cascade="all, delete-orphan"
)
participants: Mapped[list["Participant"]] = relationship(
"Participant", back_populates="lottery", cascade="all, delete-orphan"
)
winners: Mapped[list["Winner"]] = relationship(
"Winner", back_populates="lottery", cascade="all, delete-orphan"
)
+19
View File
@@ -0,0 +1,19 @@
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, DateTime, String
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base
class ManagedChat(Base):
__tablename__ = "managed_chats"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True) # Telegram chat_id
title: Mapped[str] = mapped_column(String(255))
username: Mapped[str | None] = mapped_column(String(255), nullable=True)
chat_type: Mapped[str] = mapped_column(String(50)) # group, supergroup, channel
invite_link: Mapped[str | None] = mapped_column(String(512), nullable=True)
last_message_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
added_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
+28
View File
@@ -0,0 +1,28 @@
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
if TYPE_CHECKING:
from .lottery import Lottery
from .user import User
class Participant(Base):
__tablename__ = "participants"
__table_args__ = (UniqueConstraint("lottery_id", "user_id"),)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
lottery_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("lotteries.id", ondelete="CASCADE")
)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id"))
joined_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
lottery: Mapped["Lottery"] = relationship("Lottery", back_populates="participants")
user: Mapped["User"] = relationship("User")
@@ -0,0 +1,20 @@
import uuid
from datetime import datetime
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base
class PassphraseVerification(Base):
__tablename__ = "passphrase_verifications"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
lottery_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("lotteries.id", ondelete="CASCADE"))
chat_id: Mapped[int] = mapped_column(BigInteger)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id"))
verified_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
__table_args__ = (UniqueConstraint("lottery_id", "chat_id", "user_id"),)
+26
View File
@@ -0,0 +1,26 @@
import uuid
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
if TYPE_CHECKING:
from .lottery import Lottery
class Prize(Base):
__tablename__ = "prizes"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
lottery_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("lotteries.id", ondelete="CASCADE")
)
name: Mapped[str] = mapped_column(String(255))
description: Mapped[str | None] = mapped_column(Text, nullable=True)
quantity: Mapped[int] = mapped_column(Integer, default=1)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
lottery: Mapped["Lottery"] = relationship("Lottery", back_populates="prizes")
+16
View File
@@ -0,0 +1,16 @@
from datetime import datetime
from sqlalchemy import BigInteger, DateTime, String
from sqlalchemy.orm import Mapped, mapped_column
from .base import Base
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
username: Mapped[str | None] = mapped_column(String(255), nullable=True)
first_name: Mapped[str] = mapped_column(String(255))
last_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
+34
View File
@@ -0,0 +1,34 @@
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import BigInteger, DateTime, ForeignKey, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
if TYPE_CHECKING:
from .lottery import Lottery
from .user import User
from .prize import Prize
class Winner(Base):
__tablename__ = "winners"
__table_args__ = ()
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
lottery_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("lotteries.id", ondelete="CASCADE")
)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id"))
prize_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("prizes.id", ondelete="SET NULL"), nullable=True
)
prize_name: Mapped[str] = mapped_column(String(255)) # snapshot at draw time
drawn_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
lottery: Mapped["Lottery"] = relationship("Lottery", back_populates="winners")
user: Mapped["User"] = relationship("User")
prize: Mapped["Prize | None"] = relationship("Prize")