Introduction

This API is a REST API. It is built on HTTP standards, with intuitive URIs, leveraging HTTP response codes and HTTP verbs that can be consumed by off-the-shelf HTTP clients. It uses cross-origin resource sharing (CORS) to support secure interactions between your client and our resources. All responses are in JSON format, including payloads associated with errors.

API Endpoint

https://api.example.com

Versioning

By default, all requests receive the latest version of the API. We encourage you to explicitly request a version by date via the Accept header. When requesting a resource via a date, you are able to get the latest version, as of the time that you are developing. Future changes to the API will be ignored by your client, unless you change the date.

Accept Header with Version and Content-Type Declaration

Accept: application/vnd.oncourse.20150820+json

Example Request

# When the `-I` flag is used with `curl`, only the document info is displayed (HEAD).
# When the `-H` flag is used with `curl`, you can add a custom header to pass to the server.
curl "http://localhost:3000/example/legos" -I \
    -H "Accept: application/vnd.oncourse.20150820+json"
fetch('/example/legos', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150820+json"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

You can check the current version through every response’s headers. Look for the X-OnCourse-Media-Type header:

Example Response Header

X-OnCourse-Media-Type: "application/vnd.oncourse.20150820+json"

Important: The default version of the API may change in the future. If you're building an application and care about the stability of the API, be sure to request a specific version in the Accept header as shown in the examples.

HTTP Status Codes

This API uses these HTTP Status codes

Error Code Meaning
200 Success -- rejoice
201 Created -- a new resource was created successfully
202 Accepted -- The request was accepted and queued, actual success will be delivered via notifications
400 Bad Request -- Umm. I don't know. Who am I?
401 Unauthorized -- Did you authenticate first? Do you have a JWT token in your headers?
403 Forbidden -- You are not authorized to access the resource you requested
404 Not Found -- The endpoint could not be found
405 Method Not Allowed -- The method/verb you requested is not supported
406 Not Acceptable -- You requested a format that isn't JSON
410 Gone -- The resource was removed from our servers
418 I'm a teapot
429 Too Many Requests -- You're requesting too many resources! Slow down!
500 Internal Server Error -- We had a problem with our server. Try again later.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.

Example Flows

Register user / Login / Send Events

  • Register a new user (can use gmail address with +something after name. ie josh+3@gmail.com)
curl -X POST \
  http://localhost:3000/auth/register \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: 8f30f9b4-d791-ead6-874f-fe4c2b9e61b7' \
  -d '{
   "firstName": "NAME",
   "email" : "EMAIL ADDRESS",
   "password" : "PASSWORD"
}'
  • Will send you an email, but the link will point to staging server
  • copy the link to the browser and change to http and localhost:3000
  • when you execute the request, you should see the user become active in your db
  • Login
curl -X POST \
  http://localhost:3000/auth/login \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: c1e4666e-d4d2-8d0b-7169-a9043c4a6be2' \
  -d '{
   "email" : "EMAIL",
   "password": "PASSWORD",
   "deviceId" : "ffffee99b8f-a8019ff"
}'
  • will get back an authToken to use in other requests
  • send in some BS events. replace auth token in authorization header with the one from login
