backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
254 lines
9.2 KiB
Python
254 lines
9.2 KiB
Python
"""
|
|
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
|