Полное руководство по загрузке файлов в Blue с использованием мутаций GraphQL или REST API
Обзор
В этом руководстве показано, как загружать файлы в Blue, используя два различных подхода:
- Прямая загрузка GraphQL (Рекомендуется) - Простая одноступенчатая загрузка с ограничением на размер файла в 256 МБ
- Загрузка через REST API - Процесс из трех этапов, поддерживающий более крупные файлы до 4,8 ГБ
Это сравнение двух методов:
Особенность | Загрузка GraphQL | Загрузка через 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 |
Загрузка файлов через GraphQL
Метод загрузки GraphQL предоставляет простой и прямой способ загрузки файлов с помощью одного запроса.
uploadFile
Загружает один файл в систему хранения файлов и создает ссылку на файл в базе данных.
Входные данные:
file: Upload!
- Файл для загрузки (с использованием multipart/form-data)projectId: String!
- Идентификатор проекта или его сокращение, где будет храниться файлcompanyId: String!
- Идентификатор компании или ее сокращение, где будет храниться файл
Возвращает: File!
- Созданный объект файла
Пример:
mutation UploadFile($input: UploadFileInput!) {
uploadFile(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
project {
id
name
}
folder {
id
title
}
}
}
uploadFiles
Загружает несколько файлов в систему хранения файлов и создает ссылки на файлы в базе данных.
Входные данные:
files: [Upload!]!
- Массив файлов для загрузки (максимум 10)projectId: String!
- Идентификатор проекта или его сокращение, где будут храниться файлыcompanyId: String!
- Идентификатор компании или ее сокращение, где будут храниться файлы
Возвращает: [File!]!
- Массив созданных объектов файлов
Пример:
mutation UploadFiles($input: UploadFilesInput!) {
uploadFiles(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
}
}
Реализация клиента
Apollo Client (JavaScript)
Загрузка одного файла:
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"
}
}
});
Vanilla JavaScript
Single File Upload:
<!-- HTML -->
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload File</button>
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##885830ca-411f-4397-8939-eebfe3f6eb75##CB@@html
<!-- 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} файлов успешно загружено!`);
}
} 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:
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"\
This is diagram that shows the flow of the upload process:
Uploading to File Tab
::code-group
@@CB##64d7d501-ee74-4078-9c9b-2663f5d5434a##CB@@
::
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##d2805fc0-e6b6-45d3-bdff-84a1b3b105b9##CB@@
Step 2: Upload File to S3
- Uses the
files@@CODE##39207736-3625-4195-8a39-c1e32bba44b8##CODE@@requests.post@@CODE##d6b78904-aa5b-4906-9fb8-ebc9b1f9d0e2##CODE@@https://api.blue.cc/graphql
- Dynamically calculates file size with `os.path.getsize@@CODE##6f73e644-eeeb-44ec-9a8c-427bd9e3cf16##CODE@@todoId@@CODE##1b398f58-e26e-42fa-81f7-8e026de897b1##CODE@@customFieldId@@CODE##c9f10c2f-2825-43a6-8437-833c75fe81ec##CODE@@``json
{
"data": {
"createTodoCustomFieldFile": true
}
}