Guida completa per il caricamento di file su Blue utilizzando mutazioni GraphQL o REST API


Panoramica

Questa guida dimostra come caricare file su Blue utilizzando due approcci diversi:

  1. Caricamento Diretto GraphQL (Consigliato) - Caricamento semplice in un solo passaggio con un limite di dimensione del file di 256MB
  2. Caricamento REST API - Processo in tre passaggi che supporta file di dimensioni maggiori fino a 4.8GB

Questa è una comparazione dei due metodi:

Caratteristica Caricamento GraphQL Caricamento REST API
Complexity Simple (one request) Complex (three steps)
File Size Limit 256MB per file 4.8GB per file
Batch Upload Up to 10 files Single file only
Implementation Direct mutation Multi-step process
Best For Most use cases Large files only

Caricamento File GraphQL

Il metodo di caricamento GraphQL fornisce un modo semplice e diretto per caricare file con una singola richiesta.

uploadFile

Carica un singolo file nel sistema di archiviazione dei file e crea un riferimento al file nel database.

Input:

  • file: Upload! - Il file da caricare (utilizzando multipart/form-data)
  • projectId: String! - ID del progetto o slug dove il file sarà archiviato
  • companyId: String! - ID dell'azienda o slug dove il file sarà archiviato

Restituisce: File! - L'oggetto file creato

Esempio:

mutation UploadFile($input: UploadFileInput!) {
  uploadFile(input: $input) {
    id
    uid
    name
    size
    type
    extension
    shared
    createdAt
    project {
      id
      name
    }
    folder {
      id
      title
    }
  }
}

uploadFiles

Carica più file nel sistema di archiviazione dei file e crea riferimenti ai file nel database.

Input:

  • files: [Upload!]! - Array di file da caricare (max 10)
  • projectId: String! - ID del progetto o slug dove i file saranno archiviati
  • companyId: String! - ID dell'azienda o slug dove i file saranno archiviati

Restituisce: [File!]! - Array di oggetti file creati

Esempio:

mutation UploadFiles($input: UploadFilesInput!) {
  uploadFiles(input: $input) {
    id
    uid
    name
    size
    type
    extension
    shared
    createdAt
  }
}

Implementazione del Client

Apollo Client (JavaScript)

Caricamento di un Singolo File:

import { gql } from '@apollo/client';

const UPLOAD_FILE = gql`
  mutation UploadFile($input: UploadFileInput!) {
    uploadFile(input: $input) {
      id
      uid
      name
      size
      type
      extension
      shared
      createdAt
    }
  }
`;

// Using file input
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

const { data } = await uploadFile({
  variables: {
    input: {
      file: file,
      projectId: "project_123", // or "my-project-slug"
      companyId: "company_456"  // or "my-company-slug"
    }
  }
});

Multiple Files Upload:

const UPLOAD_FILES = gql`
  mutation UploadFiles($input: UploadFilesInput!) {
    uploadFiles(input: $input) {
      id
      uid
      name
      size
      type
      extension
      shared
      createdAt
    }
  }
`;

// Using multiple file inputs
const fileInputs = document.querySelectorAll('input[type="file"]');
const files = Array.from(fileInputs).map(input => input.files[0]).filter(Boolean);

