"""数据库操作""" import sqlite3 import json from datetime import datetime from typing import Optional, List, Dict, Any from contextlib import contextmanager from .config import DATABASE_URL, TODO_STATUS, PRIORITY_LEVELS class Database: """SQLite数据库管理""" def __init__(self, db_path: str = DATABASE_URL): self.db_path = db_path self._initialized = False def _ensure_init(self): """确保数据库已初始化""" if self._initialized: return self._init_db() self._initialized = True @contextmanager def get_conn(self): """获取数据库连接""" conn = sqlite3.connect(self.db_path, timeout=30.0) conn.row_factory = sqlite3.Row # 启用WAL模式,提高并发性能 conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA busy_timeout=30000") try: yield conn finally: conn.close() def _init_db(self): """初始化数据库表""" with self.get_conn() as conn: cursor = conn.cursor() # 主内容表 cursor.execute(""" CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL DEFAULT 'text', title TEXT, content TEXT, url TEXT, source TEXT, status TEXT DEFAULT 'pending', priority TEXT DEFAULT 'medium', due_date TEXT, note TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) """) # 标签表 cursor.execute(""" CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, color TEXT DEFAULT '#3498db', created_at TEXT NOT NULL ) """) # 内容-标签关联表 cursor.execute(""" CREATE TABLE IF NOT EXISTS item_tags ( item_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (item_id, tag_id), FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ) """) # 邮箱表 cursor.execute(""" CREATE TABLE IF NOT EXISTS emails ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, name TEXT, created_at TEXT NOT NULL ) """) # 创建索引 cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_created ON items(created_at)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_item_tags_item ON item_tags(item_id)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_item_tags_tag ON item_tags(tag_id)") conn.commit() # ============ Item 操作 ============ def create_item(self, type: str = "text", title: str = None, content: str = None, url: str = None, source: str = None, status: str = "pending", priority: str = "medium", due_date: str = None, note: str = None, tags: List[str] = None) -> int: """创建新条目""" self._ensure_init() now = datetime.now().isoformat() # 验证状态 if type == "todo" and status not in TODO_STATUS: status = "pending" if priority not in PRIORITY_LEVELS: priority = "medium" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute(""" INSERT INTO items (type, title, content, url, source, status, priority, due_date, note, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, (type, title, content, url, source, status, priority, due_date, note, now, now)) item_id = cursor.lastrowid # 添加标签 if tags: self._add_tags_to_item(conn, item_id, tags) conn.commit() return item_id def get_item(self, item_id: int) -> Optional[Dict[str, Any]]: """获取单个条目""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM items WHERE id = ?", (item_id,)) row = cursor.fetchone() if not row: return None item = dict(row) item['tags'] = self._get_item_tags(conn, item_id) return item def list_items(self, type: str = None, status: str = None, tag: str = None, keyword: str = None, limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]: """列出条目""" with self.get_conn() as conn: cursor = conn.cursor() query = "SELECT DISTINCT i.* FROM items i" params = [] conditions = [] # 标签过滤需要JOIN if tag: query += " JOIN item_tags it ON i.id = it.item_id JOIN tags t ON it.tag_id = t.id" conditions.append("t.name = ?") params.append(tag) if type: conditions.append("i.type = ?") params.append(type) if status: conditions.append("i.status = ?") params.append(status) if keyword: conditions.append("(i.title LIKE ? OR i.content LIKE ? OR i.note LIKE ?)") keyword_pattern = f"%{keyword}%" params.extend([keyword_pattern, keyword_pattern, keyword_pattern]) if conditions: query += " WHERE " + " AND ".join(conditions) query += " ORDER BY i.created_at DESC LIMIT ? OFFSET ?" params.extend([limit, offset]) cursor.execute(query, params) items = [] for row in cursor.fetchall(): item = dict(row) item['tags'] = self._get_item_tags(conn, item['id']) items.append(item) return items def update_item(self, item_id: int, **kwargs) -> bool: """更新条目""" allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note'] update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields} if not update_fields and 'tags' not in kwargs: return False now = datetime.now().isoformat() with self.get_conn() as conn: cursor = conn.cursor() if update_fields: set_clause = ", ".join(f"{k} = ?" for k in update_fields.keys()) set_clause += ", updated_at = ?" values = list(update_fields.values()) + [now, item_id] cursor.execute(f"UPDATE items SET {set_clause} WHERE id = ?", values) if 'tags' in kwargs: # 先删除旧标签关联 cursor.execute("DELETE FROM item_tags WHERE item_id = ?", (item_id,)) # 添加新标签 if kwargs['tags']: self._add_tags_to_item(conn, item_id, kwargs['tags']) conn.commit() return cursor.rowcount > 0 def delete_item(self, item_id: int) -> bool: """删除条目""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute("DELETE FROM items WHERE id = ?", (item_id,)) conn.commit() return cursor.rowcount > 0 # ============ Tag 操作 ============ def create_tag(self, name: str, color: str = "#3498db") -> int: """创建标签""" now = datetime.now().isoformat() with self.get_conn() as conn: cursor = conn.cursor() try: cursor.execute("INSERT INTO tags (name, color, created_at) VALUES (?, ?, ?)", (name, color, now)) conn.commit() return cursor.lastrowid except sqlite3.IntegrityError: # 标签已存在 cursor.execute("SELECT id FROM tags WHERE name = ?", (name,)) return cursor.fetchone()['id'] def list_tags(self) -> List[Dict[str, Any]]: """列出所有标签""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute(""" SELECT t.*, COUNT(it.item_id) as item_count FROM tags t LEFT JOIN item_tags it ON t.id = it.tag_id GROUP BY t.id ORDER BY t.name """) return [dict(row) for row in cursor.fetchall()] def update_tag(self, tag_id: int, name: str) -> bool: """更新标签名称""" with self.get_conn() as conn: cursor = conn.cursor() # 检查名称是否已存在(排除自己) cursor.execute("SELECT id FROM tags WHERE name = ? AND id != ?", (name, tag_id)) if cursor.fetchone(): return False # 名称已存在 cursor.execute("UPDATE tags SET name = ? WHERE id = ?", (name, tag_id)) conn.commit() return cursor.rowcount > 0 def delete_tag(self, tag_id: int = None, name: str = None) -> bool: """删除标签""" with self.get_conn() as conn: cursor = conn.cursor() if name: cursor.execute("DELETE FROM tags WHERE name = ?", (name,)) elif tag_id: cursor.execute("DELETE FROM tags WHERE id = ?", (tag_id,)) conn.commit() return cursor.rowcount > 0 # ============ 辅助方法 ============ def _add_tags_to_item(self, conn, item_id: int, tags: List[str]): """为条目添加标签""" cursor = conn.cursor() for tag_name in tags: tag_name = tag_name.strip() if not tag_name: continue # 确保标签存在 - 使用同一个连接 cursor.execute("SELECT id FROM tags WHERE name = ?", (tag_name,)) row = cursor.fetchone() if row: tag_id = row['id'] else: # 创建新标签 now = datetime.now().isoformat() cursor.execute("INSERT INTO tags (name, color, created_at) VALUES (?, '#3498db', ?)", (tag_name, now)) tag_id = cursor.lastrowid # 创建关联 cursor.execute("INSERT OR IGNORE INTO item_tags (item_id, tag_id) VALUES (?, ?)", (item_id, tag_id)) def _get_item_tags(self, conn, item_id: int) -> List[str]: """获取条目的标签""" cursor = conn.cursor() cursor.execute(""" SELECT t.name FROM tags t JOIN item_tags it ON t.id = it.tag_id WHERE it.item_id = ? ORDER BY t.name """, (item_id,)) return [row['name'] for row in cursor.fetchall()] # ============ Email 操作 ============ def create_email(self, email: str, name: str = None) -> int: """创建邮箱""" now = datetime.now().isoformat() with self.get_conn() as conn: cursor = conn.cursor() try: cursor.execute("INSERT INTO emails (email, name, created_at) VALUES (?, ?, ?)", (email, name, now)) conn.commit() return cursor.lastrowid except sqlite3.IntegrityError: # 邮箱已存在,返回已有ID cursor.execute("SELECT id FROM emails WHERE email = ?", (email,)) return cursor.fetchone()['id'] def list_emails(self) -> List[Dict[str, Any]]: """列出所有邮箱""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM emails ORDER BY created_at DESC") return [dict(row) for row in cursor.fetchall()] def get_email(self, email_id: int) -> Optional[Dict[str, Any]]: """获取单个邮箱""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM emails WHERE id = ?", (email_id,)) row = cursor.fetchone() return dict(row) if row else None def update_email(self, email_id: int, email: str = None, name: str = None) -> bool: """更新邮箱""" now = datetime.now().isoformat() with self.get_conn() as conn: cursor = conn.cursor() if email: # 检查邮箱是否已存在(排除自己) cursor.execute("SELECT id FROM emails WHERE email = ? AND id != ?", (email, email_id)) if cursor.fetchone(): return False # 邎箱已存在 if email and name: cursor.execute("UPDATE emails SET email = ?, name = ? WHERE id = ?", (email, name, email_id)) elif email: cursor.execute("UPDATE emails SET email = ? WHERE id = ?", (email, email_id)) elif name: cursor.execute("UPDATE emails SET name = ? WHERE id = ?", (name, email_id)) conn.commit() return cursor.rowcount > 0 def delete_email(self, email_id: int) -> bool: """删除邮箱""" with self.get_conn() as conn: cursor = conn.cursor() cursor.execute("DELETE FROM emails WHERE id = ?", (email_id,)) conn.commit() return cursor.rowcount > 0 def stats(self) -> Dict[str, Any]: """获取统计信息""" self._ensure_init() with self.get_conn() as conn: cursor = conn.cursor() stats = {} # 总数 cursor.execute("SELECT COUNT(*) as count FROM items") stats['total'] = cursor.fetchone()['count'] # 按类型统计 cursor.execute("SELECT type, COUNT(*) as count FROM items GROUP BY type") stats['by_type'] = {row['type']: row['count'] for row in cursor.fetchall()} # 待办状态统计 cursor.execute("SELECT status, COUNT(*) as count FROM items WHERE type = 'todo' GROUP BY status") stats['todo_status'] = {row['status']: row['count'] for row in cursor.fetchall()} # 标签数 cursor.execute("SELECT COUNT(*) as count FROM tags") stats['tags'] = cursor.fetchone()['count'] return stats # 全局数据库实例 db = Database()