curl -X POST \
  http://middle-galaxy-server-sc-01-mg-srv-staging.azurewebsites.net/events \
  -H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIzODQ5MWJlYi1hMzIyLTRkODgtOWU0NC05Y2UyMDIxODg2M2YiLCJhY2Nlc3NUb2tlbiI6Ik5UYzFPVGhoWkRNdE5XWTNZaTAwTkRNNExXRTVORGt0WW1Nd1pEWXhNalJtTlRrejBfN3E1bnZGWm42R0dxMV9Hd2hSQURmOTFEWlFoTFpOR0JnRzlfMU1pUzAtWFp0ek9mQUswSk1RSWhtVmRKRV9sUXU3MFd5cnQ1clBRRGl5T0k3UmJ3IiwidXNlcklkIjoiNThlM2U5ZGZiMWM1ZDg1ZjQ5Y2U4MzQ2Iiwic2Nob29sQ29kZSI6Ijg2NGIxMTFkNGUiLCJzaGFyZCI6MSwiZGV2aWNlSWQiOiJmZmZmZWU5OWI4Zi1hODAxOWZmIiwiZmlyc3ROYW1lIjoiQWRtaW4iLCJvcGVuSWRTdWIiOiI1OGUzZTlkZWU3MDYwYzI4Mjk3NzAwZjYiLCJpc0FkbWluIjp0cnVlLCJpYXQiOjE0OTM2OTA5MTEsImV4cCI6MTQ5MzcwMTcxMX0.jQkZGNtLi3Bl_Ak-w8u_ksIj5ANbupPG_axbLmZwK5Y' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: 57588603-beb9-b6a9-6d54-d84ae7d9753b' \
  -d '{
    "userId": "5926fa5c172a6a1260e78ddd",
    "events": [
        {
            "type": "session",
            "created": "2017-03-28T18:25:43.511Z",
            "gameId": 1,
            "action": "login",
            "location": null
        },
        {
            "type": "session",
            "created": "2017-03-28T18:25:44.511Z",
            "gameId": 1,
            "action": "new",
            "location": null
        },
        {
            "type": "card",
            "created": "2017-03-28T18:28:12.421Z",
            "gameId": 1,
            "action": "dealt",
            "subType": "cadet",
            "turnId": 1,
            "cardId": "19AE3F",
            "missionId": null
        },
        {
            "type": "game",
            "created": "2017-03-28T18:28:12.421Z",
            "gameId": 1,
            "subType": "missionComplete",
            "length": 10,
            "turnId": 1,
            "success": true
        }
    ]
}
'

Legos

Legos is not any real part of the API. It was used a demonstration at the beginning of the project (functional example) and later to test different functionality.

This section provides an example for documenting a data-type.

Legos /example/legos are ... They have the following schema:

Property Type Description
_id string The ID of the lego
color string The color of the lego
width int The number of connectors the lego has, in width
length int The number of connectors the lego has, in length
height int An integer representation of the height

Example Lego

{
    "_id": "5898c7fe0bbdccda7419997d",
    "color": "red",
    "width": 2,
    "length": 6,
    "height": 2
}

List Legos GET

This endpoint retrieves all Legos GET http://api.example.com/legos/.

Example Request