const { data } = await uploadFiles({
  variables: {
    input: {
      files: files,
      projectId: "project_123", // or "my-project-slug"
      companyId: "company_456"  // or "my-company-slug"
    }
  }
});
@@CB##677a85eb-bf55-4091-b9e7-e9a556d2aeee##CB@@html
<!-- HTML -->
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload File</button>
@@CB##1ce05f61-e886-4686-86a2-fb8a586e9e77##CB@@javascript
async function uploadFile() {
  const fileInput = document.getElementById('fileInput');
  const file = fileInput.files[0];

  if (!file) {
    alert('Please select a file');
    return;
  }

  // Create GraphQL mutation
  const query = `
    mutation UploadFile($input: UploadFileInput!) {
      uploadFile(input: $input) {
        id
        name
        size
        type
        extension
        createdAt
      }
    }
  `;

  // Prepare form data
  const formData = new FormData();
  formData.append('operations', JSON.stringify({
    query: query,
    variables: {
      input: {
        file: null, // Will be replaced by file
        projectId: "your_project_id", // or "your-project-slug"
        companyId: "your_company_id"  // or "your-company-slug"
      }
    }
  }));

  formData.append('map', JSON.stringify({
    "0": ["variables.input.file"]
  }));

  formData.append('0', file);

  try {
    const response = await fetch('/graphql', {
      method: 'POST',
      body: formData,
      headers: {
        // Don't set Content-Type - let browser set it with boundary
        'Authorization': 'Bearer your_auth_token'
      }
    });

    const result = await response.json();

    if (result.errors) {
      console.error('Upload failed:', result.errors);
      alert('Upload failed: ' + result.errors[0].message);
    } else {
      console.log('Upload successful:', result.data.uploadFile);
      alert('File uploaded successfully!');
    }
  } catch (error) {
    console.error('Network error:', error);
    alert('Network error during upload');
  }
}

Multiple Files Upload:

<!-- HTML -->
<input type="file" id="filesInput" multiple />
<button onclick="uploadFiles()">Upload Files</button>
async function uploadFiles() {
  const filesInput = document.getElementById('filesInput');
  const files = Array.from(filesInput.files);

  if (files.length === 0) {
    alert('Please select files');
    return;
  }

  if (files.length > 10) {
    alert('Maximum 10 files allowed');
    return;
  }

  const query = `
    mutation UploadFiles($input: UploadFilesInput!) {
      uploadFiles(input: $input) {
        id
        name
        size
        type
        extension
        createdAt
      }
    }
  `;

  const formData = new FormData();

  // Create file placeholders for variables
  const fileVariables = files.map((_, index) => null);

  formData.append('operations', JSON.stringify({
    query: query,
    variables: {
      input: {
        files: fileVariables,
        projectId: "your_project_id", // or "your-project-slug"
        companyId: "your_company_id"  // or "your-company-slug"
      }
    }
  }));

  // Create map for file replacements
  const map = {};
  files.forEach((_, index) => {
    map[index.toString()] = [`variables.input.files.${index}`];
  });
  formData.append('map', JSON.stringify(map));

  // Append actual files
  files.forEach((file, index) => {
    formData.append(index.toString(), file);
  });

  try {
    const response = await fetch('/graphql', {
      method: 'POST',
      body: formData,
      headers: {
        'Authorization': 'Bearer your_auth_token'
      }
    });

    const result = await response.json();

    if (result.errors) {
      console.error('Upload failed:', result.errors);
      alert('Upload failed: ' + result.errors[0].message);
    } else {
      console.log('Upload successful:', result.data.uploadFiles);
      alert(`${result.data.uploadFiles.length} file caricati con successo!`);
    }
  } catch (error) {
    console.error('Network error:', error);
    alert('Network error during upload');
  }
}
@@CB##5939ca10-1097-4007-907e-f4adbc574a87##CB@@bash
# Single file upload with cURL
curl -X POST \
  -H "Authorization: Bearer your_auth_token" \
  -F 'operations={"query":"mutation UploadFile($input: UploadFileInput!) { uploadFile(input: $input) { id name size type extension createdAt } }","variables":{"input":{"file":null,"projectId":"your_project_id","companyId":"your_company_id"}}}' \
  -F 'map={"0":["variables.input.file"]}' \
  -F '0=@/path/to/your/file.jpg' \
  https://your-api.com/graphql

REST API Upload

Use this method for files larger than 256MB (up to 4.8GB). This approach uses a three-step process: request upload credentials, upload to storage, then register the file in the database.

