PageSourceSearch

https://q01-platform.github.io/assets/js/e7f50b4c.0aa99d01.js

js q01-platform.github.io collected 2026-10-03 09:49:21 UTC 30,433 bytes, 1 lines download raw bytes

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.