diff --git a/apps/api/app/api/routes/videos.py b/apps/api/app/api/routes/videos.py index e8a0b52bd..90c6c08a7 100755 --- a/apps/api/app/api/routes/videos.py +++ b/apps/api/app/api/routes/videos.py @@ -93,7 +93,7 @@ def get_video( ): """获取单个成片详情。""" use_case = GetGeneratedVideoUseCase(repo) - item = use_case.execute(video_id) + item = use_case.execute(video_id, user_id=current_user.user.id) if item is None: raise HTTPException(status_code=404, detail="Video not found") return _to_video_response(item, storage) @@ -109,7 +109,7 @@ def update_video_review_status( ): """更新成片复核状态:pending_review / approved / rejected。""" use_case = UpdateVideoReviewStatusUseCase(repo) - item = use_case.execute(video_id, request.review_status) + item = use_case.execute(video_id, request.review_status, user_id=current_user.user.id) if item is None: raise HTTPException(status_code=404, detail="Video not found") logger.info( @@ -127,7 +127,7 @@ def delete_video( ): """删除单个成片(硬删除)。""" use_case = GetGeneratedVideoUseCase(repo) - item = use_case.execute(video_id) + item = use_case.execute(video_id, user_id=current_user.user.id) if item is None: raise HTTPException(status_code=404, detail="Video not found") @@ -139,7 +139,7 @@ def delete_video( except Exception: logger.warning("删除 OSS 视频文件失败,跳过: video_id=%s", video_id) - repo.delete(video_id) + repo.delete(video_id, user_id=current_user.user.id) return Response(status_code=204) @@ -151,7 +151,7 @@ def batch_delete_videos( current_user: AuthenticatedUser = Depends(get_current_user), ): """批量删除成片。""" - videos = repo.get_by_ids(request.video_ids) + videos = repo.get_by_ids(request.video_ids, user_id=current_user.user.id) existing_ids = {v.id for v in videos} failed_ids = [vid for vid in request.video_ids if vid not in existing_ids] failed_details = {vid: "Video not found" for vid in failed_ids} @@ -165,7 +165,7 @@ def batch_delete_videos( except Exception: logger.warning("批量删除 OSS 视频文件失败,跳过: video_id=%s", video.id) - success_count = repo.batch_delete(list(existing_ids)) + success_count = repo.batch_delete(list(existing_ids), user_id=current_user.user.id) return BatchOperationResponse( success_count=success_count, failed_ids=failed_ids, @@ -188,9 +188,9 @@ def batch_download_videos( if len(request.video_ids) > 50: raise HTTPException(status_code=400, detail="Maximum 50 videos per batch download") - # 校验视频都存在 + # 校验视频都存在且属于当前用户 use_case = GetVideosByIdsUseCase(repo) - videos = use_case.execute(request.video_ids) + videos = use_case.execute(request.video_ids, user_id=current_user.user.id) if len(videos) != len(request.video_ids): raise HTTPException(status_code=404, detail="Some videos not found") diff --git a/packages/adapters/sqlalchemy_impl/generated_video_repository.py b/packages/adapters/sqlalchemy_impl/generated_video_repository.py index 44569958f..8e766309b 100755 --- a/packages/adapters/sqlalchemy_impl/generated_video_repository.py +++ b/packages/adapters/sqlalchemy_impl/generated_video_repository.py @@ -37,8 +37,11 @@ class SQLAlchemyGeneratedVideoRepository: self.session.commit() return video - def get(self, video_id: str) -> GeneratedVideo | None: - model = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id).first() + def get(self, video_id: str, *, user_id: str | None = None) -> GeneratedVideo | None: + query = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id) + if user_id: + query = query.filter(GeneratedVideoModel.user_id == user_id) + model = query.first() if model is None: return None return GeneratedVideo( @@ -135,9 +138,14 @@ class SQLAlchemyGeneratedVideoRepository: return [self._to_domain(model) for model in models], total - def update_review_status(self, video_id: str, review_status: str) -> GeneratedVideo | None: + def update_review_status( + self, video_id: str, review_status: str, *, user_id: str | None = None + ) -> GeneratedVideo | None: """更新成片复核状态。""" - model = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id).first() + query = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id) + if user_id: + query = query.filter(GeneratedVideoModel.user_id == user_id) + model = query.first() if model is None: return None model.review_status = review_status @@ -155,31 +163,36 @@ class SQLAlchemyGeneratedVideoRepository: self.session.commit() return True - def get_by_ids(self, video_ids: list[str]) -> list[GeneratedVideo]: + def get_by_ids(self, video_ids: list[str], *, user_id: str | None = None) -> list[GeneratedVideo]: """批量获取成片记录。""" if not video_ids: return [] - models = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id.in_(video_ids)).all() + query = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id.in_(video_ids)) + if user_id: + query = query.filter(GeneratedVideoModel.user_id == user_id) + models = query.all() return [self._to_domain(model) for model in models] - def delete(self, video_id: str) -> bool: + def delete(self, video_id: str, *, user_id: str | None = None) -> bool: """删除单个成片记录(硬删除)。""" - model = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id).first() + query = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == video_id) + if user_id: + query = query.filter(GeneratedVideoModel.user_id == user_id) + model = query.first() if model is None: return False self.session.delete(model) self.session.commit() return True - def batch_delete(self, video_ids: list[str]) -> int: + def batch_delete(self, video_ids: list[str], *, user_id: str | None = None) -> int: """批量删除成片记录,返回成功删除数量。""" if not video_ids: return 0 - result = ( - self.session.query(GeneratedVideoModel) - .filter(GeneratedVideoModel.id.in_(video_ids)) - .delete(synchronize_session=False) - ) + query = self.session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id.in_(video_ids)) + if user_id: + query = query.filter(GeneratedVideoModel.user_id == user_id) + result = query.delete(synchronize_session=False) self.session.commit() return result diff --git a/packages/application/generated_videos.py b/packages/application/generated_videos.py index aa8a3d4cd..63caf7541 100755 --- a/packages/application/generated_videos.py +++ b/packages/application/generated_videos.py @@ -46,8 +46,8 @@ class GetGeneratedVideoUseCase: def __init__(self, generated_video_repository: GeneratedVideoRepository): self.generated_video_repository = generated_video_repository - def execute(self, video_id: str) -> GeneratedVideo | None: - return self.generated_video_repository.get(video_id) + def execute(self, video_id: str, *, user_id: str | None = None) -> GeneratedVideo | None: + return self.generated_video_repository.get(video_id, user_id=user_id) class ListGeneratedVideosByTaskUseCase: @@ -64,8 +64,8 @@ class GetGeneratedVideoDownloadUrlUseCase: def __init__(self, generated_video_repository: GeneratedVideoRepository): self.generated_video_repository = generated_video_repository - def execute(self, video_id: str) -> str | None: - item = self.generated_video_repository.get(video_id) + def execute(self, video_id: str, *, user_id: str | None = None) -> str | None: + item = self.generated_video_repository.get(video_id, user_id=user_id) if item is None: return None return item.file_url @@ -75,17 +75,17 @@ class UpdateVideoReviewStatusUseCase: def __init__(self, generated_video_repository: GeneratedVideoRepository): self.generated_video_repository = generated_video_repository - def execute(self, video_id: str, review_status: str) -> GeneratedVideo | None: + def execute(self, video_id: str, review_status: str, *, user_id: str | None = None) -> GeneratedVideo | None: if not video_id.strip(): raise ValueError("video_id 不能为空") if review_status not in ("pending_review", "approved", "rejected"): raise ValueError(f"无效的 review_status: {review_status}") - return self.generated_video_repository.update_review_status(video_id.strip(), review_status) + return self.generated_video_repository.update_review_status(video_id.strip(), review_status, user_id=user_id) class GetVideosByIdsUseCase: def __init__(self, generated_video_repository: GeneratedVideoRepository): self.generated_video_repository = generated_video_repository - def execute(self, video_ids: list[str]) -> list[GeneratedVideo]: - return self.generated_video_repository.get_by_ids(video_ids) + def execute(self, video_ids: list[str], *, user_id: str | None = None) -> list[GeneratedVideo]: + return self.generated_video_repository.get_by_ids(video_ids, user_id=user_id) diff --git a/packages/ports/generated_video_repository.py b/packages/ports/generated_video_repository.py index d23771b20..3fd6a1470 100755 --- a/packages/ports/generated_video_repository.py +++ b/packages/ports/generated_video_repository.py @@ -8,7 +8,7 @@ from packages.domain import GeneratedVideo class GeneratedVideoRepository(Protocol): def create(self, video: GeneratedVideo) -> GeneratedVideo: ... - def get(self, video_id: str) -> GeneratedVideo | None: ... + def get(self, video_id: str, *, user_id: str | None = None) -> GeneratedVideo | None: ... def list_by_project(self, project_id: str) -> list[GeneratedVideo]: ... @@ -27,12 +27,14 @@ class GeneratedVideoRepository(Protocol): page_size: int = 20, ) -> tuple[list[GeneratedVideo], int]: ... - def update_review_status(self, video_id: str, review_status: str) -> GeneratedVideo | None: ... + def update_review_status( + self, video_id: str, review_status: str, *, user_id: str | None = None + ) -> GeneratedVideo | None: ... def update_thumbnail(self, video_id: str, thumbnail_url: str) -> bool: ... - def get_by_ids(self, video_ids: list[str]) -> list[GeneratedVideo]: ... + def get_by_ids(self, video_ids: list[str], *, user_id: str | None = None) -> list[GeneratedVideo]: ... - def delete(self, video_id: str) -> bool: ... + def delete(self, video_id: str, *, user_id: str | None = None) -> bool: ... - def batch_delete(self, video_ids: list[str]) -> int: ... + def batch_delete(self, video_ids: list[str], *, user_id: str | None = None) -> int: ... diff --git a/tests/integration/test_generation_api.py b/tests/integration/test_generation_api.py index e516dd0d4..c6b00fd38 100755 --- a/tests/integration/test_generation_api.py +++ b/tests/integration/test_generation_api.py @@ -211,7 +211,7 @@ class StubGeneratedVideoRepository: self._videos[video.id] = video return video - def get(self, video_id: str) -> GeneratedVideo | None: + def get(self, video_id: str, **kwargs) -> GeneratedVideo | None: return self._videos.get(video_id) def list_by_project(self, project_id: str) -> list[GeneratedVideo]: diff --git a/tests/integration/test_generation_pipeline.py b/tests/integration/test_generation_pipeline.py index 0e80bd95a..b74c046f3 100755 --- a/tests/integration/test_generation_pipeline.py +++ b/tests/integration/test_generation_pipeline.py @@ -35,7 +35,7 @@ class DummyGeneratedVideoRepository: self.items[video.id] = video return video - def get(self, video_id): + def get(self, video_id, **kwargs): return self.items.get(video_id) def list_by_project(self, project_id): diff --git a/tests/unit/test_video_upload_fix_and_generated_api.py b/tests/unit/test_video_upload_fix_and_generated_api.py index 419a86739..4072c9c5b 100644 --- a/tests/unit/test_video_upload_fix_and_generated_api.py +++ b/tests/unit/test_video_upload_fix_and_generated_api.py @@ -255,7 +255,7 @@ class TestGeneratedVideoUseCases: use_case = GetGeneratedVideoUseCase(repo) result = use_case.execute("v1") - repo.get.assert_called_once_with("v1") + repo.get.assert_called_once_with("v1", user_id=None) assert result.id == "v1" def test_list_by_task_use_case(self):