Prerequisites:

  • Python 3.x installed
  • requests library installed: pip install requests
  • A valid X-Bloo-Token-ID and X-Bloo-Token-Secret for Blue API authentication
  • The file to upload (e.g., test.jpg) in the same directory as the script

This method covers two scenarios:

  1. Uploading to the "File Tab"
  2. Uploading to the "Todo File Custom Field"

Configuration

Define these constants at the top of your script:

@@CB##e902e7cb-d7a9-476d-a74b-f8dc3e2c721f##CB@@

This is diagram that shows the flow of the upload process:

Upload Process

Uploading to File Tab

::code-group

import requests
import json
import os

# Configuration
FILENAME = "test.jpg"
TOKEN_ID = "YOUR_TOKEN_ID"
TOKEN_SECRET = "YOUR_TOKEN_SECRET"
COMPANY_ID = "YOUR_COMPANY_ID_OR_SLUG"
PROJECT_ID = "YOUR_PROJECT_ID_OR_SLUG"
BASE_URL = "https://api.blue.cc"

# Headers for Blue API
HEADERS = {
    "X-Bloo-Token-ID": TOKEN_ID,
    "X-Bloo-Token-Secret": TOKEN_SECRET,
    "X-Bloo-Company-ID": COMPANY_ID,
    "X-Bloo-Project-ID": PROJECT_ID,
}

# Step 1: Get upload credentials
def get_upload_credentials():
    url = f"{BASE_URL}/uploads?filename={FILENAME}"
    response = requests.get(url, headers=HEADERS)
    if response.status_code != 200:
        raise Exception(f"Failed to fetch upload credentials: {response.status_code} - {response.text}")
    return response.json()

# Step 2: Upload file to S3
def upload_to_s3(credentials):
    s3_url = credentials["url"]
    fields = credentials["fields"]
    
    files = {
        "acl": (None, fields["acl"]),
        "Content-Disposition": (None, fields["Content-Disposition"]),
        "Key": (None, fields["Key"]),
        "X-Key": (None, fields["X-Key"]),
        "Content-Type": (None, fields["Content-Type"]),
        "bucket": (None, fields["bucket"]),
        "X-Amz-Algorithm": (None, fields["X-Amz-Algorithm"]),
        "X-Amz-Credential": (None, fields["X-Amz-Credential"]),
        "X-Amz-Date": (None, fields["X-Amz-Date"]),
        "Policy": (None, fields["Policy"]),
        "X-Amz-Signature": (None, fields["X-Amz-Signature"]),
        "file": (FILENAME, open(FILENAME, "rb"), fields["Content-Type"])
    }
    
    response = requests.post(s3_url, files=files)
    if response.status_code != 204:
        raise Exception(f"S3 upload failed: {response.status_code} - {response.text}")
    print("S3 upload successful")

# Step 3: Register file with Blue
def register_file(file_uid):
    graphql_url = f"{BASE_URL}/graphql"
    headers = HEADERS.copy()
    headers["Content-Type"] = "application/json"
    
    query = """
    mutation CreateFile($uid: String!, $name: String!, $type: String!, $extension: String!, $size: Float!, $projectId: String!, $companyId: String!) {
        createFile(input: {uid: $uid, name: $name, type: $type, size: $size, extension: $extension, projectId: $projectId, companyId: $companyId}) {
            id
            uid
            name
            __typename
        }
    }
    """
    
    variables = {
        "uid": file_uid,
        "name": FILENAME,
        "type": "image/jpeg",
        "extension": "jpg",
        "size": float(os.path.getsize(FILENAME)),  # Dynamic file size
        "projectId": PROJECT_ID,
        "companyId": COMPANY_ID
    }
    
    payload = {
        "operationName": "CreateFile",
        "query": query,
        "variables": variables
    }
    
    response = requests.post(graphql_url, headers=headers, json=payload)
    if response.status_code != 200:
        raise Exception(f"GraphQL registration failed: {response.status_code} - {response.text}")
    print("File registration successful:", response.json())

