from __future__ import annotations from datetime import datetime from sqlalchemy import Column, BigInteger, Integer, String, DateTime, JSON, Boolean, ForeignKey from sqlalchemy.orm import relationship from .base import AlchemyBase class EncryptedContent(AlchemyBase): __tablename__ = 'encrypted_contents' id = Column(Integer, autoincrement=True, primary_key=True) # CID of encrypted source stored in IPFS (CIDv1 base32) encrypted_cid = Column(String(128), nullable=False, unique=True) # Public metadata title = Column(String(512), nullable=False) description = Column(String(4096), nullable=True) content_type = Column(String(64), nullable=False) # e.g. audio/flac, video/mp4, application/octet-stream # Sizes enc_size_bytes = Column(BigInteger, nullable=True) plain_size_bytes = Column(BigInteger, nullable=True) # Preview flags and config (all preview params live here, not in derivatives) preview_enabled = Column(Boolean, nullable=False, default=False) preview_conf = Column(JSON, nullable=False, default=dict) # Crypto parameters (fixed per network) aead_scheme = Column(String(32), nullable=False, default='AES_GCM') chunk_bytes = Column(Integer, nullable=False, default=1048576) salt_b64 = Column(String(64), nullable=True) # per-content salt used for nonce derivation created_at = Column(DateTime, nullable=False, default=datetime.utcnow) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) class ContentKey(AlchemyBase): __tablename__ = 'content_keys' content_id = Column(Integer, ForeignKey('encrypted_contents.id'), primary_key=True) key_ciphertext_b64 = Column(String(512), nullable=False) key_fingerprint = Column(String(128), nullable=False) issuer_node_id = Column(String(128), nullable=False) allow_auto_grant = Column(Boolean, nullable=False, default=True) lease_expires_at = Column(DateTime, nullable=True) created_at = Column(DateTime, nullable=False, default=datetime.utcnow) content = relationship('EncryptedContent', uselist=False, foreign_keys=[content_id]) class IpfsSync(AlchemyBase): __tablename__ = 'ipfs_sync' content_id = Column(Integer, ForeignKey('encrypted_contents.id'), primary_key=True) pin_state = Column(String(32), nullable=False, default='pinned') # not_pinned|queued|pinning|pinned|failed pin_error = Column(String(1024), nullable=True) bytes_total = Column(BigInteger, nullable=True) bytes_fetched = Column(BigInteger, nullable=True) providers_cache = Column(JSON, nullable=False, default=list) first_seen_at = Column(DateTime, nullable=True) pinned_at = Column(DateTime, nullable=True) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) content = relationship('EncryptedContent', uselist=False, foreign_keys=[content_id]) class ContentDerivative(AlchemyBase): __tablename__ = 'content_derivatives' id = Column(Integer, autoincrement=True, primary_key=True) content_id = Column(Integer, ForeignKey('encrypted_contents.id'), nullable=False) kind = Column(String(64), nullable=False) # decrypted_high|decrypted_low|decrypted_thumbnail|decrypted_preview interval_start_ms = Column(Integer, nullable=True) interval_end_ms = Column(Integer, nullable=True) local_path = Column(String(1024), nullable=False) content_type = Column(String(64), nullable=True) size_bytes = Column(BigInteger, nullable=True) status = Column(String(32), nullable=False, default='pending') # pending|processing|ready|failed error = Column(String(1024), nullable=True) created_at = Column(DateTime, nullable=False, default=datetime.utcnow) last_access_at = Column(DateTime, nullable=True) content = relationship('EncryptedContent', uselist=False, foreign_keys=[content_id]) class ContentIndexItem(AlchemyBase): __tablename__ = 'content_index_items' encrypted_cid = Column(String(128), primary_key=True) payload = Column(JSON, nullable=False, default=dict) sig = Column(String(512), nullable=False) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) class KeyGrant(AlchemyBase): __tablename__ = 'key_grants' id = Column(Integer, autoincrement=True, primary_key=True) encrypted_cid = Column(String(128), nullable=False) issuer_node_id = Column(String(128), nullable=False) to_node_id = Column(String(128), nullable=False) sealed_key_b64 = Column(String(1024), nullable=False) aead_scheme = Column(String(32), nullable=False) chunk_bytes = Column(Integer, nullable=False) constraints = Column(JSON, nullable=False, default=dict) issued_at = Column(DateTime, nullable=False, default=datetime.utcnow) sig = Column(String(512), nullable=False) class UploadSession(AlchemyBase): __tablename__ = 'upload_sessions' id = Column(String(128), primary_key=True) # tus Upload.ID filename = Column(String(512), nullable=True) size_bytes = Column(BigInteger, nullable=True) state = Column(String(32), nullable=False, default='uploading') # uploading|processing|pinned|failed encrypted_cid = Column(String(128), nullable=True) storage_path = Column(String(1024), nullable=True) error = Column(String(1024), nullable=True) created_at = Column(DateTime, nullable=False, default=datetime.utcnow) updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)