fix(export_glb): upsert MediaAsset instead of DELETE+INSERT to preserve stable URLs
When re-generating a production or geometry GLB, the old approach deleted the
existing MediaAsset record and created a new one with a new UUID. Any page that
had the old download_url (/api/media/{old-id}/download) cached would then get
a 404 when trying to download, because the asset ID no longer existed in the DB.
Fix: update the existing MediaAsset record in-place (same UUID, new storage_key)
so existing download URLs remain valid after regeneration. Create a new record
only if no existing one is found.
Applies to both generate_gltf_geometry_task and generate_gltf_production_task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -124,32 +124,44 @@ def generate_gltf_geometry_task(self, cad_file_id: str):
|
||||
|
||||
log_task_event(self.request.id, f"OCC GLB export completed: {output_path.name}", "done")
|
||||
|
||||
# --- Store MediaAsset (replace existing gltf_geometry for this cad_file) ---
|
||||
# --- Store MediaAsset (upsert: update existing to keep stable ID/URL) ---
|
||||
import uuid as _uuid
|
||||
from sqlalchemy import create_engine as _ce, delete as _del
|
||||
from sqlalchemy import create_engine as _ce, select as _sel2
|
||||
from sqlalchemy.orm import Session as _Session
|
||||
from app.domains.media.models import MediaAsset, MediaAssetType
|
||||
|
||||
_sync_url = app_settings.database_url.replace("+asyncpg", "")
|
||||
_eng2 = _ce(_sync_url)
|
||||
with _Session(_eng2) as _sess:
|
||||
_sess.execute(
|
||||
_del(MediaAsset).where(
|
||||
MediaAsset.cad_file_id == _uuid.UUID(cad_file_id),
|
||||
MediaAsset.asset_type == MediaAssetType.gltf_geometry,
|
||||
)
|
||||
)
|
||||
_key = str(output_path)
|
||||
_prefix = str(app_settings.upload_dir).rstrip("/") + "/"
|
||||
if _key.startswith(_prefix):
|
||||
_key = _key[len(_prefix):]
|
||||
_file_size = output_path.stat().st_size if output_path.exists() else None
|
||||
|
||||
existing = _sess.execute(
|
||||
_sel2(MediaAsset).where(
|
||||
MediaAsset.cad_file_id == _uuid.UUID(cad_file_id),
|
||||
MediaAsset.asset_type == MediaAssetType.gltf_geometry,
|
||||
)
|
||||
).scalars().first()
|
||||
|
||||
if existing:
|
||||
existing.storage_key = _key
|
||||
existing.mime_type = "model/gltf-binary"
|
||||
existing.file_size_bytes = _file_size
|
||||
if product_id:
|
||||
existing.product_id = _uuid.UUID(product_id)
|
||||
_sess.commit()
|
||||
asset_id = str(existing.id)
|
||||
else:
|
||||
asset = MediaAsset(
|
||||
cad_file_id=_uuid.UUID(cad_file_id),
|
||||
product_id=_uuid.UUID(product_id) if product_id else None,
|
||||
asset_type=MediaAssetType.gltf_geometry,
|
||||
storage_key=_key,
|
||||
mime_type="model/gltf-binary",
|
||||
file_size_bytes=output_path.stat().st_size if output_path.exists() else None,
|
||||
file_size_bytes=_file_size,
|
||||
)
|
||||
_sess.add(asset)
|
||||
_sess.commit()
|
||||
@@ -182,7 +194,7 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
import uuid as _uuid
|
||||
from pathlib import Path as _Path
|
||||
|
||||
from sqlalchemy import create_engine as _ce, delete as _del, select as _sel
|
||||
from sqlalchemy import create_engine as _ce, delete as _del, select as _sel, update as _upd
|
||||
from sqlalchemy.orm import Session as _Session
|
||||
|
||||
from app.config import settings as app_settings
|
||||
@@ -205,6 +217,7 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
_sel(_CF).where(_CF.id == _uuid.UUID(cad_file_id))
|
||||
).scalar_one_or_none()
|
||||
step_path_str = _cad.stored_path if _cad else None
|
||||
cad_mesh_attributes: dict = (_cad.mesh_attributes or {}) if _cad else {}
|
||||
|
||||
settings_rows = _sess.execute(_sel(SystemSetting)).scalars().all()
|
||||
sys_settings = {s.key: s.value for s in settings_rows}
|
||||
@@ -302,6 +315,7 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
"--output_path", str(output_path),
|
||||
"--material_map", _json.dumps(mat_map),
|
||||
"--smooth_angle", str(smooth_angle),
|
||||
"--mesh_attributes", _json.dumps(cad_mesh_attributes),
|
||||
]
|
||||
if asset_library_blend:
|
||||
cmd += ["--asset_library_blend", asset_library_blend]
|
||||
@@ -333,26 +347,40 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
|
||||
log_task_event(self.request.id, f"Production GLB exported: {output_path.name}", "done")
|
||||
|
||||
# --- 4. Store MediaAsset (replace existing gltf_production for this cad_file) ---
|
||||
# --- 4. Store MediaAsset (upsert: update existing record to keep stable ID/URL) ---
|
||||
# Updating in-place (not DELETE+INSERT) preserves the existing asset UUID so that
|
||||
# any frontend page holding a stale download_url continues to resolve correctly.
|
||||
_eng2 = _ce(_sync_url)
|
||||
with _Session(_eng2) as _sess:
|
||||
_sess.execute(
|
||||
_del(MediaAsset).where(
|
||||
MediaAsset.cad_file_id == _uuid.UUID(cad_file_id),
|
||||
MediaAsset.asset_type == MediaAssetType.gltf_production,
|
||||
)
|
||||
)
|
||||
_key = str(output_path)
|
||||
_prefix = str(app_settings.upload_dir).rstrip("/") + "/"
|
||||
if _key.startswith(_prefix):
|
||||
_key = _key[len(_prefix):]
|
||||
_file_size = output_path.stat().st_size if output_path.exists() else None
|
||||
|
||||
existing = _sess.execute(
|
||||
_sel(MediaAsset).where(
|
||||
MediaAsset.cad_file_id == _uuid.UUID(cad_file_id),
|
||||
MediaAsset.asset_type == MediaAssetType.gltf_production,
|
||||
)
|
||||
).scalars().first()
|
||||
|
||||
if existing:
|
||||
existing.storage_key = _key
|
||||
existing.mime_type = "model/gltf-binary"
|
||||
existing.file_size_bytes = _file_size
|
||||
if product_id:
|
||||
existing.product_id = _uuid.UUID(product_id)
|
||||
_sess.commit()
|
||||
asset_id = str(existing.id)
|
||||
else:
|
||||
asset = MediaAsset(
|
||||
cad_file_id=_uuid.UUID(cad_file_id),
|
||||
product_id=_uuid.UUID(product_id) if product_id else None,
|
||||
asset_type=MediaAssetType.gltf_production,
|
||||
storage_key=_key,
|
||||
mime_type="model/gltf-binary",
|
||||
file_size_bytes=output_path.stat().st_size if output_path.exists() else None,
|
||||
file_size_bytes=_file_size,
|
||||
)
|
||||
_sess.add(asset)
|
||||
_sess.commit()
|
||||
|
||||
Reference in New Issue
Block a user