]> BookStack Code Mirror - api-scripts/commitdiff
Added node-based file attachment upload example
authorDan Brown <redacted>
Sat, 5 Feb 2022 06:35:15 +0000 (06:35 +0000)
committerDan Brown <redacted>
Sat, 5 Feb 2022 06:35:15 +0000 (06:35 +0000)
node-upload-attachment/index.js [new file with mode: 0644]
node-upload-attachment/package-lock.json [new file with mode: 0644]
node-upload-attachment/package.json [new file with mode: 0644]
node-upload-attachment/readme.md [new file with mode: 0644]

diff --git a/node-upload-attachment/index.js b/node-upload-attachment/index.js
new file mode 100644 (file)
index 0000000..1ef0ac9
--- /dev/null
@@ -0,0 +1,76 @@
+// Libraries used
+const fs = require('fs');
+const path = require('path');
+const axios = require('axios');
+const FormData = require('form-data');
+
+// BookStack API variables
+// Uses values on the environment unless hardcoded
+// To hardcode, add values to the empty strings in the below.
+const bookStackConfig = {
+    base_url: '' || process.env.BS_URL,
+    token_id: '' || process.env.BS_TOKEN_ID,
+    token_secret: '' || process.env.BS_TOKEN_SECRET,
+};
+
+// Script Logic
+////////////////
+
+// Check arguments provided
+if (process.argv.length < 4) {
+    console.error('Both <page_id> and <file_path> arguments need to be provided');
+    return;
+}
+
+// Get arguments passed via command
+const [_exec, _script, pageId, filePath] = process.argv;
+
+// Check the given file exists
+if (!fs.existsSync(filePath)) {
+    console.error(`File at "${filePath}" could not be found`);
+    return;
+}
+
+// Get the file name and create a read stream from the given file
+const fileStream = fs.createReadStream(filePath);
+const fileName = path.basename(filePath);
+
+// Gather our form data with all the required bits
+const formPostData = new FormData();
+formPostData.append('file', fileStream);
+formPostData.append('name', fileName);
+formPostData.append('uploaded_to', pageId);
+
+// Create an axios instance for our API
+const api = axios.create({
+    baseURL: bookStackConfig.base_url.replace(/\/$/, '') + '/api/',
+    timeout: 30000,
+    headers: { 'Authorization' : `Token ${bookStackConfig.token_id}:${bookStackConfig.token_secret}` },
+});
+
+// Wrap the rest of our code in an async function, so we can await within.
+(async function() {
+
+    // Upload the file using the gathered data
+    // Sends it with a "Content-Type: multipart/form-data" with the post
+    // body formatted as multipart/form-data content, with the "FormData" object
+    // from the "form-data" library does for us.
+    const {data: attachment} = await api.post('/attachments', formPostData, {
+        headers: formPostData.getHeaders(),
+    });
+
+    // Output the results
+    console.info(`File successfully uploaded to page ${pageId}.`);
+    console.info(` - Attachment ID: ${attachment.id}`);
+    console.info(` - Attachment Name: ${attachment.name}`);
+
+})().catch(err => {
+    // Handle API errors
+    if (err.response) {
+        console.error(`Request failed with status ${err.response.status} [${err.response.statusText}]`);
+        console.error(JSON.stringify(err.response.data));
+        return;
+    }
+    // Output all other errors
+    console.error(err)
+});
\ No newline at end of file
diff --git a/node-upload-attachment/package-lock.json b/node-upload-attachment/package-lock.json
new file mode 100644 (file)
index 0000000..63048f0
--- /dev/null
@@ -0,0 +1,156 @@
+{
+  "name": "upload-attachment",
+  "version": "1.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "upload-attachment",
+      "version": "1.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "axios": "^0.25.0",
+        "form-data": "^4.0.0"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "node_modules/axios": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
+      "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
+      "dependencies": {
+        "follow-redirects": "^1.14.7"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.14.7",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
+      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.51.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+      "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.34",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+      "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+      "dependencies": {
+        "mime-db": "1.51.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    }
+  },
+  "dependencies": {
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "axios": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
+      "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
+      "requires": {
+        "follow-redirects": "^1.14.7"
+      }
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "follow-redirects": {
+      "version": "1.14.7",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
+      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
+    },
+    "form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "mime-db": {
+      "version": "1.51.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+      "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
+    },
+    "mime-types": {
+      "version": "2.1.34",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+      "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+      "requires": {
+        "mime-db": "1.51.0"
+      }
+    }
+  }
+}
diff --git a/node-upload-attachment/package.json b/node-upload-attachment/package.json
new file mode 100644 (file)
index 0000000..4857ece
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "name": "upload-attachment",
+  "version": "1.0.0",
+  "description": "This script will upload the passed file into a BookStack book via the API.",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "Dan Brown",
+  "license": "MIT",
+  "dependencies": {
+    "axios": "^0.25.0",
+    "form-data": "^4.0.0"
+  }
+}
diff --git a/node-upload-attachment/readme.md b/node-upload-attachment/readme.md
new file mode 100644 (file)
index 0000000..6722df8
--- /dev/null
@@ -0,0 +1,41 @@
+# Upload a file attachment to a BookStack page
+
+This script will take a path to any local file and attempt
+to upload it to a BookStack page as an attachment
+using the API using a multipart/form-data request.
+
+This is a simplistic example of a NodeJS script. You will likely want to
+alter and extend this script to suit your use-case.
+
+## Requirements
+
+You will need NodeJS installed (Tested on v16, may work on earlier versions). 
+
+## Running
+
+First, download all the files in the same directory as this readme to a folder on your system
+and run the below from within that directory.
+
+```bash
+# Install NodeJS dependencies via NPM
+npm install 
+
+# Setup
+# ALTERNATIVELY: Open the script and add to the empty strings in the variables at the top.
+export BS_URL=https://bookstack.example.com # Set to be your BookStack base URL
+export BS_TOKEN_ID=abc123 # Set to be your API token_id
+export BS_TOKEN_SECRET=123abc # Set to be your API token_secret
+
+# Running the script
+node index.js <page_id> <file_path>
+```
+
+- `<page_id>` - The ID of the page you want to upload the attachment to.
+- `<file_path>` - File you want to upload as an attachment.
+        
+## Examples
+
+```bash
+# Upload the 'cat-image-collection.zip' file as an attachment to page of ID 205
+node index.js 205 ./cat-image-collection.zip
+```
\ No newline at end of file