class OAuthClientConfig(Base, TimestampMixin): """An imported Google OAuth client configuration (from client_secret JSON). Secrets stored encrypted. Multiple configs may exist (e.g. per project).""" __tablename__ = "oauth_client_configs" id = Column(Integer, primary_key=True) name = Column(String(200), nullable=False) client_id = Column(String(300), nullable=False) client_secret_enc = Column(String(600), nullable=False) # encrypted redirect_uri = Column(String(500), default="") project_id = Column(String(200), default="") is_active = Column(Boolean, default=True, nullable=False) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE")) @property def client_secret(self) -> str: return encryption.decrypt(self.client_secret_enc) @client_secret.setter def client_secret(self, value: str) -> None: self.client_secret_enc = encryption.encrypt(value) class GmailAccount(Base, TimestampMixin): __tablename__ = "gmail_accounts" id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) email = Column(String(320), nullable=False) display_name = Column(String(200), default="") status = Column(String(30), default="pending") # pending|authorized|expired|revoked|error sending_method = Column(String(20), default="api") # api|smtp auth_provider = Column(String(60), default="google") # google|smtp_password google_user_id = Column(String(120), default="") # Encrypted plain SMTP password (Gmail "app password") for accounts added # via the "Bulk Add via SMTP" flow (auth_provider=smtp_password). Never # stored in plaintext, never echoed back, never logged. Not used for # OAuth ("google") accounts, which authenticate via oauth_tokens instead. smtp_password_enc = Column(String(600), default="") # Per-account config (administrator-configurable) daily_limit = Column(Integer, default=200) # provider/configured daily cap campaign_limit = Column(Integer, default=200) # per-account per-campaign cap sends_today = Column(Integer, default=0) last_send_date = Column(DateTime(timezone=True)) cooldown_until = Column(DateTime(timezone=True)) last_success_at = Column(DateTime(timezone=True)) last_error = Column(Text, default="") error_count = Column(Integer, default=0) token_expiry = Column(DateTime(timezone=True)) # Imported client config reference (optional; may use server-level config) oauth_config_id = Column(Integer, ForeignKey("oauth_client_configs.id", ondelete="SET NULL"))