imetting_backend/app/api/endpoints/meetings.py

395 lines
15 KiB
Python

from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from app.models.models import Meeting, TranscriptSegment, CreateMeetingRequest, UpdateMeetingRequest
from app.core.database import get_db_connection
from app.core.config import UPLOAD_DIR, AUDIO_DIR, ALLOWED_EXTENSIONS, MAX_FILE_SIZE
from typing import Optional
import os
import uuid
import shutil
router = APIRouter()
@router.get("/meetings", response_model=list[Meeting])
def get_meetings(user_id: Optional[int] = None):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
base_query = '''
SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
m.user_id as creator_id, u.caption as creator_username
FROM meetings m
JOIN users u ON m.user_id = u.user_id
'''
if user_id:
query = f'''
{base_query}
LEFT JOIN attendees a ON m.meeting_id = a.meeting_id
WHERE m.user_id = %s OR a.user_id = %s
GROUP BY m.meeting_id
ORDER BY m.meeting_time DESC, m.created_at DESC
'''
cursor.execute(query, (user_id, user_id))
else:
query = f" {base_query} ORDER BY m.meeting_time DESC, m.created_at DESC"
cursor.execute(query)
meetings = cursor.fetchall()
meeting_list = []
for meeting in meetings:
attendees_query = '''
SELECT u.user_id, u.caption
FROM attendees a
JOIN users u ON a.user_id = u.user_id
WHERE a.meeting_id = %s
'''
cursor.execute(attendees_query, (meeting['meeting_id'],))
attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
meeting_list.append(Meeting(
meeting_id=meeting['meeting_id'],
title=meeting['title'],
meeting_time=meeting['meeting_time'],
summary=meeting['summary'],
created_at=meeting['created_at'],
attendees=attendees,
creator_id=meeting['creator_id'],
creator_username=meeting['creator_username']
))
return meeting_list
@router.get("/meetings/{meeting_id}", response_model=Meeting)
def get_meeting_details(meeting_id: int):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
query = '''
SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
m.user_id as creator_id, u.caption as creator_username,
af.file_path as audio_file_path
FROM meetings m
JOIN users u ON m.user_id = u.user_id
LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
WHERE m.meeting_id = %s
'''
cursor.execute(query, (meeting_id,))
meeting = cursor.fetchone()
if not meeting:
raise HTTPException(status_code=404, detail="Meeting not found")
attendees_query = '''
SELECT u.user_id, u.caption
FROM attendees a
JOIN users u ON a.user_id = u.user_id
WHERE a.meeting_id = %s
'''
cursor.execute(attendees_query, (meeting['meeting_id'],))
attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
meeting_data = Meeting(
meeting_id=meeting['meeting_id'],
title=meeting['title'],
meeting_time=meeting['meeting_time'],
summary=meeting['summary'],
created_at=meeting['created_at'],
attendees=attendees,
creator_id=meeting['creator_id'],
creator_username=meeting['creator_username']
)
# Add audio file path if exists
if meeting['audio_file_path']:
meeting_data.audio_file_path = meeting['audio_file_path']
return meeting_data
@router.get("/meetings/{meeting_id}/transcript", response_model=list[TranscriptSegment])
def get_meeting_transcript(meeting_id: int):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# First check if meeting exists
meeting_query = "SELECT meeting_id FROM meetings WHERE meeting_id = %s"
cursor.execute(meeting_query, (meeting_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Meeting not found")
# Get transcript segments
transcript_query = '''
SELECT segment_id, meeting_id, speaker_tag, start_time_ms, end_time_ms, text_content
FROM transcript_segments
WHERE meeting_id = %s
ORDER BY start_time_ms ASC
'''
cursor.execute(transcript_query, (meeting_id,))
segments = cursor.fetchall()
return [TranscriptSegment(**segment) for segment in segments]
@router.post("/meetings")
def create_meeting(meeting_request: CreateMeetingRequest):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Create meeting
meeting_query = '''
INSERT INTO meetings (user_id, title, meeting_time, summary)
VALUES (%s, %s, %s, %s)
'''
# Note: You'll need to pass user_id, for now using hardcoded value
cursor.execute(meeting_query, (1, meeting_request.title, meeting_request.meeting_time, None))
meeting_id = cursor.lastrowid
# Add attendees
for attendee_id in meeting_request.attendee_ids:
attendee_query = '''
INSERT INTO attendees (meeting_id, user_id)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE meeting_id = meeting_id
'''
cursor.execute(attendee_query, (meeting_id, attendee_id))
connection.commit()
return {"meeting_id": meeting_id, "message": "Meeting created successfully"}
@router.put("/meetings/{meeting_id}")
def update_meeting(meeting_id: int, meeting_request: UpdateMeetingRequest):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if meeting exists
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Meeting not found")
# Update meeting
update_query = '''
UPDATE meetings
SET title = %s, meeting_time = %s, summary = %s
WHERE meeting_id = %s
'''
cursor.execute(update_query, (
meeting_request.title,
meeting_request.meeting_time,
meeting_request.summary,
meeting_id
))
# Update attendees - remove existing ones and add new ones
cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,))
for attendee_id in meeting_request.attendee_ids:
attendee_query = '''
INSERT INTO attendees (meeting_id, user_id)
VALUES (%s, %s)
'''
cursor.execute(attendee_query, (meeting_id, attendee_id))
connection.commit()
return {"message": "Meeting updated successfully"}
@router.delete("/meetings/{meeting_id}")
def delete_meeting(meeting_id: int):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if meeting exists
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Meeting not found")
# Delete related records first (foreign key constraints)
cursor.execute("DELETE FROM transcript_segments WHERE meeting_id = %s", (meeting_id,))
cursor.execute("DELETE FROM audio_files WHERE meeting_id = %s", (meeting_id,))
cursor.execute("DELETE FROM attachments WHERE meeting_id = %s", (meeting_id,))
cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,))
# Delete meeting
cursor.execute("DELETE FROM meetings WHERE meeting_id = %s", (meeting_id,))
connection.commit()
return {"message": "Meeting deleted successfully"}
@router.post("/meetings/{meeting_id}/regenerate-summary")
def regenerate_summary(meeting_id: int):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if meeting exists
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
if not cursor.fetchone():
raise HTTPException(status_code=404, detail="Meeting not found")
# For now, return a mock summary
# In a real implementation, this would call an AI service
mock_summary = """# AI 生成摘要
## 主要议题
- 项目进度回顾
- 技术方案讨论
- 下阶段规划
## 关键决策
- 采用新的技术架构
- 调整项目时间节点
- 分配任务责任
## 后续行动
- [ ] 完成技术方案文档
- [ ] 安排下次会议时间
- [ ] 跟进项目进度"""
# Update meeting summary
cursor.execute(
"UPDATE meetings SET summary = %s WHERE meeting_id = %s",
(mock_summary, meeting_id)
)
connection.commit()
return {"summary": mock_summary}
@router.get("/meetings/{meeting_id}/edit", response_model=Meeting)
def get_meeting_for_edit(meeting_id: int):
"""Get meeting details with full attendee information for editing"""
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
query = '''
SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at,
m.user_id as creator_id, u.caption as creator_username,
af.file_path as audio_file_path
FROM meetings m
JOIN users u ON m.user_id = u.user_id
LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
WHERE m.meeting_id = %s
'''
cursor.execute(query, (meeting_id,))
meeting = cursor.fetchone()
if not meeting:
raise HTTPException(status_code=404, detail="Meeting not found")
# Get attendees with full info for editing
attendees_query = '''
SELECT u.user_id, u.caption
FROM attendees a
JOIN users u ON a.user_id = u.user_id
WHERE a.meeting_id = %s
'''
cursor.execute(attendees_query, (meeting['meeting_id'],))
attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
meeting_data = Meeting(
meeting_id=meeting['meeting_id'],
title=meeting['title'],
meeting_time=meeting['meeting_time'],
summary=meeting['summary'],
created_at=meeting['created_at'],
attendees=attendees,
creator_id=meeting['creator_id'],
creator_username=meeting['creator_username']
)
# Add audio file path if exists
if meeting['audio_file_path']:
meeting_data.audio_file_path = meeting['audio_file_path']
return meeting_data
@router.post("/meetings/upload-audio")
async def upload_audio(
audio_file: UploadFile = File(...),
meeting_id: int = Form(...)
):
# Validate file extension
file_extension = os.path.splitext(audio_file.filename)[1].lower()
if file_extension not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=400,
detail=f"Unsupported file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}"
)
# Check file size
if audio_file.size > MAX_FILE_SIZE:
raise HTTPException(
status_code=400,
detail="File size exceeds 100MB limit"
)
# Generate unique filename
unique_filename = f"{uuid.uuid4()}{file_extension}"
file_path = AUDIO_DIR / unique_filename
# Store only relative path for database (audio/filename)
relative_path = f"audio/{unique_filename}"
# Save file
try:
with open(file_path, "wb") as buffer:
shutil.copyfileobj(audio_file.file, buffer)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to save file: {str(e)}")
# Save file info to database
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# Check if meeting exists
cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,))
if not cursor.fetchone():
# Clean up uploaded file if meeting doesn't exist
os.remove(file_path)
raise HTTPException(status_code=404, detail="Meeting not found")
# Insert audio file record
insert_query = '''
INSERT INTO audio_files (meeting_id, file_name, file_path, file_size, upload_time)
VALUES (%s, %s, %s, %s, NOW())
ON DUPLICATE KEY UPDATE
file_name = VALUES(file_name),
file_path = VALUES(file_path),
file_size = VALUES(file_size),
upload_time = VALUES(upload_time)
'''
cursor.execute(insert_query, (meeting_id, audio_file.filename, relative_path, audio_file.size))
connection.commit()
return {
"message": "Audio file uploaded successfully",
"file_name": audio_file.filename,
"file_path": relative_path
}
@router.get("/meetings/{meeting_id}/audio")
def get_audio_file(meeting_id: int):
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
query = '''
SELECT file_name, file_path, file_size, upload_time
FROM audio_files
WHERE meeting_id = %s
'''
cursor.execute(query, (meeting_id,))
audio_file = cursor.fetchone()
if not audio_file:
raise HTTPException(status_code=404, detail="Audio file not found for this meeting")
return {
"file_name": audio_file['file_name'],
"file_path": audio_file['file_path'],
"file_size": audio_file['file_size'],
"upload_time": audio_file['upload_time']
}