From f4e12ad3678d6f35347f20a12777d7ae786a30b5 Mon Sep 17 00:00:00 2001 From: "Noel.Sudhish" Date: Mon, 11 May 2026 11:26:56 +0530 Subject: [PATCH 1/4] isl verse markers api --- backend/app/crud/isl_verse_markers_crud.py | 351 +++++++++++++++++++++ backend/app/db_models.py | 11 +- backend/app/main.py | 2 + backend/app/router/isl_verse_markers.py | 208 ++++++++++++ backend/app/schema.py | 161 ++++++++++ 5 files changed, 732 insertions(+), 1 deletion(-) create mode 100644 backend/app/crud/isl_verse_markers_crud.py create mode 100644 backend/app/router/isl_verse_markers.py diff --git a/backend/app/crud/isl_verse_markers_crud.py b/backend/app/crud/isl_verse_markers_crud.py new file mode 100644 index 0000000..1045d3b --- /dev/null +++ b/backend/app/crud/isl_verse_markers_crud.py @@ -0,0 +1,351 @@ +"""CRUD operations for the ISL verse markers API.""" +import re +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 add_verse_markers_bulk( +# db_session: Session, +# payload: Dict[int, List[Dict[str, Any]]], +# ): +# """Creates verse markers for the given ISL Bible ID.""" +# logger.info("Adding ISL verse markers") + +# # Check if ISL Bible exists +# isl_bible = db_session.query(db_models.IslVideo).filter_by( +# id=isl_bible_id +# ).first() + +# if not isl_bible: +# logger.error("ISL Bible %s not found",isl_bible_id) +# raise NotAvailableException( +# detail=f"ISL Bible {isl_bible_id} not found" +# ) + +# # Check if markers already exist +# existing = db_session.query(db_models.IslVerseMarkers).filter_by( +# isl_id=isl_bible_id +# ).first() + +# if existing: +# logger.error("Verse markers already exist for ISL Bible %s",isl_bible_id) +# raise AlreadyExistsException( +# detail=f"Verse markers already exist for ISL Bible {isl_bible_id}" +# ) + +# markers = _ensure_verse_zero(markers) + +# record = db_models.IslVerseMarkers( +# isl_id=isl_bible_id, +# verse_markers_json=markers +# ) + +# db_session.add(record) +# db_session.commit() +# db_session.refresh(record) + +# return record + +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_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_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_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() + + 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: + hh, mm, ss, ff = map(int, timestamp.split(":")) + return (((hh * 60) + mm) * 60 + ss) * 100 + ff + +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 v in range(start, end + 1): + if v not in valid_verses: + raise UnprocessableException( + detail=f"Invalid verse {v} 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_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) + # if isl_video.chapter != 0: + + # valid_verses = { + # int(row.verse) + # for row in ( + # db_session.query(db_models.CleanBible) + # .filter_by( + # resource_id=isl_video.resource_id, + # book_id=isl_video.book_id, + # chapter=isl_video.chapter + # ) + # .all() + # ) + # } + + # for marker in markers: + + # verse = marker["verse"] + + # # Allow intro marker + # if verse == 0: + # continue + + # # merged verse validation + # if isinstance(verse, str) and "_" in verse: + + # start, end = map(int, verse.split("_")) + + # for v in range(start, end + 1): + + # if v not in valid_verses: + # raise ValueError( + # f"Invalid verse {v} " + # f"for chapter {isl_video.chapter}" + # ) + + # else: + + # if int(verse) not in valid_verses: + # raise ValueError( + # f"Invalid verse {verse} " + # f"for chapter {isl_video.chapter}" + # ) + + record = db_models.IslVerseMarkers( + isl_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 \ No newline at end of file diff --git a/backend/app/db_models.py b/backend/app/db_models.py index 9c77fc0..4f80b43 100644 --- a/backend/app/db_models.py +++ b/backend/app/db_models.py @@ -309,4 +309,13 @@ class M2MClient(Base): client_secret_hash = Column(String, nullable=False) name = Column(String, nullable=False) is_active = Column(Boolean, nullable=False, default=True) - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) \ No newline at end of file + created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) + +class IslVerseMarkers(Base): + """Corresponds to table isl_verse_markers in vachan DB(postgres)""" + __tablename__ = "isl_verse_marker" + + id = Column(Integer, primary_key=True, index=True) + isl_id = Column(Integer, ForeignKey("isl_video.id", ondelete="CASCADE"), + nullable=False, unique=True) + verse_markers_json = Column(JSONB, nullable=False) diff --git a/backend/app/main.py b/backend/app/main.py index 5c57f53..94c8dac 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -32,6 +32,7 @@ from router.structural import router as structural_router from router.m2m_auth import router as m2m_auth_router from router.content_songs import router as content_songs_router +from router.isl_verse_markers import router as isl_verse_marker_router init_db() @@ -190,3 +191,4 @@ async def root(): app.include_router(structural_router) app.include_router(m2m_auth_router) app.include_router(content_songs_router) +app.include_router(isl_verse_marker_router) diff --git a/backend/app/router/isl_verse_markers.py b/backend/app/router/isl_verse_markers.py new file mode 100644 index 0000000..de81a63 --- /dev/null +++ b/backend/app/router/isl_verse_markers.py @@ -0,0 +1,208 @@ +"""ISL verse markers Endpoints.""" +from typing import Optional, List, Union,Dict,Any +from fastapi import APIRouter, Depends,Query +from fastapi.responses import JSONResponse +from sqlalchemy.orm import Session +from supertokens_python.recipe.session import SessionContainer +from supertokens_python.recipe.session.framework.fastapi import verify_session + + +import schema +from crud import isl_verse_markers_crud +from dependencies import get_db, logger +from auth import ( + ensure_user_from_session_async, + validate_admin_only, + verify_session_or_api_key +) +router = APIRouter(tags=["ISL Verse Markers"]) +verify_session_data=verify_session() + + +# @router.post( +# "/{isl_bible_id}/verse-markers", +# response_model=schema.VerseMarkersResponse, +# status_code=201 +# ) +# async def add_verse_markers( +# isl_bible_id: int, +# request: schema.VerseMarkersCreateRequest, +# session: SessionContainer = Depends(verify_session_with_approval), +# db_session: Session = Depends(get_db) +# ): +# """Creates verse markers for the given ISL Bible ID.""" +# logger.info("POST ISL verse markers API") +# validate_admin_only(session) +# await ensure_user_from_session_async(db_session, session) + +# record = isl_verse_markers_crud.add_verse_markers( +# db_session, +# isl_bible_id, +# [m.model_dump() for m in request.markers] +# ) + +# return { +# "id": record.id, +# "isl_bible_id": record.isl_id, +# "markers": record.verse_markers_json, +# "message": "Verse markers created successfully" +# } + +@router.post( + "/isl-verse-markers", + status_code=201 +) +async def add_verse_markers( + request: schema.IslVerseMarkersBulkCreateRequest, + session: SessionContainer = Depends( + verify_session_data + ), + db_session: Session = Depends(get_db) +): + """ + Bulk create ISL verse markers. + + Request format: + { + "1": [ + { + "verse": 0, + "time": "00:00:00:00" + } + ], + "2": [ + { + "verse": "12_13", + "time": "00:01:20:10" + } + ] + } + """ + + logger.info("POST bulk ISL verse markers API") + + validate_admin_only(session) + + await ensure_user_from_session_async( + db_session, + session + ) + + records = isl_verse_markers_crud.add_verse_markers_bulk( + db_session, + request.root + ) + + return { + "message": "Verse markers created successfully", + "created": records + } +@router.put( + "/isl-verse-markers/{isl_bible_id}", + response_model=schema.VerseMarkersResponse +) +async def update_verse_markers( + isl_bible_id: int, + request: schema.VerseMarkersCreateRequest, + session: SessionContainer = Depends(verify_session_data), + db_session: Session = Depends(get_db) +): + """Updates verse markers for the given ISL Bible ID.""" + logger.info("PUT ISL verse markers API") + validate_admin_only(session) + await ensure_user_from_session_async(db_session, session) + + record = isl_verse_markers_crud.update_verse_markers( + db_session, + isl_bible_id, + [m.model_dump() for m in request.markers] + ) + + return { + "id": record.id, + "isl_bible_id": record.isl_id, + "markers": record.verse_markers_json, + "message": "Verse markers updated successfully"} + + +@router.get( + "/isl-verse-markers", + response_model=Union[schema.VerseMarkersResponse, List[schema.VerseMarkersResponse]] +) +async def get_verse_markers( + isl_bible_id: Optional[int] = Query(None), + auth: Dict[str, Any] = Depends(verify_session_or_api_key), + db_session: Session = Depends(get_db) +): + """Retrives all verse markers""" + logger.info("GET ISL verse markers API") + if auth["auth_type"] == "session": + session = auth["session"] + validate_admin_only(session) + _, _ = await ensure_user_from_session_async(db_session, session) + + if isl_bible_id is not None: + record = isl_verse_markers_crud.get_verse_markers( + db_session, + isl_bible_id + ) + return { + "id": record.id, + "isl_bible_id": record.isl_id, + "markers": record.verse_markers_json + } + + records = isl_verse_markers_crud.get_all_verse_markers(db_session) + + return [ + { + "id": r.id, + "isl_bible_id": r.isl_id, + "markers": r.verse_markers_json + } + for r in records + ] + +def _build_bulk_delete_http_response(result): + data = result["data"] + deleted_count = data["deletedCount"] + + # Reverse order of checks to avoid duplication pattern + if not result["all_failed"] and not result["has_errors"]: + status_code = 200 + elif result["has_errors"] and not result["all_failed"]: + status_code = 207 + else: + status_code = 404 + + if deleted_count > 0: + message = f"Successfully deleted {deleted_count} ISL verse marker(s)" + else: + message = "No verse markers were deleted" + + return JSONResponse( + status_code=status_code, + content={**data, "message": message}, + ) + + +@router.delete( + "/isl-verse-markers/bulk-delete", + response_model=schema.IslVerseMarkersBulkDeleteResponse +) +async def delete_verse_markers_bulk( + request: schema.IslVerseMarkersBulkDelete, + session: SessionContainer = Depends(verify_session_data), + db_session: Session = Depends(get_db) +): + """Deletes all verse markers for the given ISL Bible IDs.""" + logger.info("DELETE ISL verse markers API") + validate_admin_only(session) + await ensure_user_from_session_async(db_session, session) + + result = isl_verse_markers_crud.delete_verse_markers_bulk( + db_session, + request.isl_bible_ids + ) + + return _build_bulk_delete_http_response(result) \ No newline at end of file diff --git a/backend/app/schema.py b/backend/app/schema.py index 77763c9..fdcdf97 100644 --- a/backend/app/schema.py +++ b/backend/app/schema.py @@ -2,6 +2,7 @@ from typing import Any, Union,Literal,Optional,Dict, List,Set import re from enum import Enum +from pydantic import RootModel from datetime import datetime,date from pydantic_core import PydanticCustomError from pydantic import BaseModel,field_validator,Field, model_validator,ValidationError @@ -2293,3 +2294,163 @@ class SongDeleteResponse(BaseModel): deletedIds: List[int] invalidIds: List[int] message: str + +# --- ISL Verse Markers Schemas --- +TIME_PATTERN = re.compile(r"^\d{2}:\d{2}:\d{2}:\d{2}$") + +class VerseMarkerItem(BaseModel): + """Schema for isl marker item""" + verse: Union[int, str] = Field(...) + time: str = Field(..., example="00:00:00:00") + + @field_validator("verse") + @classmethod + def validate_verse(cls, v): + """Validate verse - must be non-negative int or a range string like '1_3'""" + if isinstance(v, str): + if v.isdigit(): + return int(v) + + # Check for negative numeric string + if v.lstrip("-").isdigit(): + raise ValueError("verse cannot be negative") + + # Allow range format like "1_3" + parts = v.split("_") + if len(parts) == 2: + try: + start, end = int(parts[0]), int(parts[1]) + if start < 0 or end < 0: + raise ValueError("verse range values cannot be negative") + if start >= end: + raise ValueError("verse range start must be less than end") + return v + except ValueError as exc: + raise ValueError( + "verse range must be in format 'start_end' " + "with integers" + ) from exc + raise ValueError("verse must be a non-negative integer or range string like '1_3'") + + if isinstance(v, int): + if v < 0: + raise ValueError("verse cannot be negative") + return v + + raise ValueError("verse must be an integer or string") + + @field_validator("time") + @classmethod + def validate_time_format(cls, v): + """Validate time format""" + if not v or not v.strip(): + raise ValueError("time is required and cannot be empty") + + if not TIME_PATTERN.match(v): + raise ValueError("time must be in format HH:MM:SS:FF") + + hh, mm, ss, ff = map(int, v.split(":")) + + if mm >= 60 or ss >= 60: + raise ValueError("minutes and seconds must be between 00 and 59") + + return v + + +def timestamp_to_frames(timestamp: str) -> int: + """ + Converts HH:MM:SS:FF to sortable integer. + """ + hh, mm, ss, ff = map(int, timestamp.split(":")) + return (((hh * 60) + mm) * 60 + ss) * 100 + ff + + +class VerseMarkersCreateRequest(BaseModel): + """Schema for create and update isl marker request""" + markers: List[VerseMarkerItem] = Field(..., min_length=1) + + @field_validator("markers") + @classmethod + def validate_markers(cls, markers): + """Validate markers""" + + verse_numbers = [str(m.verse) for m in markers] + + duplicates = { + v for v in verse_numbers + if verse_numbers.count(v) > 1 + } + + if duplicates: + raise ValueError( + f"Duplicate verse numbers are not allowed: {sorted(duplicates)}" + ) + + previous_time = -1 + + for marker in markers: + current_time = timestamp_to_frames(marker.time) + + if current_time <= previous_time: + raise ValueError( + "timestamps must be in strictly increasing order" + ) + previous_time = current_time + + return markers + +# class IslVerseMarkersBulkCreateRequest(BaseModel): +# """ +# Bulk create request where key is isl_video_id +# """ + +# data: Dict[int, List[VerseMarkerItem]] + +# @field_validator("data") +# @classmethod +# def validate_data(cls, value): +# if not value: +# raise ValueError("data cannot be empty") + +# return value + +class IslVerseMarkersBulkCreateRequest( + RootModel[Dict[int, List[VerseMarkerItem]]] +): + """ + Bulk create request. + + Example: + { + "1": [ + { + "verse": 0, + "time": "00:00:00:00" + } + ], + "2": [ + { + "verse": "12_13", + "time": "00:01:20:10" + } + ] + } + """ + +class VerseMarkersResponse(BaseModel): + """Schema for bulk delete isl marker response""" + id: int + isl_bible_id: int + markers: List[VerseMarkerItem] + message: Optional[str] = None + +class IslVerseMarkersBulkDelete(BaseModel): + """Schema for bulk delete isl marker response""" + isl_bible_ids: List[int] + +class IslVerseMarkersBulkDeleteResponse(BaseModel): + """bulk delete isl marker item""" + deletedCount: int + deletedIds: List[int] + errors: Optional[List[str]] + From 8eb4ea6d1e5eede69ae7390463a4cf1265885a94 Mon Sep 17 00:00:00 2001 From: "Noel.Sudhish" Date: Mon, 11 May 2026 11:40:42 +0530 Subject: [PATCH 2/4] pylint verse markers api --- backend/app/crud/isl_verse_markers_crud.py | 17 ++++++++--------- backend/app/router/isl_verse_markers.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/backend/app/crud/isl_verse_markers_crud.py b/backend/app/crud/isl_verse_markers_crud.py index 1045d3b..a1a4920 100644 --- a/backend/app/crud/isl_verse_markers_crud.py +++ b/backend/app/crud/isl_verse_markers_crud.py @@ -1,5 +1,4 @@ """CRUD operations for the ISL verse markers API.""" -import re from typing import List, Dict, Any from sqlalchemy.orm import Session from sqlalchemy.exc import SQLAlchemyError @@ -13,7 +12,7 @@ def _ensure_verse_zero(markers): markers.insert(0, {"verse": 0, "time": "00:00:00:00"}) return markers - + # def add_verse_markers_bulk( # db_session: Session, @@ -158,8 +157,8 @@ def get_all_verse_markers(db_session: Session): return db_session.query(db_models.IslVerseMarkers).all() def _timestamp_to_frames(timestamp: str) -> int: - hh, mm, ss, ff = map(int, timestamp.split(":")) - return (((hh * 60) + mm) * 60 + ss) * 100 + ff + 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): """ @@ -189,10 +188,10 @@ def _validate_marker_verses(db_session, isl_bible, markers): if isinstance(verse, str) and "_" in verse: start, end = map(int, verse.split("_")) - for v in range(start, end + 1): - if v not in valid_verses: + for verse_number in range(start, end + 1): + if verse_number not in valid_verses: raise UnprocessableException( - detail=f"Invalid verse {v} for chapter {chapter}" + detail=f"Invalid verse {verse_number} for chapter {chapter}" ) else: @@ -200,7 +199,7 @@ def _validate_marker_verses(db_session, isl_bible, markers): raise UnprocessableException( detail=f"Invalid verse {verse} for chapter {chapter}" ) - + def _validate_timestamp_order(markers): previous = -1 @@ -348,4 +347,4 @@ def add_verse_markers_bulk( db_session.commit() - return created_records \ No newline at end of file + return created_records diff --git a/backend/app/router/isl_verse_markers.py b/backend/app/router/isl_verse_markers.py index de81a63..bebbe1f 100644 --- a/backend/app/router/isl_verse_markers.py +++ b/backend/app/router/isl_verse_markers.py @@ -205,4 +205,4 @@ async def delete_verse_markers_bulk( request.isl_bible_ids ) - return _build_bulk_delete_http_response(result) \ No newline at end of file + return _build_bulk_delete_http_response(result) From 4968851e12fbdde799f7da762589aff3656640bb Mon Sep 17 00:00:00 2001 From: "Noel.Sudhish" Date: Mon, 11 May 2026 12:05:11 +0530 Subject: [PATCH 3/4] db models column change --- backend/app/crud/isl_verse_markers_crud.py | 96 ++-------------------- backend/app/db_models.py | 2 +- backend/app/router/isl_verse_markers.py | 34 +------- 3 files changed, 9 insertions(+), 123 deletions(-) diff --git a/backend/app/crud/isl_verse_markers_crud.py b/backend/app/crud/isl_verse_markers_crud.py index a1a4920..6691354 100644 --- a/backend/app/crud/isl_verse_markers_crud.py +++ b/backend/app/crud/isl_verse_markers_crud.py @@ -13,49 +13,6 @@ def _ensure_verse_zero(markers): return markers - -# def add_verse_markers_bulk( -# db_session: Session, -# payload: Dict[int, List[Dict[str, Any]]], -# ): -# """Creates verse markers for the given ISL Bible ID.""" -# logger.info("Adding ISL verse markers") - -# # Check if ISL Bible exists -# isl_bible = db_session.query(db_models.IslVideo).filter_by( -# id=isl_bible_id -# ).first() - -# if not isl_bible: -# logger.error("ISL Bible %s not found",isl_bible_id) -# raise NotAvailableException( -# detail=f"ISL Bible {isl_bible_id} not found" -# ) - -# # Check if markers already exist -# existing = db_session.query(db_models.IslVerseMarkers).filter_by( -# isl_id=isl_bible_id -# ).first() - -# if existing: -# logger.error("Verse markers already exist for ISL Bible %s",isl_bible_id) -# raise AlreadyExistsException( -# detail=f"Verse markers already exist for ISL Bible {isl_bible_id}" -# ) - -# markers = _ensure_verse_zero(markers) - -# record = db_models.IslVerseMarkers( -# isl_id=isl_bible_id, -# verse_markers_json=markers -# ) - -# db_session.add(record) -# db_session.commit() -# db_session.refresh(record) - -# return record - def update_verse_markers( db_session: Session, isl_bible_id: int, @@ -75,7 +32,7 @@ def update_verse_markers( ) record = db_session.query(db_models.IslVerseMarkers).filter_by( - isl_id=isl_bible_id + isl_video_id=isl_bible_id ).first() if not record: @@ -98,7 +55,7 @@ def get_verse_markers( ): """Retrieves verse markers for the given islbible id""" record = db_session.query(db_models.IslVerseMarkers).filter_by( - isl_id=isl_bible_id + isl_video_id=isl_bible_id ).first() if not record: @@ -131,7 +88,7 @@ def delete_verse_markers_bulk( for isl_id in isl_bible_ids: try: record = db_session.query(db_models.IslVerseMarkers).filter_by( - isl_id=isl_id + isl_video_id=isl_id ).first() if not record: @@ -264,7 +221,7 @@ def add_verse_markers_bulk( # Check existing verse markers existing = ( db_session.query(db_models.IslVerseMarkers) - .filter_by(isl_id=isl_bible_id) + .filter_by(isl_video_id=isl_bible_id) .first() ) @@ -289,52 +246,9 @@ def add_verse_markers_bulk( # Validate verses against clean_bible # chapter 0 allowed _validate_marker_verses(db_session,isl_video,markers) - # if isl_video.chapter != 0: - - # valid_verses = { - # int(row.verse) - # for row in ( - # db_session.query(db_models.CleanBible) - # .filter_by( - # resource_id=isl_video.resource_id, - # book_id=isl_video.book_id, - # chapter=isl_video.chapter - # ) - # .all() - # ) - # } - - # for marker in markers: - - # verse = marker["verse"] - - # # Allow intro marker - # if verse == 0: - # continue - - # # merged verse validation - # if isinstance(verse, str) and "_" in verse: - - # start, end = map(int, verse.split("_")) - - # for v in range(start, end + 1): - - # if v not in valid_verses: - # raise ValueError( - # f"Invalid verse {v} " - # f"for chapter {isl_video.chapter}" - # ) - - # else: - - # if int(verse) not in valid_verses: - # raise ValueError( - # f"Invalid verse {verse} " - # f"for chapter {isl_video.chapter}" - # ) record = db_models.IslVerseMarkers( - isl_id=isl_bible_id, + isl_video_id=isl_bible_id, verse_markers_json=markers ) diff --git a/backend/app/db_models.py b/backend/app/db_models.py index 4f80b43..836db88 100644 --- a/backend/app/db_models.py +++ b/backend/app/db_models.py @@ -316,6 +316,6 @@ class IslVerseMarkers(Base): __tablename__ = "isl_verse_marker" id = Column(Integer, primary_key=True, index=True) - isl_id = Column(Integer, ForeignKey("isl_video.id", ondelete="CASCADE"), + isl_video_id = Column(Integer, ForeignKey("isl_video.id", ondelete="CASCADE"), nullable=False, unique=True) verse_markers_json = Column(JSONB, nullable=False) diff --git a/backend/app/router/isl_verse_markers.py b/backend/app/router/isl_verse_markers.py index bebbe1f..d205ab6 100644 --- a/backend/app/router/isl_verse_markers.py +++ b/backend/app/router/isl_verse_markers.py @@ -19,34 +19,6 @@ verify_session_data=verify_session() -# @router.post( -# "/{isl_bible_id}/verse-markers", -# response_model=schema.VerseMarkersResponse, -# status_code=201 -# ) -# async def add_verse_markers( -# isl_bible_id: int, -# request: schema.VerseMarkersCreateRequest, -# session: SessionContainer = Depends(verify_session_with_approval), -# db_session: Session = Depends(get_db) -# ): -# """Creates verse markers for the given ISL Bible ID.""" -# logger.info("POST ISL verse markers API") -# validate_admin_only(session) -# await ensure_user_from_session_async(db_session, session) - -# record = isl_verse_markers_crud.add_verse_markers( -# db_session, -# isl_bible_id, -# [m.model_dump() for m in request.markers] -# ) - -# return { -# "id": record.id, -# "isl_bible_id": record.isl_id, -# "markers": record.verse_markers_json, -# "message": "Verse markers created successfully" -# } @router.post( "/isl-verse-markers", @@ -120,7 +92,7 @@ async def update_verse_markers( return { "id": record.id, - "isl_bible_id": record.isl_id, + "isl_bible_id": record.isl_video_id, "markers": record.verse_markers_json, "message": "Verse markers updated successfully"} @@ -148,7 +120,7 @@ async def get_verse_markers( ) return { "id": record.id, - "isl_bible_id": record.isl_id, + "isl_bible_id": record.isl_video_id, "markers": record.verse_markers_json } @@ -157,7 +129,7 @@ async def get_verse_markers( return [ { "id": r.id, - "isl_bible_id": r.isl_id, + "isl_bible_id": r.isl_video_id, "markers": r.verse_markers_json } for r in records From ef532f1414f533bee8b2676db5b26c561007e1cb Mon Sep 17 00:00:00 2001 From: "Noel.Sudhish" Date: Mon, 11 May 2026 14:31:08 +0530 Subject: [PATCH 4/4] verse markers post api req body modifications --- backend/app/router/isl_verse_markers.py | 6 +++++ backend/app/schema.py | 34 ++++++++++++------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/backend/app/router/isl_verse_markers.py b/backend/app/router/isl_verse_markers.py index d205ab6..2d79f03 100644 --- a/backend/app/router/isl_verse_markers.py +++ b/backend/app/router/isl_verse_markers.py @@ -49,6 +49,12 @@ async def add_verse_markers( } ] } + + Each top-level key represents an isl_video_id from the isl_video table, + and its value contains the verse markers for that specific ISL video. + + Example: "1" means isl_video_id = 1, and all markers inside that array + will be mapped to that ISL video. """ logger.info("POST bulk ISL verse markers API") diff --git a/backend/app/schema.py b/backend/app/schema.py index f3b957e..64f6446 100644 --- a/backend/app/schema.py +++ b/backend/app/schema.py @@ -2426,25 +2426,23 @@ def validate_markers(cls, markers): class IslVerseMarkersBulkCreateRequest( RootModel[Dict[int, List[VerseMarkerItem]]] ): - """ - Bulk create request. - - Example: - { - "1": [ - { - "verse": 0, - "time": "00:00:00:00" - } - ], - "2": [ - { - "verse": "12_13", - "time": "00:01:20:10" + class Config: + json_schema_extra = { + "example": { + "1": [ + { + "verse": 1, + "time": "00:09:00:00" + } + ], + "2": [ + { + "verse": "2", + "time": "00:10:20:10" + } + ] } - ] - } - """ + } class VerseMarkersResponse(BaseModel): """Schema for bulk delete isl marker response"""