Download All Files

A node.js script that downloads all files uploaded to Torii

This guide provides a script that downloads all files that were uploaded to Torii and organizes them into local folders by the application's name. In the directory where you create your project, a downloads folder will be created and subdirectories will be created underneath to organize the files.

The contract, invoice, and document files will be organized into subdirectories as follows:

/downloads/App1Name/contracts/document1.pdf
/downloads/App1Name/Invoices/attachment1.pdf
/downloads/App1Name/Documents/attachment2.pdf
...

Step 1 - Prerequisites

  1. Make sure you have Node (v14 or above) installed:
$ node -v
v20.15.1
  1. If you do not have Node installed, follow the steps in one of the articles below to install it.
    1. https://nodesource.com/blog/installing-nodejs-tutorial-mac-os-x/
    2. https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows
    3. https://nodejs.org/en/download/package-manager
  2. Install Axios, a Promise based HTTP client for the browser and node.js. A link to the Axios package documentation can be found here.
$ npm install axios

Step 2 - Create the script

  1. In the directory you've chosen for your project, create your script with the code below.
// downloader.js

const axios = require('axios');
const fs = require('fs');
const path = require('path');

// Replace with your actual API key
const API_KEY = 'YOUR_API_KEY';

// Base URL for Torii API
const BASE_URL = 'https://api.toriihq.com/v1.0';

// Counters for downloads
let contractsDownloaded = 0;
const appFilesDownloaded = {};

// Function to get contracts with documents and idApp
async function getContractsWithDocuments() {
    try {
        const response = await axios.get(`${BASE_URL}/contracts?fields=id,idApp,documents`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });
        return response.data.contracts.filter(contract => contract.documents && contract.documents.length > 0);
    } catch (error) {
        console.error('Error fetching contracts:', error.response ? error.response.data : error.message);
        return [];
    }
}

// Function to get apps with documents and idApp
async function getAppsWithDocuments(fileFields) {
    try {
        const fields = fileFields.map(field => field.systemKey)
        let url = `${BASE_URL}/apps?fields=id,name`
        if (fileFields.length > 0) {
            url += "," + fields.join(",")
        }
        const response = await axios.get(url, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });
        const apps = response.data.apps;

        // Filter only apps that have attachments
        let appsWithFiles = apps.filter(app =>
            fields.some(key => app.hasOwnProperty(key))
        )
        return appsWithFiles;
    } catch (error) {
        console.error('Error fetching contracts:', error.response ? error.response.data : error.message);
        return [];
    }
}

// Function to get the app name by idApp
async function getAppName(idApp) {
    try {
        const response = await axios.get(`${BASE_URL}/apps/${idApp}?fields=id,name`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });
        return response.data.app.name; // Corrected to access the name inside the app object
    } catch (error) {
        if (error.response && error.response.status === 404) {
            console.error(`App not found for idApp ${idApp}. Skipping...`);
            return null; // Return null if the app is not found
        } else {
            console.error(`Error fetching app name for idApp ${idApp}:`, error.response ? error.response.data : error.message);
            return `app_${idApp}`; // Fallback name in case of other errors
        }
    }
}

// Function to get all app fields of type "fileUpload"
async function getFileUploadFields() {
    try {
        const response = await axios.get(`${BASE_URL}/apps/fields`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });
        return response.data.fields
            .filter(field => field.type === 'fileUpload')
            .map(field => ({ systemKey: field.systemKey, name: field.name }));
    } catch (error) {
        console.error('Error fetching app fields:', error.response ? error.response.data : error.message);
        return [];
    }
}

// Function to get files attached to an app by idApp and specified fields
async function getAppFiles(idApp, field) {
    try {
        const response = await axios.get(`${BASE_URL}/apps/${idApp}?fields=${field.systemKey}`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });
        return response.data.app[field.systemKey] || [];
    } catch (error) {
        console.error(`Error fetching files for app ${idApp}:`, error.response ? error.response.data : error.message);
        return [];
    }
}

// Function to download a document using idUpload
async function downloadDocument(document, outputPath, fileType) {
    try {
        const response = await axios.get(`${BASE_URL}/files/${document.idUpload}/download`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            },
            responseType: 'stream' // Ensure the response is a stream so we can pipe it to a file
        });

        // Ensure the directory exists
        fs.mkdirSync(path.dirname(outputPath), { recursive: true });

        // Stream the file to disk
        const writer = fs.createWriteStream(outputPath);
        response.data.pipe(writer);

        writer.on('finish', () => {
            console.log(`Downloading (${fileType}): ${document.name}`);
        });

        writer.on('error', (error) => {
            console.error('Error writing file:', error);
        });
    } catch (error) {
        console.error(`Error downloading document ${document.name}:`, error.response ? error.response.data : error.message);
    }
}

// Main function to download all documents from contracts and app attachments
async function downloadAllDocuments() {
    const contracts = await getContractsWithDocuments();
    const fileUploadFields = await getFileUploadFields();
    const apps = await getAppsWithDocuments(fileUploadFields);

    console.log(`Found ${contracts.length} contracts with documents.`);
    console.log(`Found ${apps.length} apps with documents.`);
    console.log(`Found ${fileUploadFields.length} file upload fields.`);

    for (const contract of contracts) {
        const appName = await getAppName(contract.idApp);

        if (!appName) {
            // Skip this contract if the app name could not be retrieved
            continue;
        }

        const appFolderPath = path.join(__dirname, 'downloads', appName, 'contracts');

        for (const document of contract.documents) {
            const documentPath = path.join(appFolderPath, document.name);
            await downloadDocument(document, documentPath, 'Contract');
            contractsDownloaded++;
        }
    }

    // Download app's attachments and save them in folders named after the field names
    for (const app of apps) {
        for (const field of fileUploadFields) {
            const attachments = await getAppFiles(app.id, field);
            const fieldFolderPath = path.join(__dirname, 'downloads', app.name, field.name);

            if (!appFilesDownloaded[field.name]) {
                appFilesDownloaded[field.name] = 0;
            }

            for (const attachment of attachments) {
                const attachmentPath = path.join(fieldFolderPath, attachment.name);
                await downloadDocument(attachment, attachmentPath, field.name);
                appFilesDownloaded[field.name]++;
            }
        }
    }


    // Log the results
    console.log(`\nDownload Summary:`);
    console.log(`Contracts downloaded: ${contractsDownloaded}`);
    for (const [fieldName, count] of Object.entries(appFilesDownloaded)) {
        console.log(`${fieldName} files downloaded: ${count}`);
    }
}

// Run the script
downloadAllDocuments();
  1. Replace YOUR_API_KEY on line 8 above with an API key from your environment. The API key can be created from the Settings view in your Torii account. Additional details on how to create an API key can be found here.

Step 3 - Run the script


$ node downloader.js

📘

Need a more documented version of the script?

You can find additional information on the script in our recipes section.