# routes/project_routes.py - Project Management Routes import json from flask import Blueprint, request, jsonify from db import get_db, vacuum_db from auth import login_required project_bp = Blueprint('project', __name__) @project_bp.route('/api/projects', methods=['GET']) @login_required def list_projects(): """List all projects.""" db = get_db() cursor = db.cursor() cursor.execute(''' SELECT p.id, p.name, p.created_at, p.updated_at, (SELECT COUNT(*) FROM chapters WHERE project_id = p.id) as chapter_count, (SELECT COUNT(*) FROM markdown_blocks mb JOIN chapters c ON mb.chapter_id = c.id WHERE c.project_id = p.id) as block_count FROM projects p ORDER BY p.updated_at DESC ''') projects = [] for row in cursor.fetchall(): projects.append({ 'id': row['id'], 'name': row['name'], 'created_at': row['created_at'], 'updated_at': row['updated_at'], 'chapter_count': row['chapter_count'], 'block_count': row['block_count'] }) return jsonify({'projects': projects}) @project_bp.route('/api/projects', methods=['POST']) @login_required def create_project(): """Create a new project.""" data = request.json name = data.get('name', '').strip() if not name: return jsonify({'error': 'Project name is required'}), 400 db = get_db() cursor = db.cursor() try: cursor.execute('INSERT INTO projects (name) VALUES (?)', (name,)) db.commit() return jsonify({ 'success': True, 'project_id': cursor.lastrowid, 'name': name }) except Exception as e: if 'UNIQUE constraint' in str(e): return jsonify({'error': 'Project with this name already exists'}), 400 return jsonify({'error': str(e)}), 500 @project_bp.route('/api/projects/', methods=['GET']) @login_required def get_project(project_id): """Get a project with all its chapters and blocks.""" db = get_db() cursor = db.cursor() cursor.execute('SELECT * FROM projects WHERE id = ?', (project_id,)) project = cursor.fetchone() if not project: return jsonify({'error': 'Project not found'}), 404 cursor.execute(''' SELECT * FROM chapters WHERE project_id = ? ORDER BY chapter_number ''', (project_id,)) chapters = cursor.fetchall() chapters_data = [] for chapter in chapters: cursor.execute(''' SELECT * FROM markdown_blocks WHERE chapter_id = ? ORDER BY block_order ''', (chapter['id'],)) blocks = cursor.fetchall() blocks_data = [] for block in blocks: cursor.execute(''' SELECT * FROM block_images WHERE block_id = ? ORDER BY id ''', (block['id'],)) images = cursor.fetchall() blocks_data.append({ 'id': block['id'], 'block_order': block['block_order'], 'block_type': block['block_type'], 'content': block['content'], 'tts_text': block['tts_text'], 'audio_data': block['audio_data'], 'audio_format': block['audio_format'], 'transcription': json.loads(block['transcription']) if block['transcription'] else [], 'images': [{ 'id': img['id'], 'data': img['image_data'], 'format': img['image_format'], 'alt_text': img['alt_text'], 'position': img['position'] } for img in images] }) chapters_data.append({ 'id': chapter['id'], 'chapter_number': chapter['chapter_number'], 'voice': chapter['voice'], 'blocks': blocks_data }) return jsonify({ 'id': project['id'], 'name': project['name'], 'created_at': project['created_at'], 'updated_at': project['updated_at'], 'chapters': chapters_data }) @project_bp.route('/api/projects/', methods=['PUT']) @login_required def update_project(project_id): """Update project name.""" data = request.json name = data.get('name', '').strip() if not name: return jsonify({'error': 'Project name is required'}), 400 db = get_db() cursor = db.cursor() cursor.execute(''' UPDATE projects SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ''', (name, project_id)) db.commit() if cursor.rowcount == 0: return jsonify({'error': 'Project not found'}), 404 return jsonify({'success': True}) @project_bp.route('/api/projects/', methods=['DELETE']) @login_required def delete_project(project_id): """Delete a project and all its data.""" db = get_db() cursor = db.cursor() cursor.execute('SELECT id FROM projects WHERE id = ?', (project_id,)) if not cursor.fetchone(): return jsonify({'error': 'Project not found'}), 404 cursor.execute(''' DELETE FROM block_images WHERE block_id IN ( SELECT mb.id FROM markdown_blocks mb JOIN chapters c ON mb.chapter_id = c.id WHERE c.project_id = ? ) ''', (project_id,)) cursor.execute(''' DELETE FROM markdown_blocks WHERE chapter_id IN ( SELECT id FROM chapters WHERE project_id = ? ) ''', (project_id,)) cursor.execute('DELETE FROM chapters WHERE project_id = ?', (project_id,)) cursor.execute('DELETE FROM projects WHERE id = ?', (project_id,)) db.commit() vacuum_db() return jsonify({'success': True}) @project_bp.route('/api/projects//save', methods=['POST']) @login_required def save_project_content(project_id): """Save all chapters and blocks for a project.""" data = request.json chapters = data.get('chapters', []) db = get_db() cursor = db.cursor() cursor.execute('SELECT id FROM projects WHERE id = ?', (project_id,)) if not cursor.fetchone(): return jsonify({'error': 'Project not found'}), 404 cursor.execute(''' DELETE FROM block_images WHERE block_id IN ( SELECT mb.id FROM markdown_blocks mb JOIN chapters c ON mb.chapter_id = c.id WHERE c.project_id = ? ) ''', (project_id,)) cursor.execute(''' DELETE FROM markdown_blocks WHERE chapter_id IN ( SELECT id FROM chapters WHERE project_id = ? ) ''', (project_id,)) cursor.execute('DELETE FROM chapters WHERE project_id = ?', (project_id,)) for chapter in chapters: cursor.execute(''' INSERT INTO chapters (project_id, chapter_number, voice) VALUES (?, ?, ?) ''', (project_id, chapter['chapter_number'], chapter.get('voice', 'af_heart'))) chapter_id = cursor.lastrowid for block in chapter.get('blocks', []): cursor.execute(''' INSERT INTO markdown_blocks (chapter_id, block_order, block_type, content, tts_text, audio_data, audio_format, transcription) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', ( chapter_id, block['block_order'], block.get('block_type', 'paragraph'), block['content'], block.get('tts_text'), block.get('audio_data'), block.get('audio_format', 'mp3'), json.dumps(block.get('transcription', [])) )) block_id = cursor.lastrowid for img in block.get('images', []): cursor.execute(''' INSERT INTO block_images (block_id, image_data, image_format, alt_text, position) VALUES (?, ?, ?, ?, ?) ''', ( block_id, img['data'], img.get('format', 'png'), img.get('alt_text', ''), img.get('position', 'before') )) cursor.execute(''' UPDATE projects SET updated_at = CURRENT_TIMESTAMP WHERE id = ? ''', (project_id,)) db.commit() return jsonify({'success': True, 'message': 'Project saved successfully'})