diff --git a/sentence_api/main.py b/sentence_api/main.py
index 9fe5ea0..aa4dd95 100644
--- a/sentence_api/main.py
+++ b/sentence_api/main.py
@@ -39,6 +39,7 @@ from .models import (
AuthRequest,
AuthResponse,
CourseSummary,
+ CourseTitleUpdate,
SentenceBoundaryAdjust,
SentenceBoundary,
SentenceBoundaryDocument,
@@ -624,6 +625,20 @@ def create_app(
background_tasks.add_task(_process_safely, processor, video_hash)
return {"video_hash": video_hash.lower(), "status": "processing_queued"}
+ @application.put(
+ "/api/v1/admin/videos/{video_hash}/title",
+ response_model=VideoSummary,
+ dependencies=[Depends(require_admin)],
+ )
+ def update_course_title(
+ payload: CourseTitleUpdate,
+ video_hash: str = SHA256_PATH,
+ ) -> VideoSummary:
+ video = video_repository.update_title(video_hash, payload.title)
+ if video is None:
+ raise HTTPException(status_code=404, detail="Video was not found.")
+ return _video_summary(video, service_settings)
+
@application.put(
"/api/v1/admin/videos/{video_hash}/sentences/{sentence_index}",
response_model=SentenceBoundary,
diff --git a/sentence_api/models.py b/sentence_api/models.py
index 331b4b9..311bd95 100644
--- a/sentence_api/models.py
+++ b/sentence_api/models.py
@@ -92,6 +92,20 @@ class SentenceTextUpdate(BaseModel):
language: Optional[str] = Field(default=None, max_length=32)
+class CourseTitleUpdate(BaseModel):
+ model_config = ConfigDict(extra="forbid")
+
+ title: str = Field(max_length=200)
+
+ @field_validator("title")
+ @classmethod
+ def validate_title(cls, value: str) -> str:
+ cleaned = value.strip()
+ if not cleaned:
+ raise ValueError("title cannot be empty")
+ return cleaned
+
+
class PasswordResetRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
diff --git a/sentence_api/repository.py b/sentence_api/repository.py
index 6f9fb04..9cc92d1 100644
--- a/sentence_api/repository.py
+++ b/sentence_api/repository.py
@@ -349,6 +349,22 @@ class VideoRepository:
(remote_url, utc_now(), normalize_video_hash(video_hash)),
)
+ def update_title(self, video_hash: str, title: str) -> Optional[Dict[str, Any]]:
+ normalized_hash = normalize_video_hash(video_hash)
+ cleaned_title = title.strip()[:200]
+ if not cleaned_title:
+ raise ValueError("Title cannot be empty.")
+
+ with self._connect() as connection:
+ cursor = connection.execute(
+ "UPDATE videos SET title = ?, updated_at = ? WHERE video_hash = ?",
+ (cleaned_title, utc_now(), normalized_hash),
+ )
+ if cursor.rowcount == 0:
+ return None
+
+ return self.get_video(normalized_hash)
+
def list_videos(self) -> List[Dict[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
diff --git a/sentence_api/static/admin.html b/sentence_api/static/admin.html
index 25cd807..3a98581 100644
--- a/sentence_api/static/admin.html
+++ b/sentence_api/static/admin.html
@@ -114,6 +114,6 @@
-
+