Files
Meeting_Assistant/api_routes.py
beabigegg df5411e44c OK
2025-11-13 08:18:15 +08:00

219 lines
8.9 KiB
Python

import os
import uuid
from flask import request, jsonify, send_from_directory, Blueprint, current_app
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, get_jwt
from werkzeug.utils import secure_filename
from datetime import datetime
from models import db, User, Meeting, ActionItem
from tasks import (
celery,
extract_audio_task,
transcribe_audio_task,
translate_text_task,
summarize_text_task,
preview_action_items_task
)
api_bp = Blueprint("api_bp", __name__, url_prefix="/api")
def save_uploaded_file(upload_folder, file_key='file'):
if file_key not in request.files:
return None, (jsonify({'error': 'Request does not contain a file part'}), 400)
file = request.files[file_key]
if file.filename == '':
return None, (jsonify({'error': 'No file selected'}), 400)
if file:
original_filename = secure_filename(file.filename)
file_extension = os.path.splitext(original_filename)[1]
unique_filename = f"{uuid.uuid4()}{file_extension}"
file_path = os.path.join(upload_folder, unique_filename)
file.save(file_path)
return file_path, None
return None, (jsonify({'error': 'Unknown file error'}), 500)
@api_bp.route('/admin/users', methods=['GET'])
@jwt_required()
def get_all_users():
if get_jwt().get('role') != 'admin':
return jsonify({"msg": "Administration rights required"}), 403
users = User.query.all()
return jsonify([user.to_dict() for user in users])
@api_bp.route('/users', methods=['GET'])
@jwt_required()
def get_all_users_for_dropdown():
users = User.query.all()
return jsonify([user.to_dict() for user in users])
@api_bp.route('/admin/users/<int:user_id>', methods=['DELETE'])
@jwt_required()
def delete_user(user_id):
if get_jwt().get('role') != 'admin':
return jsonify({"msg": "Administration rights required"}), 403
# Prevent admin from deleting themselves
if str(user_id) == get_jwt_identity():
return jsonify({"error": "Admin users cannot delete their own account"}), 400
user_to_delete = User.query.get(user_id)
if not user_to_delete:
return jsonify({"error": "User not found"}), 404
try:
# Disassociate meetings created by this user (set to None instead of deleting meetings)
Meeting.query.filter_by(created_by_id=user_id).update({"created_by_id": None})
# Disassociate action items owned by this user
ActionItem.query.filter_by(owner_id=user_id).update({"owner_id": None})
db.session.delete(user_to_delete)
db.session.commit()
return jsonify({"msg": f"User {user_to_delete.display_name or user_to_delete.username} has been deleted."}), 200
except Exception as e:
db.session.rollback()
return jsonify({"error": f"An error occurred: {str(e)}"}), 500
@api_bp.route('/meetings', methods=['GET', 'POST'])
@jwt_required()
def handle_meetings():
if request.method == 'POST':
data = request.get_json()
topic = data.get('topic')
meeting_date_str = data.get('meeting_date')
if not topic or not meeting_date_str:
return jsonify({'error': 'Topic and meeting_date are required'}), 400
try:
meeting_date = datetime.fromisoformat(meeting_date_str).date()
new_meeting = Meeting(
topic=topic,
meeting_date=meeting_date,
created_by_id=get_jwt_identity(),
created_at=datetime.utcnow(),
status='In Progress'
)
db.session.add(new_meeting)
db.session.commit()
return jsonify(new_meeting.to_dict()), 201
except Exception as e:
db.session.rollback()
current_app.logger.error(f"Failed to create meeting: {e}")
return jsonify({'error': 'Failed to create meeting due to a database error.'}), 500
meetings = Meeting.query.order_by(Meeting.meeting_date.desc()).all()
return jsonify([meeting.to_dict() for meeting in meetings])
@api_bp.route('/meetings/<int:meeting_id>', methods=['GET', 'PUT', 'DELETE'])
@jwt_required()
def handle_meeting_detail(meeting_id):
meeting = Meeting.query.get_or_404(meeting_id)
if request.method == 'PUT':
data = request.get_json()
if 'topic' in data:
meeting.topic = data.get('topic')
if 'status' in data:
current_user_id = get_jwt_identity()
current_user_role = get_jwt().get('role')
is_admin = current_user_role == 'admin'
if not is_admin and str(meeting.created_by_id) != str(current_user_id):
return jsonify({"msg": "Only the meeting creator or an admin can change the status."}), 403
meeting.status = data.get('status')
if 'transcript' in data:
meeting.transcript = data.get('transcript')
if 'summary' in data:
meeting.summary = data.get('summary')
if data.get('meeting_date'):
meeting.meeting_date = datetime.fromisoformat(data['meeting_date']).date()
db.session.commit()
db.session.refresh(meeting)
return jsonify(meeting.to_dict())
if request.method == 'DELETE':
current_user_id = get_jwt_identity()
current_user_role = get_jwt().get('role')
is_admin = current_user_role == 'admin'
if not is_admin and str(meeting.created_by_id) != str(current_user_id):
return jsonify({"msg": "Only the meeting creator or an admin can delete this meeting."}), 403
db.session.delete(meeting)
db.session.commit()
return jsonify({"msg": "Meeting and associated action items deleted"}), 200
return jsonify(meeting.to_dict())
@api_bp.route('/meetings/<int:meeting_id>/summarize', methods=['POST'])
@jwt_required()
def summarize_meeting(meeting_id):
meeting = Meeting.query.get_or_404(meeting_id)
if not meeting.transcript:
return jsonify({'error': 'Meeting has no transcript to summarize.'}), 400
task = summarize_text_task.delay(meeting_id)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202
@api_bp.route('/meetings/<int:meeting_id>/preview-actions', methods=['POST'])
@jwt_required()
def preview_actions(meeting_id):
meeting = Meeting.query.get_or_404(meeting_id)
text_content = meeting.transcript
if not text_content:
return jsonify({'error': 'Meeting has no transcript to analyze.'}), 400
task = preview_action_items_task.delay(text_content)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202
# --- Tool Routes ---
@api_bp.route('/tools/extract_audio', methods=['POST'])
@jwt_required()
def handle_extract_audio():
input_path, error = save_uploaded_file(current_app.config['UPLOAD_FOLDER'])
if error: return error
output_path = os.path.splitext(input_path)[0] + ".wav"
task = extract_audio_task.delay(input_path, output_path)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202
@api_bp.route('/tools/transcribe_audio', methods=['POST'])
@jwt_required()
def handle_transcribe_audio():
input_path, error = save_uploaded_file(current_app.config['UPLOAD_FOLDER'])
if error: return error
task = transcribe_audio_task.delay(input_path)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202
@api_bp.route('/tools/translate_text', methods=['POST'])
@jwt_required()
def handle_translate_text():
data = request.get_json()
text_content = data.get('text')
target_language = data.get('target_language', '繁體中文')
if not text_content:
return jsonify({'error': 'Text content is required'}), 400
task = translate_text_task.delay(text_content, target_language)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202
# --- Task Status Routes ---
@api_bp.route('/status/<task_id>')
@jwt_required()
def get_task_status(task_id):
task = celery.AsyncResult(task_id)
response_data = {'state': task.state, 'info': task.info if isinstance(task.info, dict) else str(task.info)}
if task.state == 'SUCCESS' and isinstance(task.info, dict) and 'result_path' in task.info:
response_data['info']['download_filename'] = os.path.basename(task.info['result_path'])
return jsonify(response_data)
@api_bp.route('/task/<task_id>/stop', methods=['POST'])
@jwt_required()
def stop_task(task_id):
celery.control.revoke(task_id, terminate=True)
return jsonify({'message': f'Task {task_id} has been stopped.'}), 200
@api_bp.route('/download/<filename>')
@jwt_required()
def download_file(filename):
return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
@api_bp.route('/meetings/<int:meeting_id>/action_items', methods=['GET'])
@jwt_required()
def get_action_items_for_meeting(meeting_id):
action_items = ActionItem.query.filter_by(meeting_id=meeting_id).all()
return jsonify([item.to_dict() for item in action_items])