From fd8fcabb8542a7ba4501b4f4db2505c32463de6e Mon Sep 17 00:00:00 2001 From: Akshendra Pratap Date: Wed, 29 Dec 2021 13:55:33 +0530 Subject: [PATCH 1/4] export types for schoology, canvas and google workspace --- .eslintrc | 2 +- src/canvas.d.ts | 30 +++++-- src/canvas.js | 66 ++++++-------- src/gcl.d.ts | 208 ++++++++++++++++++++++++++------------------- src/gcl.js | 78 +++++++++-------- src/index.d.ts | 3 +- src/schoology.d.ts | 105 +++++++++++++++++++++++ src/schoology.js | 13 ++- 8 files changed, 331 insertions(+), 174 deletions(-) create mode 100644 src/schoology.d.ts diff --git a/.eslintrc b/.eslintrc index 8cd065b..fc72af7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -310,7 +310,7 @@ "words": true } ], - "spaced-comment": [2, "always"], + "spaced-comment": [0, "always"], "use-isnan": 2, "valid-typeof": 2, "vars-on-top": 2, diff --git a/src/canvas.d.ts b/src/canvas.d.ts index 08b93dc..b62735c 100644 --- a/src/canvas.d.ts +++ b/src/canvas.d.ts @@ -2,8 +2,6 @@ import { AxiosResponse } from 'axios'; import {RequestConfig} from './types'; import OAuth from './oauth2'; -export = Canvas; - interface Tokens { accessToken: string; refreshToken: string; @@ -19,7 +17,7 @@ interface AuthURLOptions { scopes: string[]; } -interface CanvasProfile { +export interface CanvasProfile { id: string; name: string; primary_email: string; @@ -31,21 +29,20 @@ declare enum SubmissionStates { GRADED = 'graded', UNSUBMITTED = 'unsubmitted', } + +export = Canvas; declare class Canvas { constructor(options: { - orgName: string; hostedUrl: string; redirectUri: string; accessToken: string; refreshToken: string; clientId: string; clientSecret: string; - fxs: { getUserToken: GetUserToken, setUserToken: SetUserToken }; + fxs: { getToken: GetUserToken, setToken: SetUserToken }; userId: string; canvasUserId?: string; }); - - orgName: string; hostedUrl: string; redirectUri: string; accessToken: string; @@ -66,8 +63,12 @@ declare class Canvas { isTokenExpired(err: Error): boolean; makeRequest(requestConfig: RequestConfig, retries: number): Promise; getProfile(): Promise; + getUserProfile(id: number): Promise; getTokensFromUser(): Promise; + getAccounts(): Promise; + getAccountUsers(id: number, data?: { enrollment_type: string[] }): Promise; + getCourses(): Promise; announce(args: { courseId: string; pinned?: boolean; title: string; message: string }): Promise; listStudents(args: { courseId: string }): Promise; @@ -87,6 +88,21 @@ interface Course { name: string; } +export interface Account { + id: number, + name: string, + uuid: string, +}; + +export interface User { + id: number, + name: string, + first_name: string, + last_name: string, + login_id: string, + email?: string, +} + interface Student { id: number; name: string; diff --git a/src/canvas.js b/src/canvas.js index 3e79f32..8ed0c6e 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -1,5 +1,6 @@ /// + const axios = require('axios'); const _ = require('lodash'); const OAuth = require('./oauth2'); @@ -11,7 +12,6 @@ const { paginatedCollect } = require('./helpers/utils'); */ class Canvas { constructor({ - orgName, hostedUrl, redirectUri, accessToken, @@ -22,7 +22,6 @@ class Canvas { userId, // mongoId canvasUserId, }) { - this.orgName = orgName; this.hostedUrl = hostedUrl; this.redirectUri = redirectUri; this.accessToken = accessToken; @@ -75,7 +74,7 @@ class Canvas { }), headers: { 'Content-Type': 'application/json', - } + }, }); this.accessToken = resp.data.access_token; @@ -97,9 +96,9 @@ class Canvas { }); this.canvasUserId = resp.data.id; return resp.data; - } catch(err) { + } catch (err) { throw new LMSError('Unable to fetch user profile', 'canvas.USER_PROFILE_ERROR', { - userId: this.userId + userId: this.userId, }); } } @@ -147,8 +146,8 @@ class Canvas { isTokenExpired(err) { // check condition for token expiration, canvas sends a `WWW-Authenticate` header if 401 is for token expiry - const headers = _.get( err, 'response.headers', {} ); - if ( headers['www-authenticate'] ) { + const headers = _.get(err, 'response.headers', {}); + if (headers['www-authenticate']) { return true; } return false; @@ -171,7 +170,7 @@ class Canvas { }), headers: { 'Content-Type': 'application/json', - } + }, }); this.accessToken = resp.data.access_token; this.canvasUserId = resp.data.user.id; @@ -204,9 +203,9 @@ class Canvas { } const url = OAuth.makeURL(this.hostedUrl, requestConfig.url, requestConfig.query || {}); const response = await axios({ - ...requestConfig, - url, - headers: { Authorization: `Bearer ${this.accessToken}` }, + ...requestConfig, + url, + headers: { Authorization: `Bearer ${this.accessToken}` }, }); const { data, status } = response; return { data, status }; @@ -222,7 +221,7 @@ class Canvas { } try { await this.refreshUserToken(this.refreshToken); - } catch(err) { + } catch (err) { console.error(err); } @@ -295,8 +294,8 @@ class Canvas { payload.only_visible_to_overrides = true; payload.assignment_overrides = [ { - "student_ids": studentIds - } + 'student_ids': studentIds, + }, ]; } @@ -325,8 +324,8 @@ class Canvas { submission: { submission_type: 'online_url', url: submissionUrl, - } - } + }, + }, }); return submission; } @@ -355,9 +354,9 @@ class Canvas { return submission; } - async listSubmissions({ courseId, assignmentId}) { + async listSubmissions({ courseId, assignmentId }) { const submissions = await paginatedCollect(this, { - url: `api/v1/courses/${courseId}/assignments/${assignmentId}/submissions` + url: `api/v1/courses/${courseId}/assignments/${assignmentId}/submissions`, }); return submissions; } @@ -371,14 +370,14 @@ class Canvas { const { data: grades } = await this.makeRequest({ url: `/api/v1/courses/${courseId}/assignments/${assignmentId}/submissions/update_grades`, method: 'POST', - data: { grade_data: gradeData } + data: { grade_data: gradeData }, }); return grades; } async getAccounts() { const accounts = await paginatedCollect(this, { - url: `/api/v1/manageable_accounts`, + url: '/api/v1/manageable_accounts', method: 'GET', }); return accounts; @@ -387,7 +386,7 @@ class Canvas { /** * Mainly added to fetch Teacher and ta, use enrollment_type in data */ - async getAccountUsers(id, data = {enrollment_type: ['teacher', 'ta']}) { + async getAccountUsers(id, data = { enrollment_type: ['teacher', 'ta'] }) { const users = await paginatedCollect(this, { url: `/api/v1/accounts/${id}/users`, method: 'GET', @@ -397,23 +396,14 @@ class Canvas { } async getUserProfile(id) { - try { - const resp = await this.makeRequest({ - url: `/api/v1/users/${id}/profile`, - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - this.canvasUserId = resp.data.id; - return resp.data; - } catch(err) { - throw new LMSError('Unable to fetch user profile', 'canvas.USER_PROFILE_ERROR', { - userId: this.userId, - id, - err, - }); - } + const resp = await this.makeRequest({ + url: `/api/v1/users/${id}/profile`, + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return resp.data; } } diff --git a/src/gcl.d.ts b/src/gcl.d.ts index 9e3b725..30b0870 100644 --- a/src/gcl.d.ts +++ b/src/gcl.d.ts @@ -1,92 +1,124 @@ export = GCL; /** - * @class GCL - */ +* @class GCL +*/ declare class GCL { - constructor(name: any, emitter: any, opts: any, urls?: {}, fxs?: {}); - name: any; - emitter: any; - apiURL: any; - authURL: any; - tokenURL: any; - getUserToken: any; - setUserToken: any; - authClient: any; - requestClient({ refresh_token, access_token, lastRefresh }: { - refresh_token: any; - access_token: any; - lastRefresh: any; - }): any; - makeRequest(userId: any, api: any, params: any): any; - requestWithToken(tokens: any, api: any, params: any): any; - getAuthorizationURL(extras: any): any; - /** - * Get token from code - * @param {String} c - * @return {Object} token - * @return {String} token.access_token - */ - getTokens(code: any): any; - refreshToken(tokens: any): any; - tokenInfo(userId: any): any; - headersWithToken(tokens: any): { - Authorization: string; - }; - getProfile(tokens: any): any; - getCourses(userId: any): any; - createAssignment(userId: any, data: any): any; - getAssignment(userId: any, { courseId, courseWorkId }: { - courseId: any; - courseWorkId: any; - }): any; - restartAssignment(userId: any, data: any): Promise; - announce(userId: any, data: any): any; - createIndividualAssignments(userId: any, data: any): any; - _getCourseStudents(userId: any, { courseId, pageToken }: { - courseId: any; - pageToken: any; - }): Promise; - getCourseStudents(userId: any, { courseId }: { - courseId: any; - }): Promise; - getStudentSubmission(userId: any, { courseId, courseWorkId }: { - courseId: any; - courseWorkId: any; - }): any; - reclaimSubmission(userId: any, { courseId, courseWorkId, subId }: { - courseId: any; - courseWorkId: any; - subId: any; - }): any; - addLinkToSubmission(userId: any, { courseId, courseWorkId, subId, url }: { - courseId: any; - courseWorkId: any; - subId: any; - url: any; - }): any; - submitStudentAssignment(userId: any, { courseId, courseWorkId, subId }: { - courseId: any; - courseWorkId: any; - subId: any; - }): any; - _getAllSubmissions(userId: any, { courseId, courseWorkId, pageToken }: { - courseId: any; - courseWorkId: any; - pageToken: any; - }): any; - getAllSubmissions(userId: any, { courseId, courseWorkId }: { - courseId: any; - courseWorkId: any; - }): Promise; - gradeAssignment(userId: any, { courseId, courseWorkId, subId, newSub }: { - courseId: any; - courseWorkId: any; - subId: any; - newSub: any; - }): any; - returnAssignment(userId: any, { courseId, courseWorkId, subId }: { - courseId: any; - courseWorkId: any; - subId: any; - }): any; + constructor(name: any, emitter: any, opts: any, urls?: {}, fxs?: {}); + name: any; + emitter: any; + apiURL: any; + authURL: any; + tokenURL: any; + getUserToken: any; + setUserToken: any; + authClient: any; + requestClient({ refresh_token, access_token, lastRefresh }: { + refresh_token: any; + access_token: any; + lastRefresh: any; + }): any; + makeRequest(userId: any, api: any, params: any): any; + requestWithToken(tokens: any, api: any, params: any): any; + getAuthorizationURL(extras: any): any; + /** + * Get token from code + * @param {String} c + * @return {Object} token + * @return {String} token.access_token + */ + getTokens(code: any): any; + refreshToken(tokens: any): any; + tokenInfo(userId: any): any; + headersWithToken(tokens: any): { + Authorization: string; + }; + getProfile(tokens: any): any; + getCourses(userId: any): any; + createAssignment(userId: any, data: any): any; + getAssignment(userId: any, { courseId, courseWorkId }: { + courseId: any; + courseWorkId: any; + }): any; + restartAssignment(userId: any, data: any): Promise; + announce(userId: any, data: any): any; + createIndividualAssignments(userId: any, data: any): any; + _getCourseStudents(userId: any, { courseId, pageToken }: { + courseId: any; + pageToken: any; + }): Promise; + getCourseStudents(userId: any, { courseId }: { + courseId: any; + }): Promise; + getStudentSubmission(userId: any, { courseId, courseWorkId }: { + courseId: any; + courseWorkId: any; + }): any; + reclaimSubmission(userId: any, { courseId, courseWorkId, subId }: { + courseId: any; + courseWorkId: any; + subId: any; + }): any; + addLinkToSubmission(userId: any, { courseId, courseWorkId, subId, url }: { + courseId: any; + courseWorkId: any; + subId: any; + url: any; + }): any; + submitStudentAssignment(userId: any, { courseId, courseWorkId, subId }: { + courseId: any; + courseWorkId: any; + subId: any; + }): any; + _getAllSubmissions(userId: any, { courseId, courseWorkId, pageToken }: { + courseId: any; + courseWorkId: any; + pageToken: any; + }): any; + getAllSubmissions(userId: any, { courseId, courseWorkId }: { + courseId: any; + courseWorkId: any; + }): Promise; + gradeAssignment(userId: any, { courseId, courseWorkId, subId, newSub }: { + courseId: any; + courseWorkId: any; + subId: any; + newSub: any; + }): any; + returnAssignment(userId: any, { courseId, courseWorkId, subId }: { + courseId: any; + courseWorkId: any; + subId: any; + }): any; + getPaginatedCourses(userId: string, options: { as: 'admin' | 'teacher' }): Promise; + getPaginatedCourseTeachers(userId: string, params: { courseId: string }): Promise; + getPaginatedDomainUsers(userId: string, query: { domain: string }): Promise; } + +export type Course = { + id: string, + name: string, + section: string, +} + +export type Teacher = { + courseId: string, + userId: string, + profile: Profile, +} + +export type Profile = { + id: string, + name: string, + emailAddress: string, + photoUrl: string, +}; + +export type DomainUser = { + id: string, + primaryEmail: string, + name: { + fullName: string, + familyName: string, + givenName: string, + } +}; diff --git a/src/gcl.js b/src/gcl.js index d9189c9..97600a3 100644 --- a/src/gcl.js +++ b/src/gcl.js @@ -322,7 +322,7 @@ class GCL { const api = classroom.courses.students.list; const request = { courseId, pageToken, pageSize: 20 }; return this.makeRequest(userId, api, request); - }; + } async getCourseStudents(userId, { courseId }) { const students = []; @@ -341,7 +341,7 @@ class GCL { async getSingleCourseStudent(teacherId, courseId, userId) { const api = classroom.courses.students.get; const request = { courseId, userId }; - return this.makeRequest(teacherId,api,request); + return this.makeRequest(teacherId, api, request); } getStudentSubmission(userId, { courseId, courseWorkId }) { @@ -437,20 +437,25 @@ class GCL { }); } - async _getCourses(userId, { pageToken }) { + async _getCourses(userId, { pageToken, as }) { const api = classroom.courses.list; - const request = { courseStates: 'ACTIVE', teacherId: 'me', pageToken, pageSize: 20 }; + let request = { courseStates: 'ACTIVE', teacherId: 'me', pageToken, pageSize: 20 }; + if (as === 'admin') { + request = { courseStates: 'ACTIVE', pageToken, pageSize: 20 }; + } return this.makeRequest(userId, api, request); - }; + } - async getPaginatedCourses(userId) { + async getPaginatedCourses(userId, options = {}) { + const { as } = options; const courses = []; let nextPageToken; - let count = 0; do { - count += 1; - const response = await this._getCourses(userId, { pageToken: nextPageToken }); - nextPageToken = response.nextPageToken, + const response = await this._getCourses(userId, { + pageToken: nextPageToken, + as: as || 'teacher', + }); + nextPageToken = response.nextPageToken; courses.push(...(response.courses || [])); } while (nextPageToken); @@ -461,16 +466,17 @@ class GCL { const api = classroom.courses.teachers.list; const request = { courseId, pageToken, pageSize: 20 }; return this.makeRequest(userId, api, request); - }; + } async getPaginatedCourseTeachers(userId, { courseId }) { const teachers = []; let nextPageToken; - let count = 0; do { - count += 1; - const response = await this._getCourseTeachers(userId, { courseId, pageToken: nextPageToken }); - nextPageToken = response.nextPageToken, + const response = await this._getCourseTeachers(userId, { + courseId, + pageToken: nextPageToken, + }); + nextPageToken = response.nextPageToken; teachers.push(...(response.teachers || [])); } while (nextPageToken); @@ -487,25 +493,23 @@ class GCL { const api = admin.users.list; const request = { ...query, pageSize: 20 }; return this.makeRequest(userId, api, request); - }; + } async getPaginatedDomainUsers(userId, query) { const users = []; let nextPageToken; - let count = 0; do { - count += 1; const response = await this._getDomainUsers(userId, { ...query, pageToken: nextPageToken }); - nextPageToken = response.nextPageToken, + nextPageToken = response.nextPageToken; users.push(...(response.users || [])); } while (nextPageToken); return users; - }; + } - async createRegistration({ userId, courseId, topicName}) { + async createRegistration({ userId, courseId, topicName }) { const response = { status: false }; - if(is.string(userId) && is.string(courseId)) { + if (is.string(userId) && is.string(courseId)) { const api = classroom.registrations.create; const registration = { feed: { @@ -516,27 +520,27 @@ class GCL { }, cloudPubsubTopic: { topicName, - } - } + }, + }; const registeredObject = await this.makeRequest(userId, api, { resource: registration }); return Object.assign({ status: true }, registeredObject); - } else { - throw new LMSError('Not Valid userId or courseId', 'gcl.INVALID_PARAMS', {userId, courseId}); - return response; } + throw new LMSError('Not Valid userId or courseId', 'gcl.INVALID_PARAMS', { userId, courseId }); + return response; + } - async deleteRegistration({userId, registrationId}) { - if(is.string(registrationId) && is.string(userId)) { - const api = classroom.registrations.delete; - const query = { - registrationId - } - const response = await this.makeRequest(userId, api, query); - return response; - } else { - throw new LMSError('Not Valid userId or registrationId', 'gcl.INVALID_PARAMS', {userId, registrationId}); + async deleteRegistration({ userId, registrationId }) { + if (is.string(registrationId) && is.string(userId)) { + const api = classroom.registrations.delete; + const query = { + registrationId, + }; + const response = await this.makeRequest(userId, api, query); + return response; } + throw new LMSError('Not Valid userId or registrationId', 'gcl.INVALID_PARAMS', { userId, registrationId }); + } } diff --git a/src/index.d.ts b/src/index.d.ts index 4de4caa..85cf9cd 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2,4 +2,5 @@ import GCL from './gcl'; import LMSError from './error'; import Edmodo from './edmodo'; import Canvas from './canvas'; -export { GCL, LMSError, Edmodo, Canvas }; +import Schoology from './schoology'; +export { GCL, LMSError, Edmodo, Canvas, Schoology }; diff --git a/src/schoology.d.ts b/src/schoology.d.ts new file mode 100644 index 0000000..0570ab9 --- /dev/null +++ b/src/schoology.d.ts @@ -0,0 +1,105 @@ + +type TokenGroup = { + token: string, + secret: string, + expireAt?: Date, +} + + +type GetUserToken = (userId: string) => Promise; +type SetUserToken = (userId: string, tokens: any) => Promise; + +type EnrollmentFilterOptions = { + type: string[] +} + + +export = Schoology; +declare class Schoology { + hostedUrl: string; + redirectUri: string; + clientId: string; + clientSecret: string; + userId: string; + schoologyProfileId: string; + cacheRequestToken: (any) => any; + getUserAccessToken: GetUserToken; + setUserAccessToken: SetUserToken; + + constructor(options: { + hostedUrl: string, + redirectUri: string, + clientId: string, + clientSecret: string, + userId: string, + schoologyProfileId: string, + requestToken?: TokenGroup, + accessToken?: TokenGroup, + fxs: { + getAccessToken: GetUserToken, + setAccessToken: SetUserToken, + cacheRequestToken: (any) => any, + }, + }); + + getAuthorizationURL(): Promise; + getProfile(): Promise; + getUserProfile(): Promise; + getSchool(id: string): Promise; + getSchoolBuildings(id: string): Promise; + getBuildingCourses(params: { buildingId: string }): Promise; + getAllSectionsForCourse(courseId: string): Promise; + listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }): Promise; + getUser(uid: string): Promise; +} +export default Schoology; + + +export type School = { + id: string, + title: string, + city: string, + state: string, +}; + +export type Building = { + id: string + title: string, + city: string, + state: string, +}; + +export type Course = { + id: string, + building_id: string, + title: string, + department: string, + description: string, +}; + +export type Section = { + id: string, + title: string, + grading_periods: number[], + description: string, +}; + +export type Enrollment = { + id: string, + uid: string, + admin: string, + status: string, +}; + +export type Profile = { + id: string, + primary_email: string, + uid: string, + school_id: string, + building_id: string, + school_uid: string, + name_title: string, + name_first: string, + name_middle: string, + name_last: string, +}; \ No newline at end of file diff --git a/src/schoology.js b/src/schoology.js index 06b82a3..e77f564 100644 --- a/src/schoology.js +++ b/src/schoology.js @@ -264,6 +264,15 @@ class Schoology { return assignment.data; } + async getAssignment({ sectionId, assignmentId }) { + const response = await this.makeRequest({ + url: `/v1/sections/${sectionId}/assignments/${assignmentId}`, + method: 'GET', + }); + + return response.data; + } + async submitAssignment({ sectionId, assignmentId, submissionUrl }) { const submission = await this.makeRequest({ url: `/v1/sections/${sectionId}/submissions/${assignmentId}/create`, @@ -579,9 +588,9 @@ class Schoology { return user; } - async getBuilding(id) { + async getSchoolBuildings(schoolId) { const { data: { building } } = await this.makeRequest({ - url: `v1/schools/${id}/buildings`, + url: `v1/schools/${schoolId}/buildings`, method: 'GET', headers: { 'Content-Type': 'application/json', From e2bd103f861d5bfcaae7a30aeac4531d70c41baf Mon Sep 17 00:00:00 2001 From: Akshendra Pratap Date: Tue, 4 Jan 2022 17:17:59 +0530 Subject: [PATCH 2/4] fix exported types --- src/canvas.d.ts | 152 +++++++++++++++++++++++---------------------- src/edmodo.d.ts | 3 +- src/error.d.ts | 3 +- src/gcl.d.ts | 67 ++++++++++---------- src/gcl.js | 2 + src/index.d.ts | 2 + src/schoology.d.ts | 116 +++++++++++++++++----------------- src/schoology.js | 2 + 8 files changed, 178 insertions(+), 169 deletions(-) diff --git a/src/canvas.d.ts b/src/canvas.d.ts index b62735c..445d86c 100644 --- a/src/canvas.d.ts +++ b/src/canvas.d.ts @@ -17,19 +17,6 @@ interface AuthURLOptions { scopes: string[]; } -export interface CanvasProfile { - id: string; - name: string; - primary_email: string; - locale: string; -} - -declare enum SubmissionStates { - SUBMITTED = 'submitted', - GRADED = 'graded', - UNSUBMITTED = 'unsubmitted', -} - export = Canvas; declare class Canvas { constructor(options: { @@ -54,7 +41,7 @@ declare class Canvas { userId: string; canvasUserId: string; - static SUBMISSION_STATE: SubmissionStates; + static SUBMISSION_STATE: Canvas.SubmissionStates; build(): Promise; getAuthorizationURL(options: AuthURLOptions): string; @@ -62,71 +49,86 @@ declare class Canvas { handleError(err: Error, code: string, redirectUrl: string): void; isTokenExpired(err: Error): boolean; makeRequest(requestConfig: RequestConfig, retries: number): Promise; - getProfile(): Promise; - getUserProfile(id: number): Promise; + getProfile(): Promise; + getUserProfile(id: number): Promise; getTokensFromUser(): Promise; - getAccounts(): Promise; - getAccountUsers(id: number, data?: { enrollment_type: string[] }): Promise; + getAccounts(): Promise; + getAccountUsers(id: number, data?: { enrollment_type: string[] }): Promise; - getCourses(): Promise; + getCourses(): Promise; announce(args: { courseId: string; pinned?: boolean; title: string; message: string }): Promise; - listStudents(args: { courseId: string }): Promise; - createAssignment(args: { courseId: string; assignmentName: string; assignmentDescription?: string; dueAt?: Date; unlockAt?: Date; }): Promise; - submitAssignment(args: { courseId: string; assignmentId: string; submissionUrl: string }): Promise; - getSubmission(args: { courseId: string; assignmentId: string; studentCanvasId: string }): Promise; - listSubmissions(args: {courseId: string; assignmentId: string}): Promise; - gradeSubmission(args: { courseId: string; assignmentId: string; studentCanvasId: string; grade: number | string; comment?: string }): Promise; + listStudents(args: { courseId: string }): Promise; + createAssignment(args: { courseId: string; assignmentName: string; assignmentDescription?: string; dueAt?: Date; unlockAt?: Date; }): Promise; + submitAssignment(args: { courseId: string; assignmentId: string; submissionUrl: string }): Promise; + getSubmission(args: { courseId: string; assignmentId: string; studentCanvasId: string }): Promise; + listSubmissions(args: {courseId: string; assignmentId: string}): Promise; + gradeSubmission(args: { courseId: string; assignmentId: string; studentCanvasId: string; grade: number | string; comment?: string }): Promise; gradeMultipleSubmissions(args: { courseId: string; assignmentId: string; userGradesAndComments: {[studentCanvasId: string]: {grade: number | string, comment?: string } } }): Promise<{id: number; url: string}>; } -interface GradeSubmissionResponse extends Submission { - all_submissions: Submission[]; -} -interface Course { - id: number; - name: string; -} - -export interface Account { - id: number, - name: string, - uuid: string, -}; - -export interface User { - id: number, - name: string, - first_name: string, - last_name: string, - login_id: string, - email?: string, -} - -interface Student { - id: number; - name: string; - email: string; -} - -interface Assignment { - id: number; - description: string; - due_at?: Date; - unlock_at?: Date; - published: boolean; -} - -interface Submission { - id: number; - url: string; - body?: string; - grade: string; - score: number; // float - user_id: string; - assignment_id: number; - submission_type: 'online_url'; - graded_at: Date; - attempt: number; - workflow_state: 'submitted' | 'graded' | 'unsubmitted'; -} +declare namespace Canvas { + interface GradeSubmissionResponse extends Submission { + all_submissions: Submission[]; + } + interface Course { + id: number; + name: string; + } + + export interface Account { + id: number, + name: string, + uuid: string, + } + + export interface User { + id: number, + name: string, + first_name: string, + last_name: string, + login_id: string, + email?: string, + } + + interface Student { + id: number; + name: string; + email: string; + } + + interface Assignment { + id: number; + description: string; + due_at?: Date; + unlock_at?: Date; + published: boolean; + } + + interface Submission { + id: number; + url: string; + body?: string; + grade: string; + score: number; // float + user_id: string; + assignment_id: number; + submission_type: 'online_url'; + graded_at: Date; + attempt: number; + workflow_state: 'submitted' | 'graded' | 'unsubmitted'; + } + + export interface Profile { + id: string; + name: string; + primary_email: string; + locale: string; + } + + enum SubmissionStates { + SUBMITTED = 'submitted', + GRADED = 'graded', + UNSUBMITTED = 'unsubmitted', + } +} \ No newline at end of file diff --git a/src/edmodo.d.ts b/src/edmodo.d.ts index d20c229..29e58c8 100644 --- a/src/edmodo.d.ts +++ b/src/edmodo.d.ts @@ -7,11 +7,10 @@ interface Tokens { refresh_token: string; } -export = Edmodo; /** * @class Edmodo */ -declare class Edmodo { +export default class Edmodo { constructor( name: string, emitter: events.EventEmitter, diff --git a/src/error.d.ts b/src/error.d.ts index a7bb128..3c28675 100644 --- a/src/error.d.ts +++ b/src/error.d.ts @@ -1,5 +1,4 @@ -export = LMSError; -declare class LMSError extends Error { +export default class LMSError extends Error { /** * @param {string} message * @param {string} [type=error] diff --git a/src/gcl.d.ts b/src/gcl.d.ts index 30b0870..35c475f 100644 --- a/src/gcl.d.ts +++ b/src/gcl.d.ts @@ -1,7 +1,9 @@ -export = GCL; + /** * @class GCL */ + +export = GCL; declare class GCL { constructor(name: any, emitter: any, opts: any, urls?: {}, fxs?: {}); name: any; @@ -89,36 +91,37 @@ declare class GCL { courseWorkId: any; subId: any; }): any; - getPaginatedCourses(userId: string, options: { as: 'admin' | 'teacher' }): Promise; - getPaginatedCourseTeachers(userId: string, params: { courseId: string }): Promise; - getPaginatedDomainUsers(userId: string, query: { domain: string }): Promise; -} - -export type Course = { - id: string, - name: string, - section: string, + getPaginatedCourses(userId: string, options: { as: 'admin' | 'teacher' }): Promise; + getPaginatedCourseTeachers(userId: string, params: { courseId: string }): Promise; + getPaginatedDomainUsers(userId: string, query: { domain: string }): Promise; } - -export type Teacher = { - courseId: string, - userId: string, - profile: Profile, -} - -export type Profile = { - id: string, - name: string, - emailAddress: string, - photoUrl: string, -}; - -export type DomainUser = { - id: string, - primaryEmail: string, - name: { - fullName: string, - familyName: string, - givenName: string, +declare namespace GCL { + export type Course = { + id: string, + name: string, + section: string, + } + + export type Teacher = { + courseId: string, + userId: string, + profile: Profile, } -}; + + export type Profile = { + id: string, + name: string, + emailAddress: string, + photoUrl: string, + }; + + export type DomainUser = { + id: string, + primaryEmail: string, + name: { + fullName: string, + familyName: string, + givenName: string, + } + }; +} \ No newline at end of file diff --git a/src/gcl.js b/src/gcl.js index 97600a3..af340e4 100644 --- a/src/gcl.js +++ b/src/gcl.js @@ -1,3 +1,5 @@ +/// + /* eslint camelcase: 0 */ const moment = require('moment'); diff --git a/src/index.d.ts b/src/index.d.ts index 85cf9cd..f187b82 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,3 +1,5 @@ + + import GCL from './gcl'; import LMSError from './error'; import Edmodo from './edmodo'; diff --git a/src/schoology.d.ts b/src/schoology.d.ts index 0570ab9..bc473bb 100644 --- a/src/schoology.d.ts +++ b/src/schoology.d.ts @@ -13,7 +13,6 @@ type EnrollmentFilterOptions = { type: string[] } - export = Schoology; declare class Schoology { hostedUrl: string; @@ -43,63 +42,64 @@ declare class Schoology { }); getAuthorizationURL(): Promise; - getProfile(): Promise; - getUserProfile(): Promise; - getSchool(id: string): Promise; - getSchoolBuildings(id: string): Promise; - getBuildingCourses(params: { buildingId: string }): Promise; - getAllSectionsForCourse(courseId: string): Promise; - listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }): Promise; - getUser(uid: string): Promise; + getProfile(): Promise; + getUserProfile(): Promise; + getSchool(id: string): Promise; + getSchoolBuildings(id: string): Promise; + getBuildingCourses(params: { buildingId: string }): Promise; + getAllSectionsForCourse(courseId: string): Promise; + listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }): Promise; + getUser(uid: string): Promise; } -export default Schoology; - - -export type School = { - id: string, - title: string, - city: string, - state: string, -}; - -export type Building = { - id: string - title: string, - city: string, - state: string, -}; - -export type Course = { - id: string, - building_id: string, - title: string, - department: string, - description: string, -}; - -export type Section = { - id: string, - title: string, - grading_periods: number[], - description: string, -}; -export type Enrollment = { - id: string, - uid: string, - admin: string, - status: string, -}; -export type Profile = { - id: string, - primary_email: string, - uid: string, - school_id: string, - building_id: string, - school_uid: string, - name_title: string, - name_first: string, - name_middle: string, - name_last: string, -}; \ No newline at end of file +declare namespace Schoology { + export type School = { + id: string, + title: string, + city: string, + state: string, + }; + + export type Building = { + id: string + title: string, + city: string, + state: string, + }; + + export type Course = { + id: string, + building_id: string, + title: string, + department: string, + description: string, + }; + + export type Section = { + id: string, + title: string, + grading_periods: number[], + description: string, + }; + + export type Enrollment = { + id: string, + uid: string, + admin: string, + status: string, + }; + + export type Profile = { + id: string, + primary_email: string, + uid: string, + school_id: string, + building_id: string, + school_uid: string, + name_title: string, + name_first: string, + name_middle: string, + name_last: string, + }; +} \ No newline at end of file diff --git a/src/schoology.js b/src/schoology.js index e77f564..ec19993 100644 --- a/src/schoology.js +++ b/src/schoology.js @@ -1,3 +1,5 @@ +/// + const _ = require('lodash'); const is = require('is_js'); From b35384a97e63785917ae3fb49f7be5239c18649f Mon Sep 17 00:00:00 2001 From: Akshendra Pratap Date: Wed, 5 Jan 2022 16:45:32 +0530 Subject: [PATCH 3/4] feat(schoology): ability to fetch past sections --- src/schoology.d.ts | 2 +- src/schoology.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/schoology.d.ts b/src/schoology.d.ts index bc473bb..4c1a3d9 100644 --- a/src/schoology.d.ts +++ b/src/schoology.d.ts @@ -47,7 +47,7 @@ declare class Schoology { getSchool(id: string): Promise; getSchoolBuildings(id: string): Promise; getBuildingCourses(params: { buildingId: string }): Promise; - getAllSectionsForCourse(courseId: string): Promise; + getAllSectionsForCourse(courseId: string, params?: { includePast?: 0 | 1 }): Promise; listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }): Promise; getUser(uid: string): Promise; } diff --git a/src/schoology.js b/src/schoology.js index ec19993..58179da 100644 --- a/src/schoology.js +++ b/src/schoology.js @@ -134,10 +134,11 @@ class Schoology { })); } - async getAllSectionsForCourse(courseId) { + async getAllSectionsForCourse(courseId, params = {}) { const sections = await this.paginatedCollect({ url: `/v1/courses/${courseId}/sections`, method: 'GET', + query: { include_past: params.includePast }, }, 'section'); return sections; From f4706fa9fcae47a68ce19c6c0f7a405f181cfc13 Mon Sep 17 00:00:00 2001 From: Akshendra Pratap Date: Wed, 5 Jan 2022 19:03:47 +0530 Subject: [PATCH 4/4] feat(schoology): ability to throttle paginated requests --- src/schoology.d.ts | 10 +++++++--- src/schoology.js | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/schoology.d.ts b/src/schoology.d.ts index 4c1a3d9..6036ed8 100644 --- a/src/schoology.d.ts +++ b/src/schoology.d.ts @@ -13,6 +13,10 @@ type EnrollmentFilterOptions = { type: string[] } +type RequestOptions = { + throttle?: { delay?: number }, +} + export = Schoology; declare class Schoology { hostedUrl: string; @@ -46,9 +50,9 @@ declare class Schoology { getUserProfile(): Promise; getSchool(id: string): Promise; getSchoolBuildings(id: string): Promise; - getBuildingCourses(params: { buildingId: string }): Promise; - getAllSectionsForCourse(courseId: string, params?: { includePast?: 0 | 1 }): Promise; - listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }): Promise; + getBuildingCourses(params: { buildingId: string }, options?: RequestOptions): Promise; + getAllSectionsForCourse(courseId: string, params?: { includePast?: 0 | 1 }, options?: RequestOptions): Promise; + listUsers(params: { sectionId: string, query: EnrollmentFilterOptions }, options?: RequestOptions): Promise; getUser(uid: string): Promise; } diff --git a/src/schoology.js b/src/schoology.js index 58179da..1e8e5c3 100644 --- a/src/schoology.js +++ b/src/schoology.js @@ -7,6 +7,11 @@ const OAuth = require('./oauth'); const LMSError = require('./error'); const debug = require('debug')('q:lms:schoology'); +function wait(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} /** * @class Schoology @@ -134,12 +139,12 @@ class Schoology { })); } - async getAllSectionsForCourse(courseId, params = {}) { + async getAllSectionsForCourse(courseId, params = {}, options = {}) { const sections = await this.paginatedCollect({ url: `/v1/courses/${courseId}/sections`, method: 'GET', query: { include_past: params.includePast }, - }, 'section'); + }, 'section', options.throttle); return sections; } @@ -505,7 +510,7 @@ class Schoology { } } - async paginatedCollect(requestConfig, keyWithPaginatedResults) { + async paginatedCollect(requestConfig, keyWithPaginatedResults, throttle = {}) { const results = []; let pageUrl = requestConfig.url; let pages = 0; @@ -517,6 +522,11 @@ class Schoology { }); pages += 1; + if (throttle && throttle.delay) { + debug('Waiting for %d', throttle.delay); + await wait(throttle.delay); + } + const listData = _.get(result, `data.${keyWithPaginatedResults}`, []); const nextPageUrl = _.get(result, 'data.links.next', ''); const isThereANextPage = is.url(nextPageUrl); @@ -557,11 +567,11 @@ class Schoology { /** * Mainly added to fetch all courses for a school, also we can fetch course for a building by passing building_id */ - async getBuildingCourses({ buildingId }) { + async getBuildingCourses({ buildingId }, options = { }) { const courses = await this.paginatedCollect({ url: '/v1/courses', query: { building_id: buildingId }, - }, 'course'); + }, 'course', options.throttle); return courses; } @@ -569,12 +579,12 @@ class Schoology { /** * Mainly added to fetch all teacher for each sections */ - async listUsers({ sectionId, query = { 'type': ['admin'] } }) { + async listUsers({ sectionId, query = { 'type': ['admin'] } }, options = {}) { const users = await this.paginatedCollect({ url: `/v1/sections/${sectionId}/enrollments`, method: 'GET', query, - }, 'enrollment'); + }, 'enrollment', options.throttle); return users; }