Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ repos:
additional_dependencies: ['flake8-bugbear']

- repo: https://github.com/agritheory/test_utils
rev: v1.19.0
rev: v1.20.3
hooks:
- id: update_pre_commit_config
- id: validate_frappe_project
Expand Down
51 changes: 51 additions & 0 deletions cloud_storage/cloud_storage/overrides/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,54 @@ def add_child_file_association(attached_to_doctype, attached_to_name):
"user": frappe.session.user,
"timestamp": get_datetime(),
}


@frappe.whitelist()
def proxy_file(key: str):
"""
Fetch file from S3 server-side and stream back to browser.
Used for 3D preview to avoid CORS issues with direct S3 URLs.
"""
import requests # type: ignore[import-untyped]

if not key:
frappe.throw(_("Key not found"))

# Check file permissions
file = frappe.get_value("File", {"s3_key": key}, ["name", "is_private"], as_dict=True)
if not file:
frappe.throw(_("File not found"))

if file.is_private:
file_doc = frappe.get_doc("File", file.name)
frappe.has_permission(
doctype="File", ptype="read", doc=file_doc, user=frappe.session.user, throw=True
)

# Get presigned URL and fetch content server-side
client = get_cloud_storage_client()
signed_url = client.generate_presigned_url(
ClientMethod="get_object",
Params={"Bucket": client.bucket, "Key": key},
ExpiresIn=60,
)

response = requests.get(signed_url)
response.raise_for_status()

# Return file content directly to browser
ext = key.split(".")[-1].lower()
content_types = {
"obj": "text/plain",
"glb": "model/gltf-binary",
"gltf": "model/gltf+json",
"stl": "model/stl",
"ply": "application/octet-stream",
"fbx": "application/octet-stream",
"dae": "model/vnd.collada+xml",
}

frappe.local.response.filename = key.split("/")[-1]
frappe.local.response.filecontent = response.content
frappe.local.response.type = "download"
frappe.local.response["Content-Type"] = content_types.get(ext, "application/octet-stream")
Loading
Loading