from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, inspect, null, text from sqlalchemy.orm import relationship from datetime import datetime from pydantic import BaseModel Base = declarative_base() # Модели базы данных class User(Base): __tablename__ = "user" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, nullable=False, index=True) password_hash = Column(String(200), nullable=False) profile_picture = Column(String(255), nullable=True) bio = Column(Text, nullable=True) online = Column(Boolean, default=False) last_seen = Column(DateTime, default=datetime.now) created_at = Column(DateTime, default=datetime.now) messages = relationship("Message", back_populates="author", lazy="select") class Message(Base): __tablename__ = "message" id = Column(Integer, primary_key=True, index=True) content = Column(Text, nullable=False) timestamp = Column(DateTime, default=datetime.now) user_id = Column(Integer, ForeignKey("user.id"), nullable=False) is_read = Column(Boolean, default=False) reply_to_id = Column(Integer, ForeignKey("message.id"), nullable=True) is_edited = Column(Boolean, default=False) author = relationship("User", back_populates="messages") reply_to = relationship("Message", remote_side=[id]) files = relationship("MessageFile", back_populates="message", cascade="all, delete-orphan", lazy="select") class MessageFile(Base): __tablename__ = "message_file" id = Column(Integer, primary_key=True, index=True) message_id = Column(Integer, ForeignKey("message.id"), nullable=False, index=True) path = Column(Text, nullable=False) name = Column(Text, nullable=False) message = relationship("Message", back_populates="files") class CryptoPublicKey(Base): __tablename__ = "crypto_public_key" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("user.id"), nullable=False, unique=True) public_key_b64 = Column(Text, nullable=False) class CryptoBackup(Base): __tablename__ = "crypto_backup" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("user.id"), nullable=False, unique=True) blob_json = Column(Text, nullable=False) class DMEnvelope(Base): __tablename__ = "dm_envelope" id = Column(Integer, primary_key=True, index=True) sender_id = Column(Integer, ForeignKey("user.id"), nullable=False) recipient_id = Column(Integer, ForeignKey("user.id"), nullable=False) iv_b64 = Column(Text, nullable=False) ciphertext_b64 = Column(Text, nullable=False) salt_b64 = Column(Text, nullable=False) iv2_b64 = Column(Text, nullable=False) wrapped_mk_b64 = Column(Text, nullable=False) reply_to_id = Column(Integer, nullable=True) timestamp = Column(DateTime, default=datetime.now) files = relationship("DMFile", back_populates="message", cascade="all, delete-orphan", lazy="select") class DMFile(Base): __tablename__ = "dm_file" id = Column(Integer, primary_key=True, index=True) message_id = Column(Integer, ForeignKey("dm_envelope.id"), nullable=False, index=True) sender_id = Column(Integer, ForeignKey("user.id"), nullable=False) recipient_id = Column(Integer, ForeignKey("user.id"), nullable=False) name = Column(Text, nullable=False) path = Column(Text, nullable=False) message = relationship("DMEnvelope", back_populates="files") class PushSubscription(Base): __tablename__ = "push_subscription" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("user.id"), nullable=False) endpoint = Column(Text, nullable=False) p256dh_key = Column(Text, nullable=False) auth_key = Column(Text, nullable=False) created_at = Column(DateTime, default=datetime.now) updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) # Pydantic модели class LoginRequest(BaseModel): username: str password: str class RegisterRequest(BaseModel): username: str password: str confirm_password: str class SendMessageRequest(BaseModel): content: str reply_to_id: int | None = None class EditMessageRequest(BaseModel): content: str class DeleteMessageRequest(BaseModel): message_id: int class UpdateBioRequest(BaseModel): bio: str class PushSubscriptionRequest(BaseModel): endpoint: str keys: dict class UserProfileResponse(BaseModel): id: int username: str profile_picture: str | None bio: str | None online: bool last_seen: datetime created_at: datetime class Config: from_attributes = True class MessageResponse(BaseModel): id: int content: str timestamp: datetime is_author: bool is_read: bool username: str profile_picture: str | None class Config: from_attributes = True # Tables are now created through Alembic migrations # Base.metadata.create_all(bind=engine)