rairo commited on
Commit
cb7a161
·
verified ·
1 Parent(s): 6c7c430

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +23 -16
main.py CHANGED
@@ -893,6 +893,7 @@ def submit_quiz_attempt(quiz_id):
893
  # Modified speak_notes endpoint with ElevenLabs Studio API and chunking
894
  @app.route('/api/tutor/notes/<uuid:notes_id>/speak', methods=['POST'])
895
  def speak_notes(notes_id):
 
896
  user, error = verify_token(request.headers.get('Authorization'))
897
  if error:
898
  return jsonify({'error': error['error']}), error['status']
@@ -901,7 +902,7 @@ def speak_notes(notes_id):
901
  return jsonify({'error': 'Backend service unavailable'}), 503
902
 
903
  try:
904
- # Verify note ownership first
905
  note_res = supabase.table('notes') \
906
  .select('user_id, content, tts_audio_url') \
907
  .eq('id', notes_id) \
@@ -912,18 +913,19 @@ def speak_notes(notes_id):
912
  if not note_res.data:
913
  return jsonify({'error': 'Note not found or unauthorized'}), 404
914
 
915
- # Check user status and credits
916
  profile_res = supabase.table('profiles') \
917
  .select('credits, suspended') \
918
  .eq('id', user.id) \
919
- .single().execute()
920
-
 
921
  if profile_res.data['suspended']:
922
  return jsonify({'error': 'Account suspended'}), 403
923
  if profile_res.data['credits'] < 5:
924
  return jsonify({'error': 'Insufficient credits (Need 5)'}), 402
925
 
926
- # Return existing audio if available
927
  if note_res.data.get('tts_audio_url'):
