├── .gitattributes ├── src ├── index.js ├── utils │ ├── isObject.js │ ├── buildSearchParams.js │ ├── merge.js │ └── createRequestInstance.js ├── routes │ ├── index.js │ ├── Comments.js │ ├── Webhooks.js │ ├── KeyResults.js │ ├── Authorization.js │ ├── Goals.js │ ├── Views.js │ ├── Checklists.js │ ├── Folders.js │ ├── Spaces.js │ ├── Lists.js │ ├── Tasks.js │ └── Teams.js └── structures │ ├── Clickup.js │ └── Request.js ├── docs ├── .gitignore ├── content │ ├── settings.json │ └── en │ │ ├── index.md │ │ ├── setup.md │ │ ├── examples │ │ ├── authorization.md │ │ ├── teams.md │ │ └── tasks.md │ │ └── usage.md ├── nuxt.config.js ├── package.json └── README.md ├── .prettierrc.json ├── .npmignore ├── .eslintrc.json ├── .github └── workflows │ ├── pr-test.yml │ └── npm-publish.yml ├── .eslintignore ├── .prettierignore ├── LICENSE ├── package.json ├── .gitignore ├── test ├── utils.spec.js ├── clickup.spec.js └── request.spec.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { Clickup } = require('./structures/Clickup'); 2 | 3 | module.exports = { 4 | Clickup, 5 | }; 6 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "useTabs": true, 7 | "printWidth": 120 8 | } 9 | -------------------------------------------------------------------------------- /docs/content/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "clickup.js", 3 | "url": "https://clickup-js.netlify.app", 4 | "github": "ComfortablyCoding/clickup.js", 5 | "defaultBranch": "master" 6 | } 7 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import theme from '@nuxt/content-theme-docs' 2 | 3 | export default theme({ 4 | docs: { 5 | primaryColor: '#7b68ee' 6 | }, 7 | loading: { color: '#00CD81' }, 8 | }) 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # NPM 2 | 3 | # node 4 | node_modules 5 | 6 | # git 7 | .git 8 | .gitattributes 9 | .gitignore 10 | 11 | # vscode 12 | .vscode 13 | 14 | # ESLint 15 | .eslintrc.json 16 | .eslintignore 17 | 18 | # prettier 19 | .prettierrc.json 20 | .prettierignore 21 | 22 | # project 23 | *.log 24 | test/ 25 | docs/ 26 | 27 | # github 28 | .github/ 29 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "generate": "nuxt generate" 10 | }, 11 | "dependencies": { 12 | "@nuxt/content-theme-docs": "^0.11.0", 13 | "nuxt": "^2.15.8" 14 | }, 15 | "devDependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "commonjs": true, 5 | "es2020": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "extends": ["airbnb-base", "prettier"], 10 | "plugins": ["prettier"], 11 | "parserOptions": { 12 | "ecmaVersion": 11 13 | }, 14 | "rules": { 15 | "prettier/prettier": "error", 16 | "no-underscore-dangle": 0, 17 | "no-param-reassign": "off", 18 | "no-restricted-syntax": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/isObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Determines if the passed in value is an object 3 | * 4 | * @param value The value to check. 5 | * @returns {boolean} indicating if the passed in value is an object. 6 | */ 7 | const isObject = (value) => { 8 | const isObjectType = typeof value === 'object'; 9 | const isArray = Array.isArray(value); 10 | const isNull = value == null; 11 | return isObjectType && !isArray && !isNull; 12 | }; 13 | 14 | module.exports = { 15 | isObject, 16 | }; 17 | -------------------------------------------------------------------------------- /.github/workflows/pr-test.yml: -------------------------------------------------------------------------------- 1 | name: PR Test 2 | on: [pull_request] 3 | jobs: 4 | test-pr: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout branch 8 | uses: actions/checkout@v2 9 | 10 | - name: Install Node v16 11 | uses: actions/setup-node@v2 12 | with: 13 | node-version: '16.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | 16 | - name: Clean install deps 17 | run: npm ci 18 | 19 | - name: Run tests 20 | run: npm test 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs 2 | 3 | ## Setup 4 | 5 | Install dependencies: 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | ## Development 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Static Generation 18 | 19 | This will create the `dist/` directory for publishing to static hosting: 20 | 21 | ```bash 22 | yarn generate 23 | ``` 24 | 25 | To preview the static generated app, run `yarn start` 26 | 27 | For detailed explanation on how things work, checkout [nuxt/content](https://content.nuxtjs.org) and [@nuxt/content theme docs](https://content.nuxtjs.org/themes-docs). 28 | -------------------------------------------------------------------------------- /src/utils/buildSearchParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts parameters for a request to URLSearchParams 3 | * 4 | * @param {Object} query parameters to be converted 5 | * @returns {URLSearchParams} searchParams The query in LHS bracket style format 6 | */ 7 | const buildSearchParams = (query) => { 8 | const params = new URLSearchParams(); 9 | 10 | for (const key in query) { 11 | if (key.endsWith('[]')) { 12 | query[key].forEach((entry) => { 13 | params.append(key, entry); 14 | }); 15 | } else { 16 | params.set(key, query[key]); 17 | } 18 | } 19 | 20 | return params; 21 | }; 22 | 23 | module.exports = { 24 | buildSearchParams, 25 | }; 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # ESLint 2 | # git 3 | .git 4 | .gitattributes 5 | .gitignore 6 | 7 | # vscode 8 | .vscode 9 | 10 | # docs 11 | docs/ 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Dependency directories 29 | node_modules/ 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional eslint cache 35 | .eslintcache 36 | 37 | # Output of 'npm pack' 38 | *.tgz 39 | 40 | # Yarn Integrity file 41 | .yarn-integrity 42 | 43 | # dotenv environment variables file 44 | .env 45 | .env.test -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Prettier 2 | # git 3 | .git 4 | .gitattributes 5 | .gitignore 6 | 7 | # vscode 8 | .vscode 9 | 10 | # docs 11 | docs/ 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Dependency directories 29 | node_modules/ 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional eslint cache 35 | .eslintcache 36 | 37 | # Output of 'npm pack' 38 | *.tgz 39 | 40 | # Yarn Integrity file 41 | .yarn-integrity 42 | 43 | # dotenv environment variables file 44 | .env 45 | .env.test -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | const Authorization = require('./Authorization'); 2 | const Checklists = require('./Checklists'); 3 | const Comments = require('./Comments'); 4 | const Folders = require('./Folders'); 5 | const Goals = require('./Goals'); 6 | const KeyResults = require('./KeyResults'); 7 | const Lists = require('./Lists'); 8 | const Spaces = require('./Spaces'); 9 | const Tasks = require('./Tasks'); 10 | const Teams = require('./Teams'); 11 | const Views = require('./Views'); 12 | const Webhooks = require('./Webhooks'); 13 | 14 | module.exports = { 15 | Authorization, 16 | Checklists, 17 | Comments, 18 | Folders, 19 | Goals, 20 | KeyResults, 21 | Lists, 22 | Spaces, 23 | Tasks, 24 | Teams, 25 | Views, 26 | Webhooks, 27 | }; 28 | -------------------------------------------------------------------------------- /docs/content/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: '' 4 | position: 00 5 | category: 'Getting Started' 6 | features: 7 | - Authorization 8 | - Checklists 9 | - Comments 10 | - Folders 11 | - Goals 12 | - KeyResults 13 | - Lists 14 | - Spaces 15 | - Tasks 16 | - Teams 17 | - Views 18 | - Webhooks 19 | --- 20 | 21 | # clickup.js 22 | 23 | A Node.js wrapper for the [Clickup API](https://clickup.com/api). 24 | 25 | ## Features 26 | 27 | The available features are: 28 | 29 | 30 | 31 | ## Disclaimer 32 | 33 | The [clickup.js](https://github.com/ComfortablyCoding/clickup.js) package is **unofficial** and therefor not endorsed or affiliated with ClickUp or it's subsidiaries. 34 | -------------------------------------------------------------------------------- /src/utils/merge.js: -------------------------------------------------------------------------------- 1 | const { isObject } = require('./isObject'); 2 | 3 | /** 4 | * Performs a deep merge of source into object. It does not override properties 5 | * 6 | * @param object The destination object. 7 | * @param sources The values to be applied to the object 8 | * @returns The merged object 9 | */ 10 | const merge = (object, ...sources) => { 11 | const result = object; 12 | for (const source of sources) { 13 | for (const key in source) { 14 | if (isObject(result[key])) { 15 | result[key] = merge(result[key], source[key]); 16 | } else if (!Object.prototype.hasOwnProperty.call(result, key)) { 17 | result[key] = source[key]; 18 | } 19 | } 20 | } 21 | 22 | return result; 23 | }; 24 | 25 | module.exports = { 26 | merge, 27 | }; 28 | -------------------------------------------------------------------------------- /docs/content/en/setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Setup 3 | description: '' 4 | position: 01 5 | category: 'Getting Started' 6 | --- 7 | 8 | ## Installation 9 | 10 | First we need to install `clickup.js`. 11 | 12 | 13 | 14 | 15 | ```bash 16 | yarn add clickup.js 17 | ``` 18 | 19 | 20 | 21 | 22 | ```bash 23 | npm install clickup.js 24 | ``` 25 | 26 | 27 | 28 | 29 | ## Obtain Access Token 30 | 31 | Before the package is able to be used we must authenticate with the Clickup API and obtain an `Access Token`. You can read how to do so at the [Clickup API docs](https://clickup.com/api). 32 | 33 | Once complete you are now ready to use the package. 34 | -------------------------------------------------------------------------------- /src/utils/createRequestInstance.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | const { merge } = require('./merge'); 3 | 4 | /** 5 | * Creates a got instance with clickup default config 6 | * @private 7 | * @param {got.ExtendOptions} requestOptions Options for the created got instance. All options can be found [here](https://github.com/sindresorhus/got#options) 8 | * @returns {got.Got} A got instance 9 | */ 10 | const createRequestInstance = (token, requestOptions = {}) => { 11 | const requestDefaultOptions = { 12 | headers: { 13 | Authorization: token, 14 | 'Content-Type': 'application/json', 15 | }, 16 | responseType: 'json', 17 | prefixUrl: 'https://api.clickup.com/api/v2', 18 | }; 19 | // apply defaults where necessary 20 | const requestConfig = merge(requestOptions, requestDefaultOptions); 21 | return got.extend(requestConfig); 22 | }; 23 | 24 | module.exports = { 25 | createRequestInstance, 26 | }; 27 | -------------------------------------------------------------------------------- /src/routes/Comments.js: -------------------------------------------------------------------------------- 1 | class Comments { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'comment'; 18 | } 19 | 20 | /** 21 | * Update a comment 22 | * 23 | * @param {Number} commentId The comment id 24 | * @param {Object} data The comment data 25 | */ 26 | async update(commentId, data) { 27 | return this._request.put({ 28 | endpoint: `${this.route}/${commentId}`, 29 | data, 30 | }); 31 | } 32 | 33 | /** 34 | * Delete a comment 35 | * 36 | * @param {Number} commentId The comment id 37 | */ 38 | async delete(commentId) { 39 | return this._request.delete({ 40 | endpoint: `${this.route}/${commentId}`, 41 | }); 42 | } 43 | } 44 | 45 | module.exports = Comments; 46 | -------------------------------------------------------------------------------- /src/routes/Webhooks.js: -------------------------------------------------------------------------------- 1 | class Webhooks { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'webhook'; 18 | } 19 | 20 | /** 21 | * Update a webhook 22 | * 23 | * @param {String} webhookId The webhook id 24 | * @param {Object} data The webhook data 25 | */ 26 | async update(webhookId, data) { 27 | return this._request.put({ 28 | endpoint: `${this.route}/${webhookId}`, 29 | data, 30 | }); 31 | } 32 | 33 | /** 34 | * Delete a webhook 35 | * 36 | * @param {String} webhookId The webhook id 37 | */ 38 | async delete(webhookId) { 39 | return this._request.delete({ 40 | endpoint: `${this.route}/${webhookId}`, 41 | }); 42 | } 43 | } 44 | 45 | module.exports = Webhooks; 46 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v2 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: '16.x' 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | -------------------------------------------------------------------------------- /src/routes/KeyResults.js: -------------------------------------------------------------------------------- 1 | class KeyResults { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'key_result'; 18 | } 19 | 20 | /** 21 | * Update a key result 22 | * 23 | * @param {String} keyResultId The key result id 24 | * @param {Object} data The key result data 25 | */ 26 | updateKeyResult(keyResultId, data) { 27 | return this._request.put({ 28 | endpoint: `${this.route}/${keyResultId}`, 29 | data, 30 | }); 31 | } 32 | 33 | /** 34 | * Delete a key result 35 | * 36 | * @param {String} keyResultId The key result id 37 | */ 38 | deleteKeyResult(keyResultId) { 39 | return this._request.delete({ 40 | endpoint: `${this.route}/${keyResultId}`, 41 | }); 42 | } 43 | } 44 | module.exports = KeyResults; 45 | -------------------------------------------------------------------------------- /docs/content/en/examples/authorization.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Authorization 3 | description: 'Examples on using the Authorization endpoints' 4 | position: 00 5 | category: 'Examples' 6 | --- 7 | 8 | ### Get the authorized team(s) for this token 9 | 10 | ```js 11 | (async () => { 12 | try { 13 | // get the authorized team(s) for the given token 14 | const { body } = await clickup.authorization.getAuthorizedTeams(); 15 | console.log(body); 16 | } catch (error) { 17 | if (error.response) { 18 | // The request was made and the server responded with a status code 19 | // that falls out of the range of 2xx 20 | console.log(error.response.body); 21 | console.log(error.response.statusCode); 22 | console.log(error.response.headers); 23 | } else if (error.request) { 24 | // The request was made but no response was received 25 | console.log(error.request); 26 | } else { 27 | // Something happened in setting up the request that triggered an Error 28 | console.log('Error', error.message); 29 | } 30 | console.log(error.options); 31 | } 32 | })(); 33 | ``` 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 @ComfortablyCoding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/content/en/examples/teams.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Teams 3 | description: 'Examples on using the Teams endpoints' 4 | position: 02 5 | category: 'Examples' 6 | --- 7 | 8 | ### Get Filtered Team Tasks 9 | 10 | ```js 11 | (async () => { 12 | try { 13 | // return all tasks that meet the criteria 14 | const filterData = { 15 | date_created: 1508369194377, 16 | 'list_ids[]': [123], 17 | 'statuses[]': ['active', 'in progress'], 18 | }; 19 | const { body } = await clickup.teams.getFilteredTasks(543, filterData); 20 | console.log(body); 21 | } catch (error) { 22 | if (error.response) { 23 | // The request was made and the server responded with a status code 24 | // that falls out of the range of 2xx 25 | console.log(error.response.body); 26 | console.log(error.response.statusCode); 27 | console.log(error.response.headers); 28 | } else if (error.request) { 29 | // The request was made but no response was received 30 | console.log(error.request); 31 | } else { 32 | // Something happened in setting up the request that triggered an Error 33 | console.log('Error', error.message); 34 | } 35 | console.log(error.options); 36 | } 37 | })(); 38 | ``` 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clickup.js", 3 | "version": "3.0.2", 4 | "description": "A Node.js wrapper for the Clickup API", 5 | "main": "src/index.js", 6 | "keywords": [ 7 | "clickup", 8 | "clickupjs", 9 | "clickup-js", 10 | "clickup.js", 11 | "api", 12 | "wrapper", 13 | "wrapper-api" 14 | ], 15 | "scripts": { 16 | "test": "mocha", 17 | "lint": "eslint src test --fix", 18 | "format": "prettier --write ." 19 | }, 20 | "dependencies": { 21 | "form-data": "^4.0.0", 22 | "got": "^11.8.2" 23 | }, 24 | "devDependencies": { 25 | "chai": "^4.3.4", 26 | "eslint": "^8.4.1", 27 | "eslint-config-airbnb-base": "^15.0.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-import": "^2.25.3", 30 | "eslint-plugin-prettier": "^4.0.0", 31 | "mocha": "^9.1.3", 32 | "prettier": "^2.5.1", 33 | "sinon": "^12.0.1" 34 | }, 35 | "author": "@ComfortablyCoding", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/ComfortablyCoding/clickup.js.git" 39 | }, 40 | "homepage": "https://github.com/ComfortablyCoding/clickup.js#readme", 41 | "bugs": { 42 | "url": "https://github.com/ComfortablyCoding/clickup.js/issues" 43 | }, 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /src/routes/Authorization.js: -------------------------------------------------------------------------------- 1 | class Authorization { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | } 14 | 15 | /** 16 | * Get the access token for the given client 17 | * 18 | * @param {String} clientId Oauth app client id 19 | * @param {String} clientSecret Oauth app client secret 20 | * @param {String} code Code given in redirect url 21 | */ 22 | async accessToken(clientId, clientSecret, code) { 23 | return this._request.post({ 24 | endpoint: 'oauth/token', 25 | params: { 26 | client_id: clientId, 27 | client_secret: clientSecret, 28 | code, 29 | }, 30 | }); 31 | } 32 | 33 | /** 34 | * Get the user that this token belongs to 35 | */ 36 | async getAuthorizedUser() { 37 | return this._request.get({ 38 | endpoint: 'user', 39 | }); 40 | } 41 | 42 | /** 43 | * Get the authorized teams for this token 44 | */ 45 | async getAuthorizedTeams() { 46 | return this._request.get({ 47 | endpoint: 'team', 48 | }); 49 | } 50 | } 51 | 52 | module.exports = Authorization; 53 | -------------------------------------------------------------------------------- /src/routes/Goals.js: -------------------------------------------------------------------------------- 1 | class Goals { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'goal'; 18 | } 19 | 20 | /** 21 | * Get a goal 22 | * 23 | * @param {String} goalId The goal id 24 | */ 25 | async get(goalId) { 26 | return this._request.get({ 27 | endpoint: `${this.route}/${goalId}`, 28 | }); 29 | } 30 | 31 | /** 32 | * Update a goal 33 | * 34 | * @param {String} goalId The goal id 35 | * @param {Object} data The goal data 36 | */ 37 | async update(goalId, data) { 38 | return this._request.put({ 39 | endpoint: `${this.route}/${goalId}`, 40 | data, 41 | }); 42 | } 43 | 44 | /** 45 | * Delete a goal 46 | * 47 | * @param {String} goalId The goal id 48 | */ 49 | async delete(goalId) { 50 | return this._request.delete({ 51 | endpoint: `${this.route}/${goalId}`, 52 | }); 53 | } 54 | 55 | /** 56 | * Create a key result 57 | * 58 | * @param {String} goalId The goal id 59 | * @param {Object} data The key result data 60 | */ 61 | async createKeyResult(goalId, data) { 62 | return this._request.post({ 63 | endpoint: `${this.route}/${goalId}/key_result`, 64 | data, 65 | }); 66 | } 67 | } 68 | 69 | module.exports = Goals; 70 | -------------------------------------------------------------------------------- /src/routes/Views.js: -------------------------------------------------------------------------------- 1 | class Views { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'view'; 18 | } 19 | 20 | /** 21 | * Get a view 22 | * 23 | * @param {String} viewId The view id 24 | */ 25 | async get(viewId) { 26 | return this._request.get({ 27 | endpoint: `${this.route}/${viewId}`, 28 | }); 29 | } 30 | 31 | /** 32 | * Update a view 33 | * 34 | * @param {String} viewId The view id 35 | * @param {Object} data The view data 36 | */ 37 | async update(viewId, data) { 38 | return this._request.put({ 39 | endpoint: `${this.route}/${viewId}`, 40 | data, 41 | }); 42 | } 43 | 44 | /** 45 | * Delete a view 46 | * 47 | * @param {String} viewId The view id 48 | */ 49 | async delete(viewId) { 50 | return this._request.delete({ 51 | endpoint: `${this.route}/${viewId}`, 52 | }); 53 | } 54 | 55 | /** 56 | * Add a comment on a view 57 | * 58 | * @param {String} viewId The view id 59 | * @param {Object} data The comment data 60 | */ 61 | async addComment(viewId, data) { 62 | return this._request.post({ 63 | endpoint: `${this.route}/${viewId}/comment`, 64 | data, 65 | }); 66 | } 67 | 68 | /** 69 | * Get all comments on a view 70 | * 71 | * @param {String} viewId The view id 72 | */ 73 | async getComments(viewId) { 74 | return this._request.get({ 75 | endpoint: `${this.route}/${viewId}/comment`, 76 | }); 77 | } 78 | 79 | /** 80 | * Get all tasks in a view 81 | * 82 | * @param {String} viewId The view id 83 | * @param {Integer} [page=0] The page to get 84 | */ 85 | async getTasks(viewId, page = 0) { 86 | return this._request.get({ 87 | endpoint: `${this.route}/${viewId}/task`, 88 | params: { 89 | page, 90 | }, 91 | }); 92 | } 93 | } 94 | 95 | module.exports = Views; 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /src/routes/Checklists.js: -------------------------------------------------------------------------------- 1 | class Checklists { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'checklist'; 18 | } 19 | 20 | /** 21 | * Update a checklist 22 | * 23 | * @param {String} checklistId The checklist id 24 | * @param {Object} data The checklist data 25 | */ 26 | async update(checklistId, data) { 27 | return this._request.put({ 28 | endpoint: `${this.route}/${checklistId}`, 29 | data, 30 | }); 31 | } 32 | 33 | /** 34 | * Delete a checklist 35 | * 36 | * @param {String} checklistId The checklist id 37 | */ 38 | async delete(checklistId) { 39 | return this._request.delete({ 40 | endpoint: `${this.route}/${checklistId}`, 41 | }); 42 | } 43 | 44 | /** 45 | * Create a checklist item 46 | * 47 | * @param {String} checklistId The checklist id 48 | * @param {Object} data The checklist item data 49 | */ 50 | async createChecklistItem(checklistId, data) { 51 | return this._request.post({ 52 | endpoint: `${this.route}/${checklistId}/checklist_item`, 53 | data, 54 | }); 55 | } 56 | 57 | /** 58 | * Update a checklist item 59 | * 60 | * @param {String} checklistId The checklist id 61 | * @param {String} checklistItemId The checklist item id 62 | * @param {Object} data The checklist item data 63 | */ 64 | async updateChecklistItem(checklistId, checklistItemId, data) { 65 | return this._request.put({ 66 | endpoint: `${this.route}/${checklistId}/checklist_item/${checklistItemId}`, 67 | data, 68 | }); 69 | } 70 | 71 | /** 72 | * Delete a checklist item 73 | * 74 | * @param {String} checklistId The checklist id 75 | * @param {String} checklistItemId The checklist item id 76 | */ 77 | async deleteChecklistItem(checklistId, checklistItemId) { 78 | return this._request.delete({ 79 | endpoint: `${this.route}/${checklistId}/checklist_item/${checklistItemId}`, 80 | }); 81 | } 82 | } 83 | 84 | module.exports = Checklists; 85 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('chai'); 2 | const { buildSearchParams } = require('../src/utils/buildSearchParams'); 3 | const { isObject } = require('../src/utils/isObject'); 4 | const { merge } = require('../src/utils/merge'); 5 | 6 | describe('Testing buildSearchParams util', () => { 7 | it('should construct an instance of URLSearchParams', () => { 8 | assert.instanceOf(buildSearchParams({}), URLSearchParams); 9 | }); 10 | 11 | it('should build URLSearchParams to support LHS notation', () => { 12 | const params = { 13 | archive: false, 14 | order_by: 'due_date', 15 | 'statuses[]': ['in progress', 'completed'], 16 | }; 17 | 18 | const expectedOutput = new URLSearchParams([ 19 | ['archived', 'false'], 20 | ['order_by', 'due_date'], 21 | ['statuses[]', 'in progress'], 22 | ['statuses[]', 'completed'], 23 | ]); 24 | 25 | assert.deepEqual(buildSearchParams(params), expectedOutput); 26 | }); 27 | }); 28 | 29 | describe('Testing isObject util', () => { 30 | it('should be false for an array', () => { 31 | assert.isFalse(isObject([])); 32 | }); 33 | 34 | it('should be false for an number', () => { 35 | assert.isFalse(isObject(1)); 36 | }); 37 | 38 | it('should be false for an string', () => { 39 | assert.isFalse(isObject('test')); 40 | }); 41 | 42 | it('should be true for an object', () => { 43 | assert.isTrue(isObject({ id: 1 })); 44 | }); 45 | }); 46 | 47 | describe('Testing merge util', () => { 48 | it('should merge source into object', () => { 49 | const object = { 50 | text: 'original', 51 | headers: { 52 | 'Content-Type': 'application/json', 53 | }, 54 | }; 55 | const source1 = { 56 | id: 2, 57 | type: 'text', 58 | headers: { 59 | Authorization: 'token', 60 | }, 61 | }; 62 | const source2 = { 63 | id: 3, 64 | text: 'changed', 65 | headers: { 66 | 'Content-Type': 'text/html', 67 | }, 68 | }; 69 | const expectedResult = { 70 | id: 2, 71 | text: 'original', 72 | headers: { 73 | Authorization: 'token', 74 | 'Content-Type': 'application/json', 75 | }, 76 | type: 'text', 77 | }; 78 | 79 | const mergeResult = merge(object, source1, source2); 80 | assert.deepEqual(mergeResult, expectedResult); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/clickup.spec.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('chai'); 2 | const { Clickup } = require('../src/index'); 3 | const routes = require('../src/routes'); 4 | 5 | const token = 'token'; 6 | 7 | describe('Testing Clickup Client Instance', () => { 8 | let clickup; 9 | before(() => { 10 | clickup = new Clickup(token); 11 | }); 12 | 13 | it('should construct a Clickup instance', () => { 14 | assert.instanceOf(clickup, Clickup); 15 | }); 16 | 17 | it('should have default prefix url', () => { 18 | const { prefixUrl } = clickup._service.getOptions(); 19 | assert.strictEqual(prefixUrl, 'https://api.clickup.com/api/v2/'); 20 | }); 21 | 22 | it('should have default headers', () => { 23 | const headers = clickup._service.getHeaders(); 24 | assert.property(headers, 'authorization'); 25 | assert.property(headers, 'content-type'); 26 | assert.strictEqual(headers.authorization, token); 27 | assert.strictEqual(headers['content-type'], 'application/json'); 28 | }); 29 | 30 | it('should have the default response type', () => { 31 | const { responseType } = clickup._service.getOptions(); 32 | assert.strictEqual(responseType, 'json'); 33 | }); 34 | 35 | it('should instantiate all routes', () => { 36 | assert.instanceOf(clickup.authorization, routes.Authorization); 37 | assert.instanceOf(clickup.checklists, routes.Checklists); 38 | assert.instanceOf(clickup.comments, routes.Comments); 39 | assert.instanceOf(clickup.folders, routes.Folders); 40 | assert.instanceOf(clickup.goals, routes.Goals); 41 | assert.instanceOf(clickup.keyResults, routes.KeyResults); 42 | assert.instanceOf(clickup.lists, routes.Lists); 43 | assert.instanceOf(clickup.spaces, routes.Spaces); 44 | assert.instanceOf(clickup.tasks, routes.Tasks); 45 | assert.instanceOf(clickup.teams, routes.Teams); 46 | assert.instanceOf(clickup.views, routes.Views); 47 | assert.instanceOf(clickup.webhooks, routes.Webhooks); 48 | }); 49 | }); 50 | 51 | describe('Testing Client Got Options', () => { 52 | let clickup; 53 | before(() => { 54 | clickup = new Clickup(token, { 55 | hooks: { 56 | beforeRequest: [ 57 | (options) => { 58 | options.headers.foo = 'bar'; 59 | }, 60 | ], 61 | }, 62 | }); 63 | }); 64 | 65 | it('should have beforeRequest hook(s)', () => { 66 | const { hooks } = clickup._service.getOptions(); 67 | assert.isArray(hooks.beforeRequest); 68 | assert.lengthOf(hooks.beforeRequest, 1); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/content/en/examples/tasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tasks 3 | description: 'Examples on using the Tasks endpoints' 4 | position: 01 5 | category: 'Examples' 6 | --- 7 | 8 | ### Create a task 9 | 10 | ```js 11 | (async () => { 12 | try { 13 | // create a task on a list 14 | const taskData = { 15 | name: 'New Task Name', 16 | description: 'New Task Description', 17 | assignees: [183], 18 | tags: ['new'], 19 | status: 'Open', 20 | priority: 3, 21 | due_date: 1508369194377, 22 | due_date_time: false, 23 | time_estimate: 8640000, 24 | start_date: 1567780450202, 25 | start_date_time: false, 26 | notify_all: true, 27 | parent: null, 28 | links_to: null, 29 | check_required_custom_fields: true, 30 | custom_fields: [ 31 | { 32 | id: '0a52c486-5f05-403b-b4fd-c512ff05131c', 33 | value: 23, 34 | }, 35 | { 36 | id: '03efda77-c7a0-42d3-8afd-fd546353c2f5', 37 | value: 'Text field input', 38 | }, 39 | ], 40 | }; 41 | 42 | const { body } = await clickup.lists.createTask(1234, taskData); 43 | console.log(body); 44 | } catch (error) { 45 | if (error.response) { 46 | // The request was made and the server responded with a status code 47 | // that falls out of the range of 2xx 48 | console.log(error.response.body); 49 | console.log(error.response.statusCode); 50 | console.log(error.response.headers); 51 | } else if (error.request) { 52 | // The request was made but no response was received 53 | console.log(error.request); 54 | } else { 55 | // Something happened in setting up the request that triggered an Error 56 | console.log('Error', error.message); 57 | } 58 | console.log(error.options); 59 | } 60 | })(); 61 | ``` 62 | 63 | ### Fetch a task 64 | 65 | ```js 66 | (async () => { 67 | try { 68 | // get a specific task 69 | const { body } = await clickup.tasks.get('9hz'); 70 | console.log(body); 71 | } catch (error) { 72 | if (error.response) { 73 | // The request was made and the server responded with a status code 74 | // that falls out of the range of 2xx 75 | console.log(error.response.body); 76 | console.log(error.response.statusCode); 77 | console.log(error.response.headers); 78 | } else if (error.request) { 79 | // The request was made but no response was received 80 | console.log(error.request); 81 | } else { 82 | // Something happened in setting up the request that triggered an Error 83 | console.log('Error', error.message); 84 | } 85 | console.log(error.options); 86 | } 87 | })(); 88 | ``` 89 | -------------------------------------------------------------------------------- /src/structures/Clickup.js: -------------------------------------------------------------------------------- 1 | const { 2 | Authorization, 3 | Checklists, 4 | Comments, 5 | Folders, 6 | Goals, 7 | KeyResults, 8 | Lists, 9 | Spaces, 10 | Tasks, 11 | Teams, 12 | Views, 13 | Webhooks, 14 | } = require('../routes'); 15 | const { Request } = require('./Request'); 16 | 17 | class Clickup { 18 | /** 19 | * Creates a client instance that connects to the Clickup API 20 | * 21 | * @constructor 22 | * @param {String} token Clickup API Access Token 23 | * @param {import('got/dist/source').ExtendOptions} requestOptions Options for the created got instance. 24 | */ 25 | constructor(token, requestOptions) { 26 | // create service instance 27 | /** 28 | * The clickup request service 29 | * @type {Request} 30 | * @private 31 | */ 32 | this._service = new Request(token, requestOptions); 33 | 34 | // pull in all routes 35 | /** 36 | * authorization 37 | * 38 | * @type {Authorization} 39 | * @public 40 | */ 41 | this.authorization = new Authorization(this._service); 42 | /** 43 | * checklists 44 | * 45 | * @type {Checklists} 46 | * @public 47 | */ 48 | this.checklists = new Checklists(this._service); 49 | /** 50 | * comments 51 | * 52 | * @type {Comments} 53 | * @public 54 | */ 55 | this.comments = new Comments(this._service); 56 | /** 57 | * folders 58 | * 59 | * @type {Folder} 60 | * @public 61 | */ 62 | this.folders = new Folders(this._service); 63 | /** 64 | * goals 65 | * 66 | * @type {Goals} 67 | * @public 68 | */ 69 | this.goals = new Goals(this._service); 70 | /** 71 | * keyResults 72 | * 73 | * @type {KeyResults} 74 | * @public 75 | */ 76 | this.keyResults = new KeyResults(this._service); 77 | /** 78 | * lists 79 | * 80 | * @type {Lists} 81 | * @public 82 | */ 83 | this.lists = new Lists(this._service); 84 | /** 85 | * spaces 86 | * 87 | * @type {Spaces} 88 | * @public 89 | */ 90 | this.spaces = new Spaces(this._service); 91 | /** 92 | * tasks 93 | * 94 | * @type {Tasks} 95 | * @public 96 | */ 97 | this.tasks = new Tasks(this._service); 98 | /** 99 | * teams 100 | * 101 | * @type {Teams} 102 | * @public 103 | */ 104 | this.teams = new Teams(this._service); 105 | /** 106 | * views 107 | * 108 | * @type {Views} 109 | * @public 110 | */ 111 | this.views = new Views(this._service); 112 | /** 113 | * webhooks 114 | * 115 | * @type {Webhooks} 116 | * @public 117 | */ 118 | this.webhooks = new Webhooks(this._service); 119 | } 120 | } 121 | 122 | module.exports = { 123 | Clickup, 124 | }; 125 | -------------------------------------------------------------------------------- /docs/content/en/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | description: '' 4 | position: 02 5 | category: 'Getting Started' 6 | --- 7 | 8 | In your project, initialize an instance of clickup.js 9 | 10 | ```js 11 | const { Clickup } = require('clickup.js'); 12 | const token = '...'; // API access token 13 | const clickup = new Clickup(token); 14 | ``` 15 | 16 | Once you've created an instance, you can use it to access all the features provided by the wrapper, the following example fetches a task by id and displays the response to the console. 17 | 18 | ```js 19 | (async () => { 20 | try { 21 | // get a specific task 22 | const { body } = await clickup.tasks.get('9hz'); 23 | console.log(body); 24 | } catch (error) { 25 | if (error.response) { 26 | // The request was made and the server responded with a status code 27 | // that falls out of the range of 2xx 28 | console.log(error.response.body); 29 | console.log(error.response.statusCode); 30 | console.log(error.response.headers); 31 | } else if (error.request) { 32 | // The request was made but no response was received 33 | console.log(error.request); 34 | } else { 35 | // Something happened in setting up the request that triggered an Error 36 | console.log('Error', error.message); 37 | } 38 | console.log(error.options); 39 | } 40 | })(); 41 | ``` 42 | 43 | Additional examples can be view in the [example section](./examples). 44 | 45 | 46 | 47 | Due to the HTTP request library being used each error contains an `options` property which are the options Got used to create a request - just to make debugging easier. Additionally, the errors may have `request` and `response` properties depending on which phase of the request failed. Read more about HTTP request library [Got](https://github.com/sindresorhus/got). 48 | 49 | 50 | 51 | ## Caveats 52 | 53 | 54 | 55 | The library is structured to match classes with their respective routes, **NOT** how they are sectioned in the Clickup API docs. 56 | 57 | 58 | 59 | For example adding a guest to a task is under the `Tasks` class instead of the `Guests` class as its route is via `task` and not `guest`. Due to this a request to add a guest to a task will look like so: 60 | 61 | ```js 62 | (async () => { 63 | try { 64 | // guest data 65 | const guestData = { 66 | permission_level: 'read', 67 | }; 68 | // add guest to task 69 | const { body } = await clickup.tasks.addGuest('c04', 403, guestData); 70 | console.log(body); 71 | } catch (error) { 72 | if (error.response) { 73 | // The request was made and the server responded with a status code 74 | // that falls out of the range of 2xx 75 | console.log(error.response.body); 76 | console.log(error.response.statusCode); 77 | console.log(error.response.headers); 78 | } else if (error.request) { 79 | // The request was made but no response was received 80 | console.log(error.request); 81 | } else { 82 | // Something happened in setting up the request that triggered an Error 83 | console.log('Error', error.message); 84 | } 85 | console.log(error.options); 86 | } 87 | })(); 88 | ``` 89 | -------------------------------------------------------------------------------- /src/routes/Folders.js: -------------------------------------------------------------------------------- 1 | class Folders { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'folder'; 18 | } 19 | 20 | /** 21 | * Get a folder 22 | * 23 | * @param {Number} folderId The folder id 24 | */ 25 | async get(folderId) { 26 | return this._request.get({ 27 | endpoint: `${this.route}/${folderId}`, 28 | }); 29 | } 30 | 31 | /** 32 | * Update a folder 33 | * 34 | * @param {Number} folderId The folder id 35 | * @param {Object} data The folder data 36 | */ 37 | async update(folderId, data) { 38 | return this._request.put({ 39 | endpoint: `${this.route}/${folderId}`, 40 | data, 41 | }); 42 | } 43 | 44 | /** 45 | * Delete a folder 46 | * 47 | * @param {Number} folderId The folder id 48 | */ 49 | async delete(folderId) { 50 | return this._request.delete({ 51 | endpoint: `${this.route}/${folderId}`, 52 | }); 53 | } 54 | 55 | /** 56 | *Add a guest to a folder 57 | * 58 | * @param {Number} folderId The folder id 59 | * @param {Number} guestId The guest id 60 | * @param {Object} data The guest data 61 | */ 62 | async addGuest(folderId, guestId, data) { 63 | return this._request.post({ 64 | endpoint: `${this.route}/${folderId}/guest/${guestId}`, 65 | data, 66 | }); 67 | } 68 | 69 | /** 70 | * Remove a guest from a folder 71 | * 72 | * @param {Number} folderId The folder id 73 | * @param {Number} guestId The guest id 74 | */ 75 | async removeGuest(folderId, guestId) { 76 | return this._request.delete({ 77 | endpoint: `${this.route}/${folderId}/guest/${guestId}`, 78 | }); 79 | } 80 | 81 | /** 82 | * Create a list 83 | * 84 | * @param {Number} folderId The folder id 85 | * @param {Object} data The list data 86 | */ 87 | async createList(folderId, data) { 88 | return this._request.post({ 89 | endpoint: `${this.route}/${folderId}/list`, 90 | data, 91 | }); 92 | } 93 | 94 | /** 95 | * Get all lists in a folder 96 | * 97 | * @param {Number} folderId The folder id 98 | * @param {Boolean} [archived=false] If archived lists should be returned or not 99 | */ 100 | async getLists(folderId, archived = false) { 101 | return this._request.get({ 102 | endpoint: `${this.route}/${folderId}/list`, 103 | params: { 104 | archived, 105 | }, 106 | }); 107 | } 108 | 109 | /** 110 | * Create a view for a folder 111 | * 112 | * @param {Number} folderId The folder id 113 | * @param {Object} data The view data 114 | */ 115 | async createView(folderId, data) { 116 | return this._request.post({ 117 | endpoint: `${this.route}/${folderId}/view`, 118 | data, 119 | }); 120 | } 121 | 122 | /** 123 | * Get all views for a folder 124 | * 125 | * @param {Number} folderId The folder id 126 | */ 127 | async getViews(folderId) { 128 | return this._request.get({ 129 | endpoint: `${this.route}/${folderId}/view`, 130 | }); 131 | } 132 | } 133 | 134 | module.exports = Folders; 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clickup.js 2 | 3 | A Node.js wrapper for the [Clickup API](https://clickup.com/api). 4 | 5 | [![Downloads](https://img.shields.io/npm/dm/clickup.js.svg?style=for-the-badge)](https://www.npmjs.com/package/clickup.js) 6 | [![Install size](https://img.shields.io/bundlephobia/min/clickup.js?style=for-the-badge)](https://packagephobia.now.sh/result?p=clickup.js) 7 | ![Package version](https://img.shields.io/github/package-json/v/ComfortablyCoding/clickup.js?style=for-the-badge) 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm install clickup.js 13 | ``` 14 | 15 | or 16 | 17 | ```sh 18 | yarn add clickup.js 19 | ``` 20 | 21 | ## Usage 22 | 23 | Before you can use this library you will need to first authenticate with the Clickup API and obtain an `Access Token`. You can read how to do so at the [Clickup API docs](https://clickup.com/api). 24 | 25 | In your project, initialize an instance of clickup.js: 26 | 27 | ```js 28 | const { Clickup } = require('clickup.js'); 29 | const token = '...'; // API access token 30 | const clickup = new Clickup(token); 31 | ``` 32 | 33 | Once you've created an instance, you can use it to access all the features provided by the wrapper, the following example fetches a task by id and displays the response to the console: 34 | 35 | ```js 36 | (async () => { 37 | try { 38 | // get a specific task 39 | const { body } = await clickup.tasks.get('9hz'); 40 | console.log(body); 41 | } catch (error) { 42 | if (error.response) { 43 | // The request was made and the server responded with a status code 44 | // that falls out of the range of 2xx 45 | console.log(error.response.body); 46 | console.log(error.response.statusCode); 47 | console.log(error.response.headers); 48 | } else if (error.request) { 49 | // The request was made but no response was received 50 | console.log(error.request); 51 | } else { 52 | // Something happened in setting up the request that triggered an Error 53 | console.log('Error', error.message); 54 | } 55 | console.log(error.options); 56 | } 57 | })(); 58 | ``` 59 | 60 | **Note:** Due to the HTTP request library being used each error contains an `options` property which are the options Got used to create a request - just to make debugging easier. Additionally, the errors may have `request` and `response` properties depending on which phase of the request failed. Read more about HTTP request library [Got](https://github.com/sindresorhus/got). 61 | 62 | ## Important Note 63 | 64 | The library is structured to match classes with their respective routes, **NOT** how they are sectioned in the Clickup API docs. For example adding a guest to a task is under the `Tasks` class instead of the `Guests` class as its route is via `task` and not `guest`. Due to this a request to add a guest to a task will look like so: 65 | 66 | ```js 67 | (async () => { 68 | try { 69 | // guest data 70 | const guestData = { 71 | permission_level: 'read', 72 | }; 73 | // add guest to task 74 | const { body } = await clickup.tasks.addGuest('c04', 403, guestData); 75 | console.log(body); 76 | } catch (error) { 77 | if (error.response) { 78 | // The request was made and the server responded with a status code 79 | // that falls out of the range of 2xx 80 | console.log(error.response.body); 81 | console.log(error.response.statusCode); 82 | console.log(error.response.headers); 83 | } else if (error.request) { 84 | // The request was made but no response was received 85 | console.log(error.request); 86 | } else { 87 | // Something happened in setting up the request that triggered an Error 88 | console.log('Error', error.message); 89 | } 90 | console.log(error.options); 91 | } 92 | })(); 93 | ``` 94 | 95 | ## Documentation 96 | 97 | You can read the library documentation at the [clickup.js docs](https://clickup-js.netlify.app) 98 | 99 | ## Features 100 | 101 | The available features are: 102 | 103 | - `Authorization` 104 | - `Checklists` 105 | - `Comments` 106 | - `Folders` 107 | - `Goals` 108 | - `KeyResults` 109 | - `Lists` 110 | - `Spaces` 111 | - `Tasks` 112 | - `Teams` 113 | - `Views` 114 | - `Webhooks` 115 | 116 | ## Disclaimer 117 | 118 | The [clickup.js](https://github.com/ComfortablyCoding/clickup.js) package is **unofficial** and therefor not endorsed or affiliated with ClickUp or it's subsidiaries. 119 | -------------------------------------------------------------------------------- /src/routes/Spaces.js: -------------------------------------------------------------------------------- 1 | class Spaces { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'space'; 18 | } 19 | 20 | /** 21 | * Get a space 22 | * 23 | * @param {Number} spaceId The space id 24 | */ 25 | async get(spaceId) { 26 | return this._request.get({ 27 | endpoint: `${this.route}/${spaceId}`, 28 | }); 29 | } 30 | 31 | /** 32 | * Update a space 33 | * 34 | * @param {Number} spaceId The space id 35 | * @param {Object} data The space data 36 | */ 37 | async update(spaceId, data) { 38 | return this._request.put({ 39 | endpoint: `${this.route}/${spaceId}`, 40 | data, 41 | }); 42 | } 43 | 44 | /** 45 | * Delete a space 46 | * 47 | * @param {Numnber} spaceId The space id 48 | */ 49 | async delete(spaceId) { 50 | return this._request.delete({ 51 | endpoint: `${this.route}/${spaceId}`, 52 | }); 53 | } 54 | 55 | /** 56 | * Create a folder 57 | * 58 | * @param {Number} spaceId The space id 59 | * @param {Object} data The folder data 60 | */ 61 | async createFolder(spaceId, data) { 62 | return this._request.post({ 63 | endpoint: `${this.route}/${spaceId}/folder`, 64 | data, 65 | }); 66 | } 67 | 68 | /** 69 | * Get all folders in a space 70 | * 71 | * @param {Number} spaceId The space id 72 | * @param {Boolean} [archived=false] If archived folders should be returned or not 73 | */ 74 | async getFolders(spaceId, archived = false) { 75 | return this._request.get({ 76 | endpoint: `${this.route}/${spaceId}/folder`, 77 | params: { 78 | archived, 79 | }, 80 | }); 81 | } 82 | 83 | /** 84 | * Create a folderless list 85 | * 86 | * @param {Number} spaceId The space id 87 | * @param {Object} data The folderless list data 88 | */ 89 | async createFolderlessList(spaceId, data) { 90 | return this._request.post({ 91 | endpoint: `${this.route}/${spaceId}/list`, 92 | data, 93 | }); 94 | } 95 | 96 | /** 97 | * Get all folderless lists in a space 98 | * 99 | * @param {Number} spaceId The space id 100 | * @param {Boolean} [archived=false] If archived folderless lists should be returned or not 101 | */ 102 | async getFolderlessLists(spaceId, archived = false) { 103 | return this._request.get({ 104 | endpoint: `${this.route}/${spaceId}/list`, 105 | params: { 106 | archived, 107 | }, 108 | }); 109 | } 110 | 111 | /** 112 | * Get all tags in a space 113 | * 114 | * @param {Number} spaceId The space id 115 | */ 116 | async getTags(spaceId) { 117 | return this._request.get({ 118 | endpoint: `${this.route}/${spaceId}/tag`, 119 | }); 120 | } 121 | 122 | /** 123 | * Create a space tag 124 | * 125 | * @param {Number} spaceId The space id 126 | * @param {Object} data The space tag data 127 | */ 128 | async createTag(spaceId, data) { 129 | return this._request.post({ 130 | endpoint: `${this.route}/${spaceId}/tag`, 131 | data, 132 | }); 133 | } 134 | 135 | /** 136 | * Update a space tag 137 | * 138 | * @param {Number} spaceId The space id 139 | * @param {String} tagName The tag name 140 | */ 141 | async updateTag(spaceId, tagName) { 142 | return this._request.put({ 143 | endpoint: `${this.route}/${spaceId}/tag/${tagName}`, 144 | }); 145 | } 146 | 147 | /** 148 | * Delete a space tag 149 | * 150 | * @param {Number} spaceId The space id 151 | * @param {String} tagName The tag name 152 | */ 153 | async deleteTag(spaceId, tagName) { 154 | return this._request.delete({ 155 | endpoint: `${this.route}/${spaceId}/tag/${tagName}`, 156 | }); 157 | } 158 | 159 | /** 160 | * Create a view for a space 161 | * 162 | * @param {Number} spaceId The space id 163 | * @param {Object} data The view data 164 | */ 165 | async createView(spaceId, data) { 166 | return this._request.post({ 167 | endpoint: `${this.route}/${spaceId}/view`, 168 | data, 169 | }); 170 | } 171 | 172 | /** 173 | * Get all views for a space 174 | * 175 | * @param {Number} spaceId The space id 176 | */ 177 | async getViews(spaceId) { 178 | return this._request.get({ 179 | endpoint: `${this.route}/${spaceId}/view`, 180 | }); 181 | } 182 | } 183 | 184 | module.exports = Spaces; 185 | -------------------------------------------------------------------------------- /src/structures/Request.js: -------------------------------------------------------------------------------- 1 | const { createRequestInstance } = require('../utils/createRequestInstance'); 2 | const { buildSearchParams } = require('../utils/buildSearchParams'); 3 | 4 | class Request { 5 | /** 6 | * Creates a client instance that connects to the Clickup API 7 | * 8 | * @constructor 9 | * @param {import('got/dist/source').ExtendOptions} requestOptions Options for the created request instance. 10 | */ 11 | constructor(token, requestOptions) { 12 | /** 13 | * The access token 14 | */ 15 | this._token = token; 16 | // create service instance 17 | /** 18 | * The request instance 19 | * @private 20 | */ 21 | this._instance = createRequestInstance(token, requestOptions); 22 | } 23 | 24 | /** 25 | * Makes an HTTP GET request 26 | * 27 | * @param {Object} options Options to pass to the api call 28 | * @param {String} options.endpoint The endpoint to make a request to 29 | * @param {Object} options.params The parameters to add to the endpoint 30 | */ 31 | async get({ endpoint, params }) { 32 | return this._request({ endpoint, method: 'GET', params }); 33 | } 34 | 35 | /** 36 | * Makes an HTTP POST request 37 | * 38 | * @param {Object} options Options to pass to the api call 39 | * @param {String} options.endpoint The endpoint to make a request to 40 | * @param {Object} options.params The query parameters to add to the request 41 | * @param {Object} options.data The data to send in the body of the request 42 | * @param {Object} options.headers The headers to send along with the request 43 | */ 44 | async post({ endpoint, params, data, headers }) { 45 | return this._request({ endpoint, method: 'POST', params, data, headers }); 46 | } 47 | 48 | /** 49 | * Makes an HTTP PUT request 50 | * 51 | * @param {Object} options Options to pass to the api call 52 | * @param {String} options.endpoint The endpoint to make a request to 53 | * @param {Object} options.params The query parameters to add to the request 54 | * @param {Object} options.data The data to send in the body of the request 55 | */ 56 | async put({ endpoint, params, data }) { 57 | return this._request({ endpoint, method: 'PUT', params, data }); 58 | } 59 | 60 | /** 61 | * Makes an HTTP DELETE request 62 | * 63 | * @param {Object} options Options to pass to the api call 64 | * @param {String} options.endpoint The endpoint to make a request to 65 | * @param {Object} options.params The query parameters to add to the request 66 | */ 67 | async delete({ endpoint, params }) { 68 | return this._request({ endpoint, method: 'DELETE', params }); 69 | } 70 | 71 | /** 72 | * Makes an HTTP request 73 | * 74 | * @param {Object} options Options to pass to the api call 75 | * @param {String} options.endpoint The endpoint to make a request to 76 | * @param {String} options.method The request method to use in the request 77 | * @param {Object} options.params The query parameters to add to the request 78 | * @param {Object} options.data The data to send in the body of the request 79 | * @param {Object} options.headers The headers to send along with the request 80 | */ 81 | async _request({ endpoint, method = 'GET', params, data = {}, headers }) { 82 | const options = { 83 | method, 84 | }; 85 | 86 | if (params) { 87 | options.searchParams = buildSearchParams(params); 88 | } 89 | 90 | let contentType = this.getHeader('content-type'); 91 | 92 | if (headers) { 93 | options.headers = headers; 94 | if (headers['content-type']) { 95 | contentType = headers['content-type']; 96 | } 97 | } 98 | 99 | if (method !== 'GET') { 100 | // json data must be sent via json property, all others are sent via body 101 | const dataType = contentType === 'application/json' ? 'json' : 'body'; 102 | options[dataType] = data; 103 | } 104 | 105 | return this._instance(endpoint, options); 106 | } 107 | 108 | /** 109 | * Helper to obtain the instance headers 110 | */ 111 | getHeaders() { 112 | const options = this.getOptions(); 113 | return options.headers; 114 | } 115 | 116 | /** 117 | * Helper to obtain a specific header 118 | */ 119 | getHeader(name) { 120 | const options = this.getOptions(); 121 | return options.headers[name]; 122 | } 123 | 124 | /** 125 | * Helper to obtain the access token 126 | */ 127 | getToken() { 128 | return this._token; 129 | } 130 | 131 | /** 132 | * Helper to obtain the options 133 | */ 134 | getOptions() { 135 | return this._instance.defaults.options; 136 | } 137 | } 138 | 139 | module.exports = { Request }; 140 | -------------------------------------------------------------------------------- /src/routes/Lists.js: -------------------------------------------------------------------------------- 1 | class Lists { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'list'; 18 | } 19 | 20 | /** 21 | * Get a list 22 | * 23 | * @param {Number} listId The list id 24 | */ 25 | async get(listId) { 26 | return this._request.get({ 27 | endpoint: `${this.route}/${listId}`, 28 | }); 29 | } 30 | 31 | /** 32 | * Update a list 33 | * 34 | * @param {Number} listId The list id 35 | * @param {Object} data The list data 36 | */ 37 | async update(listId, data) { 38 | return this._request.put({ 39 | endpoint: `${this.route}/${listId}`, 40 | data, 41 | }); 42 | } 43 | 44 | /** 45 | * Delete a list 46 | * 47 | * @param {Number} listId The list id 48 | */ 49 | async delete(listId) { 50 | return this._request.delete({ 51 | endpoint: `${this.route}/${listId}`, 52 | }); 53 | } 54 | 55 | /** 56 | * Add a list comment 57 | * 58 | * @param {Number} listId The list id 59 | * @param {Object} data The comment data 60 | */ 61 | async addComment(listId, data) { 62 | return this._request.post({ 63 | endpoint: `${this.route}/${listId}/comment`, 64 | data, 65 | }); 66 | } 67 | 68 | /** 69 | * Get all comments on a list 70 | * 71 | * @param {Number} listId The list id 72 | */ 73 | async getComments(listId) { 74 | return this._request.get({ 75 | endpoint: `${this.route}/${listId}/comment`, 76 | }); 77 | } 78 | 79 | /** 80 | * Get all accessible custom fields of a list 81 | * 82 | * @param {Number} listId The list id 83 | */ 84 | async getAccessibleCustomFields(listId) { 85 | return this._request.get({ 86 | endpoint: `${this.route}/${listId}/field`, 87 | }); 88 | } 89 | 90 | /** 91 | * Add a guest to a list 92 | * 93 | * @param {Number} listId The list id 94 | * @param {Number} guestId The guest id 95 | * @param {Object} data The guest data 96 | */ 97 | async addGuest(listId, guestId, data) { 98 | return this._request.post({ 99 | endpoint: `${this.route}/${listId}/guest/${guestId}`, 100 | data, 101 | }); 102 | } 103 | 104 | /** 105 | * Remove a guest from a list 106 | * 107 | * @param {Number} listId The list id 108 | * @param {Number} guestId The guest id 109 | */ 110 | async removeGuest(listId, guestId) { 111 | return this._request.delete({ 112 | endpoint: `${this.route}/${listId}/guest/${guestId}`, 113 | }); 114 | } 115 | 116 | /** 117 | * Get all members of a list 118 | * 119 | * @param {Number} listId The list id 120 | */ 121 | async getMembers(listId) { 122 | return this._request.get({ 123 | endpoint: `${this.route}/${listId}/member`, 124 | }); 125 | } 126 | 127 | /** 128 | * Create a task 129 | * 130 | * @param {Number} listId The list id 131 | * @param {Object} data The task data 132 | */ 133 | async createTask(listId, data) { 134 | return this._request.post({ 135 | endpoint: `${this.route}/${listId}/task`, 136 | data, 137 | }); 138 | } 139 | 140 | /** 141 | * Get all tasks in a list 142 | * 143 | * @param {Number} listId The list id 144 | * @param {Object} [options] The parameter options to pass in 145 | */ 146 | async getTasks(listId, options = {}) { 147 | // eslint-disable-next-line no-param-reassign 148 | options.archived = options.archived || false; 149 | return this._request.get({ 150 | endpoint: `${this.route}/${listId}/task`, 151 | params: options, 152 | }); 153 | } 154 | 155 | /** 156 | * Create a task from a template 157 | * 158 | * @param {Number} listId The list id 159 | * @param {String} templateId The template id 160 | * @param {Object} data The task data 161 | */ 162 | async createTaskFromTemplate(listId, templateId, data) { 163 | return this._request.post({ 164 | endpoint: `${this.route}/${listId}/taskTemplate/${templateId}`, 165 | data, 166 | }); 167 | } 168 | 169 | /** 170 | * Create a view for a list 171 | * 172 | * @param {Number} listId The list id 173 | * @param {Object} data The view data 174 | */ 175 | async createView(listId, data) { 176 | return this._request.post({ 177 | endpoint: `${this.route}/${listId}/view`, 178 | data, 179 | }); 180 | } 181 | 182 | /** 183 | * Get all views for a list 184 | * 185 | * @param {Number} listId The list id 186 | */ 187 | async getViews(listId) { 188 | return this._request.get({ 189 | endpoint: `${this.route}/${listId}/view`, 190 | }); 191 | } 192 | 193 | /** 194 | * Add task to a list 195 | * 196 | * @param {String} listId The list id 197 | * @param {String} taskId The task id 198 | */ 199 | async addTaskToList(listId, taskId) { 200 | return this._request.post({ 201 | endpoint: `${this.route}/${listId}/task/${taskId}`, 202 | }); 203 | } 204 | 205 | /** 206 | * Remove a task from a list 207 | * 208 | * @param {Sting} listId The list id 209 | * @param {String} taskId The task id 210 | */ 211 | async removeTaskFromList(listId, taskId) { 212 | return this._request.delete({ 213 | endpoint: `${this.route}/${listId}/task/${taskId}`, 214 | }); 215 | } 216 | 217 | /** 218 | * Get list members 219 | * 220 | * @param {String} listId The list id 221 | */ 222 | async getListMembers(listId) { 223 | return this._request.get({ 224 | endpoint: `${this.route}/${listId}/member`, 225 | }); 226 | } 227 | } 228 | 229 | module.exports = Lists; 230 | -------------------------------------------------------------------------------- /test/request.spec.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('chai'); 2 | const sinon = require('sinon'); 3 | const { Clickup } = require('../src/index'); 4 | 5 | describe('Testing the Request HTTP methods', () => { 6 | let clickup; 7 | let stub; 8 | before(() => { 9 | clickup = new Clickup('token'); 10 | stub = sinon.stub(clickup._service, '_instance').resolves(); 11 | }); 12 | 13 | afterEach(() => { 14 | stub.resetHistory(); 15 | }); 16 | 17 | it('should use proper request method per HTTP method helper', async () => { 18 | await clickup._service.get({ 19 | endpoint: 'tasks/task_id', 20 | }); 21 | await clickup._service.post({ 22 | endpoint: 'tasks/task_id', 23 | }); 24 | await clickup._service.put({ 25 | endpoint: 'tasks/task_id', 26 | }); 27 | await clickup._service.delete({ 28 | endpoint: 'tasks/task_id', 29 | }); 30 | 31 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args; 32 | 33 | const [, getOptions] = getArgs; 34 | const [, postOptions] = postArgs; 35 | const [, putOptions] = putArgs; 36 | const [, deleteOptions] = deleteArgs; 37 | 38 | assert.strictEqual(getOptions.method, 'GET'); 39 | assert.strictEqual(postOptions.method, 'POST'); 40 | assert.strictEqual(putOptions.method, 'PUT'); 41 | assert.strictEqual(deleteOptions.method, 'DELETE'); 42 | }); 43 | 44 | it('should preserve the endpoint in request', async () => { 45 | await clickup._service.get({ 46 | endpoint: 'tasks/task_id', 47 | }); 48 | await clickup._service.post({ 49 | endpoint: 'tasks/task_id', 50 | }); 51 | await clickup._service.put({ 52 | endpoint: 'tasks/task_id', 53 | }); 54 | await clickup._service.delete({ 55 | endpoint: 'tasks/task_id', 56 | }); 57 | 58 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args; 59 | 60 | const [getEndpoint] = getArgs; 61 | const [postEndpoint] = postArgs; 62 | const [putEndpoint] = putArgs; 63 | const [deleteEndpoint] = deleteArgs; 64 | 65 | assert.strictEqual(getEndpoint, 'tasks/task_id'); 66 | assert.strictEqual(postEndpoint, 'tasks/task_id'); 67 | assert.strictEqual(putEndpoint, 'tasks/task_id'); 68 | assert.strictEqual(deleteEndpoint, 'tasks/task_id'); 69 | }); 70 | 71 | it('should construct the url params correctly', async () => { 72 | await clickup._service.get({ 73 | endpoint: 'tasks/task_id', 74 | params: { 75 | team_id: 123, 76 | }, 77 | }); 78 | await clickup._service.post({ 79 | endpoint: 'tasks/task_id', 80 | params: { 81 | team_id: 123, 82 | }, 83 | }); 84 | await clickup._service.put({ 85 | endpoint: 'tasks/task_id', 86 | params: { 87 | team_id: 123, 88 | }, 89 | }); 90 | await clickup._service.delete({ 91 | endpoint: 'tasks/task_id', 92 | params: { 93 | team_id: 123, 94 | }, 95 | }); 96 | const expectedParams = new URLSearchParams([['team_id', 123]]); 97 | 98 | const [getArgs, postArgs, putArgs, deleteArgs] = stub.args; 99 | 100 | const [, getOptions] = getArgs; 101 | const [, postOptions] = postArgs; 102 | const [, putOptions] = putArgs; 103 | const [, deleteOptions] = deleteArgs; 104 | 105 | assert.deepEqual(getOptions.searchParams, expectedParams); 106 | assert.deepEqual(postOptions.searchParams, expectedParams); 107 | assert.deepEqual(putOptions.searchParams, expectedParams); 108 | assert.deepEqual(deleteOptions.searchParams, expectedParams); 109 | }); 110 | 111 | it('should use correct request content placement per content type', async () => { 112 | await clickup._service.post({ 113 | endpoint: 'tasks/task_id', 114 | params: { 115 | team_id: 123, 116 | }, 117 | data: { 118 | priority: 1, 119 | }, 120 | }); 121 | await clickup._service.post({ 122 | endpoint: 'tasks/task_id', 123 | params: { 124 | team_id: 123, 125 | }, 126 | data: { 127 | priority: 1, 128 | }, 129 | headers: { 130 | 'content-type': 'multipart/form-data', 131 | }, 132 | }); 133 | await clickup._service.put({ 134 | endpoint: 'tasks/task_id', 135 | params: { 136 | team_id: 123, 137 | }, 138 | data: { 139 | priority: 1, 140 | }, 141 | }); 142 | await clickup._service.delete({ 143 | endpoint: 'tasks/task_id', 144 | params: { 145 | team_id: 123, 146 | }, 147 | data: { 148 | priority: 1, 149 | }, 150 | }); 151 | const [postArgs, postWithHeaderArgs, putArgs, deleteArgs] = stub.args; 152 | 153 | const [, postOptions] = postArgs; 154 | const [, postWithHeaderOptions] = postWithHeaderArgs; 155 | const [, putOptions] = putArgs; 156 | const [, deleteOptions] = deleteArgs; 157 | 158 | assert.isObject(postOptions.json); 159 | assert.isUndefined(postOptions.body); 160 | assert.isObject(postWithHeaderOptions.body); 161 | assert.isUndefined(postWithHeaderOptions.json); 162 | assert.isObject(putOptions.json); 163 | assert.isUndefined(putOptions.body); 164 | assert.isObject(deleteOptions.json); 165 | assert.isUndefined(deleteOptions.body); 166 | }); 167 | }); 168 | 169 | describe('Testing the Request helper methods', () => { 170 | let clickup; 171 | before(() => { 172 | clickup = new Clickup('token'); 173 | }); 174 | 175 | it('should get all current request options', () => { 176 | const currentOptions = clickup._service._instance.defaults.options; 177 | const options = clickup._service.getOptions(); 178 | 179 | assert.deepEqual(options, currentOptions); 180 | }); 181 | 182 | it('should get all current request headers', () => { 183 | const currentHeaders = clickup._service._instance.defaults.options.headers; 184 | const headers = clickup._service.getHeaders(); 185 | 186 | assert.deepEqual(headers, currentHeaders); 187 | }); 188 | 189 | it('should get the current authorization header', () => { 190 | const currentAuthorizationHeader = clickup._service._instance.defaults.options.headers.authorization; 191 | const authorizationHeader = clickup._service.getHeader('authorization'); 192 | 193 | assert.deepEqual(authorizationHeader, currentAuthorizationHeader); 194 | }); 195 | 196 | it('should get current token', () => { 197 | const currentToken = clickup._service._token; 198 | const token = clickup._service.getToken(); 199 | 200 | assert.deepEqual(token, currentToken); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /src/routes/Tasks.js: -------------------------------------------------------------------------------- 1 | const { createReadStream } = require('fs'); 2 | const FormData = require('form-data'); 3 | 4 | class Tasks { 5 | /** 6 | * @constructor 7 | * @param {Request} request A request instance 8 | */ 9 | constructor(request) { 10 | /** 11 | * A request instance 12 | * @type {Request} 13 | * @private 14 | */ 15 | this._request = request; 16 | /** 17 | * The main route for the collection 18 | * @type {String} 19 | */ 20 | this.route = 'task'; 21 | } 22 | 23 | /** 24 | * Get a task 25 | * 26 | * @param {String} taskId The task id 27 | * @param {Object} [options] The parameter options to pass in 28 | */ 29 | async get(taskId, options) { 30 | return this._request.get({ 31 | endpoint: `${this.route}/${taskId}`, 32 | params: options, 33 | }); 34 | } 35 | 36 | /** 37 | * Update a task 38 | * 39 | * @param {String} taskId The task id 40 | * @param {Object} data The task data 41 | * @param {Object} [options] The parameter options to pass in 42 | */ 43 | async update(taskId, data, options) { 44 | return this._request.put({ 45 | endpoint: `${this.route}/${taskId}`, 46 | params: options, 47 | data, 48 | }); 49 | } 50 | 51 | /** 52 | * Delete a task 53 | * 54 | * @param {String} taskId The task id 55 | * @param {Object} [options] The parameter options to pass in 56 | */ 57 | async delete(taskId, options) { 58 | return this._request.delete({ 59 | endpoint: `${this.route}/${taskId}`, 60 | params: options, 61 | }); 62 | } 63 | 64 | /** 65 | * Add an attachment to a task 66 | * 67 | * @param {String} taskId The task id 68 | * @param {Object} fileSettings The file settings 69 | * @param {String} fileSettings.filePath The path to the file 70 | * @param {String} fileSettings.fileName The name of the attachment file along with its extension type. Example: 'notes.txt' 71 | * @param {Object} [options] The parameter options to pass in 72 | */ 73 | async addAttachment(taskId, fileSettings, options) { 74 | // ensure fileSettings are provided 75 | if (fileSettings) { 76 | if (!fileSettings.filePath) { 77 | throw new Error('A file path must be provided'); 78 | } 79 | if (!fileSettings.fileName) { 80 | throw new Error('A file name must be provided'); 81 | } 82 | } else { 83 | throw new Error('File settings must be provided'); 84 | } 85 | 86 | // building form-data 87 | const form = new FormData(); 88 | form.append('filename', fileSettings.fileName); 89 | form.append('attachment', createReadStream(fileSettings.filePath)); 90 | 91 | // setting headers 92 | const headers = form.getHeaders(); 93 | headers.authorization = this._request.getToken(); 94 | 95 | return this._request.post({ 96 | endpoint: `${this.route}/${taskId}/attachment`, 97 | params: options, 98 | data: form, 99 | headers, 100 | }); 101 | } 102 | 103 | /** 104 | * Add a comment to as task 105 | * 106 | * @param {String} taskId The task id 107 | * @param {Object} data The comment data 108 | * @param {Object} [options] The parameter options to pass in 109 | */ 110 | async addComment(taskId, data, options) { 111 | return this._request.post({ 112 | endpoint: `${this.route}/${taskId}/comment`, 113 | params: options, 114 | data, 115 | }); 116 | } 117 | 118 | /** 119 | * Get all comments on a task 120 | * 121 | * @param {String} taskId The task id 122 | * @param {Object} [options] The parameter options to pass in 123 | */ 124 | async getComments(taskId, options) { 125 | return this._request.get({ 126 | endpoint: `${this.route}/${taskId}/comment`, 127 | params: options, 128 | }); 129 | } 130 | 131 | /** 132 | * Create a checklist in a task 133 | * 134 | * @param {String} taskId The task id 135 | * @param {Object} data The checklist data 136 | * @param {Object} [options] The parameter options to pass in 137 | */ 138 | async createChecklist(taskId, data, options) { 139 | return this._request.post({ 140 | endpoint: `${this.route}/${taskId}/checklist`, 141 | params: options, 142 | data, 143 | }); 144 | } 145 | 146 | /** 147 | * Add a custom field value for a task 148 | * 149 | * @param {String} taskId The task id 150 | * @param {String} fieldId The custom field id 151 | * @param {Object} data The custom field data 152 | * @param {Object} [options] The parameter options to pass in 153 | */ 154 | async addCustomFieldValue(taskId, fieldId, data, options) { 155 | return this._request.post({ 156 | endpoint: `${this.route}/${taskId}/field/${fieldId}`, 157 | params: options, 158 | data, 159 | }); 160 | } 161 | 162 | /** 163 | * Delete a custom field value for a task 164 | * 165 | * @param {String} taskId The task id 166 | * @param {String} fieldId The custom field id 167 | * @param {Object} [options] The parameter options to pass in 168 | */ 169 | async deleteCustomFieldValue(taskId, fieldId, options) { 170 | return this._request.delete({ 171 | endpoint: `${this.route}/${taskId}/field/${fieldId}`, 172 | params: options, 173 | }); 174 | } 175 | 176 | /** 177 | * Create a dependancy for a task 178 | * 179 | * @param {String} taskId The task id 180 | * @param {Object} data The dependency data 181 | * @param {Object} [options] The parameter options to pass in 182 | */ 183 | async addDependency(taskId, data, options) { 184 | return this._request.post({ 185 | endpoint: `${this.route}/${taskId}/dependency`, 186 | params: options, 187 | data, 188 | }); 189 | } 190 | 191 | /** 192 | * Delete a dependancy for a task 193 | * 194 | * @param {String} taskId The task id 195 | * @param {Object} options The parameter options to pass in 196 | */ 197 | async deleteDependency(taskId, options) { 198 | return this._request.delete({ 199 | endpoint: `${this.route}/${taskId}/dependency`, 200 | params: options, 201 | }); 202 | } 203 | 204 | /** 205 | * Add a task link 206 | * 207 | * @param {String} taskId The task id 208 | * @param {String} linksTo The id of the task to link to 209 | * @param {Object} [options] The parameter options to pass in 210 | */ 211 | async addTaskLink(taskId, linksTo, options) { 212 | return this._request.post({ 213 | endpoint: `${this.route}/${taskId}/link/${linksTo}`, 214 | params: options, 215 | }); 216 | } 217 | 218 | /** 219 | * Delete a task link 220 | * 221 | * @param {String} taskId The task id 222 | * @param {String} linksTo The id of the task to link to 223 | * @param {String} [options] The parameter options to pass in 224 | */ 225 | async deleteTaskLink(taskId, linksTo, options) { 226 | return this._request.delete({ 227 | endpoint: `${this.route}/${taskId}/link/${linksTo}`, 228 | params: options, 229 | }); 230 | } 231 | 232 | /** 233 | * Add a guest to a task 234 | * 235 | * @param {String} taskId The task id 236 | * @param {Number} guestId The guest id 237 | * @param {Object} data The guest data 238 | * @param {Object} [options] The parameter options to pass in 239 | */ 240 | async addGuest(taskId, guestId, data, options) { 241 | return this._request.post({ 242 | endpoint: `${this.route}/${taskId}/guest/${guestId}`, 243 | params: options, 244 | data, 245 | }); 246 | } 247 | 248 | /** 249 | * Remove a guest from a task 250 | * 251 | * @param {String} taskId The task id 252 | * @param {Number} guestId The guest id 253 | * @param {Object} [options] The parameter options to pass in 254 | */ 255 | async removeGuest(taskId, guestId, options) { 256 | return this._request.delete({ 257 | endpoint: `${this.route}/${taskId}/guest/${guestId}`, 258 | params: options, 259 | }); 260 | } 261 | 262 | /** 263 | * Get all members of a task 264 | * 265 | * @param {String} taskId The task id 266 | */ 267 | async getMembers(taskId) { 268 | return this._request.get({ 269 | endpoint: `${this.route}/${taskId}/member`, 270 | }); 271 | } 272 | 273 | /** 274 | * Add a tag to a task 275 | * 276 | * @param {String} taskId The task id 277 | * @param {String} tagName The tag name 278 | * @param {Object} [options] The parameter options to pass in 279 | */ 280 | async addTag(taskId, tagName, options) { 281 | return this._request.post({ 282 | endpoint: `${this.route}/${taskId}/tag/${tagName}`, 283 | params: options, 284 | }); 285 | } 286 | 287 | /** 288 | * Remove a tag from a task 289 | * 290 | * @param {String} taskId The task id 291 | * @param {String} tagName The tag name 292 | * @param {Object} [options] The parameter options to pass in 293 | */ 294 | async removeTag(taskId, tagName, options) { 295 | return this._request.delete({ 296 | endpoint: `${this.route}/${taskId}/tag/${tagName}`, 297 | params: options, 298 | }); 299 | } 300 | 301 | /** 302 | * Track time for a task (Time Tracking Legacy API) 303 | * 304 | * @param {String} taskId The task id 305 | * @param {Object} data The time tracking data 306 | * @param {Object} [options] The parameter options to pass in 307 | */ 308 | async trackTime(taskId, data, options) { 309 | return this._request.post({ 310 | endpoint: `${this.route}/${taskId}/time`, 311 | params: options, 312 | data, 313 | }); 314 | } 315 | 316 | /** 317 | * Get tracked time for a task (Time Tracking Legacy API) 318 | * 319 | * @param {String} taskId The task id 320 | * @param {Object} [options] The parameter options to pass in 321 | */ 322 | async getTrackedTime(taskId, options) { 323 | return this._request.get({ 324 | endpoint: `${this.route}/${taskId}/time`, 325 | params: options, 326 | }); 327 | } 328 | 329 | /** 330 | * Edit tracked time for a task (Time Tracking Legacy API) 331 | * 332 | * @param {String} taskId The task id 333 | * @param {String} intervalId The interval id 334 | * @param {Object} data The time tracking data 335 | * @param {Object} [options] The parameter options to pass in 336 | */ 337 | async editTrackedTime(taskId, intervalId, data, options) { 338 | return this._request.put({ 339 | endpoint: `${this.route}/${taskId}/time/${intervalId}`, 340 | params: options, 341 | data, 342 | }); 343 | } 344 | 345 | /** 346 | * Delete tracked time for a task 347 | * 348 | * @param {String} taskId The task id 349 | * @param {String} intervalId The interval id 350 | * @param {Object} [options] The parameter options to pass in 351 | */ 352 | async deleteTrackedTime(taskId, intervalId, options) { 353 | return this._request.delete({ 354 | endpoint: `${this.route}/${taskId}/time/${intervalId}`, 355 | params: options, 356 | }); 357 | } 358 | 359 | /** 360 | * Get tasks time in status 361 | * 362 | * @param {String} taskId The task id 363 | * @param {Object} options The parameter options to pass in 364 | */ 365 | async getTimeInStatus(taskId, options) { 366 | return this._request.get({ 367 | endpoint: `${this.route}/${taskId}/time_in_status`, 368 | params: options, 369 | }); 370 | } 371 | 372 | /** 373 | * Get bulk tasks time in status 374 | * 375 | * @param {Object} options The parameter options to pass in 376 | */ 377 | async getBulkTimeInStatus(options) { 378 | return this._request.get({ 379 | endpoint: `${this.route}/bulk_time_in_status/task_ids`, 380 | params: options, 381 | }); 382 | } 383 | } 384 | 385 | module.exports = Tasks; 386 | -------------------------------------------------------------------------------- /src/routes/Teams.js: -------------------------------------------------------------------------------- 1 | class Teams { 2 | /** 3 | * @constructor 4 | * @param {Request} request A request instance 5 | */ 6 | constructor(request) { 7 | /** 8 | * A request instance 9 | * @type {Request} 10 | * @private 11 | */ 12 | this._request = request; 13 | /** 14 | * The main route for the collection 15 | * @type {String} 16 | */ 17 | this.route = 'team'; 18 | } 19 | 20 | /** 21 | * Get teams 22 | */ 23 | async get() { 24 | return this._request.get({ 25 | endpoint: `${this.route}`, 26 | }); 27 | } 28 | 29 | /** 30 | * Create a goal 31 | * 32 | * @param {Number} teamId The team id 33 | * @param {Object} data Goal data 34 | */ 35 | async createGoal(teamId, data) { 36 | return this._request.post({ 37 | endpoint: `${this.route}/${teamId}/goal`, 38 | data, 39 | }); 40 | } 41 | 42 | /** 43 | * Get all goals for a team 44 | * 45 | * @param {Number} teamId The team id 46 | */ 47 | async getGoals(teamId) { 48 | return this._request.get({ 49 | endpoint: `${this.route}/${teamId}/goal`, 50 | }); 51 | } 52 | 53 | /** 54 | * Invite a guest to a workspace/team 55 | * 56 | * @param {Number} teamId The team id 57 | * @param {Object} data The guest data 58 | */ 59 | async inviteGuest(teamId, data) { 60 | return this._request.post({ 61 | endpoint: `${this.route}/${teamId}/guest`, 62 | data, 63 | }); 64 | } 65 | 66 | /** 67 | * Get a guest in a workspace/team 68 | * 69 | * @param {Number} teamId The team id 70 | * @param {Number} guestId The guest id 71 | */ 72 | async getGuest(teamId, guestId) { 73 | return this._request.get({ 74 | endpoint: `${this.route}/${teamId}/guest/${guestId}`, 75 | }); 76 | } 77 | 78 | /** 79 | * 80 | * @param {Number} teamId The team id 81 | * @param {Number} guestId The guest id 82 | * @param {Object} data The guest data 83 | */ 84 | async editGuest(teamId, guestId, data) { 85 | return this._request.put({ 86 | endpoint: `${this.route}/${teamId}/guest/${guestId}`, 87 | data, 88 | }); 89 | } 90 | 91 | /** 92 | * Remove a guest from a workspace/team 93 | * 94 | * @param {Number} teamId The team id 95 | * @param {Number} guestId The guest id 96 | */ 97 | async removeGuest(teamId, guestId) { 98 | return this._request.delete({ 99 | endpoint: `${this.route}/${teamId}/guest/${guestId}`, 100 | }); 101 | } 102 | 103 | /** 104 | * Returns all resources you have access to where you don't have access to its parent. 105 | * 106 | * @param {Number} teamId 107 | */ 108 | async sharedHierarchy(teamId) { 109 | return this._request.get({ 110 | endpoint: `${this.route}/${teamId}/shared`, 111 | }); 112 | } 113 | 114 | /** 115 | * Create a space for a team 116 | * 117 | * @param {Number} teamId The team id 118 | * @param {Object} data The space data 119 | */ 120 | async createSpace(teamId, data) { 121 | return this._request.post({ 122 | endpoint: `${this.route}/${teamId}/space`, 123 | data, 124 | }); 125 | } 126 | 127 | /** 128 | * Get spaces for a team 129 | * 130 | * @param {Number} teamId The team id 131 | * @param {Boolean} [archived=false] If archived spaces should be returned or not 132 | */ 133 | async getSpaces(teamId, archived = false) { 134 | return this._request.get({ 135 | endpoint: `${this.route}/${teamId}/space`, 136 | params: { 137 | archived, 138 | }, 139 | }); 140 | } 141 | 142 | /** 143 | * Get filtered tasks for a team 144 | * 145 | * @param {Number} teamId The team id 146 | * @param {Object} [options] The parameter options to pass in 147 | */ 148 | async getFilteredTasks(teamId, options = {}) { 149 | // eslint-disable-next-line no-param-reassign 150 | options.page = options.page || 0; 151 | return this._request.get({ 152 | endpoint: `${this.route}/${teamId}/task`, 153 | params: options, 154 | }); 155 | } 156 | 157 | /** 158 | * Get task templates for a team 159 | * 160 | * @param {Number} teamId The team id 161 | * @param {Integer} [page=0] The page to get 162 | */ 163 | async getTaskTemplates(teamId, page = 0) { 164 | return this._request.get({ 165 | endpoint: `${this.route}/${teamId}/taskTemplate`, 166 | params: { 167 | page, 168 | }, 169 | }); 170 | } 171 | 172 | /** 173 | * Get a user for a team. Only available to enterprise teams 174 | * 175 | * @param {Number} teamId The team id 176 | * @param {Number} userId The user id 177 | */ 178 | async getUser(teamId, userId) { 179 | return this._request.get({ 180 | endpoint: `${this.route}/${teamId}/user/${userId}`, 181 | }); 182 | } 183 | 184 | /** 185 | * Invite a user to a workspace/team. Only available to enterprise teams 186 | * 187 | * @param {Number} teamId The team id 188 | * @param {Object} data The user data 189 | */ 190 | async inviteUser(teamId, data) { 191 | return this._request.post({ 192 | endpoint: `${this.route}/${teamId}/user`, 193 | data, 194 | }); 195 | } 196 | 197 | /** 198 | * Edit a user for a workspace/team. Only available to enterprise teams 199 | * 200 | * @param {Number} teamId The team id 201 | * @param {Number} userId The user id 202 | * @param {Object} data The user data 203 | */ 204 | async editUser(teamId, userId, data) { 205 | return this._request.put({ 206 | endpoint: `${this.route}/${teamId}/user/${userId}`, 207 | data, 208 | }); 209 | } 210 | 211 | /** 212 | * Remove a user from a workspace/team. Only available to enterprise teams 213 | * 214 | * @param {Number} teamId The team id 215 | * @param {Number} userId The team id 216 | */ 217 | async removeUser(teamId, userId) { 218 | return this._request.delete({ 219 | endpoint: `${this.route}/${teamId}/user/${userId}`, 220 | }); 221 | } 222 | 223 | /** 224 | * Create a team view for a team 225 | * 226 | * @param {Number} teamId The team id 227 | * @param {Object} data The view data 228 | */ 229 | async createView(teamId, data) { 230 | return this._request.post({ 231 | endpoint: `${this.route}/${teamId}/view`, 232 | data, 233 | }); 234 | } 235 | 236 | /** 237 | * Get all team views for a team 238 | * 239 | * @param {Number} teamId The team id 240 | */ 241 | async getViews(teamId) { 242 | return this._request.get({ 243 | endpoint: `${this.route}/${teamId}/view`, 244 | }); 245 | } 246 | 247 | /** 248 | * Create a webhook 249 | * 250 | * @param {Number} teamId The team id 251 | * @param {Object} data The webhook data 252 | */ 253 | async createWebhook(teamId, data) { 254 | return this._request.post({ 255 | endpoint: `${this.route}/${teamId}/webhook`, 256 | data, 257 | }); 258 | } 259 | 260 | /** 261 | * Get all webhooks 262 | * 263 | * @param {Number} teamId the team id 264 | */ 265 | async getWebhooks(teamId) { 266 | return this._request.get({ 267 | endpoint: `${this.route}/${teamId}/webhook`, 268 | }); 269 | } 270 | 271 | /** 272 | * Get time entries within a data range 273 | * 274 | * @param {Number} teamId The team id 275 | * @param {Object} [options] The parameter options to pass in 276 | */ 277 | async getTimeEntries(teamId, options) { 278 | return this._request.get({ 279 | endpoint: `${this.route}/${teamId}/time_entries`, 280 | params: options, 281 | }); 282 | } 283 | 284 | /** 285 | * Get a single time entry 286 | * 287 | * @param {Number} teamId The team id 288 | * @param {String} timerId The timer id 289 | * @param {Object} [options] The parameter options to pass in 290 | */ 291 | async getSingleTimeEntry(teamId, timerId, options) { 292 | return this._request.get({ 293 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`, 294 | params: options, 295 | }); 296 | } 297 | 298 | /** 299 | * Get running time entry 300 | * 301 | * @param {Number} teamId The team id 302 | * @param {Object} [options] The parameter options to pass in 303 | */ 304 | async getRunningTimeEntry(teamId, options) { 305 | return this._request.get({ 306 | endpoint: `${this.route}/${teamId}/time_entries/current`, 307 | params: options, 308 | }); 309 | } 310 | 311 | /** 312 | * Create a time entry 313 | * 314 | * @param {Number} teamId The team id 315 | * @param {Object} data The time entry data 316 | * @param {Object} [options] The parameter options to pass in 317 | */ 318 | async createTimeEntry(teamId, data, options) { 319 | return this._request.post({ 320 | endpoint: `${this.route}/${teamId}/time_entries`, 321 | params: options, 322 | data, 323 | }); 324 | } 325 | 326 | /** 327 | * Remove tags from time entries 328 | * 329 | * @param {Number} teamId The team id 330 | * @param {Object} data The time entries data 331 | */ 332 | async removeTagsFromTimeEntries(teamId, data) { 333 | return this._request.delete({ 334 | endpoint: `${this.route}/${teamId}/time_entries/tags`, 335 | data, 336 | }); 337 | } 338 | 339 | /** 340 | * Get all tags from time entries 341 | * 342 | * @param {Number} teamId The team id 343 | */ 344 | async getAllTagsFromTimeEntries(teamId) { 345 | return this._request.get({ 346 | endpoint: `${this.route}/${teamId}/time_entries/tags`, 347 | }); 348 | } 349 | 350 | /** 351 | * Add tags from time entries 352 | * 353 | * @param {Number} teamId The team id 354 | * @param {Object} data The time entries and tag data 355 | */ 356 | async addTagsFromTimeEntries(teamId, data) { 357 | return this._request.post({ 358 | endpoint: `${this.route}/${teamId}/time_entries/tags`, 359 | data, 360 | }); 361 | } 362 | 363 | /** 364 | * Change tag names from time entries 365 | * 366 | * @param {Number} teamId The team id 367 | * @param {Object} data The tag data 368 | */ 369 | async changeTagsFromTimeEntries(teamId, data) { 370 | return this._request.put({ 371 | endpoint: `${this.route}/${teamId}/time_entries/tags`, 372 | data, 373 | }); 374 | } 375 | 376 | /** 377 | * Start a time entry 378 | * 379 | * @param {Number} teamId The team id 380 | * @param {Number} timerId The timer id 381 | * @param {Object} data The time entry data 382 | * @param {Object} [options] The parameter options to pass in 383 | */ 384 | async startTimeEntry(teamId, timerId, data, options) { 385 | return this._request.post({ 386 | endpoint: `${this.route}/${teamId}/time_entries/start/${timerId}`, 387 | params: options, 388 | data, 389 | }); 390 | } 391 | 392 | /** 393 | * Stop a time entry 394 | * 395 | * @param {Number} teamId The team id 396 | */ 397 | async stopTimeEntry(teamId) { 398 | return this._request.post({ 399 | endpoint: `${this.route}/${teamId}/time_entries/stop`, 400 | }); 401 | } 402 | 403 | /** 404 | * Delete a time entry 405 | * 406 | * @param {Number} teamId The team id 407 | * @param {Number} timerId The timer id 408 | */ 409 | async deleteTimeEntry(teamId, timerId) { 410 | return this._request.delete({ 411 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`, 412 | }); 413 | } 414 | 415 | /** 416 | * Update a time entry 417 | * 418 | * @param {Number} teamId The team id 419 | * @param {Number} timerId The timer id 420 | * @param {Object} data The time entry data 421 | * @param {Object} [options] The parameter options to pass in 422 | */ 423 | async updateTimeEntry(teamId, timerId, data, options) { 424 | return this._request.put({ 425 | endpoint: `${this.route}/${teamId}/time_entries/${timerId}`, 426 | params: options, 427 | data, 428 | }); 429 | } 430 | } 431 | module.exports = Teams; 432 | --------------------------------------------------------------------------------