fix(admin): limit generate-missing-usd-masters to product-linked CadFiles
Previously the endpoint queued USD generation for ALL 295 completed CadFiles, including 250 orphan CadFiles not linked to any product. Now filters to only CadFiles referenced by at least one Product.cad_file_id, reducing the backfill from ~285 to ~41 tasks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ from app.models.system_setting import SystemSetting
|
||||
from app.models.cad_file import CadFile, ProcessingStatus
|
||||
from app.models.output_type import OutputType as OutputTypeModel
|
||||
from app.schemas.user import UserOut, UserUpdate, UserCreate
|
||||
from app.utils.auth import require_admin, hash_password
|
||||
from app.utils.auth import require_global_admin, hash_password
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
@@ -122,7 +122,7 @@ class SettingsUpdate(BaseModel):
|
||||
|
||||
@router.get("/users", response_model=list[UserOut])
|
||||
async def list_users(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(User).order_by(User.created_at.desc()))
|
||||
@@ -132,7 +132,7 @@ async def list_users(
|
||||
@router.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(
|
||||
body: UserCreate,
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(User).where(User.email == body.email))
|
||||
@@ -155,7 +155,7 @@ async def create_user(
|
||||
async def update_user(
|
||||
user_id: uuid.UUID,
|
||||
body: UserUpdate,
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
@@ -173,7 +173,7 @@ async def update_user(
|
||||
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_user(
|
||||
user_id: uuid.UUID,
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
@@ -241,7 +241,7 @@ def _settings_to_out(raw: dict[str, str]) -> SettingsOut:
|
||||
|
||||
@router.get("/settings", response_model=SettingsOut)
|
||||
async def get_settings(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return _settings_to_out(await _load_settings(db))
|
||||
@@ -250,7 +250,7 @@ async def get_settings(
|
||||
@router.put("/settings", response_model=SettingsOut)
|
||||
async def update_settings(
|
||||
body: SettingsUpdate,
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if body.thumbnail_renderer is not None and body.thumbnail_renderer not in VALID_RENDERERS:
|
||||
@@ -373,7 +373,7 @@ async def update_settings(
|
||||
|
||||
@router.post("/settings/process-unprocessed", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def process_unprocessed_steps(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Queue all STEP files that are not yet completed.
|
||||
@@ -416,7 +416,7 @@ async def process_unprocessed_steps(
|
||||
|
||||
@router.post("/settings/regenerate-thumbnails", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def regenerate_thumbnails(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Re-queue completed CAD files that are linked to a product for thumbnail regeneration."""
|
||||
@@ -439,7 +439,7 @@ async def regenerate_thumbnails(
|
||||
|
||||
@router.get("/settings/orphaned-cad-files")
|
||||
async def get_orphaned_cad_files(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Return count and total disk size of CadFiles not linked to any product."""
|
||||
@@ -459,7 +459,7 @@ async def get_orphaned_cad_files(
|
||||
|
||||
@router.post("/settings/cleanup-orphaned-cad-files")
|
||||
async def cleanup_orphaned_cad_files(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Delete CadFile DB records and associated files on disk for all orphaned CadFiles.
|
||||
@@ -504,7 +504,7 @@ async def cleanup_orphaned_cad_files(
|
||||
|
||||
@router.post("/settings/reextract-metadata", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def reextract_all_metadata(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Re-extract OCC metadata (dimensions, sharp edges) for all completed CAD files.
|
||||
@@ -534,7 +534,7 @@ async def reextract_all_metadata(
|
||||
|
||||
@router.post("/settings/generate-missing-canonical-scenes", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def generate_missing_canonical_scenes(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Queue canonical scene (geometry GLB + USD master) generation for every completed CAD file that has no gltf_geometry MediaAsset."""
|
||||
@@ -565,14 +565,28 @@ async def generate_missing_canonical_scenes(
|
||||
|
||||
@router.post("/settings/generate-missing-usd-masters", status_code=status.HTTP_202_ACCEPTED)
|
||||
async def generate_missing_usd_masters(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Queue USD master export for every completed CAD file that has no usd_master MediaAsset."""
|
||||
"""Queue USD master export for completed CAD files linked to a product that have no usd_master MediaAsset.
|
||||
|
||||
Only CadFiles referenced by at least one Product are included — orphan CadFiles
|
||||
(uploaded but never linked to a product) are skipped to avoid unnecessary work.
|
||||
"""
|
||||
from app.domains.media.models import MediaAsset, MediaAssetType
|
||||
from app.domains.products.models import Product
|
||||
|
||||
# Only CadFiles that are actually used by a product
|
||||
product_cad_ids_result = await db.execute(
|
||||
select(Product.cad_file_id).where(Product.cad_file_id.isnot(None)).distinct()
|
||||
)
|
||||
product_cad_ids = {row[0] for row in product_cad_ids_result.all()}
|
||||
|
||||
result = await db.execute(
|
||||
select(CadFile).where(CadFile.processing_status == ProcessingStatus.completed)
|
||||
select(CadFile).where(
|
||||
CadFile.processing_status == ProcessingStatus.completed,
|
||||
CadFile.id.in_(product_cad_ids),
|
||||
)
|
||||
)
|
||||
cad_files = result.scalars().all()
|
||||
|
||||
@@ -595,7 +609,7 @@ async def generate_missing_usd_masters(
|
||||
|
||||
@router.post("/settings/recover-stuck-processing", status_code=status.HTTP_200_OK)
|
||||
async def recover_stuck_processing(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Reset CAD files stuck in 'processing' for more than 10 minutes to 'failed'.
|
||||
@@ -629,7 +643,7 @@ async def recover_stuck_processing(
|
||||
|
||||
@router.post("/settings/seed-workflows", status_code=status.HTTP_200_OK)
|
||||
async def seed_workflows(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Create the standard workflow definitions if they do not already exist."""
|
||||
@@ -685,7 +699,7 @@ async def seed_workflows(
|
||||
|
||||
@router.get("/settings/renderer-status")
|
||||
async def renderer_status(
|
||||
admin: User = Depends(require_admin),
|
||||
admin: User = Depends(require_global_admin),
|
||||
):
|
||||
"""Check health of renderer services."""
|
||||
from app.services.render_blender import find_blender, is_blender_available
|
||||
@@ -706,7 +720,7 @@ async def renderer_status(
|
||||
@router.post("/import-media-assets")
|
||||
async def import_existing_media_assets(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_admin),
|
||||
current_user: User = Depends(require_global_admin),
|
||||
):
|
||||
"""Import existing cad thumbnails and order line renders as MediaAsset records."""
|
||||
from app.domains.media.models import MediaAsset, MediaAssetType
|
||||
|
||||
Reference in New Issue
Block a user