Fix: stream large project responses to avoid proxy truncation
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify, Response, stream_with_context
|
||||||
|
|
||||||
from db import get_db, vacuum_db
|
from db import get_db, vacuum_db
|
||||||
from auth import login_required
|
from auth import login_required
|
||||||
@@ -86,7 +86,13 @@ def create_project():
|
|||||||
@project_bp.route('/api/projects/<int:project_id>', methods=['GET'])
|
@project_bp.route('/api/projects/<int:project_id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def get_project(project_id):
|
def get_project(project_id):
|
||||||
"""Get a project with all its chapters and blocks."""
|
"""
|
||||||
|
Get a project with all its chapters and blocks.
|
||||||
|
|
||||||
|
Streamed response: large projects (with many audio blocks) can produce
|
||||||
|
10-50 MB of JSON. We stream it in chunks so that the reverse proxy
|
||||||
|
(Traefik in Coolify) doesn't buffer the entire payload and truncate it.
|
||||||
|
"""
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
|
|
||||||
@@ -115,6 +121,14 @@ def get_project(project_id):
|
|||||||
''', (block['id'],))
|
''', (block['id'],))
|
||||||
images = cursor.fetchall()
|
images = cursor.fetchall()
|
||||||
|
|
||||||
|
# Safely parse transcription (might be NULL, empty, or malformed)
|
||||||
|
transcription = []
|
||||||
|
if block['transcription']:
|
||||||
|
try:
|
||||||
|
transcription = json.loads(block['transcription'])
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
transcription = []
|
||||||
|
|
||||||
blocks_data.append({
|
blocks_data.append({
|
||||||
'id': block['id'],
|
'id': block['id'],
|
||||||
'block_order': block['block_order'],
|
'block_order': block['block_order'],
|
||||||
@@ -123,7 +137,7 @@ def get_project(project_id):
|
|||||||
'tts_text': block['tts_text'],
|
'tts_text': block['tts_text'],
|
||||||
'audio_data': block['audio_data'],
|
'audio_data': block['audio_data'],
|
||||||
'audio_format': block['audio_format'],
|
'audio_format': block['audio_format'],
|
||||||
'transcription': json.loads(block['transcription']) if block['transcription'] else [],
|
'transcription': transcription,
|
||||||
'images': [{
|
'images': [{
|
||||||
'id': img['id'],
|
'id': img['id'],
|
||||||
'data': img['image_data'],
|
'data': img['image_data'],
|
||||||
@@ -141,13 +155,30 @@ def get_project(project_id):
|
|||||||
'blocks': blocks_data
|
'blocks': blocks_data
|
||||||
})
|
})
|
||||||
|
|
||||||
return jsonify({
|
response_data = {
|
||||||
'id': project['id'],
|
'id': project['id'],
|
||||||
'name': project['name'],
|
'name': project['name'],
|
||||||
'created_at': project['created_at'],
|
'created_at': project['created_at'],
|
||||||
'updated_at': project['updated_at'],
|
'updated_at': project['updated_at'],
|
||||||
'chapters': chapters_data
|
'chapters': chapters_data
|
||||||
})
|
}
|
||||||
|
|
||||||
|
# Stream the JSON in chunks. ensure_ascii=False keeps Unicode (e.g. Bangla)
|
||||||
|
# compact and avoids the JSON ballooning to 2-3x its size.
|
||||||
|
def generate():
|
||||||
|
json_str = json.dumps(response_data, ensure_ascii=False)
|
||||||
|
chunk_size = 64 * 1024 # 64 KB per chunk
|
||||||
|
for i in range(0, len(json_str), chunk_size):
|
||||||
|
yield json_str[i:i + chunk_size]
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
stream_with_context(generate()),
|
||||||
|
mimetype='application/json; charset=utf-8',
|
||||||
|
headers={
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'X-Accel-Buffering': 'no' # Tell Nginx/Traefik: don't buffer this response
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@project_bp.route('/api/projects/<int:project_id>', methods=['PUT'])
|
@project_bp.route('/api/projects/<int:project_id>', methods=['PUT'])
|
||||||
|
|||||||
Reference in New Issue
Block a user