# Main execution
def main():
    try:
        if not os.path.exists(FILENAME):
            raise Exception(f"File '{FILENAME}' not found")
        
        # Step 1: Fetch credentials
        credentials = get_upload_credentials()
        print("Upload credentials fetched:", credentials)
        
        # Step 2: Upload to S3
        upload_to_s3(credentials)
        
        # Step 3: Register file
        file_uid = credentials["fields"]["Key"].split("/")[0]
        register_file(file_uid)
        
        print("Upload completed successfully!")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

::

Steps Explained

Step 1: Request Upload Credentials

  • Send a GET request to https://api.blue.cc/uploads?filename=test.jpg
  • Returns S3 credentials in JSON format

Sample Response:

{
  "url": "https://s3.ap-southeast-1.amazonaws.com/bloo-uploads",
  "fields": {
    "acl": "private",
    "Content-Disposition": "attachment; filename=\"test.jpg\"",
    "Key": "cm8qujq3b01p22lrv9xbb2q62/test.jpg",
    "X-Key": "cm8qujq3b01p22lrv9xbb2q62",
    "Content-Type": "image/jpeg",
    "bucket": "bloo-uploads",
    "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
    "X-Amz-Credential": "AKIA2LAMM6FPGT53RMQY/20250327/ap-southeast-1/s3/aws4_request",
    "X-Amz-Date": "20250327T042138Z",
    "Policy": "...",
    "X-Amz-Signature": "e1d2446a0bdbfcbd3a73f926c7d92c87460f8d8ae030d717355f2790d6609452"
  }
}

Step 2: Upload File to S3

  • Uses the files parameter in requests.post to send a multipart/form-data request to the S3 URL
  • Ensures all fields match the policy exactly, avoiding curl's formatting issues

Step 3: Register File Metadata

  • Sends a GraphQL mutation to https://api.blue.cc/graphql
  • Dynamically calculates file size with os.path.getsize

Sample Response:

{
  "data": {
    "createFile": {
      "id": "cm8qudguy2ouprv2ljq3widd8",
      "uid": "cm8qujq3b01p22lrv9xbb2q62",
      "name": "test.jpg",
      "__typename": "File"
    }
  }
}

Uploading to Custom Field

::code-group

import requests
import json
import os

# Configuration
FILENAME = "test.jpg"
TOKEN_ID = "YOUR_TOKEN_ID"
TOKEN_SECRET = "YOUR_TOKEN_SECRET"
COMPANY_ID = "YOUR_COMPANY_ID_OR_SLUG"
PROJECT_ID = "YOUR_PROJECT_ID_OR_SLUG"
BASE_URL = "https://api.blue.cc"

# Headers for Blue API
HEADERS = {
    "X-Bloo-Token-ID": TOKEN_ID,
    "X-Bloo-Token-Secret": TOKEN_SECRET,
    "X-Bloo-Company-ID": COMPANY_ID,
    "X-Bloo-Project-ID": PROJECT_ID,
}

# Step 1: Get upload credentials
def get_upload_credentials():
    url = f"{BASE_URL}/uploads?filename={FILENAME}"
    response = requests.get(url, headers=HEADERS)
    if response.status_code != 200:
        raise Exception(f"Failed to fetch upload credentials: {response.status_code} - {response.text}")
    return response.json()

# Step 2: Upload file to S3
def upload_to_s3(credentials):
    s3_url = credentials["url"]
    fields = credentials["fields"]
    
    files = {
        "acl": (None, fields["acl"]),
        "Content-Disposition": (None, fields["Content-Disposition"]),
        "Key": (None, fields["Key"]),
        "X-Key": (None, fields["X-Key"]),
        "Content-Type": (None, fields["Content-Type"]),
        "bucket": (None, fields["bucket"]),
        "X-Amz-Algorithm": (None, fields["X-Amz-Algorithm"]),
        "X-Amz-Credential": (None, fields["X-Amz-Credential"]),
        "X-Amz-Date": (None, fields["X-Amz-Date"]),
        "Policy": (None, fields["Policy"]),
        "X-Amz-Signature": (None, fields["X-Amz-Signature"]),
        "file": (FILENAME, open(FILENAME, "rb"), fields["Content-Type"])
    }
    
    response = requests.post(s3_url, files=files)
    if response.status_code != 204:
        raise Exception(f"S3 upload failed: {response.status_code} - {response.text}")
    print("S3 upload successful")