curl "http://localhost:3000/example/legos/" -H "Accept: application/vnd.example.20150828+json"
fetch('/example/legos', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

[
    { "_id": "5898c79e0675deda44a77c7e", "color": "red", "width": 2, "length": 6, "height": 2 },
    { "_id": "5898c7fe0bbdccda7419997d", "color": "green", "width": 2, "length": 6, "height": 2 },
    { "_id": "5898deb3d5411ae918f3058d", "color": "blue", "width": 2, "length": 6, "height": 2 }
]

Get Lego GET

This endpoint retrieves a single Lego GET http://api.example.com/legos/:id.

Parameters

Name Type Description
id int The id of the lego you wish to retrieve

Example Request

curl "http://localhost:3000/example/legos/5898c7fe0bbdccda7419997d" -H "Accept: application/vnd.example.20150828+json"
fetch('/example/legos/5898c7fe0bbdccda7419997d', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{
    "_id": "5898c7fe0bbdccda7419997d",
    "color": "red",
    "width": 2,
    "length": 6,
    "height": 2
}

Create Lego POST

This endpoint creates a Lego POST http://api.example.com/legos/.

Body

The body of your request should include a Lego.

Example Request

curl "http://localhost:3000/example/legos/" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.example.20150828+json" /
    -d '{"color":"yellow","width":2,"length":6,"height":2}'
fetch('/example/legos', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20150828+json"
    }),
    mode: 'cors',
    body: JSON.stringify({ color: 'yellow', width: 2, length: 6, height: 2 })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 201

{
    "_id": "5898c7fe0bbdccda7419997d",
    "op": "UPDATE",
    "recordsMatched": 1,
    "recordsEffected": 1
}

Example Response 400. This response would be generated if you omitted the height property, or if you provided a height that isn't a number.

{
    "type": "InvalidArgumentException",
    "messages": ["This implementation does not satisfy blueprint, Lego. It should have the property, height, with type, number."],
    "isException": true
}

Update Lego PUT

This endpoint creates a Lego PUT http://api.example.com/legos/:id.

Body

The body of your request should include a Lego.

Example Request

curl "http://localhost:3000/example/legos/5898c7fe0bbdccda7419997d" \
    -X PUT \
    -H "Content-Type: application/json" \
    -H "Accept: application/vnd.example.20150828+json" \
    -d '{"color":"yellow","width":2,"length":6,"height":2}'
fetch('/example/legos/5898c7fe0bbdccda7419997d', {
    method: 'PUT',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json",
        "Content-Type": "application/json"
    }),
    mode: 'cors',
    body: JSON.stringify({ color: 'yellow', width: 2, length: 6, height: 2 })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{
    "op": "UPDATE",
    "recordsMatched": 1,
    "recordsEffected": 1
}

Example Response 400. This response would be generated if you omitted the height property, or if you provided a height that isn't a number.

{
    "type": "InvalidArgumentException",
    "messages": ["This implementation does not satisfy blueprint, Lego. It should have the property, height, with type, number."],
    "isException": true
}

Update Lego PATCH

This endpoint creates a Lego PATCH http://api.example.com/legos/:id.

Body

The body of your request should include properties that belong to a Lego.

Example Request

curl "http://localhost:3000/example/legos/5898c7fe0bbdccda7419997d" \
    -X PUT \
    -H "Content-Type: application/json" \
    -H "Accept: application/vnd.example.20150828+json" \
    -d '{"color":"yellow"}'
fetch('/example/legos/5898c7fe0bbdccda7419997d', {
    method: 'PATCH',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json",
        "Content-Type": "application/json"
    }),
    mode: 'cors',
    body: JSON.stringify({ color: 'yellow' })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{
    "op": "UPDATE",
    "recordsMatched": 1,
    "recordsEffected": 1
}

Example Response 400. This response would be generated if you omitted the height property, or if you provided a height that isn't a number.

{
    "type": "InvalidArgumentException",
    "messages": ["This implementation does not satisfy blueprint, Lego. It should have the property, height, with type, number."],
    "isException": true
}

Delete Lego DELETE

This endpoint deletes a Lego DELETE http://api.example.com/legos/:id.

Example Request

curl "http://localhost:3000/example/legos/0" \
    -X DELETE \
    -H "Accept: application/vnd.example.20150828+json"
fetch('/example/legos/5898c7fe0bbdccda7419997d', {
    method: 'DELETE',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json"
    }),
    mode: 'cors'
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{
    "op": "ARCHIVE",
    "recordsMatched": 1,
    "recordsEffected": 1
}

Example Response 400. This response would be generated if you attempted to delete an id that doesn't exist.

{
    "type": "InvalidArgumentException",
    "messages": ["The index cannot be deleted: it does not exist"],
    "isException": true
}

About

This documentation is rendered from Git Flavored Markdown (GFM), using marked and hightlight.js. The docs can be found in the ./docs folder. To add a document, add a path to it in the ./environment/environment.json file, in the docs.files object.

This engine supports language-switching. To set your supported languages, set the docs.languages object in the ./environment/environment.json file. If you only support a single language, this object can be omitted from the environment.json file. The languge schema follows:

Property Type Description
name string The name as you would like it to appear in the Language Menu
highlightClass string The class name that highlight.js transforms the markdown to

Example Language

{
    "name": "JavaScript",
    "highlightClass": "lang-js"
}

Conventions

Code Blocks

Since we're using highlight.js to color the code blocks, you'll want to make sure to add your language to your code block ticks:

```js
// ...

Blockquote Headings

To get the code-block-header styles in this guide, use blockquote heading combos.

In the future, we may support side-by-side text-code layouts. The blockquotes and code blocks will be displayed in the side-bar, so make sure you take advantage of these conventions if you're interested in using the other layout options.

> # Code Block Header

It looks like this:

Code Block Header

console.log('hello world!');

There's also a blockquote h2 combo, meant to be used with code blocks

> ## Code Info

It looks like this:

Code Block H1

console.log('hello world!');

Code Block H2

Alterations

Once the markdown is rendered, we convert the #### (h4) and ##### (h5) layout into notifications and warnings:

Example Info: We convert h4 elements #### to info alerts.

Example Warning: We convert h5 elements ##### to danger alerts.

If you would prefer that your h4 and h5 styles not be altered, set docs.useHeadingAlerts to false in your environment.json file.

Auth controller

Data Model

AuthToken

AuthToken is the payload that will be used to form the JWT token

AuthToken has the following schema:

Property Type Description
_id string TBD
accessToken string open id connect access token from EE
userId string unique identifier for a user
schoolCode string what is now called class code. Can be null
shard number Shard the users information can be found on (not used)
deviceId string device the user is authenticating from
firstName string token returned from authentication with EE
openIdSub string token returned from authentication with EE
isAdmin string token returned from authentication with EE
iat number Issued At - added by jwt lib
exp number Expiration - added by jwt lib

Example Decoded AuthToken

{
    "_id": "82222ef7-ca93-4d3b-a9bd-a806449e3c8a",
    "accessToken": "NjliYTA0OGQtNzk5MC00YTUxLWIwN2ItMTc2Y2NmOThiMGEyAMK0pWE1Drz2taI1q22g9DI0M7UZ1FZrvi1wxuDhIg4xuRt6HVu0IqMkMPZzaQiAN5aZs_jQzRHuV42iRMlACQ",
    "userId": "5a9e28fdf21088169ceb9748",
    "schoolCode": "199312",
    "shard": 1,
    "deviceId": "ffffee99b8f-a8019ff",
    "firstName": "John",
    "openIdSub": "5a9e28fcfb7b7e26df71cb75",
    "isAdmin": "false",
    "iat": 1520314776,
    "exp": 1520325576
}

Register new user POST

POST http://api.example.com/auth/register

The endpoint wraps the EE registration API. If schoolCode is provided then all fields are required. On success there will be an email verification step (TBD). After that /auth/login can be called to login and obtain a JWT token for all other server API calls.

Query Parameters

Property Type Required Description
skipEE boolean False true will cause a user record to be created in the database, but will not interact with EE (oidc)

Parameters

Property Type Required Description
email string True student's email will be used as username
password string True password associated with login
schoolCode string False previously validated class code
firstName string True student's first name
lastName string False student's last name
dateOfBirth string False date of birth in format YYYY-MM-DD
nameOfClass string False name of class (unused)
curl "http://localhost:3000/auth/register/" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.oncourse.20170217+json" /
    -d '{"email":"jdoe@rocketmail.com","password":"td$yuma","firstName":"John"'
fetch('/auth/register', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20170217+json"
    }),
    mode: 'cors',
    body: JSON.stringify({ email: 'jdoe@rocketmail.com', password: 'td$yuma', firstName: "John" })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Response Body

Property Type Description
errors string[] on error will be populated

Response Codes

HTTP Status Description
200 OK - last step is email verification (TBD)
400 Problem with one of the fields, see errors
500 Server issue (like openId secrete invalid)

Login POST

POST http://api.example.com/auth/login

The endpoint logs into the server and obtains a JWT token for accessing secured game server resources.

Parameters

Property Type Required Description
email string True student's email will be used as username
password string True password associated with login
deviceId string True unique ID for the device or browser
curl "http://localhost:3000/auth/login/" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.oncourse.20170217+json" /
    -d '{"email":"jdoe@rocketmail.com","password":"td$yuma", "deviceId":"13008e16-0000-0000-0000-58adfefd0000"'
fetch('/auth/login', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20170217+json"
    }),
    mode: 'cors',
    body: JSON.stringify({ email: 'jdoe@rocketmail.com', password: 'td$yuma', deviceId:'13008e16-0000-0000-0000-58adfefd0000' })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Response Body

Property Type Description
authToken string encoded AuthToken
errors string[] on error will be populated

Response Codes

HTTP Status Description
200 OK - last step is email verification (TBD)
400 Problem with one of the fields, see errors

Logout POST

POST http://api.example.com/auth/logout

The endpoint logs out of the server. Not sure what it will really be doing yet

Parameters

none

curl "http://localhost:3000/auth/logout/" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.oncourse.20170217+json" /
    -H "Authorization: Bearer ew0KICAgICJvcGVuSWRUb2tlbiI6ICJUQkQiLA0KICAgICJ1c2VySWQiOiAiMTIzZTQ1NjctZTg5
Yi0xMmQzLWE0NTYtNDI2NjU1NDQwMDAwIiwNCiAgICAic2Nob29sQ29kZSI6ICJUQkQiLA0KICAg
ICJzaGFyZCI6IDEsDQogICAgImRldmljZUlkIjogIlRCRCAtIFVVSUQgZ2VuZXJhdGVkIGJ5IGFw
cCINCn0="
fetch('/auth/login', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20170217+json",
        "Authorization" : "Bearer " + token
    }),
    mode: 'cors',
    body: JSON.stringify({ email: 'jdoe@rocketmail.com', password: 'td$yuma' })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Response Body

Property Type Description
authToken string encoded AuthToken
errors string[] on error will be populated

Response Codes

HTTP Status Description
200 OK - last step is email verification (TBD)
400 Problem with one of the fields, see errors
401 Unauthorized - user hasn't registered or is locked

Validate controller

Validate class code POST

POST http://api.example.com/validate/school

The endpoint wraps the EE registration API's route to validate class code

Parameters

Property Type Description
code string class code given by teacher

Response Body

Property Type Description
schoolName string Name of school associated with code in request
errors string[] on error will contain an array of string messages

Response Codes

HTTP Status Description
200 OK - school name in response
400 Class code invalid, error message in response
500 Server issue (like openId secrete invalid)

Users controller

Current implementation is only to facilitate saving and loading user game data. The specifics of the data and version will be managed by the game client.

Game State Data POST

POST http://api.example.com/users/game-state

JWT Secured Route. Use Authorization header with Bearer schema

Persist game state data (save game) to database for current user as identified by the JWT token. Existing data will be overwritten.

Parameters

any json object

curl "http://localhost:3000/users/game-state" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.oncourse.20170217+json" /
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg" /
    -d '{ "any" : "data you like" } '
fetch('/users/game-state', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20170217+json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg"
    }),
    mode: 'cors',
    body: JSON.stringify({ any : "data you like" })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Response Body

no body

Response Codes

HTTP Status Description
201 Created
401 Unauthorized
500 Server issue (like openId secrete invalid)

Game State Data GET

GET http://api.example.com/users/game-state

JWT Secured Route. Use Authorization header with Bearer schema

Retrieves game data for current user as identified by the JWT token

Example Request

curl "http://localhost:3000/users/game-state" /
     -H "Accept: application/vnd.example.20150828+json"
     -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg" /
fetch('/users/game-state', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{ "any" : "data you like" }

Example Response 204

empty body

HTTP Status Description
200 OK - data loaded
204 No Content - no game data for user
401 Unauthorized
500 Server issue (like openId secrete invalid)

Settings Data POST

POST http://api.example.com/users/settings

JWT Secured Route. Use Authorization header with Bearer schema

Persist game settings data (has user seend tutorial, etc) to database for current user as identified by the JWT token. Existing data will be overwritten.

Parameters

any json object

curl "http://localhost:3000/users/settings" /
    -H "Content-Type: application/json" /
    -H "Accept: application/vnd.oncourse.20170217+json" /
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg" /
    -d '{ "any" : "data you like" } '
fetch('/users/settings', {
    method: 'POST',
    headers: new Headers({
        "Content-Type": "application/json",
        "Accept": "application/vnd.oncourse.20170217+json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg"
    }),
    mode: 'cors',
    body: JSON.stringify({ any : "data you like" })
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Response Body

no body

Response Codes

HTTP Status Description
201 Created
401 Unauthorized
500 Server issue (like openId secrete invalid)

Settings Data GET

GET http://api.example.com/users/settings

JWT Secured Route. Use Authorization header with Bearer schema

Retrieves game settings data for current user as identified by the JWT token

Example Request

curl "http://localhost:3000/users/settings" /
     -H "Accept: application/vnd.example.20150828+json"
     -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg" /
fetch('/users/settings', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{ "any" : "data you like" }

Example Response 204

empty body

HTTP Status Description
200 OK - data loaded
204 No Content - no game data for user
401 Unauthorized
500 Server issue (like openId secrete invalid)

Events controller

Background

Events are generated in the game client and postedin batches to the server for persistence. Currently two storage options are supported

  • Mongo DB (or Azure Cosmos with mongo api)
  • Azure Filestore

Additionally every event is pushed to the MQTT server if defined. This happens after events are queued for batch persistence. Events are pushed to MQTT as single events currently.

Batches of events that are queued (see bulkProcess.js) will go through the following sequence:

  • Request format is validated
  • Associated user ID is verified against the db
  • Individual events are validated against the schemas
  • Events are filtered (see below)
  • Events are prepared for write
    • user's open ID subject ID and version added to each event

Filtering

The system was built without knowing where the data might end up being persisted to or what the costs might be. There was a concern that if there were too many data points chosen that costs or write time might not scale. Instead of restricting the requested datapoints, a filtering mechinism was created so that at any point the events could be restricted without needing a deployment.

environment.json defines the key ONCOURSE:EVENTS:FILTER that defines an object with memebers which are events and explicitly includes events by action or subtype as well as determining whether details are persisted.

Take for example the filter object for the card event:

"card": {
    "action": [
      "dealt",
      "inspected",
      "placed",
      "discarded",
      "unlocked"
    ],
    "subType": [
      "cadet",
      "training",
      "mission"
    ],
    "detail": true
  }

This tells the sytem to include card events of action type dealt, inspected, placed, discarded, and unlocked. If unlocked was removed from the list, card events with action type placed would be filtered.

Note - if additional events, event actions or subtypes are added to the schemas and are transmitted from the game client, you will need to include these in the filter in order for the data to be persisted.

Queue Configuation

The batch size to pull from the queue is decided by the queue (the pull method takes no parameters). Three settings determine the functionality.

  • ONCOURSE:EVENTS:SERVER_MIN_BATCH_SIZE
    • This is the smallest batch size unless SERVER_MAX_WRITE_INTERVAL has been exceeded
  • ONCOURSE:EVENTS:SERVER_MAX_BATCH_SIZE
    • The number of events to return if there are at many in the queue
  • ONCOURSE:EVENTS:SERVER_MAX_WRITE_INTERVAL
    • The longest time to wait before more events are written if there are some events queued

The idea here is that it won't keep writing tiny batches, it tries to group events into batches if there is low activity. Because these events might be feeding dashboards, there is a maximum amount of time these might sit in the queue before they are written reguardless of batch size. Normally, the batch size will be between SERVER_MIN_BATCH_SIZE and SERVER_MAX_BATCH_SIZE with normal system activity.

Event Configurations

Keys under ONCOURSE:EVENTS which control the event framework

Key Description
CLIENT_BATCH_SIZE Configures game clients to sent this maximum batch size
SERVER_WRITE_INTERVAL How often items are pulled from the queue and written to defined storage options
HEALTH_CHECK_INTERVAL How often to check if a storage option is in failure state. If failing, the next storage option will be made primary until none are available and the system restarts.
FAILURE_THRESHOLD percentage of errors since last healthcheck representing a failing storage option
FILTER Defined under Filtering above

Bulk Events POST

POST http://api.example.com/events

JWT Secured Route. Use Authorization header with Bearer schema

This endpoint is used for bulk persisting events from the game client

Parameters

TODO: need to update. specific schemas for bulk loading now exist

curl -X POST \
  https://middle-galaxy-server-sc-nonprod.azurewebsites.net/events \
  -H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI5ODU4ZmI0Yy0wZTU0LTQ0NGEtYjczNC0xZTU0ZWMwYTZhYmQiLCJhY2Nlc3NUb2tlbiI6Ik9EQXlOemN6WWpZdE1EUXhaQzAwT1RsbUxXSTVaVFV0WmpKaVkyTTFPVFUxWmpjeGp6X2w1ZjRXcE1keWwxX3c4QURwUkpnYlFhemtPSGE4Y0dHV3ZrSmdZblNNN2lfdkFLT01waS1hSzdEbE14b3N2VnloTVg1M2VtMkttanhTcUNXOElRIiwidXNlcklkIjoiNWFhNWJkNjAyNjM4ZWIxYjdjOGE5OWM2Iiwic2Nob29sQ29kZSI6bnVsbCwic2hhcmQiOjEsImRldmljZUlkIjoiZmZmZmVlOTliOGYtYTgwMTlmZiIsImZpcnN0TmFtZSI6IkhhcnJ5Iiwib3BlbklkU3ViIjoiNThlMjgxMDQxYTM2NTIxZGQxNzg0NDIwIiwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTUyMDgxMTM2MCwiZXhwIjoxNTIwODIyMTYwfQ.aW6MrpGvIP-0cOQUjCBRwUEPCBfcxJDTd0Z-PcxFDZs' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: 4810e9f0-df4d-c158-2fd7-0e73395f552b' \
  -d '{
  "userId": "5926fa5c172a6a1260e78ddd",
  "events": [
    {
      "type": "session",
      "created": "2017-12-06T14:16:18.863Z",
      "gameId": 2,
      "action": "login",
      "ver": "2017-08-15",
      "openIdSubjectId": "{{user1subjectId}}",
      "version": 20170808
    },
    {
      "type": "other",
      "created": "2017-12-06T14:16:22.001Z",
      "subType": "codex",
      "action": "opened",
      "ver": "2017-08-15",
      "openIdSubjectId": "{{user1subjectId}}",
      "version": 20170808
    },
    {
      "type": "card",
      "created": "2017-12-06T14:16:27.203Z",
      "gameId": 2,
      "action": "inspected",
      "subType": "cadet",
      "turnId": -1,
      "cardId": "25",
      "cadetId": null,
      "missionId": null,
      "detail": {
        "inCodex": true
      },
      "ver": "2017-08-15",
      "openIdSubjectId": "{{user1subjectId}}",
      "version": 20170808
    }
  ]
}'

Response Body

No response is expected. Bulk events are accepted and queued

Response Codes

HTTP Status Description
202 Accepted
400 Problem with one of the fields, see errors
401 Unauthorized
500 Server issue (like openId secrete invalid)

My controller

Routes under my work with resources identified by the userId in the JWT token

Profile GET

GET http://api.example.com/my/profile

JWT Secured Route. Use Authorization header with Bearer schema

Retrieves users profile information and class code information

Example Request

curl "http://localhost:3000/my/profile" /
     -H "Accept: application/vnd.example.20150828+json"
     -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg" /
fetch('/my/profile', {
    method: 'GET',
    headers: new Headers({
        "Accept": "application/vnd.oncourse.20150828+json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIyMzA5MTI4MzIzIiwib3BlbklkVG9rZW4iOiJleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKemRXSWlPaUpoYkdsalpTSXNJbWx6Y3lJNkltaDBkSEJ6T2k4dmIzQmxibWxrTG1NeWFXUXVZMjl0SWl3aVlYVmtJam9pWTJ4cFpXNTBMVEV5TXpRMUlpd2libTl1WTJVaU9pSnVMVEJUTmw5WGVrRXlUV29pTENKaGRYUm9YM1JwYldVaU9qRXpNVEV5T0RBNU5qa3NJbUZqY2lJNkltTXlhV1F1Ykc5aExtaHBjMlZqSWl3aWFXRjBJam94TXpFeE1qZ3dPVGN3TENKbGVIQWlPakV6TVRFeU9ERTVOekI5LjBOVkl4RzlYcDZJOHFwSlpaWlQ2T1Y2ZmVBdDFfWC03bmxWSmVQR0xsM0kiLCJ1c2VySWQiOiIyOTg0OTgyMzQ5ODIiLCJzY2hvb2xDb2RlIjoiOEFBQiIsInNoYXJkIjoxLCJkZXZpY2VJZCI6Ijg4YTg4Mzk5YjhmLWE4MDE5ZmYiLCJpYXQiOjE0ODc4ODc5NzIsImV4cCI6MTQ4Nzg5MTU3Mn0.KmCehgqGEiMkc0iE_iQMs7s0PSfQ4zl3kJLo_hGx0jg"
    }),
    mode: 'cors',
}).then(function (res) {
    return res.json();
}).then(function (res) {
    console.log(res);
});

Example Response 200

{
  "firstName": "Rodney",
  "lastName": "Dangerfield",
  "email": "dev02@mordore.com",
  "dateOfBirth": "04/12/1978",
  "schoolCode": "864b111d4e",
  "schoolInfo": {
    "schoolName": "BROWNSVILLE ACADEMIC CENTER",
    "district": "BROWNSVILLE ISD"
  }
}