-
Notifications
You must be signed in to change notification settings - Fork 0
Islverse markers api #482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Islverse markers api #482
Changes from all commits
f4e12ad
8eb4ea6
4968851
b1572da
ef532f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| """CRUD operations for the ISL verse markers API.""" | ||
| from typing import List, Dict, Any | ||
| from sqlalchemy.orm import Session | ||
| from sqlalchemy.exc import SQLAlchemyError | ||
| import db_models | ||
| from custom_exceptions import NotAvailableException, AlreadyExistsException,UnprocessableException | ||
| from dependencies import logger | ||
|
|
||
|
|
||
| def _ensure_verse_zero(markers): | ||
| if not any(str(m["verse"]) == "0" for m in markers): | ||
| markers.insert(0, {"verse": 0, "time": "00:00:00:00"}) | ||
| return markers | ||
|
|
||
|
|
||
| def update_verse_markers( | ||
| db_session: Session, | ||
| isl_bible_id: int, | ||
| markers: List[Dict[str, Any]], | ||
| ): | ||
| """Updates verse markers for the given ISL Bible ID.""" | ||
| logger.info("Updating ISL verse markers") | ||
|
|
||
| isl_bible_rec = db_session.query(db_models.IslVideo).filter_by( | ||
| id=isl_bible_id | ||
| ).first() | ||
|
|
||
| if not isl_bible_rec: | ||
| logger.error("ISL Bible %s not found",isl_bible_id) | ||
| raise NotAvailableException( | ||
| detail=f"ISL Bible {isl_bible_id} not found" | ||
| ) | ||
|
|
||
| record = db_session.query(db_models.IslVerseMarkers).filter_by( | ||
| isl_video_id=isl_bible_id | ||
| ).first() | ||
|
|
||
| if not record: | ||
| logger.error("Verse markers not found for ISL Bible %s", isl_bible_id) | ||
| raise NotAvailableException( | ||
| detail=f"Verse markers not found for ISL Bible {isl_bible_id}" | ||
| ) | ||
|
|
||
| markers = _ensure_verse_zero(markers) | ||
| record.verse_markers_json = markers | ||
|
|
||
| db_session.commit() | ||
| db_session.refresh(record) | ||
|
|
||
| return record | ||
|
|
||
| def get_verse_markers( | ||
| db_session: Session, | ||
| isl_bible_id: int, | ||
| ): | ||
| """Retrieves verse markers for the given islbible id""" | ||
| record = db_session.query(db_models.IslVerseMarkers).filter_by( | ||
| isl_video_id=isl_bible_id | ||
| ).first() | ||
|
|
||
| if not record: | ||
| logger.error("Verse markers not found for ISL Bible %s",isl_bible_id) | ||
| raise NotAvailableException( | ||
| detail=f"Verse markers not found for ISL Bible {isl_bible_id}" | ||
| ) | ||
|
|
||
| return record | ||
| def _build_bulk_delete_response(deleted_ids, errors): | ||
| """Build consistent bulk delete response structure.""" | ||
| return { | ||
| "data": {"deletedCount": len(deleted_ids), | ||
| "deletedIds": deleted_ids, | ||
| "errors": errors if errors else None, | ||
| }, | ||
| "all_failed": len(deleted_ids) == 0 and len(errors) > 0, | ||
| "has_errors": len(errors) > 0, | ||
| } | ||
|
|
||
| def delete_verse_markers_bulk( | ||
| db_session: Session, | ||
| isl_bible_ids: List[int], | ||
| ): | ||
| """Deletes verse markers for the given ISL Bible IDs.""" | ||
| logger.info("Deleting ISL verse markers") | ||
| deleted_ids = [] | ||
| errors = [] | ||
|
|
||
| for isl_id in isl_bible_ids: | ||
| try: | ||
| record = db_session.query(db_models.IslVerseMarkers).filter_by( | ||
| isl_video_id=isl_id | ||
| ).first() | ||
|
|
||
| if not record: | ||
| logger.error("Verse markers not found for ISL Bible %s",isl_id) | ||
| errors.append(f"Verse markers not found for ISL Bible {isl_id}") | ||
| continue | ||
|
|
||
| db_session.delete(record) | ||
| deleted_ids.append(isl_id) | ||
|
|
||
| except SQLAlchemyError as exc: | ||
| logger.error("Error deleting ISL Bible %s: %s", isl_id, exc) | ||
| errors.append(f"Error deleting ISL Bible {isl_id}: {exc}") | ||
|
|
||
| db_session.commit() | ||
|
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The loop catches Severity Level: Major
|
||
|
|
||
| return _build_bulk_delete_response(deleted_ids, errors) | ||
|
|
||
|
|
||
|
|
||
| def get_all_verse_markers(db_session: Session): | ||
| """Get all verse markers without isl bible id""" | ||
| return db_session.query(db_models.IslVerseMarkers).all() | ||
|
|
||
| def _timestamp_to_frames(timestamp: str) -> int: | ||
| hours, minutes, seconds, frames = map(int, timestamp.split(":")) | ||
| return (((hours * 60) + minutes) * 60 + seconds) * 100 + frames | ||
|
|
||
| def _validate_marker_verses(db_session, isl_bible, markers): | ||
| """ | ||
| Validate verses exist for given book/chapter. | ||
| """ | ||
|
|
||
| book_id = isl_bible.book_id | ||
| chapter = isl_bible.chapter | ||
|
|
||
| # Allow intro chapter | ||
| if chapter == 0: | ||
| return | ||
| valid_verses = { | ||
| int(row.verse) | ||
| for row in db_session.query(db_models.CleanBible).filter_by( | ||
| book_id=book_id, | ||
| chapter=chapter | ||
| ).all() | ||
| } | ||
| for marker in markers: | ||
| verse = marker["verse"] | ||
|
|
||
| # intro marker | ||
| if verse == 0: | ||
| continue | ||
|
|
||
| if isinstance(verse, str) and "_" in verse: | ||
| start, end = map(int, verse.split("_")) | ||
|
|
||
| for verse_number in range(start, end + 1): | ||
| if verse_number not in valid_verses: | ||
| raise UnprocessableException( | ||
| detail=f"Invalid verse {verse_number} for chapter {chapter}" | ||
| ) | ||
|
|
||
| else: | ||
| if int(verse) not in valid_verses: | ||
| raise UnprocessableException( | ||
| detail=f"Invalid verse {verse} for chapter {chapter}" | ||
| ) | ||
|
|
||
| def _validate_timestamp_order(markers): | ||
| previous = -1 | ||
|
|
||
| for marker in markers: | ||
| current = _timestamp_to_frames(marker["time"]) | ||
|
|
||
| if current <= previous: | ||
| raise UnprocessableException( | ||
| detail="timestamps must be in increasing order" | ||
| ) | ||
|
|
||
| previous = current | ||
|
|
||
| def add_verse_markers_bulk( | ||
| db_session: Session, | ||
| payload: Dict[int, List[Dict[str, Any]]], | ||
| ): | ||
| """ | ||
| Bulk create verse markers. | ||
|
|
||
| Payload format: | ||
| { | ||
| 1: [ | ||
| { | ||
| "verse": 0, | ||
| "time": "00:00:00:00" | ||
| } | ||
| ], | ||
| 2: [ | ||
| { | ||
| "verse": "12_13", | ||
| "time": "00:01:20:10" | ||
| } | ||
| ] | ||
| } | ||
| """ | ||
|
|
||
| logger.info("Adding bulk ISL verse markers") | ||
|
|
||
| created_records = [] | ||
|
|
||
| for isl_bible_id, markers in payload.items(): | ||
| markers = [ | ||
| m.model_dump() if hasattr(m, "model_dump") else m | ||
| for m in markers] | ||
| # Validate ISL video exists | ||
| isl_video = ( | ||
| db_session.query(db_models.IslVideo) | ||
| .filter_by(id=isl_bible_id) | ||
| .first() | ||
| ) | ||
|
|
||
| if not isl_video: | ||
| logger.error( | ||
| "ISL Video %s not found", | ||
| isl_bible_id | ||
| ) | ||
| raise NotAvailableException( | ||
| detail=f"ISL Video {isl_bible_id} not found" | ||
| ) | ||
|
|
||
| # Check existing verse markers | ||
| existing = ( | ||
| db_session.query(db_models.IslVerseMarkers) | ||
| .filter_by(isl_video_id=isl_bible_id) | ||
| .first() | ||
| ) | ||
|
|
||
| if existing: | ||
| logger.error( | ||
| "Verse markers already exist for ISL Video %s", | ||
| isl_bible_id | ||
| ) | ||
| raise AlreadyExistsException( | ||
| detail=( | ||
| f"Verse markers already exist " | ||
| f"for ISL Video {isl_bible_id}" | ||
| ) | ||
| ) | ||
|
|
||
| # Ensure verse 0 exists | ||
| markers = _ensure_verse_zero(markers) | ||
|
|
||
| # Validate timestamp order | ||
| _validate_timestamp_order(markers) | ||
|
|
||
| # Validate verses against clean_bible | ||
| # chapter 0 allowed | ||
| _validate_marker_verses(db_session,isl_video,markers) | ||
|
|
||
| record = db_models.IslVerseMarkers( | ||
| isl_video_id=isl_bible_id, | ||
| verse_markers_json=markers | ||
| ) | ||
|
|
||
| db_session.add(record) | ||
|
|
||
| created_records.append({ | ||
| "id": isl_bible_id, | ||
| "markers": markers | ||
| }) | ||
|
|
||
| db_session.commit() | ||
|
|
||
| return created_records | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: The update path writes marker data without validating verse values against
clean_bible, unlike the create path. This allows updates to persist non-existent verses/ranges for the book/chapter, causing inconsistent data and downstream lookup failures. Run the same verse validation used in bulk create before saving. [incomplete implementation]Severity Level: Major⚠️
Steps of Reproduction ✅
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