diff --git a/server/reflector/db/meetings.py b/server/reflector/db/meetings.py index 9e2f89bd..ec9ad70b 100644 --- a/server/reflector/db/meetings.py +++ b/server/reflector/db/meetings.py @@ -189,15 +189,19 @@ class MeetingController: class MeetingConsentController: async def get_by_meeting_id(self, meeting_id: str) -> list[MeetingConsent]: - query = meeting_consent.select().where(meeting_consent.c.meeting_id == meeting_id) + query = meeting_consent.select().where( + meeting_consent.c.meeting_id == meeting_id + ) results = await database.fetch_all(query) return [MeetingConsent(**result) for result in results] - - async def get_by_meeting_and_user(self, meeting_id: str, user_id: str) -> MeetingConsent | None: + + async def get_by_meeting_and_user( + self, meeting_id: str, user_id: str + ) -> MeetingConsent | None: """Get existing consent for a specific user and meeting""" query = meeting_consent.select().where( meeting_consent.c.meeting_id == meeting_id, - meeting_consent.c.user_id == user_id + meeting_consent.c.user_id == user_id, ) result = await database.fetch_one(query) return MeetingConsent(**result) if result else None @@ -207,16 +211,20 @@ class MeetingConsentController: if consent.user_id: # For authenticated users, check if consent already exists # not transactional but we're ok with that; the consents ain't deleted anyways - existing = await self.get_by_meeting_and_user(consent.meeting_id, consent.user_id) + existing = await self.get_by_meeting_and_user( + consent.meeting_id, consent.user_id + ) if existing: - query = meeting_consent.update().where( - meeting_consent.c.id == existing.id - ).values( - consent_given=consent.consent_given, - consent_timestamp=consent.consent_timestamp, + query = ( + meeting_consent.update() + .where(meeting_consent.c.id == existing.id) + .values( + consent_given=consent.consent_given, + consent_timestamp=consent.consent_timestamp, + ) ) await database.execute(query) - + existing.consent_given = consent.consent_given existing.consent_timestamp = consent.consent_timestamp return existing @@ -224,12 +232,12 @@ class MeetingConsentController: query = meeting_consent.insert().values(**consent.model_dump()) await database.execute(query) return consent - + async def has_any_denial(self, meeting_id: str) -> bool: """Check if any participant denied consent for this meeting""" query = meeting_consent.select().where( meeting_consent.c.meeting_id == meeting_id, - meeting_consent.c.consent_given.is_(False) + meeting_consent.c.consent_given.is_(False), ) result = await database.fetch_one(query) return result is not None diff --git a/server/reflector/db/recordings.py b/server/reflector/db/recordings.py index 4b517754..31670609 100644 --- a/server/reflector/db/recordings.py +++ b/server/reflector/db/recordings.py @@ -22,6 +22,7 @@ recordings = sa.Table( sa.Column("meeting_id", sa.String), ) + class Recording(BaseModel): id: str = Field(default_factory=generate_uuid4) bucket_name: str diff --git a/server/reflector/db/transcripts.py b/server/reflector/db/transcripts.py index 524de3ed..67f66aac 100644 --- a/server/reflector/db/transcripts.py +++ b/server/reflector/db/transcripts.py @@ -76,6 +76,7 @@ transcripts = sqlalchemy.Table( sqlalchemy.Column("audio_deleted", sqlalchemy.Boolean, nullable=True), ) + def generate_transcript_name() -> str: now = datetime.utcnow() return f"Transcript {now.strftime('%Y-%m-%d %H:%M:%S')}" @@ -550,13 +551,17 @@ class TranscriptController: """ if transcript.audio_deleted: - raise FileNotFoundError(f"Invalid state of transcript {transcript.id}: audio_deleted mark is set true") + raise FileNotFoundError( + f"Invalid state of transcript {transcript.id}: audio_deleted mark is set true" + ) if transcript.audio_location == "local": # store the audio on external storage if it's not already there if not transcript.audio_mp3_filename.exists(): - raise FileNotFoundError(f"Audio file not found: {transcript.audio_mp3_filename}") - + raise FileNotFoundError( + f"Audio file not found: {transcript.audio_mp3_filename}" + ) + await get_transcripts_storage().put_file( transcript.storage_audio_path, transcript.audio_mp3_filename.read_bytes(), diff --git a/server/reflector/pipelines/main_live_pipeline.py b/server/reflector/pipelines/main_live_pipeline.py index 8b00b9eb..aca28586 100644 --- a/server/reflector/pipelines/main_live_pipeline.py +++ b/server/reflector/pipelines/main_live_pipeline.py @@ -581,7 +581,9 @@ async def cleanup_consent(transcript: Transcript, logger: Logger): if recording and recording.meeting_id: meeting = await meetings_controller.get_by_id(recording.meeting_id) if meeting: - consent_denied = await meeting_consent_controller.has_any_denial(meeting.id) + consent_denied = await meeting_consent_controller.has_any_denial( + meeting.id + ) except Exception as e: logger.error(f"Failed to get fetch consent: {e}") consent_denied = True @@ -600,8 +602,12 @@ async def cleanup_consent(transcript: Transcript, logger: Logger): aws_secret_access_key=settings.AWS_WHEREBY_ACCESS_KEY_SECRET, ) try: - s3_whereby.delete_object(Bucket=recording.bucket_name, Key=recording.object_key) - logger.info(f"Deleted original Whereby recording: {recording.bucket_name}/{recording.object_key}") + s3_whereby.delete_object( + Bucket=recording.bucket_name, Key=recording.object_key + ) + logger.info( + f"Deleted original Whereby recording: {recording.bucket_name}/{recording.object_key}" + ) except Exception as e: logger.error(f"Failed to delete Whereby recording: {e}") @@ -613,19 +619,21 @@ async def cleanup_consent(transcript: Transcript, logger: Logger): storage = get_transcripts_storage() try: await storage.delete_file(transcript.storage_audio_path) - logger.info(f"Deleted processed audio from storage: {transcript.storage_audio_path}") + logger.info( + f"Deleted processed audio from storage: {transcript.storage_audio_path}" + ) except Exception as e: logger.error(f"Failed to delete processed audio: {e}") # 3. Delete local audio files try: - if hasattr(transcript, 'audio_mp3_filename') and transcript.audio_mp3_filename: + if hasattr(transcript, "audio_mp3_filename") and transcript.audio_mp3_filename: transcript.audio_mp3_filename.unlink(missing_ok=True) - if hasattr(transcript, 'audio_wav_filename') and transcript.audio_wav_filename: + if hasattr(transcript, "audio_wav_filename") and transcript.audio_wav_filename: transcript.audio_wav_filename.unlink(missing_ok=True) except Exception as e: logger.error(f"Failed to delete local audio files: {e}") - + logger.info("Consent cleanup done") diff --git a/server/reflector/storage/__init__.py b/server/reflector/storage/__init__.py index 6b0eed3a..ee6c7318 100644 --- a/server/reflector/storage/__init__.py +++ b/server/reflector/storage/__init__.py @@ -1,7 +1,9 @@ from .base import Storage # noqa + def get_transcripts_storage() -> Storage: from reflector.settings import settings + return Storage.get_instance( name=settings.TRANSCRIPT_STORAGE_BACKEND, settings_prefix="TRANSCRIPT_STORAGE_", diff --git a/server/reflector/utils/__init__.py b/server/reflector/utils/__init__.py index 254ce4b6..909f735b 100644 --- a/server/reflector/utils/__init__.py +++ b/server/reflector/utils/__init__.py @@ -2,4 +2,4 @@ from uuid import uuid4 def generate_uuid4() -> str: - return str(uuid4()) \ No newline at end of file + return str(uuid4()) diff --git a/server/reflector/views/_range_requests_response.py b/server/reflector/views/_range_requests_response.py index f74529a0..8e3770ae 100644 --- a/server/reflector/views/_range_requests_response.py +++ b/server/reflector/views/_range_requests_response.py @@ -45,6 +45,7 @@ def range_requests_response( if not os.path.exists(file_path): from fastapi import HTTPException + raise HTTPException(status_code=404, detail="File not found") file_size = os.stat(file_path).st_size diff --git a/server/reflector/views/meetings.py b/server/reflector/views/meetings.py index d6d30610..832b7c29 100644 --- a/server/reflector/views/meetings.py +++ b/server/reflector/views/meetings.py @@ -28,16 +28,16 @@ async def meeting_audio_consent( meeting = await meetings_controller.get_by_id(meeting_id) if not meeting: raise HTTPException(status_code=404, detail="Meeting not found") - + user_id = user["sub"] if user else None - + consent = MeetingConsent( meeting_id=meeting_id, user_id=user_id, consent_given=request.consent_given, consent_timestamp=datetime.utcnow(), ) - + updated_consent = await meeting_consent_controller.upsert(consent) - - return {"status": "success", "consent_id": updated_consent.id} \ No newline at end of file + + return {"status": "success", "consent_id": updated_consent.id} diff --git a/server/reflector/views/transcripts_audio.py b/server/reflector/views/transcripts_audio.py index 0c177163..baf6772a 100644 --- a/server/reflector/views/transcripts_audio.py +++ b/server/reflector/views/transcripts_audio.py @@ -87,8 +87,10 @@ async def transcript_get_audio_mp3( ) if transcript.audio_deleted: - raise HTTPException(status_code=404, detail="Audio unavailable due to privacy settings") - + raise HTTPException( + status_code=404, detail="Audio unavailable due to privacy settings" + ) + if not transcript.audio_mp3_filename.exists(): raise HTTPException(status_code=404, detail="Audio file not found") diff --git a/server/reflector/worker/process.py b/server/reflector/worker/process.py index 706396bb..85e249e5 100644 --- a/server/reflector/worker/process.py +++ b/server/reflector/worker/process.py @@ -131,6 +131,7 @@ async def process_recording(bucket_name: str, object_key: str): task_pipeline_process.delay(transcript_id=transcript.id) + @shared_task @asynctask async def process_meetings():