Skip to content

Commit

Permalink
fixed some bugs and updated readme (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
justin0022 authored Oct 30, 2024
1 parent 73a68d9 commit 8506653
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 179 deletions.
36 changes: 11 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest)

# Canvas LMS API for Node.js
Canvas API functions bundled as a NPM package for Node.js.
Canvas API functions bundled as a NPM package for Node.js. Note: this package no longer supports CJS as of version 2.0.0. If you need CJS support, please use [version 1.8.0](https://www.npmjs.com/package/node-canvas-api/v/1.8.0).

## Getting Started
These instructions will get you a copy of the project up and running on your local machine for use with your own API tokens and Canvas domains.
Expand All @@ -14,33 +14,28 @@ These instructions will get you a copy of the project up and running on your loc

### Installation

To use with node:
To use with NodeJS:
```bash
$ npm install node-canvas-api
```
Then, in wherever you want to use this package:
```js
const canvasAPI = require('node-canvas-api')
```

Rename the `sample.env` file to `.env` and add your institution's domain and API access token.

Attached to the `canvasAPI` are a [bunch of functions](https://github.com/ubccapico/node-canvas-api/tree/master/src).
Attached to the `canvasAPI` are a [bunch of functions](https://github.com/ubc/node-canvas-api/tree/master/src).
Run the attached functions!

### Example Usage

#### Get information about self:
```js
const canvasAPI = require('node-canvas-api')
import { getSelf } from 'node-canvas-api'

canvasAPI.getSelf()
.then(self => console.log(self))
getSelf().then(x => console.log(x))
```

#### Get students in a course:
```js
const { getUsersInCourse, getOptions } = require('node-canvas-api')
import { getUsersInCourse, getOptions } from 'node-canvas-api'

getUsersInCourse(12345, getOptions.users.enrollmentType.student) // first argument is Canvas course ID
.then(students => console.log(students))
Expand All @@ -50,23 +45,14 @@ getUsersInCourse(12345, getOptions.users.enrollmentType.student) // first argume
Contributions are welcome and greatly appreciated!

### How to contribute
1. Create an [issue](https://github.com/ubccapico/node-canvas-api/issues) describing what contribution you are planning to make.
1. Create an [issue](https://github.com/ubc/node-canvas-api/issues) describing what contribution you are planning to make.
1. Fork the repo.
1. Add your contributions to the [`source` directory](https://github.com/ubccapico/node-canvas-api/tree/master/source) (and not the `src` directory. This directory is generated by [Rollup](https://rollupjs.org/guide/en/) during the build phase, and is what gets published to [npm](https://www.npmjs.com/package/node-canvas-api).)
1. If you add a file to `source`, please add the file to the existing [`index.js` inside `source`](https://github.com/ubccapico/node-canvas-api/blob/master/source/index.js), so that during the build your contribution will be included in `src`.
1. Test your code by creating a file in the root of the project directory, importing your code addition from `src`, and running it.
1. Once you're happy with your contribution, open an [pull request](https://github.com/ubccapico/node-canvas-api/pulls) and I'll take a look.

### Hypothetical scenario for adding new feature
1. Add new file to `source` folder (say that the new file you want to add is `getUsersWithGradeThreshold.js`)
2. In the `source` folder, there’s an `index.js` file that lists all of the files you want exported to `src`. There you will add: `export { default as getUsersWithGradeThreshold } from './getUsersWithGradeThreshold'`
3. Run `npm run build`, which will build the project and output your new function into `src`.
4. In the `index.js` at the root of the project that you create (this is not the `index.js` that’s in the `source` folder), import your new file from `src` like this: `const getUsersWithGradeThreshold = require('.src/getUsersWithGradeThreshold')` and test by running the code: `node index.js`.
5. Once you’re happy that it works, create a PR.
1. Add your contributions.
1. Once you're happy with your contribution, open an [pull request](https://github.com/ubc/node-canvas-api/pulls) and I'll take a look.

## Usage
* [Canvas Rubrics](https://github.com/ubccapico/canvas-rubric)
* [Canvas Discussions](https://github.com/ubccapico/canvas-discussion)
* [Canvas Rubrics](https://github.com/ubc/canvas-rubric)
* [Canvas Discussions](https://github.com/ubc/canvas-discussion)
* [Canvas Syllabus](https://github.com/UBC-LFS/lfs-canvas-syllabus)

## License
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"files": [
"src"
],
"main": "src/index.js",
"author": "Justin Lee",
"license": "MIT",
"type": "module",
Expand Down
61 changes: 37 additions & 24 deletions src/downloadFile.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
import request from 'request-promise'
import fetch from 'node-fetch'
import getFile from './getFile.js'
import fs from 'fs.js'
import fs from 'fs'
import path from 'path'

const token = process.env.CANVAS_API_TOKEN

const requestObj = url => ({
'method': 'GET',
'uri': url,
'json': true,
'resolveWithFullResponse': true,
'headers': {
'Authorization': 'Bearer ' + token
}
})

/**
* Downloads specified fileID from Canvas to specified path
* @param {Number} fileId the fileId of the file
* @param {String} path the path that the file should be downloaded to
* @return {Promise} A Promise that resolves to a log that inidicated what the filename of the downloaded file is. On error, logs the error
* @param {String} downloadPath the path that the file should be downloaded to
* @return {Promise} A Promise that resolves to a log that indicates what the filename of the downloaded file is. On error, logs the error
*/

const downloadFile = (fileId, path) =>
getFile(fileId, path)
.then(({ url, filename }) => {
const modifiedName = filename.replace(/%28/g, '(').replace(/%29/g, ')').replace(/%E2%80%93/g, '-')
const pdfStream = fs.createWriteStream(path + modifiedName)
request(requestObj(url)).pipe(pdfStream)
return new Promise((resolve, reject) => {
pdfStream.on('finish', () => resolve(modifiedName))
pdfStream.on('error', err => reject(err))
})
const downloadFile = async (fileId, downloadPath) => {
try {
const { url, filename } = await getFile(fileId, downloadPath)
const modifiedName = filename
.replace(/%28/g, '(')
.replace(/%29/g, ')')
.replace(/%E2%80%93/g, '-')

const fileStream = fs.createWriteStream(path.join(downloadPath, modifiedName))

const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
})

if (!response.ok) {
throw new Error(`Failed to download file: ${response.statusText}`)
}

// Pipe the response body to the file stream
response.body.pipe(fileStream)

return new Promise((resolve, reject) => {
fileStream.on('finish', () => resolve(modifiedName))
fileStream.on('error', err => reject(err))
})
} catch (error) {
console.error(`Error downloading file: ${error.message}`)
throw error // Rethrow the error after logging
}
}

export default downloadFile
33 changes: 2 additions & 31 deletions src/getQuizSubmissionEvents.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
import buildOptions from './internal/util.js'

import request from 'request-promise'
import linkparser from 'parse-link-header'
import Bottleneck from 'bottleneck'

const token = process.env.CANVAS_API_TOKEN

const limiter = new Bottleneck({
maxConcurrent: 20,
minTime: 100
})

const requestObj = url => ({
'method': 'GET',
'uri': url,
'json': true,
'resolveWithFullResponse': true,
'headers': {
'Authorization': 'Bearer ' + token
}
})

const fetchAll = (url, result = []) =>
request(requestObj(url))
.then(response => {
result = [...result, response.body]
const links = linkparser(response.headers.link)
return links.next ? fetchAll(links.next.url, result) : result
}).catch(err => console.log(err))

const fetchAllRateLimited = limiter.wrap(fetchAll)
import fetchAll from './internal/fetchAll.js'

const canvasDomain = process.env.CANVAS_API_DOMAIN

Expand All @@ -43,5 +14,5 @@ const canvasDomain = process.env.CANVAS_API_DOMAIN
*/

export default function getQuizSubmissionEvents (courseId, quizId, submissionId, ...options) {
return fetchAllRateLimited(canvasDomain + `/courses/${courseId}/quizzes/${quizId}/submissions/${submissionId}/events?` + buildOptions(options))
return fetchAll(canvasDomain + `/courses/${courseId}/quizzes/${quizId}/submissions/${submissionId}/events?` + buildOptions(options))
}
33 changes: 2 additions & 31 deletions src/getQuizSubmissions.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
import buildOptions from './internal/util.js'

import request from 'request-promise'
import linkparser from 'parse-link-header'
import Bottleneck from 'bottleneck'

const token = process.env.CANVAS_API_TOKEN

const limiter = new Bottleneck({
maxConcurrent: 20,
minTime: 100
})

const requestObj = url => ({
'method': 'GET',
'uri': url,
'json': true,
'resolveWithFullResponse': true,
'headers': {
'Authorization': 'Bearer ' + token
}
})

const fetchAll = (url, result = []) =>
request(requestObj(url))
.then(response => {
result = [...result, response.body]
const links = linkparser(response.headers.link)
return links.next ? fetchAll(links.next.url, result) : result
}).catch(err => console.log(err))

const fetchAllRateLimited = limiter.wrap(fetchAll)
import fetchAll from './internal/fetchAll.js'

const canvasDomain = process.env.CANVAS_API_DOMAIN

Expand All @@ -42,5 +13,5 @@ const canvasDomain = process.env.CANVAS_API_DOMAIN
*/

export default function getQuizSubmissions (courseId, quizId, ...options) {
return fetchAllRateLimited(canvasDomain + `/courses/${courseId}/quizzes/${quizId}/submissions?` + buildOptions(options))
return fetchAll(canvasDomain + `/courses/${courseId}/quizzes/${quizId}/submissions?` + buildOptions(options))
}
136 changes: 68 additions & 68 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,68 @@
export { default as batchCopyCourseContent } from './batchCopyCourseContent'
export { default as copyCourseContent } from './copyCourseContent'
export { default as createCourse } from './createCourse'
export { default as createCustomGradebookColumn } from './createCustomGradebookColumn'
export { default as createUser } from './createUser'
export { default as createUserCourseEnrollment } from './createUserCourseEnrollment'
export { default as createUserSectionEnrollment } from './createUserSectionEnrollment'
export { default as deleteAllCustomGradebookColumns } from './deleteAllCustomGradebookColumns'
export { default as downloadFile } from './downloadFile'
export { default as deleteCustomGradebookColumn } from './deleteCustomGradebookColumn'
export { default as getAccountIds } from './getAccountIds'
export { default as getAccounts } from './getAccounts'
export { default as getAllCoursesInAccount } from './getAllCoursesInAccount'
export { default as getAllCoursesInDept } from './getAllCoursesInDept'
export { default as getAllCourseSyllabiInAccount } from './getAllCourseSyllabiInAccount'
export { default as getAnalytics } from './getAnalytics'
export { default as getAssignments } from './getAssignments'
export { default as getCourses } from './getCourses'
export { default as getCoursesByUser } from './getCoursesByUser'
export { default as getCustomGradeBookColumns } from './getCustomGradeBookColumns'
export { default as getDeptIdsInAccount } from './getDeptIdsInAccount'
export { default as getProgress } from './getProgress'
export { default as getSubaccounts } from './getSubaccounts'
export { default as getSyllabusOfCourse } from './getSyllabusOfCourse'
export { default as getUserPageViews } from './getUserPageViews'
export { default as getUsersInAccount } from './getUsersInAccount'
export { default as getUsersInCourse } from './getUsersInCourse'
export { default as hideCustomGradebookColumn } from './hideCustomGradebookColumn'
export { default as hideCustomGradebookColumnsByName } from './hideCustomGradebookColumnsByName'
export { default as putStudentNumberInExistingCustomColumn } from './putStudentNumberInExistingCustomColumn'
export { default as putStudentNumberInGradeColumn } from './putStudentNumberInGradeColumn'
export { default as putStudentNumbersInGradebook } from './putStudentNumbersInGradebook'
export { default as showCustomGradebookColumn } from './showCustomGradebookColumn'
export { default as showCustomGradebookColumnsByName } from './showCustomGradebookColumnsByName'
export { default as checkProgressStatus } from './checkProgressStatus'
export { default as getOptions } from './internal/getOptions'
export { default as getUser } from './getUser'
export { default as getRubricsInCourse } from './getRubricsInCourse'
export { default as getRubric } from './getRubric'
export { default as getAssignmentSubmissions } from './getAssignmentSubmissions'
export { default as getEnrollmentsInCourse } from './getEnrollmentsInCourse'
export { default as getSections } from './getSections'
export { default as getModules } from './getModules'
export { default as getModuleItems } from './getModuleItems'
export { default as getDiscussionTopics } from './getDiscussionTopics'
export { default as getDiscussionTopic } from './getDiscussionTopic'
export { default as getFullDiscussion } from './getFullDiscussion'
export { default as getSelf } from './getSelf'
export { default as getOutcome } from './getOutcome'
export { default as getQuizSubmissions } from './getQuizSubmissions'
export { default as getQuizSubmissionEvents } from './getQuizSubmissionEvents'
export { default as getQuizQuestions } from './getQuizQuestions'
export { default as getCourseAnalytics } from './getCourseAnalytics'
export { default as getPlannerItemsByUser } from './getPlannerItemsByUser'
export { default as getGroupsInCourse } from './getGroupsInCourse'
export { default as getGroupDiscussionTopics } from './getGroupDiscussionTopics'
export { default as getFullGroupDiscussion } from './getFullGroupDiscussion'
export { default as getGroupDiscussionTopic } from './getGroupDiscussionTopic'
export { default as getQuizSubmission } from './getQuizSubmission'
export { default as postAssignmentSubmissionComment } from './postAssignmentSubmissionComment'
export { default as deleteSubmissionComment } from './deleteSubmissionComment'
export { default as getCourse } from './getCourse'
export { default as getCourseSections } from './getCourseSections'
export { default as getEnrollmentsInSection } from './getEnrollmentsInSection'
export { default as createGroup } from './createGroup'
export { default as createGroupCategories } from './createGroupCategories'
export { default as createGroupMembership } from './createGroupMembership'
export { default as getHistory } from './getHistory'
export { default as batchCopyCourseContent } from './batchCopyCourseContent.js'
export { default as copyCourseContent } from './copyCourseContent.js'
export { default as createCourse } from './createCourse.js'
export { default as createCustomGradebookColumn } from './createCustomGradebookColumn.js'
export { default as createUser } from './createUser.js'
export { default as createUserCourseEnrollment } from './createUserCourseEnrollment.js'
export { default as createUserSectionEnrollment } from './createUserSectionEnrollment.js'
export { default as deleteAllCustomGradebookColumns } from './deleteAllCustomGradebookColumns.js'
export { default as downloadFile } from './downloadFile.js'
export { default as deleteCustomGradebookColumn } from './deleteCustomGradebookColumn.js'
export { default as getAccountIds } from './getAccountIds.js'
export { default as getAccounts } from './getAccounts.js'
export { default as getAllCoursesInAccount } from './getAllCoursesInAccount.js'
export { default as getAllCoursesInDept } from './getAllCoursesInDept.js'
export { default as getAllCourseSyllabiInAccount } from './getAllCourseSyllabiInAccount.js'
export { default as getAnalytics } from './getAnalytics.js'
export { default as getAssignments } from './getAssignments.js'
export { default as getCourses } from './getCourses.js'
export { default as getCoursesByUser } from './getCoursesByUser.js'
export { default as getCustomGradeBookColumns } from './getCustomGradeBookColumns.js'
export { default as getDeptIdsInAccount } from './getDeptIdsInAccount.js'
export { default as getProgress } from './getProgress.js'
export { default as getSubaccounts } from './getSubaccounts.js'
export { default as getSyllabusOfCourse } from './getSyllabusOfCourse.js'
export { default as getUserPageViews } from './getUserPageViews.js'
export { default as getUsersInAccount } from './getUsersInAccount.js'
export { default as getUsersInCourse } from './getUsersInCourse.js'
export { default as hideCustomGradebookColumn } from './hideCustomGradebookColumn.js'
export { default as hideCustomGradebookColumnsByName } from './hideCustomGradebookColumnsByName.js'
export { default as putStudentNumberInExistingCustomColumn } from './putStudentNumberInExistingCustomColumn.js'
export { default as putStudentNumberInGradeColumn } from './putStudentNumberInGradeColumn.js'
export { default as putStudentNumbersInGradebook } from './putStudentNumbersInGradebook.js'
export { default as showCustomGradebookColumn } from './showCustomGradebookColumn.js'
export { default as showCustomGradebookColumnsByName } from './showCustomGradebookColumnsByName.js'
export { default as checkProgressStatus } from './checkProgressStatus.js'
export { default as getOptions } from './internal/getOptions.js'
export { default as getUser } from './getUser.js'
export { default as getRubricsInCourse } from './getRubricsInCourse.js'
export { default as getRubric } from './getRubric.js'
export { default as getAssignmentSubmissions } from './getAssignmentSubmissions.js'
export { default as getEnrollmentsInCourse } from './getEnrollmentsInCourse.js'
export { default as getSections } from './getSections.js'
export { default as getModules } from './getModules.js'
export { default as getModuleItems } from './getModuleItems.js'
export { default as getDiscussionTopics } from './getDiscussionTopics.js'
export { default as getDiscussionTopic } from './getDiscussionTopic.js'
export { default as getFullDiscussion } from './getFullDiscussion.js'
export { default as getSelf } from './getSelf.js'
export { default as getOutcome } from './getOutcome.js'
export { default as getQuizSubmissions } from './getQuizSubmissions.js'
export { default as getQuizSubmissionEvents } from './getQuizSubmissionEvents.js'
export { default as getQuizQuestions } from './getQuizQuestions.js'
export { default as getCourseAnalytics } from './getCourseAnalytics.js'
export { default as getPlannerItemsByUser } from './getPlannerItemsByUser.js'
export { default as getGroupsInCourse } from './getGroupsInCourse.js'
export { default as getGroupDiscussionTopics } from './getGroupDiscussionTopics.js'
export { default as getFullGroupDiscussion } from './getFullGroupDiscussion.js'
export { default as getGroupDiscussionTopic } from './getGroupDiscussionTopic.js'
export { default as getQuizSubmission } from './getQuizSubmission.js'
export { default as postAssignmentSubmissionComment } from './postAssignmentSubmissionComment.js'
export { default as deleteSubmissionComment } from './deleteSubmissionComment.js'
export { default as getCourse } from './getCourse.js'
export { default as getCourseSections } from './getCourseSections.js'
export { default as getEnrollmentsInSection } from './getEnrollmentsInSection.js'
export { default as createGroup } from './createGroup.js'
export { default as createGroupCategories } from './createGroupCategories.js'
export { default as createGroupMembership } from './createGroupMembership.js'
export { default as getHistory } from './getHistory.js'

0 comments on commit 8506653

Please sign in to comment.