151 lines
7.7 KiB
Python
151 lines
7.7 KiB
Python
from sqlalchemy import Column, Integer, String, Sequence, create_engine
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy import Column, Integer, Text, ForeignKey, DateTime
|
|
from sqlalchemy.sql import func
|
|
from sqlalchemy import text
|
|
from app.core.config import config
|
|
|
|
SQLALCHEMY_DATABASE_URL = config.DATABASE_URL
|
|
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
Base = declarative_base()
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
id = Column(Integer, Sequence('user_id_seq', start=100), primary_key=True, index=True)
|
|
first_name = Column(String(50), nullable=False, server_default="User")
|
|
last_name = Column(String(50), nullable=True)
|
|
username = Column(String, unique=True, index=True)
|
|
about = Column(String, nullable=True)
|
|
phone = Column(String(20), unique=True, nullable=True)
|
|
email = Column(String(255), unique=True, nullable=True)
|
|
totp_secret = Column(String(32), nullable=True)
|
|
totp_temp_secret = Column(String(32), nullable=True) # Temporary secret until verified
|
|
hashed_password = Column(String)
|
|
public_key = Column(String, nullable=True)
|
|
encrypted_private_key = Column(String, nullable=True)
|
|
fcm_token = Column(String, nullable=True)
|
|
avatar_file_id = Column(String, nullable=True)
|
|
|
|
# Privacy settings
|
|
show_email = Column(Integer, nullable=False, server_default="1") # 1 = true, 0 = false
|
|
show_phone = Column(Integer, nullable=False, server_default="1")
|
|
show_avatar = Column(Integer, nullable=False, server_default="1")
|
|
show_about = Column(Integer, nullable=False, server_default="1")
|
|
show_username = Column(Integer, nullable=False, server_default="1")
|
|
show_last_online = Column(Integer, nullable=False, server_default="1")
|
|
last_online = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
class Message(Base):
|
|
__tablename__ = "messages"
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
sender_id = Column(Integer, ForeignKey("users.id"))
|
|
receiver_id = Column(Integer, ForeignKey("users.id"))
|
|
content = Column(Text)
|
|
timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
|
delivered_at = Column(DateTime(timezone=True), nullable=True)
|
|
read_at = Column(DateTime(timezone=True), nullable=True)
|
|
reply_to_id = Column(Integer, ForeignKey("messages.id"), nullable=True)
|
|
reply_to_text = Column(Text, nullable=True)
|
|
edited_at = Column(DateTime(timezone=True), nullable=True)
|
|
message_type = Column(String, nullable=False, server_default="text")
|
|
file_id = Column(String, nullable=True)
|
|
encrypted_key = Column(String, nullable=True)
|
|
|
|
class CloudMediaItem(Base):
|
|
__tablename__ = "cloud_media_items"
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
file_id = Column(String, unique=True, nullable=False, index=True)
|
|
owner_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
original_filename = Column(String, nullable=True)
|
|
content_type = Column(String, nullable=True)
|
|
local_filename = Column(String, nullable=False)
|
|
size_bytes = Column(Integer, nullable=False)
|
|
status = Column(String, nullable=False, server_default="pending")
|
|
is_avatar = Column(Integer, nullable=False, server_default="0")
|
|
attempts = Column(Integer, nullable=False, server_default="0")
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
sent_at = Column(DateTime(timezone=True), nullable=True)
|
|
error_message = Column(Text, nullable=True)
|
|
|
|
class HomeMediaFile(Base):
|
|
__tablename__ = "home_media_files"
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
file_id = Column(String, unique=True, nullable=False, index=True)
|
|
owner_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
original_filename = Column(String, nullable=True)
|
|
content_type = Column(String, nullable=True)
|
|
storage_filename = Column(String, nullable=False)
|
|
size_bytes = Column(Integer, nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
|
|
def _ensure_sqlite_message_columns():
|
|
# Простая авто-миграция для SQLite без Alembic.
|
|
# Добавляет колонки, если приложение обновилось на уже существующей БД.
|
|
with engine.connect() as conn:
|
|
cols = conn.execute(text("PRAGMA table_info(messages)")).fetchall()
|
|
existing = {row[1] for row in cols} # row[1] = name
|
|
|
|
if "delivered_at" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN delivered_at DATETIME"))
|
|
if "read_at" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN read_at DATETIME"))
|
|
if "reply_to_id" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN reply_to_id INTEGER REFERENCES messages(id)"))
|
|
if "reply_to_text" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN reply_to_text TEXT"))
|
|
if "edited_at" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN edited_at DATETIME"))
|
|
if "message_type" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN message_type VARCHAR(32) DEFAULT 'text' NOT NULL"))
|
|
if "file_id" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN file_id VARCHAR(255)"))
|
|
if "encrypted_key" not in existing:
|
|
conn.execute(text("ALTER TABLE messages ADD COLUMN encrypted_key VARCHAR(1024)"))
|
|
conn.commit()
|
|
|
|
|
|
_ensure_sqlite_message_columns()
|
|
|
|
|
|
def _ensure_sqlite_user_columns():
|
|
with engine.connect() as conn:
|
|
cols = conn.execute(text("PRAGMA table_info(users)")).fetchall()
|
|
existing = {row[1] for row in cols}
|
|
|
|
if "about" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN about TEXT"))
|
|
if "phone" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN phone VARCHAR(20)"))
|
|
if "email" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN email VARCHAR(255)"))
|
|
if "show_email" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_email INTEGER DEFAULT 1"))
|
|
if "show_phone" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_phone INTEGER DEFAULT 1"))
|
|
if "show_avatar" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_avatar INTEGER DEFAULT 1"))
|
|
if "show_about" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_about INTEGER DEFAULT 1"))
|
|
if "show_username" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_username INTEGER DEFAULT 1"))
|
|
if "show_last_online" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN show_last_online INTEGER DEFAULT 1"))
|
|
if "last_online" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN last_online DATETIME"))
|
|
conn.execute(text("UPDATE users SET last_online = datetime('now')"))
|
|
if "avatar_file_id" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN avatar_file_id VARCHAR(255)"))
|
|
if "totp_temp_secret" not in existing:
|
|
conn.execute(text("ALTER TABLE users ADD COLUMN totp_temp_secret VARCHAR(32)"))
|
|
conn.commit()
|
|
|
|
|
|
_ensure_sqlite_user_columns()
|