# Step 3: Register file with Blue
def register_file(file_uid):
    graphql_url = f"{BASE_URL}/graphql"
    headers = HEADERS.copy()
    headers["Content-Type"] = "application/json"
    
    query = """
    mutation CreateFile($uid: String!, $name: String!, $type: String!, $extension: String!, $size: Float!, $projectId: String!, $companyId: String!) {
        createFile(input: {uid: $uid, name: $name, type: $type, size: $size, extension: $extension, projectId: $projectId, companyId: $companyId}) {
            id
            uid
            name
            __typename
        }
    }
    """
    
    variables = {
        "uid": file_uid,
        "name": FILENAME,
        "type": "image/jpeg",
        "extension": "jpg",
        "size": float(os.path.getsize(FILENAME)),
        "projectId": PROJECT_ID,
        "companyId": COMPANY_ID
    }
    
    payload = {
        "operationName": "CreateFile",
        "query": query,
        "variables": variables
    }
    
    response = requests.post(graphql_url, headers=headers, json=payload)
    if response.status_code != 200:
        raise Exception(f"GraphQL registration failed: {response.status_code} - {response.text}")
    print("File registration successful:", response.json())
    return file_uid

# Step 4: Associate file with Todo Custom Field
def associate_file_with_todo(file_uid):
    graphql_url = f"{BASE_URL}/graphql"
    headers = HEADERS.copy()
    headers["Content-Type"] = "application/json"
    
    query = """
    mutation CreateTodoCustomFieldFile($input: CreateTodoCustomFieldFileInput!) {
        createTodoCustomFieldFile(input: $input)
    }
    """
    
    variables = {
        "input": {
            "todoId": "YOUR_TODO_ID",
            "customFieldId": "YOUR_CUSTOM_FIELD_ID",
            "fileUid": file_uid
        }
    }
    
    payload = {
        "operationName": "CreateTodoCustomFieldFile",
        "query": query,
        "variables": variables
    }
    
    response = requests.post(graphql_url, headers=headers, json=payload)
    if response.status_code != 200:
        raise Exception(f"Todo association failed: {response.status_code} - {response.text}")
    print("Todo association successful:", response.json())

# Main execution
def main():
    try:
        if not os.path.exists(FILENAME):
            raise Exception(f"File '{FILENAME}' not found")
        
        # Step 1: Fetch credentials
        credentials = get_upload_credentials()
        print("Upload credentials fetched:", credentials)
        
        # Step 2: Upload to S3
        upload_to_s3(credentials)
        
        # Step 3: Register file
        file_uid = credentials["fields"]["Key"].split("/")[0]
        register_file(file_uid)
        
        # Step 4: Associate with Todo
        associate_file_with_todo(file_uid)
        
        print("Upload completed successfully!")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

::

Steps 1-3 are the same as the File Tab process (fetch credentials, upload to S3, and register file metadata).

For step 4, you need to associate the file with a todo custom field.

  • Sends a GraphQL mutation to link the file to a todo custom field
  • Uses todoId and customFieldId specific to your setup

Sample Response:

{
  "data": {
    "createTodoCustomFieldFile": true
  }
}

Assistente AI

Le risposte sono generate utilizzando l'IA e potrebbero contenere errori.

Come posso aiutarti?

Chiedimi qualsiasi cosa su Blue o su questa documentazione.

Invia per inviare • Maiusc+Invio per una nuova riga • ⌘I per aprire