WEB-APP/ Optimization/ Build fix/ Hotkey edit mode/ Log rotation/ Form a11y/ E2E non-blocking
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
from ._base import Base, DictLikeMixin
|
||||
from .admin import Admin, Setting
|
||||
from .audit import AuditEvent
|
||||
from .coupons import Coupon, CouponUsage
|
||||
from .gifts import Gift, GiftUsage
|
||||
from .identity import Identity
|
||||
from .keys import Key
|
||||
from .notifications import Notification, ScheduledBroadcast
|
||||
from .payments import Payment
|
||||
from .referrals import Referral
|
||||
from .servers import Server, ServerSpecialgroup, ServerSubgroup
|
||||
from .tariffs import Tariff
|
||||
from .users import BlockedUser, ManualBan, TemporaryData, TrackingSource, User
|
||||
from .web import (
|
||||
WebBlock,
|
||||
WebCustomElementBuild,
|
||||
WebFlow,
|
||||
WebFlowEvent,
|
||||
WebNotification,
|
||||
WebPage,
|
||||
WebPageVariant,
|
||||
WebPageVariantBlock,
|
||||
WebPushSubscription,
|
||||
WebTheme,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"DictLikeMixin",
|
||||
"Identity",
|
||||
"User",
|
||||
"ManualBan",
|
||||
"TemporaryData",
|
||||
"BlockedUser",
|
||||
"TrackingSource",
|
||||
"Key",
|
||||
"Tariff",
|
||||
"Server",
|
||||
"ServerSubgroup",
|
||||
"ServerSpecialgroup",
|
||||
"Payment",
|
||||
"Coupon",
|
||||
"CouponUsage",
|
||||
"Referral",
|
||||
"Notification",
|
||||
"ScheduledBroadcast",
|
||||
"Gift",
|
||||
"GiftUsage",
|
||||
"AuditEvent",
|
||||
"Admin",
|
||||
"Setting",
|
||||
"WebPage",
|
||||
"WebTheme",
|
||||
"WebBlock",
|
||||
"WebPageVariant",
|
||||
"WebPageVariantBlock",
|
||||
"WebPushSubscription",
|
||||
"WebNotification",
|
||||
"WebFlow",
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class DictLikeMixin:
|
||||
"""Позволяет обращаться к ORM-объектам как к словарю.
|
||||
|
||||
Используется legacy-кодом, который мигрировал с dict-результатов asyncpg
|
||||
на ORM и не хочет переписывать все `row["field"]` / `row.get("field")`.
|
||||
"""
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return getattr(self, key, default)
|
||||
|
||||
def to_dict(self):
|
||||
return {column.name: getattr(self, column.name) for column in self.__table__.columns}
|
||||
@@ -0,0 +1,32 @@
|
||||
import secrets
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Column, DateTime, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Admin(Base):
|
||||
__tablename__ = "admins"
|
||||
|
||||
tg_id = Column(BigInteger, primary_key=True)
|
||||
token = Column(String, unique=True, nullable=True)
|
||||
description = Column(String, nullable=True)
|
||||
role = Column(String, nullable=False, default="admin")
|
||||
added_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
@staticmethod
|
||||
def generate_token() -> str:
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
class Setting(DictLikeMixin, Base):
|
||||
__tablename__ = "settings"
|
||||
|
||||
key = Column(String, primary_key=True)
|
||||
value = Column(JSONB, nullable=True)
|
||||
description = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
@@ -0,0 +1,44 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
text as sql_text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class AuditEvent(DictLikeMixin, Base):
|
||||
"""События аудита (флоу пользователя)."""
|
||||
__tablename__ = "audit_events"
|
||||
__table_args__ = (
|
||||
Index("ix_audit_events_tg_created", "actor_tg_id", "created_at"),
|
||||
Index("ix_audit_events_identity_created", "actor_identity_id", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
event_type = Column(String(64), nullable=False, index=True)
|
||||
channel = Column(String(32), nullable=False, index=True)
|
||||
actor_identity_id = Column(
|
||||
String(36),
|
||||
ForeignKey("identities.id", ondelete="SET NULL", onupdate="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
actor_tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
path_or_handler = Column(String(255), nullable=False)
|
||||
entity_type = Column(String(64), nullable=True, index=True)
|
||||
entity_id = Column(String(255), nullable=True, index=True)
|
||||
result = Column(String(32), nullable=False, server_default=sql_text("'success'"))
|
||||
reason = Column(Text, nullable=True)
|
||||
metadata_ = Column("metadata", JSONB, nullable=True)
|
||||
request_id = Column(String(64), nullable=True, index=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, index=True)
|
||||
@@ -0,0 +1,40 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Integer,
|
||||
String,
|
||||
text as sql_text,
|
||||
)
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Coupon(DictLikeMixin, Base):
|
||||
__tablename__ = "coupons"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
code = Column(String, unique=True)
|
||||
amount = Column(Integer)
|
||||
usage_limit = Column(Integer)
|
||||
usage_count = Column(Integer, default=0)
|
||||
is_used = Column(Boolean, default=False)
|
||||
days = Column(Integer, nullable=True)
|
||||
new_users_only = Column(Boolean, nullable=False, server_default=sql_text("false"))
|
||||
|
||||
percent = Column(Integer, nullable=True)
|
||||
max_discount_amount = Column(Integer, nullable=True)
|
||||
min_order_amount = Column(Integer, nullable=True)
|
||||
|
||||
|
||||
class CouponUsage(DictLikeMixin, Base):
|
||||
__tablename__ = "coupon_usages"
|
||||
|
||||
coupon_id = Column(Integer, ForeignKey("coupons.id", ondelete="CASCADE"), primary_key=True)
|
||||
user_id = Column(BigInteger, primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
used_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -0,0 +1,39 @@
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, Column, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Gift(DictLikeMixin, Base):
|
||||
__tablename__ = "gifts"
|
||||
|
||||
gift_id = Column(String, primary_key=True, default=lambda: uuid.uuid4().hex)
|
||||
sender_user_id = Column(BigInteger, nullable=True)
|
||||
recipient_user_id = Column(BigInteger, nullable=True)
|
||||
sender_tg_id = Column(BigInteger, ForeignKey("users.tg_id"), nullable=True, index=True)
|
||||
recipient_tg_id = Column(BigInteger, ForeignKey("users.tg_id"), nullable=True, index=True)
|
||||
selected_months = Column(Integer)
|
||||
expiry_time = Column(DateTime)
|
||||
gift_link = Column(String)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
is_used = Column(Boolean, default=False)
|
||||
is_unlimited = Column(Boolean, default=False)
|
||||
max_usages = Column(Integer, nullable=True)
|
||||
tariff_id: Mapped[int | None] = mapped_column(ForeignKey("tariffs.id"))
|
||||
|
||||
selected_device_limit = Column(Integer, nullable=True)
|
||||
selected_traffic_gb = Column(Integer, nullable=True)
|
||||
selected_price_rub = Column(Integer, nullable=True)
|
||||
|
||||
|
||||
class GiftUsage(DictLikeMixin, Base):
|
||||
__tablename__ = "gift_usages"
|
||||
|
||||
gift_id = Column(String, ForeignKey("gifts.gift_id"), primary_key=True)
|
||||
user_id = Column(BigInteger, nullable=False, primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
used_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -0,0 +1,31 @@
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
String,
|
||||
text as sql_text,
|
||||
)
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Identity(DictLikeMixin, Base):
|
||||
"""Слой идентификации: к одному identity можно привязать email и/или Telegram (tg_id)."""
|
||||
|
||||
__tablename__ = "identities"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
email = Column(String(255), unique=True, nullable=True, index=True)
|
||||
tg_id = Column(BigInteger, unique=True, nullable=True, index=True)
|
||||
api_token_hash = Column(String(64), nullable=True, index=True)
|
||||
token_issued_at = Column(DateTime, nullable=True)
|
||||
password_hash = Column(String(64), nullable=True)
|
||||
email_verified = Column(Boolean, nullable=False, server_default=sql_text("false"))
|
||||
is_admin = Column(Boolean, nullable=False, server_default=sql_text("false"))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
@@ -0,0 +1,29 @@
|
||||
from sqlalchemy import BigInteger, Boolean, Column, ForeignKey, Integer, String
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Key(DictLikeMixin, Base):
|
||||
__tablename__ = "keys"
|
||||
|
||||
tg_id = Column(BigInteger, ForeignKey("users.tg_id"), primary_key=True, nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True)
|
||||
client_id = Column(String, primary_key=True)
|
||||
email = Column(String, unique=True)
|
||||
created_at = Column(BigInteger)
|
||||
expiry_time = Column(BigInteger)
|
||||
key = Column(String)
|
||||
server_id = Column(String)
|
||||
remnawave_link = Column(String)
|
||||
tariff_id = Column(Integer, ForeignKey("tariffs.id", ondelete="SET NULL"))
|
||||
is_frozen = Column(Boolean, default=False)
|
||||
alias = Column(String)
|
||||
notified = Column(Boolean, default=False)
|
||||
notified_24h = Column(Boolean, default=False)
|
||||
|
||||
selected_device_limit = Column(Integer, nullable=True)
|
||||
selected_traffic_limit = Column(BigInteger, nullable=True)
|
||||
selected_price_rub = Column(Integer, nullable=True)
|
||||
|
||||
current_device_limit = Column(Integer, nullable=True)
|
||||
current_traffic_limit = Column(BigInteger, nullable=True)
|
||||
@@ -0,0 +1,55 @@
|
||||
import uuid
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
text as sql_text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Notification(DictLikeMixin, Base):
|
||||
__tablename__ = "notifications"
|
||||
|
||||
tg_id = Column(BigInteger, ForeignKey("users.tg_id", ondelete="CASCADE"), nullable=True, index=True)
|
||||
user_id = Column(BigInteger, nullable=False, primary_key=True)
|
||||
notification_type = Column(String, primary_key=True)
|
||||
last_notification_time = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class ScheduledBroadcast(DictLikeMixin, Base):
|
||||
__tablename__ = "scheduled_broadcasts"
|
||||
__table_args__ = (
|
||||
Index("ix_scheduled_broadcasts_status_time", "status", "scheduled_for"),
|
||||
Index("ix_scheduled_broadcasts_creator_time", "created_by_tg_id", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
created_by_user_id = Column(BigInteger, nullable=True, index=True)
|
||||
created_by_tg_id = Column(BigInteger, ForeignKey("users.tg_id", ondelete="SET NULL"), nullable=True, index=True)
|
||||
status = Column(String(32), nullable=False, server_default=sql_text("'scheduled'"), index=True)
|
||||
send_to = Column(String(32), nullable=False, index=True)
|
||||
cluster_name = Column(String, nullable=True)
|
||||
text = Column(Text, nullable=False)
|
||||
photo = Column(String, nullable=True)
|
||||
keyboard_json = Column(JSONB, nullable=True)
|
||||
scheduled_for = Column(DateTime(timezone=True), nullable=False, index=True)
|
||||
workers = Column(Integer, nullable=False, server_default=sql_text("5"))
|
||||
messages_per_second = Column(Integer, nullable=False, server_default=sql_text("35"))
|
||||
stats_json = Column(JSONB, nullable=True)
|
||||
error_text = Column(Text, nullable=True)
|
||||
started_at = Column(DateTime(timezone=True), nullable=True)
|
||||
sent_at = Column(DateTime(timezone=True), nullable=True)
|
||||
cancelled_at = Column(DateTime(timezone=True), nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
@@ -0,0 +1,22 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Column, DateTime, Float, ForeignKey, Integer, Numeric, String
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Payment(DictLikeMixin, Base):
|
||||
__tablename__ = "payments"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True)
|
||||
tg_id = Column(BigInteger, ForeignKey("users.tg_id"), nullable=True, index=True)
|
||||
amount = Column(Float)
|
||||
payment_system = Column(String)
|
||||
status = Column(String)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
original_amount = Column(Numeric(18, 8), nullable=True)
|
||||
currency = Column(String(10), nullable=False, server_default="RUB")
|
||||
payment_id = Column(String(128), nullable=True, index=True)
|
||||
metadata_ = Column("metadata", JSONB, nullable=True)
|
||||
@@ -0,0 +1,13 @@
|
||||
from sqlalchemy import BigInteger, Boolean, Column, ForeignKey
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Referral(DictLikeMixin, Base):
|
||||
__tablename__ = "referrals"
|
||||
|
||||
referred_user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
|
||||
referrer_user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
|
||||
referred_tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
referrer_tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
reward_issued = Column(Boolean, default=False)
|
||||
@@ -0,0 +1,47 @@
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Server(DictLikeMixin, Base):
|
||||
__tablename__ = "servers"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
cluster_name = Column(String)
|
||||
server_name = Column(String, unique=True)
|
||||
api_url = Column(String)
|
||||
subscription_url = Column(String)
|
||||
inbound_id = Column(String)
|
||||
panel_type = Column(String)
|
||||
max_keys = Column(Integer)
|
||||
tariff_group = Column(String)
|
||||
enabled = Column(Boolean, default=True)
|
||||
|
||||
subgroups = relationship("ServerSubgroup", back_populates="server", cascade="all, delete-orphan")
|
||||
groups = relationship("ServerSpecialgroup", back_populates="server", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class ServerSubgroup(DictLikeMixin, Base):
|
||||
__tablename__ = "server_subgroups"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), index=True, nullable=False)
|
||||
group_code = Column(String, nullable=False)
|
||||
subgroup_title = Column(String, nullable=False)
|
||||
|
||||
server = relationship("Server", back_populates="subgroups")
|
||||
|
||||
__table_args__ = (UniqueConstraint("server_id", "subgroup_title", name="uq_server_subgroup"),)
|
||||
|
||||
|
||||
class ServerSpecialgroup(DictLikeMixin, Base):
|
||||
__tablename__ = "server_specialgroups"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), index=True, nullable=False)
|
||||
group_code = Column(String, nullable=False)
|
||||
|
||||
server = relationship("Server")
|
||||
|
||||
__table_args__ = (UniqueConstraint("server_id", "group_code", name="uq_server_group"),)
|
||||
@@ -0,0 +1,37 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, Column, DateTime, Integer, String
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class Tariff(DictLikeMixin, Base):
|
||||
__tablename__ = "tariffs"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
group_code = Column(String)
|
||||
duration_days = Column(Integer)
|
||||
price_rub = Column(Integer)
|
||||
traffic_limit = Column(BigInteger, nullable=True)
|
||||
device_limit = Column(Integer, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow)
|
||||
subgroup_title = Column(String, nullable=True)
|
||||
sort_order = Column(Integer, nullable=True)
|
||||
vless = Column(Boolean, default=False)
|
||||
external_squad: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
|
||||
configurable = Column(Boolean, nullable=False, server_default="false")
|
||||
|
||||
device_options = Column(JSONB, nullable=True)
|
||||
traffic_options_gb = Column(JSONB, nullable=True)
|
||||
|
||||
device_step_rub = Column(Integer, nullable=True)
|
||||
device_overrides = Column(JSONB, nullable=True)
|
||||
|
||||
traffic_step_rub = Column(Integer, nullable=True)
|
||||
traffic_overrides = Column(JSONB, nullable=True)
|
||||
@@ -0,0 +1,88 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
BigInteger,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Identity as SAIdentity,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
)
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class User(DictLikeMixin, Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(BigInteger, SAIdentity(always=False), primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, unique=True, index=True)
|
||||
identity_id = Column(
|
||||
String(36),
|
||||
ForeignKey("identities.id", ondelete="SET NULL", onupdate="CASCADE"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
username = Column(String)
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
language_code = Column(String)
|
||||
is_bot = Column(Boolean, default=False)
|
||||
balance = Column(Float, default=0.0)
|
||||
trial = Column(Integer, default=0)
|
||||
preferred_currency = Column(String(10), nullable=False, server_default="RUB", index=True)
|
||||
source_code = Column(
|
||||
String,
|
||||
ForeignKey(
|
||||
"tracking_sources.code",
|
||||
ondelete="SET NULL",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
nullable=True,
|
||||
)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class ManualBan(DictLikeMixin, Base):
|
||||
__tablename__ = "manual_bans"
|
||||
|
||||
user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
banned_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
||||
reason = Column(Text)
|
||||
banned_by = Column(BigInteger)
|
||||
until = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
class TemporaryData(DictLikeMixin, Base):
|
||||
__tablename__ = "temporary_data"
|
||||
|
||||
user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
state = Column(String)
|
||||
data = Column(JSON)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class BlockedUser(DictLikeMixin, Base):
|
||||
__tablename__ = "blocked_users"
|
||||
|
||||
user_id = Column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, primary_key=True)
|
||||
tg_id = Column(BigInteger, nullable=True, index=True)
|
||||
|
||||
|
||||
class TrackingSource(DictLikeMixin, Base):
|
||||
__tablename__ = "tracking_sources"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
code = Column(String, unique=True)
|
||||
type = Column(String)
|
||||
created_by = Column(BigInteger)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -0,0 +1,166 @@
|
||||
import uuid
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
text as sql_text,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
from ._base import Base, DictLikeMixin
|
||||
|
||||
|
||||
class WebPage(DictLikeMixin, Base):
|
||||
__tablename__ = "web_pages"
|
||||
|
||||
slug = Column(String(64), primary_key=True)
|
||||
title = Column(String(255), nullable=True)
|
||||
|
||||
|
||||
class WebTheme(DictLikeMixin, Base):
|
||||
__tablename__ = "web_themes"
|
||||
|
||||
page_slug = Column(String(64), ForeignKey("web_pages.slug", ondelete="CASCADE"), primary_key=True)
|
||||
tokens = Column(JSONB, nullable=False, default=dict)
|
||||
|
||||
|
||||
class WebBlock(DictLikeMixin, Base):
|
||||
__tablename__ = "web_blocks"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
page_slug = Column(String(64), ForeignKey("web_pages.slug", ondelete="CASCADE"), index=True, nullable=False)
|
||||
order = Column(Integer, nullable=False, default=0)
|
||||
type = Column(String(64), nullable=False)
|
||||
data = Column(JSONB, nullable=False, default=dict)
|
||||
|
||||
|
||||
class WebPageVariant(DictLikeMixin, Base):
|
||||
__tablename__ = "web_page_variants"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("page_slug", "variant_key", name="uq_web_page_variants_page_slug_variant_key"),
|
||||
Index("ix_web_page_variants_page_slug_is_active", "page_slug", "is_active"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
page_slug = Column(String(64), ForeignKey("web_pages.slug", ondelete="CASCADE"), index=True, nullable=False)
|
||||
variant_key = Column(String(64), nullable=False)
|
||||
name = Column(String(255), nullable=False, default="Default")
|
||||
is_active = Column(Boolean, nullable=False, server_default=sql_text("false"))
|
||||
theme_tokens = Column(JSONB, nullable=False, default=dict)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class WebPageVariantBlock(DictLikeMixin, Base):
|
||||
__tablename__ = "web_page_variant_blocks"
|
||||
__table_args__ = (Index("ix_web_page_variant_blocks_variant_id_order", "variant_id", "order"),)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
variant_id = Column(String(36), ForeignKey("web_page_variants.id", ondelete="CASCADE"), index=True, nullable=False)
|
||||
order = Column(Integer, nullable=False, default=0)
|
||||
type = Column(String(64), nullable=False)
|
||||
data = Column(JSONB, nullable=False, default=dict)
|
||||
|
||||
|
||||
class WebPushSubscription(DictLikeMixin, Base):
|
||||
__tablename__ = "web_push_subscriptions"
|
||||
__table_args__ = (
|
||||
Index("ix_web_push_subscriptions_user_id", "user_id"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
user_id = Column(BigInteger, nullable=False)
|
||||
identity_id = Column(String(36), nullable=True, index=True)
|
||||
endpoint = Column(Text, nullable=False, unique=True)
|
||||
keys_json = Column(JSONB, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class WebNotification(DictLikeMixin, Base):
|
||||
__tablename__ = "web_notifications"
|
||||
__table_args__ = (
|
||||
Index("ix_web_notifications_user_read", "user_id", "read"),
|
||||
Index("ix_web_notifications_created", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
user_id = Column(BigInteger, nullable=False, index=True)
|
||||
identity_id = Column(String(36), nullable=True, index=True)
|
||||
type = Column(String(32), nullable=False, default="system")
|
||||
title = Column(String(255), nullable=False)
|
||||
message = Column(Text, nullable=False, default="")
|
||||
read = Column(Boolean, nullable=False, default=False)
|
||||
data = Column(JSONB, nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class WebFlowEvent(DictLikeMixin, Base):
|
||||
__tablename__ = "web_flow_events"
|
||||
__table_args__ = (
|
||||
Index("ix_web_flow_events_flow_node", "flow_id", "node_id"),
|
||||
Index("ix_web_flow_events_created", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
flow_id = Column(String(64), nullable=False)
|
||||
node_id = Column(String(64), nullable=False)
|
||||
node_type = Column(String(32), nullable=False, default="")
|
||||
event_type = Column(String(32), nullable=False)
|
||||
ab_variant = Column(String(16), nullable=True)
|
||||
device = Column(String(16), nullable=True)
|
||||
locale = Column(String(8), nullable=True)
|
||||
authenticated = Column(Boolean, nullable=True)
|
||||
event_metadata = Column("metadata", JSONB, nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class WebCustomElementBuild(DictLikeMixin, Base):
|
||||
__tablename__ = "web_custom_element_builds"
|
||||
__table_args__ = (
|
||||
Index("ix_web_custom_element_builds_status", "status"),
|
||||
Index("ix_web_custom_element_builds_created", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
label = Column(String(255), nullable=False, default="")
|
||||
slug = Column(String(128), nullable=False, default="")
|
||||
runtime = Column(String(32), nullable=False, default="react-component")
|
||||
source_kind = Column(String(32), nullable=False, default="inline-code")
|
||||
source_value = Column(Text, nullable=False, default="")
|
||||
export_name = Column(String(128), nullable=False, default="default")
|
||||
props_schema_text = Column(Text, nullable=False, default="")
|
||||
sample_props_text = Column(Text, nullable=False, default="")
|
||||
events_text = Column(Text, nullable=False, default="")
|
||||
notes = Column(Text, nullable=False, default="")
|
||||
status = Column(String(32), nullable=False, default="queued")
|
||||
summary = Column(Text, nullable=False, default="")
|
||||
next_steps = Column(JSONB, nullable=False, default=list)
|
||||
artifact = Column(JSONB, nullable=True)
|
||||
upload_meta = Column(JSONB, nullable=True)
|
||||
worker_id = Column(String(64), nullable=True)
|
||||
worker_claimed_at = Column(DateTime(timezone=True), nullable=True)
|
||||
completed_at = Column(DateTime(timezone=True), nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
|
||||
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
||||
|
||||
|
||||
class WebFlow(DictLikeMixin, Base):
|
||||
__tablename__ = "web_flows"
|
||||
|
||||
id = Column(String(64), primary_key=True, default="default")
|
||||
name = Column(String(255), nullable=False, default="Основной flow")
|
||||
nodes = Column(JSONB, nullable=False, default=list)
|
||||
edges = Column(JSONB, nullable=False, default=list)
|
||||
entry_node_id = Column(String(64), nullable=True)
|
||||
version = Column(Integer, nullable=False, default=1)
|
||||
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
|
||||
Reference in New Issue
Block a user