""" Database module for managing user contributions. Uses parameterized queries (no SQL injection) and context managers. """ import sqlite3 import os from datetime import datetime DB_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data") def _get_db_path(db_name: str) -> str: os.makedirs(DB_DIR, exist_ok=True) return os.path.join(DB_DIR, db_name) def _init_db(db_path: str): with sqlite3.connect(db_path) as conn: conn.execute( """CREATE TABLE IF NOT EXISTS contributions ( num INTEGER PRIMARY KEY, name TEXT NOT NULL, title TEXT NOT NULL, keywords TEXT, text TEXT NOT NULL, date TEXT NOT NULL, password TEXT DEFAULT '' )""" ) def write_contribution( db_name: str, name: str, title: str, keywords: str, text: str, password: str = "" ) -> int: """Write a new contribution. Returns the new contribution ID.""" db_path = _get_db_path(db_name) _init_db(db_path) with sqlite3.connect(db_path) as conn: cursor = conn.cursor() cursor.execute("SELECT COALESCE(MAX(num), 0) FROM contributions") next_id = cursor.fetchone()[0] + 1 cursor.execute( "INSERT INTO contributions (num, name, title, keywords, text, date, password) " "VALUES (?, ?, ?, ?, ?, ?, ?)", (next_id, name, title, keywords, text, datetime.now().strftime("%Y-%m-%d"), password), ) return next_id def search_contributions( db_name: str, name_filter: str = "", title_filter: str = "", keywords_filter: str = "", content_filter: str = "", limit: int = 50, ) -> list[dict]: """Search contributions with optional filters. Returns list of dicts.""" db_path = _get_db_path(db_name) if not os.path.isfile(db_path): return [] _init_db(db_path) with sqlite3.connect(db_path) as conn: conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute( """SELECT num, name, title, keywords, text, date, password FROM contributions WHERE name LIKE ? AND title LIKE ? AND keywords LIKE ? AND text LIKE ? ORDER BY num DESC LIMIT ?""", ( f"%{name_filter}%", f"%{title_filter}%", f"%{keywords_filter}%", f"%{content_filter}%", limit, ), ) return [dict(row) for row in cursor.fetchall()] def delete_contribution(db_name: str, num: int, password: str) -> bool: """Delete a contribution if the password matches. Returns True on success.""" db_path = _get_db_path(db_name) if not os.path.isfile(db_path): return False with sqlite3.connect(db_path) as conn: cursor = conn.cursor() cursor.execute( "SELECT password FROM contributions WHERE num = ?", (num,) ) row = cursor.fetchone() if row is None: return False if row[0] != password: return False cursor.execute("DELETE FROM contributions WHERE num = ?", (num,)) return True