""" Mail Database - Connection Pool and Schema Initialization. """ import os # Database Configuration - from Vault or environment (test default for CI) DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test") # Flag to check if using test defaults _DB_CONFIGURED = DATABASE_URL != "postgresql://test:test@localhost:5432/test" # Connection pool (shared with metrics_db) _pool = None async def get_pool(): """Get or create database connection pool.""" global _pool if _pool is None: try: import asyncpg _pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10) except ImportError: print("Warning: asyncpg not installed. Mail database disabled.") return None except Exception as e: print(f"Warning: Failed to connect to PostgreSQL: {e}") return None return _pool async def init_mail_tables() -> bool: """Initialize mail tables in PostgreSQL.""" pool = await get_pool() if pool is None: return False create_tables_sql = """ -- ============================================================================= -- External Email Accounts -- ============================================================================= CREATE TABLE IF NOT EXISTS external_email_accounts ( id VARCHAR(36) PRIMARY KEY, user_id VARCHAR(36) NOT NULL, tenant_id VARCHAR(36) NOT NULL, email VARCHAR(255) NOT NULL, display_name VARCHAR(255), account_type VARCHAR(50) DEFAULT 'personal', -- IMAP Settings (password stored in Vault) imap_host VARCHAR(255) NOT NULL, imap_port INTEGER DEFAULT 993, imap_ssl BOOLEAN DEFAULT TRUE, -- SMTP Settings smtp_host VARCHAR(255) NOT NULL, smtp_port INTEGER DEFAULT 465, smtp_ssl BOOLEAN DEFAULT TRUE, -- Vault path for credentials vault_path VARCHAR(500), -- Status tracking status VARCHAR(20) DEFAULT 'pending', last_sync TIMESTAMP, sync_error TEXT, email_count INTEGER DEFAULT 0, unread_count INTEGER DEFAULT 0, -- Timestamps created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), -- Constraints UNIQUE(user_id, email) ); CREATE INDEX IF NOT EXISTS idx_mail_accounts_user ON external_email_accounts(user_id); CREATE INDEX IF NOT EXISTS idx_mail_accounts_tenant ON external_email_accounts(tenant_id); CREATE INDEX IF NOT EXISTS idx_mail_accounts_status ON external_email_accounts(status); -- ============================================================================= -- Aggregated Emails -- ============================================================================= CREATE TABLE IF NOT EXISTS aggregated_emails ( id VARCHAR(36) PRIMARY KEY, account_id VARCHAR(36) REFERENCES external_email_accounts(id) ON DELETE CASCADE, user_id VARCHAR(36) NOT NULL, tenant_id VARCHAR(36) NOT NULL, -- Email identification message_id VARCHAR(500) NOT NULL, folder VARCHAR(100) DEFAULT 'INBOX', -- Email content subject TEXT, sender_email VARCHAR(255), sender_name VARCHAR(255), recipients JSONB DEFAULT '[]', cc JSONB DEFAULT '[]', body_preview TEXT, body_text TEXT, body_html TEXT, has_attachments BOOLEAN DEFAULT FALSE, attachments JSONB DEFAULT '[]', headers JSONB DEFAULT '{}', -- Status flags is_read BOOLEAN DEFAULT FALSE, is_starred BOOLEAN DEFAULT FALSE, is_deleted BOOLEAN DEFAULT FALSE, -- Dates date_sent TIMESTAMP, date_received TIMESTAMP, -- AI enrichment category VARCHAR(50), sender_type VARCHAR(50), sender_authority_name VARCHAR(255), detected_deadlines JSONB DEFAULT '[]', suggested_priority VARCHAR(20), ai_summary TEXT, ai_analyzed_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), -- Prevent duplicate imports UNIQUE(account_id, message_id) ); CREATE INDEX IF NOT EXISTS idx_emails_account ON aggregated_emails(account_id); CREATE INDEX IF NOT EXISTS idx_emails_user ON aggregated_emails(user_id); CREATE INDEX IF NOT EXISTS idx_emails_tenant ON aggregated_emails(tenant_id); CREATE INDEX IF NOT EXISTS idx_emails_date ON aggregated_emails(date_received DESC); CREATE INDEX IF NOT EXISTS idx_emails_category ON aggregated_emails(category); CREATE INDEX IF NOT EXISTS idx_emails_unread ON aggregated_emails(is_read) WHERE is_read = FALSE; CREATE INDEX IF NOT EXISTS idx_emails_starred ON aggregated_emails(is_starred) WHERE is_starred = TRUE; CREATE INDEX IF NOT EXISTS idx_emails_sender ON aggregated_emails(sender_email); -- ============================================================================= -- Inbox Tasks (Arbeitsvorrat) -- ============================================================================= CREATE TABLE IF NOT EXISTS inbox_tasks ( id VARCHAR(36) PRIMARY KEY, user_id VARCHAR(36) NOT NULL, tenant_id VARCHAR(36) NOT NULL, email_id VARCHAR(36) REFERENCES aggregated_emails(id) ON DELETE SET NULL, account_id VARCHAR(36) REFERENCES external_email_accounts(id) ON DELETE SET NULL, -- Task content title VARCHAR(500) NOT NULL, description TEXT, priority VARCHAR(20) DEFAULT 'medium', status VARCHAR(20) DEFAULT 'pending', deadline TIMESTAMP, -- Source information source_email_subject TEXT, source_sender VARCHAR(255), source_sender_type VARCHAR(50), -- AI extraction info ai_extracted BOOLEAN DEFAULT FALSE, confidence_score FLOAT, -- Completion tracking completed_at TIMESTAMP, reminder_at TIMESTAMP, -- Timestamps created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_tasks_user ON inbox_tasks(user_id); CREATE INDEX IF NOT EXISTS idx_tasks_tenant ON inbox_tasks(tenant_id); CREATE INDEX IF NOT EXISTS idx_tasks_status ON inbox_tasks(status); CREATE INDEX IF NOT EXISTS idx_tasks_deadline ON inbox_tasks(deadline) WHERE deadline IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_tasks_priority ON inbox_tasks(priority); CREATE INDEX IF NOT EXISTS idx_tasks_email ON inbox_tasks(email_id) WHERE email_id IS NOT NULL; -- ============================================================================= -- Email Templates -- ============================================================================= CREATE TABLE IF NOT EXISTS email_templates ( id VARCHAR(36) PRIMARY KEY, user_id VARCHAR(36), -- NULL for system templates tenant_id VARCHAR(36), name VARCHAR(255) NOT NULL, category VARCHAR(100), subject_template TEXT, body_template TEXT, variables JSONB DEFAULT '[]', is_system BOOLEAN DEFAULT FALSE, usage_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_templates_user ON email_templates(user_id); CREATE INDEX IF NOT EXISTS idx_templates_tenant ON email_templates(tenant_id); CREATE INDEX IF NOT EXISTS idx_templates_system ON email_templates(is_system); -- ============================================================================= -- Mail Audit Log -- ============================================================================= CREATE TABLE IF NOT EXISTS mail_audit_log ( id VARCHAR(36) PRIMARY KEY, user_id VARCHAR(36) NOT NULL, tenant_id VARCHAR(36), action VARCHAR(100) NOT NULL, entity_type VARCHAR(50), -- account, email, task entity_id VARCHAR(36), details JSONB, ip_address VARCHAR(45), user_agent TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_mail_audit_user ON mail_audit_log(user_id); CREATE INDEX IF NOT EXISTS idx_mail_audit_created ON mail_audit_log(created_at DESC); CREATE INDEX IF NOT EXISTS idx_mail_audit_action ON mail_audit_log(action); -- ============================================================================= -- Sync Status Tracking -- ============================================================================= CREATE TABLE IF NOT EXISTS mail_sync_status ( id VARCHAR(36) PRIMARY KEY, account_id VARCHAR(36) REFERENCES external_email_accounts(id) ON DELETE CASCADE, folder VARCHAR(100), last_uid INTEGER DEFAULT 0, last_sync TIMESTAMP, sync_errors INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(account_id, folder) ); """ try: async with pool.acquire() as conn: await conn.execute(create_tables_sql) print("Mail tables initialized successfully") return True except Exception as e: print(f"Failed to initialize mail tables: {e}") return False