Skip to content

Commit 6e33c32

Browse files
committed
update: added graphql endpoint
1 parent adea793 commit 6e33c32

File tree

8 files changed

+180
-5
lines changed

8 files changed

+180
-5
lines changed

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"trailingComma": "es5",
3+
"tabWidth": 2,
4+
"semi": false,
5+
"singleQuote": false,
6+
"useTabs": false
7+
}

index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ const { KhanOauth, KhanAPIWrapper } = require("./lib/api-base")
22
const { KhanAPIWrapperV1Mixin } = require("./lib/api-v1")
33
const { KhanAPIWrapperV2Mixin } = require("./lib/api-v2")
44
const { KhanAPIWrapperInternalMixin } = require("./lib/api-internal")
5+
const { KhanAPIWrapperGraphQLMixin } = require("./lib/api-graphql")
56

67
// combining all the methods across the different api versions.
7-
class _KhanAPIWrapper extends KhanAPIWrapperInternalMixin(
8-
KhanAPIWrapperV2Mixin(KhanAPIWrapperV1Mixin(KhanAPIWrapper))
8+
class _KhanAPIWrapper extends KhanAPIWrapperGraphQLMixin(
9+
KhanAPIWrapperInternalMixin(
10+
KhanAPIWrapperV2Mixin(KhanAPIWrapperV1Mixin(KhanAPIWrapper))
11+
)
912
) {}
1013

1114
module.exports = { KhanOauth, KhanAPIWrapper: _KhanAPIWrapper }

lib/_tests_/api-graphql.test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require("dotenv").config()
2+
const fs = require("fs")
3+
const { promisify } = require("util")
4+
const readAsync = promisify(fs.readFile)
5+
const writeAsync = promisify(fs.writeFile)
6+
const { KhanAPIWrapper, KhanOauth } = require("../../index")
7+
8+
const {
9+
KHAN_PASSWORD,
10+
KHAN_IDENTIFIER,
11+
KHAN_CONSUMER_SECRET,
12+
KHAN_CONSUMER_KEY,
13+
TEST_KHAN_COURSEID,
14+
} = process.env
15+
16+
const fetchAccessToken = async () => {
17+
const kauth = new KhanOauth(
18+
KHAN_CONSUMER_KEY,
19+
KHAN_CONSUMER_SECRET,
20+
KHAN_IDENTIFIER,
21+
KHAN_PASSWORD
22+
)
23+
24+
const [token, secret] = await kauth.authorizeSelf()
25+
26+
return await writeAsync(
27+
"tokens.json",
28+
JSON.stringify({
29+
token,
30+
secret,
31+
timestamp: new Date().getTime(),
32+
}),
33+
{ encoding: "utf8" }
34+
)
35+
.then(() => [token, secret])
36+
.catch(err => {
37+
throw err
38+
})
39+
}
40+
41+
// Check if our cached token is valid. If so, return it, otherwise fetch a new one.
42+
const getAccessToken = async () => {
43+
return await readAsync("tokens.json", { encoding: "utf8" })
44+
.then(jsonString => JSON.parse(jsonString))
45+
.then(({ token, secret, timestamp }) => {
46+
const now = new Date().getTime()
47+
if (now - timestamp > 24 * 3600 * 1000) {
48+
return fetchAccessToken()
49+
}
50+
51+
return [token, secret]
52+
})
53+
.catch(err => {
54+
if (err.code === "ENOENT") {
55+
// file not found, which should happen the first time
56+
return fetchAccessToken()
57+
}
58+
59+
throw err
60+
})
61+
}
62+
63+
jest.setTimeout(60000)
64+
65+
test("graphql progressByStudent endpoint works", async () => {
66+
const [token, secret] = await getAccessToken()
67+
const kapi = new KhanAPIWrapper(
68+
KHAN_CONSUMER_KEY,
69+
KHAN_CONSUMER_SECRET,
70+
token,
71+
secret
72+
)
73+
const progressByStudent = await kapi.graphqlProgressByStudent(
74+
TEST_KHAN_COURSEID
75+
)
76+
77+
console.log({ progressByStudent })
78+
expect(progressByStudent.errors).toBeNull()
79+
expect(progressByStudent.data).toBeTruthy()
80+
})

lib/_tests_/api-internal.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const getAccessToken = async () => {
6565

6666
jest.setTimeout(60000)
6767

68-
test("/api/internal/user/missions endpoint works", async () => {
68+
test.skip("/api/internal/user/missions endpoint works", async () => {
6969
const [token, secret] = await getAccessToken()
7070
const kapi = new KhanAPIWrapper(
7171
KHAN_CONSUMER_KEY,

lib/api-base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class KhanAPIWrapper {
177177

178178
case "POST": {
179179
const config = { params: needsAuth ? oauth_params : params }
180-
const res = await axios.post(url, data, config)
180+
const res = await axios.post(url, data, config).then(res => res.data)
181181
return res
182182
}
183183

lib/api-graphql.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const { KhanAPIWrapper } = require("./api-base")
2+
3+
const KhanAPIWrapperGraphQLMixin = SuperClass =>
4+
class extends SuperClass {
5+
/* These are endpoints that go with /api/internal. They are currently
6+
* undocumented, so any contributions and documentation are welcome.
7+
*/
8+
9+
async graphqlProgressByStudent(classId) {
10+
const payload = {
11+
query: `query ProgressByStudent($assignmentFilters: CoachAssignmentFilters, $contentKinds: [LearnableContentKind], $classId: String!, $pageSize: Int, $after: ID) {
12+
coach {
13+
id
14+
studentList(id: $classId) {
15+
id
16+
cacheId
17+
studentKaidsAndNicknames {
18+
id
19+
coachNickname
20+
__typename
21+
}
22+
assignmentsPage(filters: $assignmentFilters, after: $after, pageSize: $pageSize) {
23+
assignments(contentKinds: $contentKinds) {
24+
id
25+
dueDate
26+
contents {
27+
id
28+
translatedTitle
29+
kind
30+
defaultUrlPath
31+
__typename
32+
}
33+
itemCompletionStates {
34+
completedOn
35+
studentKaid
36+
bestScore {
37+
numAttempted
38+
numCorrect
39+
__typename
40+
}
41+
__typename
42+
}
43+
__typename
44+
}
45+
pageInfo {
46+
nextCursor
47+
__typename
48+
}
49+
__typename
50+
}
51+
__typename
52+
}
53+
__typename
54+
}
55+
}
56+
`,
57+
variables: {
58+
classId,
59+
assignmentFilters: {
60+
dueAfter: null,
61+
dueBefore: null,
62+
},
63+
contentKinds: null,
64+
after: null,
65+
pageSize: 1000,
66+
},
67+
operationName: "ProgressByStudent",
68+
}
69+
70+
return await this.fetchResource(
71+
"/api/internal/graphql",
72+
true,
73+
"POST",
74+
null,
75+
payload
76+
)
77+
}
78+
}
79+
80+
module.exports = {
81+
KhanAPIWrapperGraphQLMixin,
82+
KhanAPIWrapperGraphQL: KhanAPIWrapperGraphQLMixin(KhanAPIWrapper),
83+
}

lib/api-internal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const KhanAPIWrapperInternalMixin = SuperClass =>
66
* undocumented, so any contributions and documentation are welcome.
77
*/
88

9+
// endpoint no longer works ☹️
910
async internalUserMissions() {
1011
;`Retrieve data about the user missions
1112
`

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"private": false,
1010
"scripts": {
1111
"test": "jest",
12-
"test-dev": "jest --watch"
12+
"test-dev": "jest --watch",
13+
"test-one": "jest ./examples/_tests_/api-graphql.test.js"
1314
},
1415
"dependencies": {
1516
"axios": "^0.19.0",

0 commit comments

Comments
 (0)