Guía completa para cargar archivos en Blue utilizando mutaciones de GraphQL o la API REST
Resumen
Esta guía demuestra cómo cargar archivos en Blue utilizando dos enfoques diferentes:
- Carga Directa de GraphQL (Recomendado) - Carga simple de un solo paso con un límite de tamaño de archivo de 256MB
- Carga de API REST - Proceso de tres pasos que admite archivos más grandes de hasta 4.8GB
Esta es una comparación de los dos métodos:
Característica | Carga de GraphQL | Carga de API REST |
---|---|---|
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 |
Carga de Archivos con GraphQL
El método de carga de GraphQL proporciona una forma simple y directa de cargar archivos con una sola solicitud.
uploadFile
Carga un solo archivo en el sistema de almacenamiento de archivos y crea una referencia de archivo en la base de datos.
Entrada:
file: Upload!
- El archivo a cargar (usando multipart/form-data)projectId: String!
- ID del proyecto o slug donde se almacenará el archivocompanyId: String!
- ID de la empresa o slug donde se almacenará el archivo
Devuelve: File!
- El objeto de archivo creado
Ejemplo:
mutation UploadFile($input: UploadFileInput!) {
uploadFile(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
project {
id
name
}
folder {
id
title
}
}
}
uploadFiles
Carga múltiples archivos en el sistema de almacenamiento de archivos y crea referencias de archivos en la base de datos.
Entrada:
files: [Upload!]!
- Array de archivos a cargar (máx. 10)projectId: String!
- ID del proyecto o slug donde se almacenarán los archivoscompanyId: String!
- ID de la empresa o slug donde se almacenarán los archivos
Devuelve: [File!]!
- Array de objetos de archivo creados
Ejemplo:
mutation UploadFiles($input: UploadFilesInput!) {
uploadFiles(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
}
}
Implementación del Cliente
Cliente Apollo (JavaScript)
Carga de Archivo Único:
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##11c94497-dcd2-49eb-8a8d-db8f5d766bfa##CB@@html
<!-- HTML -->
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload File</button>
@@CB##dfcced8a-b06e-4822-af51-f99f2a874b77##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');
}
}
@@CB##06033095-27b7-44e1-a67d-4cd296fd5a0b##CB@@html
<!-- HTML -->
<input type="file" id="filesInput" multiple />
<button onclick="uploadFiles()">Upload Files</button>
@@CB##f9062ba0-84f8-4fad-942c-3edc9895263e##CB@@javascript
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} archivos cargados con éxito!`);
}
} catch (error) {
console.error('Network error:', error);
alert('Network error during upload');
}
}
cURL Example
# 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:
- Uploading to the "File Tab"
- Uploading to the "Todo File Custom Field"
Configuration
Define these constants at the top of your script:
@@CB##a6552a31-934e-44e8-be94-98cb4815ba1e##CB@@
This is diagram that shows the flow of the 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:
@@CB##1cc5ab16-c283-4779-a629-7d3baeddc119##CB@@
Step 2: Upload File to S3
- Uses the
files
parameter inrequests.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
@@CB##a81334fb-b6c1-45f7-b857-372c2c1526d2##CB@@
::
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
andcustomFieldId
specific to your setup
Sample Response:
{
"data": {
"createTodoCustomFieldFile": true
}
}