ARCH - TASKER BIM: серверный CAD-шлюз и независимый просмотр из Ops
This commit is contained in:
@@ -27,6 +27,7 @@ from plane.utils.host import base_host
|
||||
from plane.utils.upload_limits import get_project_storage_quota_response, resolve_workspace_upload_size_limit
|
||||
from plane.utils.attachment_preview import attachment_object_exists, get_attachment_preview_response
|
||||
from plane.utils.file_dedup import finalize_uploaded_file_asset, release_file_asset_blob, UploadedObjectMissing
|
||||
from plane.utils.nodedc_bim_gateway import BimGatewayError, bim_gateway_request, get_bim_registry_identity
|
||||
|
||||
|
||||
class IssueAttachmentEndpoint(BaseAPIView):
|
||||
@@ -109,12 +110,6 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
||||
name = request.data.get("name")
|
||||
type = request.data.get("type", False)
|
||||
size = int(request.data.get("size", settings.FILE_SIZE_LIMIT))
|
||||
beam_viewer = request.data.get("beamViewer")
|
||||
is_beam_viewer_reference = (
|
||||
isinstance(beam_viewer, dict)
|
||||
and beam_viewer.get("src")
|
||||
and beam_viewer.get("downloadUrl")
|
||||
)
|
||||
|
||||
if not type or type not in settings.ATTACHMENT_MIME_TYPES:
|
||||
return Response(
|
||||
@@ -126,45 +121,6 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
project = Project.objects.get(id=project_id, workspace=workspace)
|
||||
|
||||
if is_beam_viewer_reference:
|
||||
asset = FileAsset.objects.create(
|
||||
attributes={
|
||||
"name": name,
|
||||
"type": type,
|
||||
"size": size,
|
||||
"beamViewer": beam_viewer,
|
||||
},
|
||||
asset=f"{workspace.id}/beam-viewer/{uuid.uuid4().hex}-{name}",
|
||||
size=0,
|
||||
workspace_id=workspace.id,
|
||||
created_by=request.user,
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
is_uploaded=True,
|
||||
external_source="beam-viewer",
|
||||
)
|
||||
serializer = IssueAttachmentSerializer(asset)
|
||||
issue_activity.delay(
|
||||
type="attachment.activity.created",
|
||||
requested_data=None,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
return Response(
|
||||
{
|
||||
"asset_id": str(asset.id),
|
||||
"attachment": serializer.data,
|
||||
"asset_url": asset.asset_url,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
# asset key
|
||||
asset_key = f"{workspace.id}/{uuid.uuid4().hex}-{name}"
|
||||
|
||||
@@ -205,7 +161,32 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN], creator=True, model=FileAsset)
|
||||
def delete(self, request, slug, project_id, issue_id, pk):
|
||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
issue_attachment = FileAsset.objects.select_related("created_by").get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
attributes = issue_attachment.attributes if isinstance(issue_attachment.attributes, dict) else {}
|
||||
beam_viewer = attributes.get("beamViewer")
|
||||
if issue_attachment.external_source == "beam-viewer" and isinstance(beam_viewer, dict):
|
||||
try:
|
||||
bim_gateway_request(
|
||||
"DELETE",
|
||||
"/api/uploads/asset",
|
||||
identity=get_bim_registry_identity(issue_attachment, beam_viewer),
|
||||
json_payload=beam_viewer,
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": error.code,
|
||||
"message": error.message,
|
||||
**({"upstream_status": error.upstream_status} if error.upstream_status else {}),
|
||||
},
|
||||
status=error.status_code,
|
||||
)
|
||||
if not issue_attachment.is_uploaded:
|
||||
release_file_asset_blob(issue_attachment, request=request, delete_untracked_object=True)
|
||||
issue_attachment.is_deleted = True
|
||||
@@ -256,28 +237,22 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def patch(self, request, slug, project_id, issue_id, pk):
|
||||
issue_attachment = FileAsset.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
issue_attachment = FileAsset.objects.get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
)
|
||||
beam_viewer = request.data.get("beamViewer")
|
||||
if isinstance(beam_viewer, dict):
|
||||
attributes = issue_attachment.attributes or {}
|
||||
existing_beam_viewer = attributes.get("beamViewer")
|
||||
if not isinstance(existing_beam_viewer, dict):
|
||||
return Response(
|
||||
{"error": "The attachment is not a BIM Viewer reference.", "status": False},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
attributes["beamViewer"] = {
|
||||
**existing_beam_viewer,
|
||||
**beam_viewer,
|
||||
}
|
||||
for attribute_key in ("name", "size", "type", "version"):
|
||||
if attribute_key in request.data:
|
||||
attributes[attribute_key] = request.data.get(attribute_key)
|
||||
issue_attachment.attributes = attributes
|
||||
issue_attachment.is_uploaded = True
|
||||
issue_attachment.save(update_fields=["attributes", "is_uploaded", "updated_at"])
|
||||
return Response(IssueAttachmentSerializer(issue_attachment).data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
{
|
||||
"error": "BIM attachments are managed by the Ops BIM gateway.",
|
||||
"status": False,
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = IssueAttachmentSerializer(issue_attachment)
|
||||
if not attachment_object_exists(issue_attachment):
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from plane.app.permissions import ROLE, allow_permission
|
||||
from plane.app.serializers import IssueAttachmentSerializer
|
||||
from plane.app.views import BaseAPIView
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import FileAsset, Project, Workspace
|
||||
from plane.utils.host import base_host
|
||||
from plane.utils.nodedc_bim_gateway import (
|
||||
BimGatewayError,
|
||||
bim_gateway_request,
|
||||
build_bim_attachment,
|
||||
build_bim_embed_url,
|
||||
find_bim_version,
|
||||
get_bim_model_mime_type,
|
||||
get_bim_model_type,
|
||||
get_bim_registry_identity,
|
||||
make_bim_registry_owner_id,
|
||||
merge_bim_versions,
|
||||
resolve_bim_viewer_model,
|
||||
to_bim_public_url,
|
||||
to_bim_relative_asset_url,
|
||||
)
|
||||
from plane.utils.upload_limits import resolve_workspace_upload_size_limit
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _error_response(error):
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": error.code,
|
||||
"message": error.message,
|
||||
**({"upstream_status": error.upstream_status} if error.upstream_status else {}),
|
||||
},
|
||||
status=error.status_code,
|
||||
)
|
||||
|
||||
|
||||
def _get_attachment(slug, project_id, issue_id, attachment_id):
|
||||
return (
|
||||
FileAsset.objects.select_related("created_by")
|
||||
.filter(
|
||||
id=attachment_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
is_uploaded=True,
|
||||
external_source="beam-viewer",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def _get_beam_viewer(attachment):
|
||||
attributes = attachment.attributes if isinstance(attachment.attributes, dict) else {}
|
||||
beam_viewer = attributes.get("beamViewer")
|
||||
return beam_viewer if isinstance(beam_viewer, dict) else None
|
||||
|
||||
|
||||
def _upload_group_id(slug, project_id, issue_id):
|
||||
value = "{}_{}_{}".format(slug, project_id, issue_id)
|
||||
return re.sub(r"[^a-zA-Z0-9_-]", "_", value) or "tasker"
|
||||
|
||||
|
||||
def _validate_upload(workspace, uploaded_file):
|
||||
if uploaded_file is None:
|
||||
return Response(
|
||||
{"ok": False, "error": "bim_file_required", "message": "A BIM model file is required."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if not get_bim_model_type(uploaded_file.name):
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": "bim_format_not_supported",
|
||||
"message": "This BIM model format is not supported.",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
size_limit = resolve_workspace_upload_size_limit(workspace, uploaded_file.size)
|
||||
if uploaded_file.size > size_limit:
|
||||
return Response(
|
||||
{
|
||||
"ok": False,
|
||||
"error": "bim_file_too_large",
|
||||
"message": "The BIM model exceeds the workspace upload limit.",
|
||||
"limit": size_limit,
|
||||
},
|
||||
status=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _activity_created(request, serializer, issue_id, project_id):
|
||||
issue_activity.delay(
|
||||
type="attachment.activity.created",
|
||||
requested_data=None,
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
|
||||
|
||||
def _delete_uploaded_asset(beam_viewer, identity):
|
||||
try:
|
||||
bim_gateway_request(
|
||||
"DELETE",
|
||||
"/api/uploads/asset",
|
||||
identity=identity,
|
||||
json_payload=beam_viewer,
|
||||
)
|
||||
except BimGatewayError:
|
||||
logger.exception("Failed to clean up BIM asset after an OPS attachment failure")
|
||||
|
||||
|
||||
def _delete_uploaded_version(version, identity):
|
||||
try:
|
||||
bim_gateway_request(
|
||||
"DELETE",
|
||||
"/api/uploads/version",
|
||||
identity=identity,
|
||||
json_payload=version,
|
||||
)
|
||||
except BimGatewayError:
|
||||
logger.exception("Failed to clean up BIM version after an OPS attachment failure")
|
||||
|
||||
|
||||
def _public_status_payload(payload):
|
||||
result = dict(payload)
|
||||
result["artifactUrl"] = to_bim_public_url(payload.get("artifactSrc"))
|
||||
result["metadataUrl"] = to_bim_public_url(payload.get("metadataSrc"))
|
||||
return result
|
||||
|
||||
|
||||
def _public_version(version):
|
||||
result = dict(version)
|
||||
for key in ("src", "sourceSrc", "downloadUrl"):
|
||||
if result.get(key):
|
||||
result[key] = to_bim_public_url(result[key])
|
||||
result["viewerUrl"] = None
|
||||
return result
|
||||
|
||||
|
||||
class IssueBimAttachmentUploadEndpoint(BaseAPIView):
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id, issue_id):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
Project.objects.get(id=project_id, workspace=workspace)
|
||||
uploaded_file = request.FILES.get("file")
|
||||
validation_error = _validate_upload(workspace, uploaded_file)
|
||||
if validation_error is not None:
|
||||
return validation_error
|
||||
|
||||
attachment_id = uuid.uuid4()
|
||||
registry_owner_id = make_bim_registry_owner_id(attachment_id)
|
||||
identity = {"id": registry_owner_id, "email": request.user.email or ""}
|
||||
uploaded_file.seek(0)
|
||||
try:
|
||||
upload_payload = bim_gateway_request(
|
||||
"POST",
|
||||
"/api/uploads",
|
||||
identity=identity,
|
||||
params={
|
||||
"filename": uploaded_file.name,
|
||||
"projectId": _upload_group_id(slug, project_id, issue_id),
|
||||
"assetId": str(attachment_id),
|
||||
"version": "1",
|
||||
},
|
||||
data=uploaded_file.file,
|
||||
content_length=uploaded_file.size,
|
||||
)
|
||||
beam_viewer = build_bim_attachment(
|
||||
upload_payload,
|
||||
filename=uploaded_file.name,
|
||||
size=uploaded_file.size,
|
||||
uploaded_by=request.user.id,
|
||||
registry_owner_id=registry_owner_id,
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
|
||||
try:
|
||||
asset = FileAsset.objects.create(
|
||||
id=attachment_id,
|
||||
attributes={
|
||||
"name": uploaded_file.name,
|
||||
"type": get_bim_model_mime_type(uploaded_file.name),
|
||||
"size": uploaded_file.size,
|
||||
"version": beam_viewer.get("version") or 1,
|
||||
"beamViewer": beam_viewer,
|
||||
},
|
||||
asset="{}/beam-viewer/{}-{}".format(workspace.id, attachment_id.hex, uploaded_file.name),
|
||||
size=0,
|
||||
workspace_id=workspace.id,
|
||||
created_by=request.user,
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
is_uploaded=True,
|
||||
external_source="beam-viewer",
|
||||
)
|
||||
serializer = IssueAttachmentSerializer(asset)
|
||||
_activity_created(request, serializer, issue_id, project_id)
|
||||
except Exception:
|
||||
_delete_uploaded_asset(beam_viewer, identity)
|
||||
raise
|
||||
|
||||
return Response(
|
||||
{
|
||||
"asset_id": str(asset.id),
|
||||
"attachment": serializer.data,
|
||||
"asset_url": asset.asset_url,
|
||||
},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
class IssueBimAttachmentStatusEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, issue_id, pk):
|
||||
attachment = _get_attachment(slug, project_id, issue_id, pk)
|
||||
if attachment is None:
|
||||
return Response({"error": "BIM attachment not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
beam_viewer = _get_beam_viewer(attachment)
|
||||
if beam_viewer is None:
|
||||
return Response({"error": "BIM attachment metadata is missing."}, status=status.HTTP_409_CONFLICT)
|
||||
source = to_bim_relative_asset_url(beam_viewer.get("src"))
|
||||
if not source:
|
||||
return Response({"error": "BIM attachment source is invalid."}, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
try:
|
||||
payload = bim_gateway_request(
|
||||
"GET",
|
||||
"/api/conversions/status",
|
||||
identity=get_bim_registry_identity(attachment, beam_viewer),
|
||||
params={"src": source},
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
return Response(_public_status_payload(payload), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueBimAttachmentVersionsEndpoint(BaseAPIView):
|
||||
parser_classes = (MultiPartParser, FormParser)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def get(self, request, slug, project_id, issue_id, pk):
|
||||
attachment = _get_attachment(slug, project_id, issue_id, pk)
|
||||
if attachment is None:
|
||||
return Response({"error": "BIM attachment not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
beam_viewer = _get_beam_viewer(attachment)
|
||||
if beam_viewer is None:
|
||||
return Response({"error": "BIM attachment metadata is missing."}, status=status.HTTP_409_CONFLICT)
|
||||
params = {
|
||||
"projectId": beam_viewer.get("projectId"),
|
||||
"assetId": beam_viewer.get("assetId"),
|
||||
}
|
||||
if not all(params.values()):
|
||||
params = {"src": to_bim_relative_asset_url(beam_viewer.get("src"))}
|
||||
try:
|
||||
payload = bim_gateway_request(
|
||||
"GET",
|
||||
"/api/uploads/versions",
|
||||
identity=get_bim_registry_identity(attachment, beam_viewer),
|
||||
params=params,
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
result = dict(payload)
|
||||
result["versions"] = [_public_version(version) for version in payload.get("versions") or []]
|
||||
return Response(result, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def post(self, request, slug, project_id, issue_id, pk):
|
||||
attachment = _get_attachment(slug, project_id, issue_id, pk)
|
||||
if attachment is None:
|
||||
return Response({"error": "BIM attachment not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
beam_viewer = _get_beam_viewer(attachment)
|
||||
if beam_viewer is None:
|
||||
return Response({"error": "BIM attachment metadata is missing."}, status=status.HTTP_409_CONFLICT)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
uploaded_file = request.FILES.get("file")
|
||||
validation_error = _validate_upload(workspace, uploaded_file)
|
||||
if validation_error is not None:
|
||||
return validation_error
|
||||
|
||||
versions = beam_viewer.get("versions") if isinstance(beam_viewer.get("versions"), list) else []
|
||||
current_versions = [int(beam_viewer.get("version") or 1)]
|
||||
current_versions.extend(int(version.get("version") or 0) for version in versions)
|
||||
next_version = max(current_versions) + 1
|
||||
identity = get_bim_registry_identity(attachment, beam_viewer)
|
||||
uploaded_file.seek(0)
|
||||
try:
|
||||
upload_payload = bim_gateway_request(
|
||||
"POST",
|
||||
"/api/uploads",
|
||||
identity=identity,
|
||||
params={
|
||||
"filename": uploaded_file.name,
|
||||
"projectId": beam_viewer.get("projectId") or _upload_group_id(slug, project_id, issue_id),
|
||||
"assetId": beam_viewer.get("assetId") or str(attachment.id),
|
||||
"version": str(next_version),
|
||||
},
|
||||
data=uploaded_file.file,
|
||||
content_length=uploaded_file.size,
|
||||
)
|
||||
next_beam_viewer = build_bim_attachment(
|
||||
upload_payload,
|
||||
filename=uploaded_file.name,
|
||||
size=uploaded_file.size,
|
||||
uploaded_by=request.user.id,
|
||||
registry_owner_id=identity["id"],
|
||||
)
|
||||
next_beam_viewer["versions"] = merge_bim_versions(beam_viewer, next_beam_viewer)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
|
||||
next_record = find_bim_version(next_beam_viewer, next_beam_viewer.get("versionId"))
|
||||
try:
|
||||
attributes = dict(attachment.attributes or {})
|
||||
attributes.update(
|
||||
{
|
||||
"name": uploaded_file.name,
|
||||
"type": get_bim_model_mime_type(uploaded_file.name),
|
||||
"size": uploaded_file.size,
|
||||
"version": next_beam_viewer.get("version") or next_version,
|
||||
"beamViewer": next_beam_viewer,
|
||||
}
|
||||
)
|
||||
attachment.attributes = attributes
|
||||
attachment.save(update_fields=["attributes", "updated_at"])
|
||||
except Exception:
|
||||
if next_record:
|
||||
_delete_uploaded_version(next_record, identity)
|
||||
raise
|
||||
return Response(IssueAttachmentSerializer(attachment).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueBimAttachmentVersionDetailEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def delete(self, request, slug, project_id, issue_id, pk, version_id):
|
||||
attachment = _get_attachment(slug, project_id, issue_id, pk)
|
||||
if attachment is None:
|
||||
return Response({"error": "BIM attachment not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
beam_viewer = _get_beam_viewer(attachment)
|
||||
if beam_viewer is None:
|
||||
return Response({"error": "BIM attachment metadata is missing."}, status=status.HTTP_409_CONFLICT)
|
||||
version = find_bim_version(beam_viewer, version_id)
|
||||
if version is None:
|
||||
return Response({"error": "BIM version not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
current_version_id = beam_viewer.get("versionId")
|
||||
if (current_version_id and current_version_id == version.get("versionId")) or (
|
||||
not current_version_id and beam_viewer.get("version") == version.get("version")
|
||||
):
|
||||
return Response(
|
||||
{"error": "The current BIM version cannot be deleted."},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
try:
|
||||
bim_gateway_request(
|
||||
"DELETE",
|
||||
"/api/uploads/version",
|
||||
identity=get_bim_registry_identity(attachment, beam_viewer),
|
||||
json_payload=version,
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
|
||||
versions = beam_viewer.get("versions") if isinstance(beam_viewer.get("versions"), list) else []
|
||||
beam_viewer = {
|
||||
**beam_viewer,
|
||||
"versions": [
|
||||
candidate
|
||||
for candidate in versions
|
||||
if not (
|
||||
(version.get("versionId") and candidate.get("versionId") == version.get("versionId"))
|
||||
or (not version.get("versionId") and candidate.get("version") == version.get("version"))
|
||||
)
|
||||
],
|
||||
}
|
||||
attributes = dict(attachment.attributes or {})
|
||||
attributes["beamViewer"] = beam_viewer
|
||||
attachment.attributes = attributes
|
||||
attachment.save(update_fields=["attributes", "updated_at"])
|
||||
return Response(IssueAttachmentSerializer(attachment).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueBimAttachmentViewerEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def post(self, request, slug, project_id, issue_id, pk):
|
||||
attachment = _get_attachment(slug, project_id, issue_id, pk)
|
||||
if attachment is None:
|
||||
return Response({"error": "BIM attachment not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
beam_viewer = _get_beam_viewer(attachment)
|
||||
if beam_viewer is None:
|
||||
return Response({"error": "BIM attachment metadata is missing."}, status=status.HTTP_409_CONFLICT)
|
||||
|
||||
version_id = request.data.get("versionId")
|
||||
version = find_bim_version(beam_viewer, version_id)
|
||||
if version is None:
|
||||
return Response({"error": "BIM version not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
live_status = None
|
||||
conversion = version.get("conversion") if isinstance(version.get("conversion"), dict) else {}
|
||||
if conversion and conversion.get("status") != "ready":
|
||||
source = to_bim_relative_asset_url(version.get("sourceSrc") or version.get("src"))
|
||||
if source:
|
||||
try:
|
||||
live_status = _public_status_payload(
|
||||
bim_gateway_request(
|
||||
"GET",
|
||||
"/api/conversions/status",
|
||||
identity=get_bim_registry_identity(attachment, beam_viewer),
|
||||
params={"src": source},
|
||||
)
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
try:
|
||||
model = resolve_bim_viewer_model(
|
||||
beam_viewer,
|
||||
version_id=version_id,
|
||||
live_status=live_status,
|
||||
)
|
||||
viewer_url, expires_at = build_bim_embed_url(
|
||||
model,
|
||||
request.user,
|
||||
slug,
|
||||
project_id,
|
||||
issue_id,
|
||||
attachment.id,
|
||||
)
|
||||
except BimGatewayError as error:
|
||||
return _error_response(error)
|
||||
return Response(
|
||||
{
|
||||
"ok": True,
|
||||
"viewerUrl": viewer_url,
|
||||
"expiresAt": expires_at,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
Reference in New Issue
Block a user