# README
HTTP GraphQL API Endpoints
The GraphQL API schema can be tested and reviewed through the GraphQL Playground that is exposed when the server is started.
Table of contents
- Authorization Response
- Authorization
- User Mutations
- Quiz Mutations and Queries
- Score Mutations and Queries
- Healthcheck Query
Authorization Response
Authorization is implemented using JSON Web Tokens. An expiration deadline for the JWT is returned in response. It is the client's responsibility to refresh the token before, but no sooner than 60 seconds, before the deadline.
The returned token schema is below.
{
"expires": "expiration time integer in seconds, Unix time stamp",
"token": "token string",
"threshold": "threshold in integer seconds before expiration when the token can be refreshed"
}
Authorization
A valid JSON Web Token must be included in the header in the HTTP request for all endpoints that require authorization.
The Authorization
header key is customizable through the GraphQL endpoint configurations.
The following queries
and mutations
do not require authorization:
- Register User:
registerUser
- Login User:
loginUser
- Healthcheck:
healthcheck
{
"Authorization":
"JSON Web Token goes here"
}
User Mutations
Register
Request: All fields are required.
mutation {
registerUser(input: {
firstname: "first name"
lastname: "last name"
email: "[email protected]",
userLoginCredentials: {
username:"someusername",
password: "somepassword"
}
}) {
token,
expires,
threshold
}
}
Response: A valid JWT will be returned as an authorization response.
Login
Request: All fields are required.
mutation {
loginUser(input: {
username:"someusername",
password: "somepassword"
}) {
token,
expires,
threshold
}
}
Response: A valid JWT will be returned as an authorization response.
Refresh
Request: A valid JWT must be provided in the request header and will be validated with a fresh token issued against it.
mutation {
refreshToken() {
token
expires
threshold
}
}
Response: A valid JWT will be returned as an authorization response.
Delete
Request: All fields are required and a valid JWT must be provided in the header. The user must supply their login
credentials as well as complete the confirmation message I understand the consequences, delete my user account **USERNAME HERE**
mutation {
deleteUser(input: {
username: "someusername"
password: "somepassword"
confirmation: "I understand the consequences, delete my user account <USERNAME HERE>"
})
}
Response: A confirmation message will be returned as a success response.
Quiz Mutations and Queries
Create
Request: All fields except asset
are required.
- A marking type of
None
,Binary
,Negative
, orNon-negative
is accepted. Details on marking are available in thegrading
package. - 1 to 10
question
s are permitted per quiz. - 1 to 5 options are permitted per
question
. - Answer must be fewer than the number of options. Each number in the answer is an index to an option and must be in the range [0, 4].
- Every question has an optional asset that is a URL Encoded URI.
mutation {
createQuiz(input:
{
title: "The title of the quiz"
markingType: "One of: None, Binary, Negative, or Non-negative",
questions: [
{
description: "actual question here"
asset: "URL encoded URI of asset"
options: ["option 1", "option 2", "option 3", "option 4", "option 5"]
answers: [0,1,2,3,4]
}
{
description: "actual question here"
asset: "URL encoded URI of asset"
options: ["option 1", "option 2", "option 3", "option 4", "option 5"]
answers: [0,1,2,3,4]
}
]
}
)
}
Response: A success response containing the quiz id
in the response.
View
Only quizzes neither published nor deleted may be viewed by non-authors. Authors of quizzes can view both deleted and unpublished quizzes. Answer keys will only be returned to requesters who are the quiz's authors. The username of the requester is extracted from their JWT.
Request: The Quiz ID must be supplied in the query.
query {
viewQuiz(quizID: "QUIZ UUID HERE"){
Title
MarkingType
Questions {
Description
Asset
Options
Answers
}
}
}
Response: A success response containing the quiz.
Update
Updates to a quiz can only be made by the author of the quiz. The username of the requester is extracted from their JWT.
The items that can be updated in a quiz are the marking type
, title
, and the entirety of the questions
. The entire
contents of the quiz core must be supplied for the update.
The intended workflow for an update is a client will make a read/view request for the quiz. The quiz will then be rendered on the client in an editor where changes can be made. The client will subsequently submit the entire quiz, with the updates, to the API.
Request: The Quiz ID must be supplied in the request the URL along with the contents of the QuizCore
in the request
body.
mutation {
updateQuiz(
quizID: "76079156-6172-11ed-a471-305a3a460e3e"
quiz:
{
title: "The title of the quiz"
markingType: "One of: None, Binary, Negative, or Non-negative",
questions: [
{
description: "actual question here"
asset: "URL encoded URI of asset"
options: ["option 1", "option 2", "option 3", "option 4", "option 5"]
answers: [0,1,2,3,4]
}
{
description: "actual question here"
asset: "URL encoded URI of asset"
options: ["option 1", "option 2", "option 3", "option 4", "option 5"]
answers: [0,1,2,3,4]
}
]
}
)
}
Response: A success response containing a confirmation message and the `quiz id.
Delete
Only the authors of a quiz may mark it as deleted. Once deleted, a quiz will be set to unpublished and will no longer be eligible for publishing and editing. The quiz will remain in the database and can only be viewed by the author.
Request: The Quiz ID must be supplied in the request.
mutation {
deleteQuiz(quizID:"QUIZ UUID HERE")
}
Response: A success response containing a confirmation message and the quiz id
.
Publish
Only the authors of a quiz may mark it as published. Once published, a quiz will be generally available to all users and will no longer be eligible for editing. The quiz can be made unavailable by deleting it.
Request: The Quiz ID must be supplied in the request.
mutation {
publishQuiz(quizID:"QUIZ UUID HERE")
}
Response: A success response containing a confirmation message and the quiz id
.
Take
Any registered user is allowed to take or submit answers to a quiz that is published and has not been deleted yet. A user may only take a quiz once.
Request: The Quiz ID must be supplied in the request. The responses are provided in a two-dimensional array of integers in the request body. The questions and answers are zero-indexed. The answers for each question must be supplied in the row number corresponding to the question number. To select options for a question, the user must specify the indices of the options in the questions row array.
mutation {
takeQuiz(
quizID:"76079156-6172-11ed-a471-305a3a460e3e"
input: {
responses: [
[0, 1, 2, 3, 4 ],
[1, 3],
[1, 2, 4]
]
}
) {
username
author
score
quizResponse
quizID
}
}
Response: A success response containing the score, if applicable. Please see the grading
package
for details on marking.
Score Mutations and Queries
Score
A user may request the score for a quiz they have already taken, whether the quiz is deleted or not.
Request: The Quiz ID must be supplied in the request.
query {
getScore(quizID:"QUIZ UUID HERE") {
username
author
score
quizResponse
quizID
}
}
Response: A success response containing the scorecard.
{
"data": {
"getScore": {
"username": "username1",
"author": "username999",
"score": 0.6666666666666666,
"quizResponse": [
[0, 1, 2],
[1, 3 ]
],
"quizID": "74522665-4d8a-11ed-b4cb-305a3a460e3e"
}
}
}
Stats - Paginated
An author of a quiz may request all the scorecards for their quiz. The scorecards will contain the username
s, score
s,
as well as answers
that all users have submitted for the quiz.
The records from this request can potentially number in the thousands. As such, this endpoint will return paged responses for the request with a link to the subsequent page of data provided in the response. The page cursor is a pointer into the records in the query where the read will resume from. The data contained in the page cursor can reveal sensitive information contained in the database and thus must be encrypted before being returned from the HTTP handler.
It is critical to consider that adding or removing rows in other queries may affect the records being retrieved by a paginated query into the same table. Since paged queries rely on a cursor to a read position in the database table returned results from a paged query may return duplicated or missed records between pages.
Request: The Quiz ID
must be supplied in the request. The encrypted and Base64 URL encoded page cursor is provided
with the query parameter cursor
. The maximum number of records to retrieve in a request is set via the pageSize
query parameter. The first request sent to this endpoint should only include the pageSize
. The subsequent responses
will include the cursor
and optional pageSize
to be used to access subsequent pages of data.
query {
getStats(quizID:"QUIZ UUID HERE", pageSize: 3, cursor: "CURSOR TO NEXT PAGE HERE") {
records {
username
author
score
quizResponse
quizID
}
metadata {
quizID
numRecords
}
nextPage {
pageSize
cursor
}
}
}
Response: A paged statistics response will be returned on a successful request. The following page of data can be
accessed by appending updating the query to include the returned nextPage.cursor
and the nextPage.pageSize
.
{
"data": {
"getStats": {
"records": [
{
"username": "username4",
"author": "username1",
"score": 0.4444,
"quizResponse": [
[0, 1, 2],
[1, 3]
],
"quizID": "0a704c4b-4ea2-11ed-bd5a-305a3a460e3e"
},
{
"username": "username6",
"author": "username1",
"score": 0.7777,
"quizResponse": [
[0, 1, 2],
[1, 3]
],
"quizID": "0a704c4b-4ea2-11ed-bd5a-305a3a460e3e"
},
{
"username": "username5",
"author": "username1",
"score": 0.5555,
"quizResponse": [
[0, 1, 2 ],
[1, 3]
],
"quizID": "0a704c4b-4ea2-11ed-bd5a-305a3a460e3e"
}
],
"metadata": {
"quizID": "0a704c4b-4ea2-11ed-bd5a-305a3a460e3e",
"numRecords": 3
},
"nextPage": {
"pageSize": 3,
"cursor": "fJV60mayZXn19umNhYtwH8wpxDWsQBJPRjoeqCMtpWQizZbf2WGqaN4FKwxUL8LeLj0JLOMeQjPOyuGiTB8d5h303A=="
}
}
}
}
To access the next page of data using the example above we would use the query below:
query {
getStats(
quizID:"0a704c4b-4ea2-11ed-bd5a-305a3a460e3e"
pageSize: 3
cursor:"fJV60mayZXn19umNhYtwH8wpxDWsQBJPRjoeqCMtpWQizZbf2WGqaN4FKwxUL8LeLj0JLOMeQjPOyuGiTB8d5h303A==") {
records {
username
author
score
quizResponse
quizID
}
metadata {
quizID
numRecords
}
nextPage {
pageSize
cursor
}
}
}
Healthcheck Query.
The health check endpoint is exposed to facilitate liveness checks on the service. The check will verify whether the service is connected to all the ancillary services and responds appropriately.
This check is essential for load balancers and container orchestrators to determine whether to route traffic or restart the container.
query {
healthcheck()
}
Healthy Response:
{
"data": {
"healthcheck": "OK"
}
}
Unhealthy Response:
{
"errors": [
{
"message": "[Cassandra|Redis] healthcheck failed",
"path": [
"healthcheck"
]
}
],
"data": null
}