# db.py - Database Configuration and Operations (v4.2) import os import sqlite3 from flask import g from contextlib import contextmanager from config import DATABASE def get_db(): if 'db' not in g: g.db = sqlite3.connect(DATABASE) g.db.row_factory = sqlite3.Row return g.db def close_db(error=None): db = g.pop('db', None) if db is not None: db.close() @contextmanager def get_db_connection(): conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row try: yield conn finally: conn.close() def init_db(): """Initialize database tables. Auto-creates parent directory.""" # ফোল্ডার না থাকলে নিজে থেকেই তৈরি করবে (Coolify volume mount-এর জন্য জরুরি) db_dir = os.path.dirname(os.path.abspath(DATABASE)) if db_dir and not os.path.exists(db_dir): os.makedirs(db_dir, exist_ok=True) print(f"📂 Created data directory: {db_dir}") with get_db_connection() as conn: cursor = conn.cursor() # Projects table cursor.execute(''' CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # Chapters table cursor.execute(''' CREATE TABLE IF NOT EXISTS chapters ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER NOT NULL, chapter_number INTEGER NOT NULL, title TEXT DEFAULT 'Section', voice TEXT DEFAULT 'af_heart', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, UNIQUE(project_id, chapter_number) ) ''') try: cursor.execute('ALTER TABLE chapters ADD COLUMN title TEXT DEFAULT "Section"') except sqlite3.OperationalError: pass # Markdown blocks table cursor.execute(''' CREATE TABLE IF NOT EXISTS markdown_blocks ( id INTEGER PRIMARY KEY AUTOINCREMENT, chapter_id INTEGER NOT NULL, block_order INTEGER NOT NULL, block_type TEXT NOT NULL DEFAULT 'paragraph', content TEXT NOT NULL, tts_text TEXT, audio_data TEXT, audio_format TEXT DEFAULT 'mp3', transcription TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE ) ''') # Images table cursor.execute(''' CREATE TABLE IF NOT EXISTS block_images ( id INTEGER PRIMARY KEY AUTOINCREMENT, block_id INTEGER NOT NULL, image_data TEXT NOT NULL, image_format TEXT DEFAULT 'png', alt_text TEXT, position TEXT DEFAULT 'before', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (block_id) REFERENCES markdown_blocks(id) ON DELETE CASCADE ) ''') # PDF Documents table cursor.execute(''' CREATE TABLE IF NOT EXISTS pdf_documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER, filename TEXT NOT NULL, page_count INTEGER DEFAULT 0, metadata TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL ) ''') # v4.2 publishing migrations migrations = [ ('projects', 'is_published', 'INTEGER DEFAULT 0'), ('projects', 'published_at', 'TIMESTAMP'), ('projects', 'thumbnail_data', 'TEXT'), ('projects', 'thumbnail_format', 'TEXT DEFAULT "png"'), ('projects', 'description', 'TEXT DEFAULT ""'), ('projects', 'author', 'TEXT DEFAULT ""'), ('projects', 'category', 'TEXT DEFAULT ""'), ('projects', 'view_count', 'INTEGER DEFAULT 0'), ] for table, column, definition in migrations: try: cursor.execute(f'ALTER TABLE {table} ADD COLUMN {column} {definition}') print(f" ✅ Added {table}.{column}") except sqlite3.OperationalError: pass conn.commit() print(f"✅ Database initialized at {DATABASE} (v4.2)") def vacuum_db(): with get_db_connection() as conn: conn.execute('VACUUM') def init_app(app): app.teardown_appcontext(close_db) init_db() from auth import init_users_table init_users_table()