928
  return jsonify({
929
  'success': True,
@@ -935,7 +937,7 @@ def speak_notes(notes_id):
935
  if not notes_content:
936
  return jsonify({'error': 'Notes content is empty'}), 400
937
 
938
- # --- Generate TTS Audio with chunking ---
939
  CHUNK_SIZE = 2000
940
  chunks = [notes_content[i:i+CHUNK_SIZE] for i in range(0, len(notes_content), CHUNK_SIZE)]
941
 
@@ -956,30 +958,30 @@ def speak_notes(notes_id):
956
  if not audio_bytes:
957
  raise RuntimeError("Generated empty audio file")
958
 
959
- # --- Save to Supabase Storage with RLS compliance ---
960
  bucket_name = 'notes-audio'
961
  file_path = f'{user.id}/{notes_id}.mp3'
962
 
963
  try:
964
- # Upload with owner metadata for RLS
965
  upload_res = supabase.storage.from_(bucket_name).upload(
966
  path=file_path,
967
  file=audio_bytes,
968
  file_options={
969
  'content-type': 'audio/mpeg',
970
  'cache-control': '3600',
971
- 'upsert': 'true',
972
- 'owner': user.id
973
  }
974
  )
975
 
976
- if upload_res.error:
977
- raise ConnectionError(upload_res.error.message)
 
978
 
979
  # Get public URL
980
  audio_url = supabase.storage.from_(bucket_name).get_public_url(file_path)
981
 
982
- # Update notes table with verification
983
  update_res = supabase.table('notes') \
984
  .update({'tts_audio_url': audio_url}) \
985
  .eq('id', notes_id) \
@@ -989,7 +991,7 @@ def speak_notes(notes_id):
989
  if update_res.error:
990
  raise ConnectionError(update_res.error.message)
991
 
992
- # Deduct credits with verification
993
  new_credits = profile_res.data['credits'] - 5
994
  credit_res = supabase.table('profiles') \
995
  .update({'credits': new_credits}) \
@@ -1007,11 +1009,16 @@ def speak_notes(notes_id):
1007
 
1008
  except Exception as upload_error:
1009
  # Clean up failed upload
1010
- supabase.storage.from_(bucket_name).remove([file_path])
 
 
 
 
 
1011
  raise upload_error
1012
 
1013
  except Exception as e:
1014
- logging.error(f"Speak error: {traceback.format_exc()}")
1015
  return jsonify({'error': str(e)}), 500
1016
 
1017
  # New endpoint to view existing audio URL
 
893
  # Modified speak_notes endpoint with ElevenLabs Studio API and chunking
894
  @app.route('/api/tutor/notes/<uuid:notes_id>/speak', methods=['POST'])
895
  def speak_notes(notes_id):
896
+ """Generate TTS audio for notes and store in Supabase Storage"""
897
  user, error = verify_token(request.headers.get('Authorization'))
898
  if error:
899
  return jsonify({'error': error['error']}), error['status']
 
902
  return jsonify({'error': 'Backend service unavailable'}), 503
903
 
904
  try:
905
+ # 1. Verify note ownership and get content
906
  note_res = supabase.table('notes') \
907
  .select('user_id, content, tts_audio_url') \
908
  .eq('id', notes_id) \
 
913
  if not note_res.data:
914
  return jsonify({'error': 'Note not found or unauthorized'}), 404
915
 
916
+ # 2. Check user status and credits
917
  profile_res = supabase.table('profiles') \
918
  .select('credits, suspended') \
919
  .eq('id', user.id) \
920
+ .single() \
921
+ .execute()
922
+
923
  if profile_res.data['suspended']:
924
  return jsonify({'error': 'Account suspended'}), 403
925
  if profile_res.data['credits'] < 5:
926
  return jsonify({'error': 'Insufficient credits (Need 5)'}), 402
927
 
928
+ # 3. Return existing audio if available
929
  if note_res.data.get('tts_audio_url'):
930
  return jsonify({
931
  'success': True,
 
937
  if not notes_content:
938
  return jsonify({'error': 'Notes content is empty'}), 400
939
 
940
+ # 4. Generate TTS Audio with chunking
941
  CHUNK_SIZE = 2000
942
  chunks = [notes_content[i:i+CHUNK_SIZE] for i in range(0, len(notes_content), CHUNK_SIZE)]
943
 
 
958
  if not audio_bytes:
959
  raise RuntimeError("Generated empty audio file")
960
 
961
+ # 5. Save to Supabase Storage
962
  bucket_name = 'notes-audio'
963
  file_path = f'{user.id}/{notes_id}.mp3'
964
 
965
  try:
966
+ # Upload audio file
967
  upload_res = supabase.storage.from_(bucket_name).upload(
968
  path=file_path,
969
  file=audio_bytes,
970
  file_options={
971
  'content-type': 'audio/mpeg',
972
  'cache-control': '3600',
973
+ 'upsert': 'true'
 
974
  }
975
  )
976
 
977
+ # Verify upload success (storage upload returns None on success)
978
+ if upload_res is not None:
979
+ raise ConnectionError("Audio upload failed")
980
 
981
  # Get public URL
982
  audio_url = supabase.storage.from_(bucket_name).get_public_url(file_path)
983
 
984
+ # 6. Update database records
985
  update_res = supabase.table('notes') \
986
  .update({'tts_audio_url': audio_url}) \
987
  .eq('id', notes_id) \
 
991
  if update_res.error:
992
  raise ConnectionError(update_res.error.message)
993
 
994
+ # 7. Deduct credits
995
  new_credits = profile_res.data['credits'] - 5
996
  credit_res = supabase.table('profiles') \
997
  .update({'credits': new_credits}) \
 
1009
 
1010
  except Exception as upload_error:
1011
  # Clean up failed upload
1012
+ try:
1013
+ supabase.storage.from_(bucket_name).remove([file_path])
1014
+ except Exception as cleanup_error:
1015
+ logging.error(f"Cleanup failed: {cleanup_error}")
1016
+
1017
+ logging.error(f"Upload failed: {str(upload_error)}")
1018
  raise upload_error
1019
 
1020
  except Exception as e:
1021
+ logging.error(f"Speak endpoint error: {traceback.format_exc()}")
1022
  return jsonify({'error': str(e)}), 500
1023
 
1024
  # New endpoint to view existing audio URL