feat(app): add shared_only filter and batch unshare endpoints

This commit is contained in:
wxy
2026-03-13 15:57:23 +08:00
parent d66b9dd8cb
commit c354618e20
2 changed files with 114 additions and 2 deletions

View File

@@ -53,6 +53,7 @@ def list_apps(
status: str | None = None, status: str | None = None,
search: str | None = None, search: str | None = None,
include_shared: bool = True, include_shared: bool = True,
shared_only: bool = False,
page: int = 1, page: int = 1,
pagesize: int = 10, pagesize: int = 10,
ids: Optional[str] = None, ids: Optional[str] = None,
@@ -84,6 +85,7 @@ def list_apps(
status=status, status=status,
search=search, search=search,
include_shared=include_shared, include_shared=include_shared,
shared_only=shared_only,
page=page, page=page,
pagesize=pagesize, pagesize=pagesize,
) )
@@ -107,6 +109,23 @@ def list_my_shared_out(
return success(data=data) return success(data=data)
@router.delete("/share/{target_workspace_id}", summary="取消对某工作空间的所有应用分享")
@cur_workspace_access_guard()
def unshare_all_apps_to_workspace(
target_workspace_id: uuid.UUID,
db: Session = Depends(get_db),
current_user=Depends(get_current_user),
):
"""Cancel all app shares from current workspace to a target workspace."""
workspace_id = current_user.current_workspace_id
service = app_service.AppService(db)
count = service.unshare_all_apps_to_workspace(
target_workspace_id=target_workspace_id,
workspace_id=workspace_id
)
return success(msg=f"已取消 {count} 个应用的分享")
@router.get("/{app_id}", summary="获取应用详情") @router.get("/{app_id}", summary="获取应用详情")
@cur_workspace_access_guard() @cur_workspace_access_guard()
def get_app( def get_app(
@@ -397,6 +416,23 @@ def list_app_shares(
return success(data=data) return success(data=data)
@router.delete("/shared/{source_workspace_id}", summary="批量移除某来源工作空间的所有共享应用")
@cur_workspace_access_guard()
def remove_all_shared_apps_from_workspace(
source_workspace_id: uuid.UUID,
db: Session = Depends(get_db),
current_user=Depends(get_current_user),
):
"""Remove all shared apps from a specific source workspace (recipient operation)."""
workspace_id = current_user.current_workspace_id
service = app_service.AppService(db)
count = service.remove_all_shared_apps_from_workspace(
source_workspace_id=source_workspace_id,
workspace_id=workspace_id
)
return success(msg=f"已移除 {count} 个共享应用")
@router.delete("/{app_id}/shared", summary="移除共享给我的应用") @router.delete("/{app_id}/shared", summary="移除共享给我的应用")
@cur_workspace_access_guard() @cur_workspace_access_guard()
def remove_shared_app( def remove_shared_app(

View File

@@ -12,7 +12,7 @@ import uuid
from typing import Annotated, Any, Dict, List, Optional, Tuple from typing import Annotated, Any, Dict, List, Optional, Tuple
from fastapi import Depends from fastapi import Depends
from sqlalchemy import and_, func, or_, select from sqlalchemy import and_, delete, func, or_, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.error_codes import BizCode from app.core.error_codes import BizCode
@@ -878,6 +878,7 @@ class AppService:
status: Optional[str] = None, status: Optional[str] = None,
search: Optional[str] = None, search: Optional[str] = None,
include_shared: bool = True, include_shared: bool = True,
shared_only: bool = False,
page: int = 1, page: int = 1,
pagesize: int = 10, pagesize: int = 10,
) -> Tuple[List[App], int]: ) -> Tuple[List[App], int]:
@@ -924,7 +925,14 @@ class AppService:
filters.append(func.lower(App.name).like(f"%{search.lower()}%")) filters.append(func.lower(App.name).like(f"%{search.lower()}%"))
# 基础查询:本工作空间的应用 # 基础查询:本工作空间的应用
if include_shared: if shared_only:
# 只返回共享给本工作空间的应用,不含自有应用
shared_app_ids_stmt = (
select(AppShare.source_app_id)
.where(AppShare.target_workspace_id == workspace_id)
)
stmt = select(App).where(App.id.in_(shared_app_ids_stmt))
elif include_shared:
# 查询本工作空间的应用 + 分享给本工作空间的应用 # 查询本工作空间的应用 + 分享给本工作空间的应用
# 使用 OR 条件workspace_id = current OR app_id IN (shared apps) # 使用 OR 条件workspace_id = current OR app_id IN (shared apps)
@@ -1891,6 +1899,39 @@ class AppService:
extra={"app_id": str(app_id), "target_workspace_id": str(target_workspace_id)} extra={"app_id": str(app_id), "target_workspace_id": str(target_workspace_id)}
) )
def unshare_all_apps_to_workspace(
self,
*,
target_workspace_id: uuid.UUID,
workspace_id: uuid.UUID
) -> int:
"""Cancel all app shares from current workspace to a target workspace.
Args:
target_workspace_id: Target workspace ID to cancel all shares to
workspace_id: Current workspace ID (source)
Returns:
Number of share records deleted
"""
from app.models import AppShare
logger.info(
"取消对目标工作空间的所有应用分享",
extra={"target_workspace_id": str(target_workspace_id), "workspace_id": str(workspace_id)}
)
stmt = delete(AppShare).where(
AppShare.source_workspace_id == workspace_id,
AppShare.target_workspace_id == target_workspace_id
)
result = self.db.execute(stmt)
self.db.commit()
count = result.rowcount
logger.info("已取消分享记录数", extra={"count": count})
return count
def list_app_shares( def list_app_shares(
self, self,
*, *,
@@ -1976,6 +2017,39 @@ class AppService:
extra={"app_id": str(app_id), "workspace_id": str(workspace_id)} extra={"app_id": str(app_id), "workspace_id": str(workspace_id)}
) )
def remove_all_shared_apps_from_workspace(
self,
*,
source_workspace_id: uuid.UUID,
workspace_id: uuid.UUID
) -> int:
"""Remove all shared apps from a specific source workspace.
Args:
source_workspace_id: The workspace that shared the apps
workspace_id: Current workspace ID (recipient)
Returns:
Number of share records deleted
"""
from app.models import AppShare
logger.info(
"批量移除来源工作空间的共享应用",
extra={"source_workspace_id": str(source_workspace_id), "workspace_id": str(workspace_id)}
)
stmt = delete(AppShare).where(
AppShare.source_workspace_id == source_workspace_id,
AppShare.target_workspace_id == workspace_id
)
result = self.db.execute(stmt)
self.db.commit()
count = result.rowcount
logger.info("已移除共享记录数", extra={"count": count})
return count
def list_my_shared_out( def list_my_shared_out(
self, self,
*, *,
@@ -2139,6 +2213,7 @@ def list_apps(
status: Optional[str] = None, status: Optional[str] = None,
search: Optional[str] = None, search: Optional[str] = None,
include_shared: bool = True, include_shared: bool = True,
shared_only: bool = False,
page: int = 1, page: int = 1,
pagesize: int = 10, pagesize: int = 10,
) -> Tuple[List[App], int]: ) -> Tuple[List[App], int]:
@@ -2151,6 +2226,7 @@ def list_apps(
status=status, status=status,
search=search, search=search,
include_shared=include_shared, include_shared=include_shared,
shared_only=shared_only,
page=page, page=page,
pagesize=pagesize, pagesize=pagesize,
) )