0422967229
BREAKING CHANGES:
- Removed Workspace, WorkspaceMember, WorkspaceInvitation entities
- Project now has owner_user_id instead of workspace_id
- Added shared_users list to Project for collaboration
- Subscription/quota moved from Workspace to User level
Changes:
- packages/domain/entities.py: Removed Workspace entities, updated Project
- packages/adapters/sqlalchemy_impl/models.py: Updated models
- packages/application/: Removed workspace use cases, updated other use cases
- packages/ports/: Removed workspace repository interfaces
- apps/api/: Updated routes, schemas, dependencies, router
- alembic/versions/007_remove_workspace_concept.py: Database migration
New APIs:
- POST /projects/{id}/share: Share project with user
- DELETE /projects/{id}/share/{user_id}: Unshare project
137 lines
5.1 KiB
Python
137 lines
5.1 KiB
Python
from typing import Any
|
|
|
|
from app.auth import AuthenticatedUser, get_current_user
|
|
from app.dependencies import (
|
|
get_asset_library_repository,
|
|
get_asset_repository,
|
|
get_project_repository,
|
|
)
|
|
from app.schemas.asset import AssetResponse, CreateAssetRequest, ListAssetsResponse, UpdateAssetReviewRequest
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from packages.application import (
|
|
CreateAssetCommand,
|
|
CreateAssetUseCase,
|
|
ListAssetsUseCase,
|
|
)
|
|
from packages.domain import AssetStatus, ClassificationStatus
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _to_asset_response(item) -> AssetResponse:
|
|
return AssetResponse(
|
|
id=item.id,
|
|
project_id=item.project_id,
|
|
library_id=item.library_id,
|
|
name=item.name,
|
|
storage_key=item.storage_key,
|
|
mime_type=item.mime_type,
|
|
metadata=item.metadata,
|
|
file_size=item.file_size,
|
|
thumbnail_url=item.thumbnail_url,
|
|
duration=item.duration,
|
|
width=item.width,
|
|
height=item.height,
|
|
fps=item.fps,
|
|
codec=item.codec,
|
|
status=item.status.value,
|
|
classification_status=item.classification_status.value,
|
|
quality_score=item.quality_score,
|
|
uploaded_by_user_id=item.uploaded_by_user_id,
|
|
)
|
|
|
|
|
|
def _check_project_access(project_id: str, user_id: str, project_repository) -> None:
|
|
"""检查用户是否有项目访问权限"""
|
|
project = project_repository.find_by_id(project_id)
|
|
if project is None:
|
|
raise HTTPException(status_code=404, detail=f"Project {project_id} not found")
|
|
if not project.can_access(user_id):
|
|
raise HTTPException(status_code=403, detail="Access denied to project")
|
|
|
|
|
|
@router.get("", response_model=ListAssetsResponse)
|
|
def list_assets(
|
|
library_id: str,
|
|
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
|
asset_repository: Any = Depends(get_asset_repository),
|
|
asset_library_repository: Any = Depends(get_asset_library_repository),
|
|
project_repository: Any = Depends(get_project_repository),
|
|
) -> ListAssetsResponse:
|
|
library = asset_library_repository.get(library_id)
|
|
if library is None:
|
|
raise HTTPException(status_code=404, detail=f"AssetLibrary {library_id} not found")
|
|
_check_project_access(library.project_id, authenticated_user.id, project_repository)
|
|
use_case = ListAssetsUseCase(asset_repository)
|
|
items = use_case.execute(library_id)
|
|
return ListAssetsResponse(items=[_to_asset_response(item) for item in items])
|
|
|
|
|
|
def _apply_asset_review_status(item, review_status: str):
|
|
item.metadata = {
|
|
**item.metadata,
|
|
"review_status": review_status,
|
|
}
|
|
return item
|
|
|
|
|
|
@router.patch("/{asset_id}/review", response_model=AssetResponse)
|
|
def update_asset_review_status(
|
|
asset_id: str,
|
|
request: UpdateAssetReviewRequest,
|
|
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
|
asset_repository: Any = Depends(get_asset_repository),
|
|
project_repository: Any = Depends(get_project_repository),
|
|
) -> AssetResponse:
|
|
item = asset_repository.get(asset_id)
|
|
if item is None:
|
|
raise HTTPException(status_code=404, detail=f"Asset {asset_id} not found")
|
|
_check_project_access(item.project_id, authenticated_user.id, project_repository)
|
|
_apply_asset_review_status(item, request.review_status)
|
|
updated = asset_repository.update(item)
|
|
return _to_asset_response(updated)
|
|
|
|
|
|
@router.post("", response_model=AssetResponse)
|
|
def create_asset(
|
|
request: CreateAssetRequest,
|
|
authenticated_user: AuthenticatedUser = Depends(get_current_user),
|
|
asset_repository: Any = Depends(get_asset_repository),
|
|
asset_library_repository: Any = Depends(get_asset_library_repository),
|
|
project_repository: Any = Depends(get_project_repository),
|
|
) -> AssetResponse:
|
|
project = project_repository.find_by_id(request.project_id)
|
|
if project is None:
|
|
raise HTTPException(status_code=404, detail=f"Project {request.project_id} not found")
|
|
if not project.can_access(authenticated_user.id):
|
|
raise HTTPException(status_code=403, detail="Access denied to project")
|
|
|
|
library = asset_library_repository.get(request.library_id)
|
|
if library is None or library.project_id != request.project_id:
|
|
raise HTTPException(status_code=404, detail=f"AssetLibrary {request.library_id} not found")
|
|
|
|
use_case = CreateAssetUseCase(asset_repository)
|
|
item = use_case.execute(
|
|
CreateAssetCommand(
|
|
project_id=request.project_id,
|
|
library_id=request.library_id,
|
|
name=request.name,
|
|
storage_key=request.storage_key,
|
|
mime_type=request.mime_type,
|
|
metadata=request.metadata,
|
|
file_size=request.file_size,
|
|
thumbnail_url=request.thumbnail_url,
|
|
duration=request.duration,
|
|
width=request.width,
|
|
height=request.height,
|
|
fps=request.fps,
|
|
codec=request.codec,
|
|
status=AssetStatus(request.status),
|
|
classification_status=ClassificationStatus(request.classification_status),
|
|
quality_score=request.quality_score,
|
|
uploaded_by_user_id=authenticated_user.id,
|
|
)
|
|
)
|
|
return _to_asset_response(item)
|