1"use strict";(self.webpackChunkq_01_docs=self.webpackChunkq_01_docs||[]).push([[4916],{564:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>t,default:()=>h,frontMatter:()=>a,metadata:()=>l,toc:()=>o});var r=i(7624),s=i(2172);const a={title:"Media Operations",sidebar_label:"Media Operations",sidebar_position:5,description:"Upload and retrieve images/files using Google Cloud Storage"},t="Media Operations",l={id:"products/map.q01.io/core-api-platform-v4/api-operations/media-operations",title:"Media Operations",description:"Upload and retrieve images/files using Google Cloud Storage",source:"@site/docs/products/map.q01.io/core-api-platform-v4/02-api-operations/media-operations.md",sourceDirName:"products/map.q01.io/core-api-platform-v4/02-api-operations",slug:"/products/map.q01.io/core-api-platform-v4/api-operations/media-operations",permalink:"/docs/products/map.q01.io/core-api-platform-v4/api-operations/media-operations",draft:!1,unlisted:!1,tags:[],version:"current",sidebarPosition:5,frontMatter:{title:"Media Operations",sidebar_label:"Media Operations",sidebar_position:5,description:"Upload and retrieve images/files using Google Cloud Storage"},sidebar:"mapSidebar",previous:{title:"Field Metadata",permalink:"/docs/products/map.q01.io/core-api-platform-v4/api-operations/field-metadata"},next:{title:"Sort Operations",permalink:"/docs/products/map.q01.io/core-api-platform-v4/api-operations/sort-operations"}},d={},o=[{value:"Overview",id:"overview",level:2},{value:"Upload Workflow",id:"upload-workflow",level:2},{value:"How Upload Works",id:"how-upload-works",level:3},{value:"Storage Path Structure",id:"storage-path-structure",level:3},{value:"Upload Example",id:"upload-example",level:2},{value:"JavaScript Implementation",id:"javascript-implementation",level:3},{value:"Request Body Format",id:"request-body-format",level:3},{value:"Response Format",id:"response-format",level:3},{value:"Download Operations",id:"download-operations",level:2},{value:"Private Image Retrieval",id:"private-image-retrieval",level:3},{value:"Public Image Retrieval",id:"public-image-retrieval",level:3},{value:"Batch Retrieval with Signed URLs",id:"batch-retrieval-with-signed-urls",level:2},{value:"Private Batch Retrieval",id:"private-batch-retrieval",level:3},{value:"Public Batch Retrieval",id:"public-batch-retrieval",level:3},{value:"Storage Configuration",id:"storage-configuration",level:2},{value:"Environment Variables",id:"environment-variables",level:3},{value:"GCS Authentication",id:"gcs-authentication",level:3},{value:"Supported File Types",id:"supported-file-types",level:2},{value:"MIME Type Detection",id:"mime-type-detection",level:3},{value:"Best Practices",id:"best-practices",level:2},{value:"\u2705 DO",id:"-do",level:3},{value:"\u274c DON'T",id:"-dont",level:3},{value:"Error Handling",id:"error-handling",level:2},{value:"Common Errors",id:"common-errors",level:3},{value:"Use Case: Product Image Upload",id:"use-case-product-image-upload",level:2},{value:"Summary",id:"summary",level:2},{value:"Related Concepts",id:"related-concepts",level:2}];function c(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.M)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.h1,{id:"media-operations",children:"Media Operations"}),"\n",(0,r.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Media operations"})," handle binary file uploads (images, PDFs, documents) and retrievals using ",(0,r.jsx)(n.strong,{children:"Google Cloud Storage (GCS)"})," as the backend. Files are stored in multi-tenant isolated paths and referenced in dimension tables via metadata."]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Key Characteristics:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Base64-encoded uploads via JSON"}),"\n",(0,r.jsx)(n.li,{children:"Automatic GCS storage with tenant isolation"}),"\n",(0,r.jsxs)(n.li,{children:["Path structure: ",(0,r.jsx)(n.code,{children:"{tenantId}/{env}/{dim}/{field}/{year}/{month}/{filename}"})]}),"\n",(0,r.jsx)(n.li,{children:"Private and public bucket support"}),"\n",(0,r.jsx)(n.li,{children:"Signed URLs for temporary access"}),"\n",(0,r.jsx)(n.li,{children:"MIME type detection"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"upload-workflow",children:"Upload Workflow"}),"\n",(0,r.jsx)(n.h3,{id:"how-upload-works",children:"How Upload Works"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Client encodes file"})," to base64"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"POST to CoreWrite"})," with base64 data in JSON body"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"CoreWrite processes"}),":","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Decodes base64"}),"\n",(0,r.jsx)(n.li,{children:"Generates unique MD5 filename"}),"\n",(0,r.jsx)(n.li,{children:"Uploads to GCS bucket"}),"\n",(0,r.jsx)(n.li,{children:"Stores metadata in dimension table field"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Response includes"})," bucket metadata and URL"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"storage-path-structure",children:"Storage Path Structure"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"{tenantId}/{env}/{dim}/{field}/{year}/{month}/{filename}\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Example:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"abc123-tenant/app.q01.io/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Components:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{tenantId}"}),": Extracted from JWT (automatic tenant isolation)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{env}"}),': Environment resource name (e.g., "app.q01.io")']}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{dim}"}),": Dimension code (e.g., PRD, ART)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{field}"}),": Field code (e.g., XPRD12)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{year}/{month}"}
1),": Upload date (YYYY/MM format)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{filename}"}),": MD5 hash + original extension (ensures uniqueness)"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"upload-example",children:"Upload Example"}),"\n",(0,r.jsx)(n.h3,{id:"javascript-implementation",children:"JavaScript Implementation"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function uploadProductImage(productId, fileInput) {\n // Read file as base64\n const file = fileInput.files[0];\n const reader = new FileReader();\n\n return new Promise((resolve, reject) => {\n reader.onload = async (e) => {\n const base64Data = e.target.result;\n\n // Create product with image\n const response = await fetch('/api/v4/core/PRD', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n XPRD01: 'Premium Widget',\
1n XPRD02: 49.99,\n XPRD12: {\n data: base64Data,\n name: file.name,\n type: file.type,\n public: false // private bucket\n },\n source: 'productCreate'\n })\n });\n\n if (!response.ok) {\n throw new Error('Upload failed');\n }\n\n const result = await response.json();\n resolve(result[0]);\n };\n\n reader.onerror = reject;\n reader.readAsDataURL(file);\n });\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"request-body-format",children:"Request Body Format"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "XPRD01": "Premium Widget",\n "XPRD02": 49.99,\n "XPRD12": {\n "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",\n "name": "product-photo.jpg",\n "type": "image/jpeg",\n "public": false\n },\n "source": "productCreate"\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Field Properties:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"data"}),": Base64-encoded file with data URI prefix"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"name"}),": Original filename"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"type"}),": MIME type"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"public"}),": Boolean - store in public bucket (true) or private bucket (false)"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"response-format",children:"Response Format"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[{\n "code": 201,\n "insertedId": "123",\n "body": {\n "XPRD01": "Premium Widget",\n "XPRD02": 49.99,\n "XPRD12": {\n "data": "",\n "name": "product-photo.jpg",\n "type": "image/jpeg",\n "bucket": {\n "name": "a3f2e8d9c1b4.jpg",\n "dim": "PRD",\n "field": "XPRD12",\n "year": "2025",\n "month": "12",\n "tenantId": "abc123-tenant",\n "env": "app.q01.io",\n "public": ""\n },\n "url": "/media/getImage/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg"\n }\n }\n}]\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Response Notes:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"data"})," field is cleared (empty string) - file stored in GCS"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"bucket"})," object contains metadata for retrieval"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"url"})," is the retrieval endpoint path"]}),"\n",(0,r.jsxs)(n.li,{children:["Public uploads include ",(0,r.jsx)(n.code,{children:"tenantId"})," and ",(0,r.jsx)(n.code,{children:"env"})," in URL"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"download-operations",children:"Download Operations"}),"\n",(0,r.jsx)(n.h3,{id:"private-image-retrieval",children:"Private Image Retrieval"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Endpoint:"})," ",(0,r.jsx)(n.code,{children:"GET /api/v4/core/media/getImage/{dim}/{field}/{year}/{month}/{file}"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Authentication:"})," Required (Bearer token)"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Use Case:"})," Retrieve images from private bucket (default)"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Example:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'curl -X GET "https://coreservice.q01.io/api/v4/core/media/getImage/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg" \\\n -H "Authorization: Bearer eyJhbGc..."\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Response:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Binary image data"}),"\n",(0,r.jsx)(n.li,{children:"Content-Type: Detected from file extension"}),"\n",(0,r.jsxs)(n.li,{children:["Content-Disposition: ",(0,r.jsx)(n.code,{children:'inline; filename="a3f2e8d9c1b4.jpg"'})]}),"\n",(0,r.jsxs)(n.li,{children:["Cache-Control: ",(0,r.jsx)(n.code,{children:"private"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"JavaScript Example:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function displayProductImage(imageMetadata) {\
1n const { dim, field, year, month, name } = imageMetadata.bucket;\n\n const url = `/api/v4/core/media/getImage/${dim}/${field}/${year}/${month}/${name}`;\n\n const response = await fetch(url, {\n headers: { 'Authorization': `Bearer ${token}` }\n });\n\n if (!response.ok) {\n throw new Error('Failed to load image');\n }\n\n const blob = await response.blob();\n const imageUrl = URL.createObjectURL(blob);\n\n // Display in img tag\n document.getElementById('productImage').src = imageUrl;\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"public-image-retrieval",children:"Public Image Retrieval"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Endpoint:"})," ",(0,r.jsx)(n.code,{children:"GET /api/v4/core/media/getImage/{tenantId}/{env}/{dim}/{field}/{year}/{month}/{file}"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Authentication:"})," Not required"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Use Case:"})," Publicly accessible images (e.g., product catalog, marketing)"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Example:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'\x3c!-- Direct image tag - no authentication needed --\x3e\n<img src="https://coreservice.q01.io/api/v4/core/media/getImage/abc123-tenant/app.q01.io/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg"\n alt="Product Image" />\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use Case:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Product catalog images"}),"\n",(0,r.jsx)(n.li,{children:"Public avatars"}),"\n",(0,r.jsx)(n.li,{children:"Marketing materials"}),"\n",(0,r.jsx)(n.li,{children:"Content accessible without login"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"batch-retrieval-with-signed-urls",children:"Batch Retrieval with Signed URLs"}),"\n",(0,r.jsx)(n.h3,{id:"private-batch-retrieval",children:"Private Batch Retrieval"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Endpoint:"})," ",(0,r.jsx)(n.code,{children:"POST /api/v4/core/media/getImages"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Authentication:"})," Required"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Purpose:"})," Get temporary signed URLs for multiple images"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Request Body:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n {\n "dim": "PRD",\n "field": "XPRD12",\n "year": "2025",\n "month": "12",\n "file": "a3f2e8d9c1b4.jpg"\n },\n {\n "dim": "PRD",\n "field": "XPRD12",\n "year": "2025",\n "month": "12",\n "file": "b2e8f1c3d5a9.jpg"\n }\n]\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Response:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n {\n "url": "https://storage.googleapis.com/q01-media/abc123-tenant/app.q01.io/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=...",\n "filename": "a3f2e8d9c1b4.jpg"\n },\n {\n "url": "https://storage.googleapis.com/q01-media/abc123-tenant/app.q01.io/PRD/XPRD12/2025/12/b2e8f1c3d5a9.jpg?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=...",\n "filename": "b2e8f1c3d5a9.jpg"\n }\n]\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Signed URL Properties:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Validity:"})," 15 minutes"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Direct access:"})," No additional authentication required during validity period"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Method:"})," GET only"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Version:"})," v4 (Google Cloud Storage standard)"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"JavaScript Example:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function loadProductGallery(images) {\n // Prepare batch request\n
1const imageRequests = images.map(img => ({\n dim: 'PRD',\n field: 'XPRD12',\n year: img.year,\n month: img.month,\n file: img.filename\n }));\n\n // Request signed URLs\n const response = await fetch('/api/v4/core/media/getImages', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(imageRequests)\n });\n\n const signedUrls = await response.json();\n\n // Render gallery with signed URLs\n const gallery = document.getElementById('gallery');\n signedUrls.forEach(item => {\n const img = document.createElement('img');\n img.src = item.url;\n img.alt = item.filename;\n gallery.appendChild(img);\n });\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"public-batch-retrieval",children:"Public Batch Retrieval"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Endpoint:"})," ",(0,r.jsx)(n.code,{children:"POST /api/v4/core/media/getPublicImages"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Authentication:"})," Not required"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Request Body:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n {\n "tenantId": "abc123-tenant",\n "env": "app.q01.io",\n "dim": "PRD",\n "field": "XPRD12",\n "year": "2025",\n "month": "12",\n "file": "a3f2e8d9c1b4.jpg"\n }\n]\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Response:"})," Same format as private batch (signed URLs with 15 min expiry)"]}),"\n",(0,r.jsx)(n.h2,{id:"storage-configuration",children:"Storage Configuration"}),"\n",(0,r.jsx)(n.h3,{id:"environment-variables",children:"Environment Variables"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"CoreQuery (for retrieval):"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'BUCKET_NAME_MEDIA="q01-media" # Private bucket\nBUCKET_NAME_MEDIA_PUBLIC="q01-media-public" # Public bucket\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"CoreWrite (for upload):"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'BUCKET_NAME_MEDIA="q01-media"\nBUCKET_NAME_MEDIA_PUBLIC="q01-media-public"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"gcs-authentication",children:"GCS Authentication"}),"\n",(0,r.jsxs)(n.p,{children:["Both services require ",(0,r.jsx)(n.code,{children:"bucket.json"})," (service account key) in root directory:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"/Users/mwpo/q01sdk/mscorequery/dkcorequery/src/bucket.json\n/Users/mwpo/q01sdk/mscorewrite/dkcorewrite/src/bucket.json\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Permissions Required:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"storage.objects.create"})," (upload)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"storage.objects.get"})," (download)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"storage.objects.delete"})," (cleanup)"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"supported-file-types",children:"Supported File Types"}),"\n",(0,r.jsx)(n.h3,{id:"mime-type-detection",children:"MIME Type Detection"}),"\n",(0,r.jsx)(n.p,{children:"CoreQuery automatically detects MIME types for proper Content-Type headers:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Extension"}),(0,r.jsx)(n.th,{children:"MIME Type"}),(0,r.jsx)(n.th,{children:"Use Case"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"jpg, jpeg"}),(0,r.jsx)(n.td,{children:"image/jpeg"}),(0,r.jsx)(n.td,{children:"Photos"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"png"}),(0,r.jsx)(n.td,{children:"image/png"}),(0,r.jsx)(n.td,{children:"Graphics"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"gif"}),(0,r.jsx)(n.td,{children:"image/gif"}),(0,r.jsx)(n.td,{children:"Animations"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"pdf"}),(0,r.jsx)(n.td,{children:"application/pdf"}),(0,r.jsx)(n.td,{children:"Documents"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"doc, docx"}),(0,r.jsx)(n.td,{children:"application/msword"}),(0,r.jsx)(n.td,{children:"Word documents"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"xls, xlsx"}),(0,r.jsx)(n.td,{children:"application/vnd.ms-excel"}),(0,r.jsx)(n.td,{children:"Spreadsheets"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"zip"}),(0,r.jsx)(n.td,{children:"application/zip"}),(0,r.jsx)(n.td,{children:"Archives"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"mp3"}),(0,r.jsx)(n.td,{children:"audio/mpeg"}),(0,r.jsx)(n.td,{children:"Audio files"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Fallback:"})," ",(0,r.jsx)(n.code,{children:"application/octet-stream"})," for unknown types"]}),"\n",(0,r.jsx)(n.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,r.jsx)(n.h3,{id:"-do",children:"\u2705 DO"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use base64 encoding for uploads:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"const reader = new FileReader();
1\nreader.readAsDataURL(file); // Returns data:mime;base64,... format\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Store metadata in dimension table:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "XPRD12": {\n "bucket": {\n "name": "a3f2e8d9c1b4.jpg",\n "dim": "PRD",\n "field": "XPRD12",\n "year": "2025",\n "month": "12"\n },\n "url": "/media/getImage/PRD/XPRD12/2025/12/a3f2e8d9c1b4.jpg"\n }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Use signed URLs for galleries:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Reduces authentication overhead"}),"\n",(0,r.jsx)(n.li,{children:"Browser can cache images"}),"\n",(0,r.jsx)(n.li,{children:"Faster page load"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Choose correct bucket:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Private: User photos, invoices, sensitive documents"}),"\n",(0,r.jsx)(n.li,{children:"Public: Product catalog, marketing content, public profiles"}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"-dont",children:"\u274c DON'T"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Don't upload without base64 encoding:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u274c Wrong\nbody: JSON.stringify({ XPRD12: fileObject })\n\n// \u2705 Correct\nbody: JSON.stringify({ XPRD12: { data: base64String } })\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Don't hardcode tenant paths:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u274c Wrong - tenant extracted from JWT automatically\nconst path = `/tenant123/app.q01.io/PRD/...`\n\n// \u2705 Correct - let backend construct path\nconst metadata = result[0].body.XPRD12.bucket;\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Don't skip file validation:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u2705 Validate before upload\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\nif (file.size > MAX_SIZE) {\n throw new Error('File too large');\n}\n\nconst ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];\nif (!ALLOWED_TYPES.includes(file.type)) {\n throw new Error('Invalid file type');\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Don't forget signed URL expiry:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// Signed URLs expire after 15 minutes\n// Refresh if needed:\nasync function refreshGallery() {\n const newUrls = await fetchSignedUrls(images);\n updateGalleryImages(newUrls);\n}\n\nsetInterval(refreshGallery, 14 * 60 * 1000); // Refresh every 14 min\n"})}),"\n",(0,r.jsx)(n.h2,{id:"error-handling",children:"Error Handling"}),"\n",(0,r.jsx)(n.h3,{id:"common-errors",children:"Common Errors"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"500 - Unable to connect to bucket:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "code": 500,\n "message": "Unable to connect to bucket",\n "errors": []\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Fix:"})," Verify ",(0,r.jsx)(n.code,{children:"bucket.json"})," exists and has correct permissions"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"404 - File not found:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "code": 404,\n "message": "File not found in bucket",\n "errors": []\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Fix:"})," Verify path components (dim, field, year, month, filename)"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"413 - Payload too large:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "code": 413,\n "message": "Request entity too large"\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Fix:"})," Compress images or reduce file size before upload"]}),"\n",(0,r.jsx)(n.h2,{id:"use-case-product-image-upload",children:"Use Case: Product Image Upload"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"class ProductImageManager {\n
1constructor(apiBase, token) {\n this.apiBase = apiBase;\n this.token = token;\n this.MAX_SIZE = 5 * 1024 * 1024; // 5MB\n }\n\n async uploadImage(file, isPublic = false) {\n // Validate\n if (file.size > this.MAX_SIZE) {\n throw new Error('File exceeds 5MB limit');\n }\n\n // Read as base64\n const base64 = await this.readFileAsBase64(file);\n\n // Create product with image\n const response = await fetch(`${this.apiBase}/api/v4/core/PRD`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.token}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n XPRD01: 'New Product',\n XPRD12: {\n data: base64,\n name: file.name,\n type: file.type,\n public: isPublic\n },\n source: 'productCreate'\n })\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.message);\n }\n\n const result = await response.json();\n return result[0].body.XPRD12;\n }\n\n async getImageUrl(imageMetadata) {\n const { bucket } = imageMetadata;\n\n if (bucket.public === 'public') {\n // Public URL - no auth needed\n return `${this.apiBase}/api/v4/core/media/getImage/${bucket.tenantId}/${bucket.env}/${bucket.dim}/${bucket.field}/${bucket.year}/${bucket.month}/${bucket.name}`;\n } else {\n // Private URL - auth required\n return `${this.apiBase}/api/v4/core/media/getImage/${bucket.dim}/${bucket.field}/${bucket.year}/${bucket.month}/${bucket.name}`;\n }\n }\n\n readFileAsBase64(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (e) => resolve(e.target.result);\n reader.onerror = reject;\n reader.readAsDataURL(file);\n });\n }\n}\n"})}),"\n",(0,r.jsx)(n.h2,{id:"summary",children:"Summary"}),"\n",(0,r.jsx)(n.p,{children:"Media operations provide:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"\u2705 Secure file storage with tenant isolation"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Automatic path generation and uniqueness"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Private and public bucket support"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Signed URLs for temporary access"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 MIME type detection"}),"\n",(0,r.jsx)(n.li,{children:"\u2705 Base64 upload via JSON (no multipart/form-data)"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Key Takeaways:"})}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Upload via POST with base64-encoded data in JSON body"}),"\n",(0,r.jsxs)(n.li,{children:["Files stored in GCS with path: ",(0,r.jsx)(n.code,{children:"{tenantId}/{env}/{dim}/{field}/{year}/{month}/{filename}"})]}),"\n",(0,r.jsx)(n.li,{children:"Metadata stored in dimension table field"}),"\n",(0,r.jsx)(n.li,{children:"Private retrieval requires authentication"}),"\n",(0,r.jsx)(n.li,{children:"Public retrieval is unauthenticated"}),"\n",(0,r.jsx)(n.li,{children:"Batch endpoints return signed URLs (15 min validity)"}),"\n",(0,r.jsx)(n.li,{children:"Always validate file size and type before upload"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Next:"})," ",(0,r.jsx)(n.a,{href:"/docs/products/map.q01.io/core-api-platform-v4/api-operations/sort-operations",children:"Sort Operations \u2192"})]}),"\n",(0,r.jsx)(n.h2,{id:"related-concepts",children:"Related Concepts"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/products/map.q01.io/core-api-platform-v4/api-operations/write-operations",children:"Write Operations"})," - Creating records with media"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/products/map.q01.io/core-api-platform-v4/core-concepts/field-visibility",children:"Field Visibility"})," - COD_ON_OFF for image fields"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/products/map.q01.io/core-api-platform-v4/core-concepts/context-chain",children:"Context Chain"})," - Environment and tenant isolation"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.M)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},2172:(e,n,i)=>{i.d(n,{I:()=>l,M:()=>t});var r=i(1504);const s={},a=r.createContext(s);function t(e){const n=r.useContext(a);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:t(e.components),r.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.