This commit is contained in:
Igor Loskutov
2025-06-19 10:13:57 -04:00
parent 98acf298d6
commit 6cb46dc64f
10 changed files with 59 additions and 31 deletions

View File

@@ -189,15 +189,19 @@ class MeetingController:
class MeetingConsentController: class MeetingConsentController:
async def get_by_meeting_id(self, meeting_id: str) -> list[MeetingConsent]: 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) results = await database.fetch_all(query)
return [MeetingConsent(**result) for result in results] 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""" """Get existing consent for a specific user and meeting"""
query = meeting_consent.select().where( query = meeting_consent.select().where(
meeting_consent.c.meeting_id == meeting_id, 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) result = await database.fetch_one(query)
return MeetingConsent(**result) if result else None return MeetingConsent(**result) if result else None
@@ -207,13 +211,17 @@ class MeetingConsentController:
if consent.user_id: if consent.user_id:
# For authenticated users, check if consent already exists # For authenticated users, check if consent already exists
# not transactional but we're ok with that; the consents ain't deleted anyways # 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: if existing:
query = meeting_consent.update().where( query = (
meeting_consent.c.id == existing.id meeting_consent.update()
).values( .where(meeting_consent.c.id == existing.id)
consent_given=consent.consent_given, .values(
consent_timestamp=consent.consent_timestamp, consent_given=consent.consent_given,
consent_timestamp=consent.consent_timestamp,
)
) )
await database.execute(query) await database.execute(query)
@@ -229,7 +237,7 @@ class MeetingConsentController:
"""Check if any participant denied consent for this meeting""" """Check if any participant denied consent for this meeting"""
query = meeting_consent.select().where( query = meeting_consent.select().where(
meeting_consent.c.meeting_id == meeting_id, 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) result = await database.fetch_one(query)
return result is not None return result is not None

View File

@@ -22,6 +22,7 @@ recordings = sa.Table(
sa.Column("meeting_id", sa.String), sa.Column("meeting_id", sa.String),
) )
class Recording(BaseModel): class Recording(BaseModel):
id: str = Field(default_factory=generate_uuid4) id: str = Field(default_factory=generate_uuid4)
bucket_name: str bucket_name: str

View File

@@ -76,6 +76,7 @@ transcripts = sqlalchemy.Table(
sqlalchemy.Column("audio_deleted", sqlalchemy.Boolean, nullable=True), sqlalchemy.Column("audio_deleted", sqlalchemy.Boolean, nullable=True),
) )
def generate_transcript_name() -> str: def generate_transcript_name() -> str:
now = datetime.utcnow() now = datetime.utcnow()
return f"Transcript {now.strftime('%Y-%m-%d %H:%M:%S')}" return f"Transcript {now.strftime('%Y-%m-%d %H:%M:%S')}"
@@ -550,12 +551,16 @@ class TranscriptController:
""" """
if transcript.audio_deleted: 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": if transcript.audio_location == "local":
# store the audio on external storage if it's not already there # store the audio on external storage if it's not already there
if not transcript.audio_mp3_filename.exists(): 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( await get_transcripts_storage().put_file(
transcript.storage_audio_path, transcript.storage_audio_path,

View File

@@ -581,7 +581,9 @@ async def cleanup_consent(transcript: Transcript, logger: Logger):
if recording and recording.meeting_id: if recording and recording.meeting_id:
meeting = await meetings_controller.get_by_id(recording.meeting_id) meeting = await meetings_controller.get_by_id(recording.meeting_id)
if meeting: 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: except Exception as e:
logger.error(f"Failed to get fetch consent: {e}") logger.error(f"Failed to get fetch consent: {e}")
consent_denied = True 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, aws_secret_access_key=settings.AWS_WHEREBY_ACCESS_KEY_SECRET,
) )
try: try:
s3_whereby.delete_object(Bucket=recording.bucket_name, Key=recording.object_key) s3_whereby.delete_object(
logger.info(f"Deleted original Whereby recording: {recording.bucket_name}/{recording.object_key}") 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: except Exception as e:
logger.error(f"Failed to delete Whereby recording: {e}") logger.error(f"Failed to delete Whereby recording: {e}")
@@ -613,15 +619,17 @@ async def cleanup_consent(transcript: Transcript, logger: Logger):
storage = get_transcripts_storage() storage = get_transcripts_storage()
try: try:
await storage.delete_file(transcript.storage_audio_path) 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: except Exception as e:
logger.error(f"Failed to delete processed audio: {e}") logger.error(f"Failed to delete processed audio: {e}")
# 3. Delete local audio files # 3. Delete local audio files
try: 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) 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) transcript.audio_wav_filename.unlink(missing_ok=True)
except Exception as e: except Exception as e:
logger.error(f"Failed to delete local audio files: {e}") logger.error(f"Failed to delete local audio files: {e}")

View File

@@ -1,7 +1,9 @@
from .base import Storage # noqa from .base import Storage # noqa
def get_transcripts_storage() -> Storage: def get_transcripts_storage() -> Storage:
from reflector.settings import settings from reflector.settings import settings
return Storage.get_instance( return Storage.get_instance(
name=settings.TRANSCRIPT_STORAGE_BACKEND, name=settings.TRANSCRIPT_STORAGE_BACKEND,
settings_prefix="TRANSCRIPT_STORAGE_", settings_prefix="TRANSCRIPT_STORAGE_",

View File

@@ -45,6 +45,7 @@ def range_requests_response(
if not os.path.exists(file_path): if not os.path.exists(file_path):
from fastapi import HTTPException from fastapi import HTTPException
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
file_size = os.stat(file_path).st_size file_size = os.stat(file_path).st_size

View File

@@ -87,7 +87,9 @@ async def transcript_get_audio_mp3(
) )
if transcript.audio_deleted: 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(): if not transcript.audio_mp3_filename.exists():
raise HTTPException(status_code=404, detail="Audio file not found") raise HTTPException(status_code=404, detail="Audio file not found")

View File

@@ -131,6 +131,7 @@ async def process_recording(bucket_name: str, object_key: str):
task_pipeline_process.delay(transcript_id=transcript.id) task_pipeline_process.delay(transcript_id=transcript.id)
@shared_task @shared_task
@asynctask @asynctask
async def process_meetings(): async def process_meetings():