From e1b8f7e5e5945ceaadaad0f88482b67b3efd9e64 Mon Sep 17 00:00:00 2001 From: chris ludden Date: Tue, 16 Jan 2018 22:24:14 -0700 Subject: [PATCH] progress --- README.md | 66 ++++++++++++++++---- architecture.dot | 20 ++++++ architecture.png | Bin 0 -> 38251 bytes brainspark/Dockerfile | 0 brainspark/config.yml | 3 + docker-compose.yml | 51 +++++++++++++++ docs/README.md | 44 +++++++++++++ docs/account-batch-get-account.md | 56 +++++++++++++++++ docs/account-batch-get-user.md | 60 ++++++++++++++++++ docs/account-get-account.md | 46 ++++++++++++++ docs/account-get-user.md | 48 ++++++++++++++ docs/content-batch-get-course.md | 57 +++++++++++++++++ docs/content-get-course.md | 46 ++++++++++++++ docs/participation-batch-get-attempt.md | 60 ++++++++++++++++++ docs/participation-get-attempt.md | 47 ++++++++++++++ sample-event.json => docs/sample-event.json | 0 test.json | 27 ++++++++ 17 files changed, 619 insertions(+), 12 deletions(-) create mode 100644 architecture.dot create mode 100644 architecture.png create mode 100644 brainspark/Dockerfile create mode 100644 brainspark/config.yml create mode 100644 docker-compose.yml create mode 100644 docs/README.md create mode 100644 docs/account-batch-get-account.md create mode 100644 docs/account-batch-get-user.md create mode 100644 docs/account-get-account.md create mode 100644 docs/account-get-user.md create mode 100644 docs/content-batch-get-course.md create mode 100644 docs/content-get-course.md create mode 100644 docs/participation-batch-get-attempt.md create mode 100644 docs/participation-get-attempt.md rename sample-event.json => docs/sample-event.json (100%) create mode 100644 test.json diff --git a/README.md b/README.md index 0b1118e..006f54d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # Mindflash Backend Coding Exercise -Our goal is to give you a small coding challenge that allows you to demonstrate your skills and also gives you a small idea of some of problems that you may encounter at Mindflash. We know you're busy with life, so our aim is to have you spend no more than 2 hours working through this exercise. We don't expect you to finish in 2 hours, so don't worry if you can't. Submit what you have along with some notes on your thoughts and how you would proceed if you have more time. Most importantly, try to have some fun with it! +Our goal is to give you a small coding challenge that gives you a chance to show off your skills and also gives you a small idea of some of the problems that you may encounter at Mindflash. We know you're busy with life, so our aim is to have you spend no more than 2 hours working through this exercise. We don't expect you to finish in 2 hours, so don't worry if you can't. Submit what you have along with some notes on your thoughts and how you would proceed if you have more time. Most importantly, try to have some fun with it! + +**Lastly, your code is yours to keep, publish, delete, or blog about. However, please no mentions of Mindflash should you choose to share or publish it.** ## Task -Your task is to build a small dockerized application that filters, transforms, and indexes a stream of events into Elasticsearch (ES). To help you get started, we've included a [docker compose](https://docs.docker.com/compose/overview/) file to set up your local environment. +Your task is to build a small application using that filters, transforms, and indexes a stream of events from NSQ (a real-time distributed messaging platform) into Elasticsearch (a highly performant and scalable search/aggregation engine). + +

+architecture diagram +

+ +This repository includes a number of resources to help you get started, including documentation, code samples, and a [docker compose](https://docs.docker.com/compose/overview/) file that you can use to set up your local/test environment. ## Overview -You just got hired to join the *badass* engineering team at *BrainSpark*, the world's leading LMS! The first story in the sprint assigned assigned to you is to build an application that indexes a stream of events into ElasticSearch for use in reporting and general anaytics. For reference, the domain model of our application for the exercise is shown below: +You just got hired to join the *badass* engineering team at *BrainSpark* (a fictional LMS)! The first story in your sprint backlog is to build an application that indexes a stream of events into ElasticSearch for use in reporting and anaytics. For reference, the domain model of our application for the exercise is shown below:

schema diagram @@ -17,9 +25,9 @@ At a high level: - A user may attempt a course many times - Users can perform actions that trigger events -Our focus for this exercise are the domain events. Events are published via an [NSQ Topic](http://nsq.io). The event topic messages represent a single domain event and are serialized as JSON. A sample event is shown below: +Our focus for this exercise are the domain events. Events are published on an [NSQ Topic](http://nsq.io). An event message represents a single event and is serialized as JSON. A sample event is shown below: -**Sample NSQ Event Message:** +###### Sample Event Message ```json { "id": "5c92de28-14f0-449e-821b-e61e871179c2", @@ -35,11 +43,11 @@ Our focus for this exercise are the domain events. Events are published via an [ Prior to indexing the event records into ES, you will need to apply the following transformations: - filter out invalid messages (ie messages that do not pass the schema above) - filter out events with type `FOO` -- denormalize the event by *hydrating* (ie *embedding*) the related attempt, course, trainee, and user (if available) using the *BrainSpark* API (see details section below for more info) +- denormalize the event by *hydrating* (ie *embedding*) the related attempt, course, trainee, and user (if available) using the *BrainSpark* JSON-RPC API (see *resources* section below for more info) An example of a successful transformation is shown below: -**Sample Transformed ES Document:** +###### Sample Transformed ElasticSearch Document ```json { "id": "5c92de28-14f0-449e-821b-e61e871179c2", @@ -79,12 +87,46 @@ An example of a successful transformation is shown below: } ``` -The events need to be indexed into the `trainee-events` index, with type `trainee-event`. +The events need to be indexed into Elasticsearch using `event` as the document type and index `events-YYYY-MM-DD`, where `YYYY-MM-DD` is replaced using the formatted event `timestamp` attribute. *Note: you do not need to preallocate the indices, they will be created on first index*. -Details: +## Resources + +- [NSQ Design Overview](http://nsq.io/overview/design.html) + - [nsqjs](https://github.com/dudleycarr/nsqjs) node client + - [go-nsq](https://github.com/nsqio/go-nsq) go client +- [Elasticsearch Document API](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/docs.html) + - [elasticsearch.js](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html) node client + - [elastic](https://github.com/olivere/elastic) go client +- [BrainSpark API](./docs/README.md) +- [JSON RPC](http://www.jsonrpc.org/specification) +- [a sample json event](./docs/sample-event.json) + +## Getting Started +The only prerequisites for using this repository are [Docker](https://www.docker.com/what-container) & [Compose](https://docs.docker.com/compose/overview/). Installation links are below: +- [Docker](https://store.docker.com/search?type=edition&offering=community) +- [Compose](https://docs.docker.com/compose/install/) + +To start the local environment: +```shell +$ docker-compose up -d + +``` +*Note: This command will start NSQ, ElasticSearch, and the BrainSpark container. It may take a few minutes the first time you run it, as it will have to pull the images for NSQ and ElasticSearch and build the BrainSpark image locally. By default, NSQ will be mapped to localhost:4150, ElasticSearch will be mapped to localhost:9200, and the BrainSpark application will be listening on port 8080. You can change these mappings and other configurations by editing the docker-compose.yml file* -Requirements: +## Requirements +- you may build your application using `node.js`, `go`, or `.net` +- you should include a `Dockerfile` in the root of your repository +- your code should be linted + - nodejs: use `eslint` with `eslint-config-airbnb-base` + - go: use `golint` +- your code should include a couple tests +- your code should include a `README.md` file in the root with instructions for building, running, and testing. It can also include notes on your throught process and any issues you may have run into. -Expectations: +## Evaluation +We will evaluate your submission using the following criteria +- Is your application well organized? +- Is your code documented? +- Is your code efficient & performant? -Submission: \ No newline at end of file +## Submission +Please upload this repository to Github and submit to @jgiless when complete. \ No newline at end of file diff --git a/architecture.dot b/architecture.dot new file mode 100644 index 0000000..7c74abc --- /dev/null +++ b/architecture.dot @@ -0,0 +1,20 @@ +digraph architecture { + nsq [label="NSQ" style="filled" fillcolor="skyblue1"] + brainspark [label="Brainspark API" shape="egg" style="filled" fillcolor="orange"] + es [label="ElasticSearch" shape="egg" style="filled" fillcolor="lightseagreen"] + + + brainspark->nsq [label="publish\nevents"] + app->nsq [label="consume\nevents"] + app->es [label="index\n events"] + brainspark->es [label="search &\n aggregate"] + app->brainspark [label="query\n related\ndata"] + + subgraph clusterApp { + color="lightgrey" + style="filled" + app [label="Your App" shape="rect" style="filled" fillcolor="gold"] + + app->app [label="filter &\n hydrate\n events"] + } +} \ No newline at end of file diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e5ed041116d6b92f9be4a5df819f9061b8d1df GIT binary patch literal 38251 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 literal 0 HcmV?d00001 diff --git a/brainspark/Dockerfile b/brainspark/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/brainspark/config.yml b/brainspark/config.yml new file mode 100644 index 0000000..59ca8cb --- /dev/null +++ b/brainspark/config.yml @@ -0,0 +1,3 @@ +log: + format: text + level: info \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e8734d2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3' +services: + brainspark: + build: + context: . + dockerfile: ./brainspark/Dockerfile + command: --jsonrpc --seed + depends_on: + - nsqd + ports: + - "8080:8080" + volumes: + - ./test.yml:/etc/brainspark/config.yml + environment: + BRAINSPARK_NSQ_HOST: nsqd:4150 + + make: + build: + context: . + dockerfile: ./Dockerfile.test + command: --jsonrpc --seed + depends_on: + - nsqd + ports: + - "8080:8080" + environment: + BRAINSPARK_NSQ_HOST: nsqd:4150 + + nsqlookupd: + image: nsqio/nsq + command: /nsqlookupd + ports: + - "4160" + - "4161" + + nsqd: + image: nsqio/nsq + command: /nsqd --lookupd-tcp-address=nsqlookupd:4160 + depends_on: + - nsqlookupd + ports: + - "4150:4150" + - "4151:4151" + + nsqadmin: + image: nsqio/nsq + command: /nsqadmin --lookupd-http-address=nsqlookupd:4161 + depends_on: + - nsqlookupd + ports: + - "4171:4171" \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b263826 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,44 @@ +# Brainspark API +The *BrainSpark* API is a JSON RPC 2.0 exposed over HTTP. + +All operations: +- must use path `/` +- must use http method `POST` +- must include header `Content-Type: application/json` +- must include a valid [JSON RPC 2.0](http://www.jsonrpc.org/specification) payload + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainspark_host} +Accept: application/json +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "{service}.{method}", + "id": "{request_id}", + "params": { + "ids": [ + "599a7dba-fb3a-11e7-93d8-a7d489958da0" + ] + } +} +``` + +## Services +See below for a list of API methods grouped by service. + +### Account +- [Account.BatchGetAccount](account-batch-get-account.md) +- [Account.BatchGetUser](account-batch-get-user.md) +- [Account.GetAccount](account-get-account.md) +- [Account.GetUser](account-get-user.md) + +### Content +- [Content.BatchGetCourse](content-batch-get-course.md) +- [Content.GetCourse](content-get-course.md) + +### Participation +- [Participation.BatchGetAttempt](participation-batch-get-attempt.md) +- [Participation.GetAttempt](participation-get-attempt.md) \ No newline at end of file diff --git a/docs/account-batch-get-account.md b/docs/account-batch-get-account.md new file mode 100644 index 0000000..f9e17aa --- /dev/null +++ b/docs/account-batch-get-account.md @@ -0,0 +1,56 @@ +[Back](README.md) + +### Batch Get Accounts + +Retrieves a set of 1 to 200 accounts by id. Missing accounts will be designated with a `null` value + +###### Method +``` +Account.BatchGetAccount +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Account.BatchGetAccount", + "params": { + "ids": [ + "599a7dba-fb3a-11e7-93d8-a7d489958da0", + "bec784b6-fb3b-11e7-9b5b-0bc91e981e31" + ] + }, + "id": "c4e0bf8a-fb3f-11e7-8241-0faf4421c718", +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": "599a7dba-fb3a-11e7-93d8-a7d489958da0", + "name": "foo", + "domain": "foo.mindflash.com" + }, + { + "id": "bec784b6-fb3b-11e7-9b5b-0bc91e981e31", + "name": "bar", + "domain": "bar.mindflash.com" + } + ] + }, + "id": "c4e0bf8a-fb3f-11e7-8241-0faf4421c718" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/docs/account-batch-get-user.md b/docs/account-batch-get-user.md new file mode 100644 index 0000000..29d22ec --- /dev/null +++ b/docs/account-batch-get-user.md @@ -0,0 +1,60 @@ +[Back](README.md) + +### Batch Get Users + +Retrieves a set of 1 to 200 users by id. Missing users will be designated with a `null` value + +###### Method +``` +Account.BatchGetUser +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Account.BatchGetUser", + "params": { + "ids": [ + "853629b4-fb40-11e7-a26c-4b5026d382d8", + "b6feec9c-fb40-11e7-89ce-374290f50084" + ] + }, + "id": "c73931e4-fb40-11e7-a95f-fb1e838d3f17" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": "853629b4-fb40-11e7-a26c-4b5026d382d8", + "account_id": "21f8c490-2e26-42d0-bbc1-4d4efab6cc4d", + "email": "Ashley.Gordon@Topicblab.mil", + "first_name": "Ashley", + "last_name": "Gordon" + }, + { + "id": "b6feec9c-fb40-11e7-89ce-374290f50084", + "account_id": "b91e575c-8ab0-4114-b11b-d7a06cdbba4f", + "email": "Frances.Larson@Rhynyx.org", + "first_name": "Frances", + "last_name": "Larson" + } + ] + }, + "id": "c73931e4-fb40-11e7-a95f-fb1e838d3f17" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/docs/account-get-account.md b/docs/account-get-account.md new file mode 100644 index 0000000..41abb9d --- /dev/null +++ b/docs/account-get-account.md @@ -0,0 +1,46 @@ +[Back](README.md) + +### Get Account + +Retrieves a single account by id. A missing account will be designated with a `null` value + +###### Method +``` +Account.GetAccount +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Account.GetAccount", + "params": { + "id": "21f8c490-2e26-42d0-bbc1-4d4efab6cc4d" + }, + "id": "00c56456-fb40-11e7-9ffe-4bb811edbd26" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": { + "id": "21f8c490-2e26-42d0-bbc1-4d4efab6cc4d", + "created_at": "2013-05-29T08:24:48Z", + "name": "Topicblab" + } + }, + "id": "00c56456-fb40-11e7-9ffe-4bb811edbd26" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/docs/account-get-user.md b/docs/account-get-user.md new file mode 100644 index 0000000..6a7ef03 --- /dev/null +++ b/docs/account-get-user.md @@ -0,0 +1,48 @@ +[Back](README.md) + +### Get User + +Retrieves a single user by id. A missing user will be designated with a `null` value + +###### Method +``` +Account.GetUser +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Account.GetUser", + "params": { + "id": "b6feec9c-fb40-11e7-89ce-374290f50084" + }, + "id": "ce4f68f4-fb40-11e7-91da-83ae2b86bcab" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": { + "id": "b6feec9c-fb40-11e7-89ce-374290f50084", + "account_id": "b91e575c-8ab0-4114-b11b-d7a06cdbba4f", + "email": "Frances.Larson@Rhynyx.org", + "first_name": "Frances", + "last_name": "Larson" + } + }, + "id": "ce4f68f4-fb40-11e7-91da-83ae2b86bcab" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/docs/content-batch-get-course.md b/docs/content-batch-get-course.md new file mode 100644 index 0000000..1439f66 --- /dev/null +++ b/docs/content-batch-get-course.md @@ -0,0 +1,57 @@ +[Back](README.md) + +### Batch Get Courses + +Retrieves a set of 1 to 200 courses by id. Missing courses will be designated with a `null` value + +###### Method +``` +Content.BatchGetCourse +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Content.BatchGetCourse", + "params": { + "ids": [ + "09d3984e-2286-4b1b-95bf-271c7ceaaefe", + "8d5bee71-9ca4-4cba-90b1-a4345a209b15" + ] + }, + "id": "a68b90c0-fb3d-11e7-8f96-c3e7a9bbe7cf" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": "09d3984e-2286-4b1b-95bf-271c7ceaaefe", + "account_id": "21f8c490-2e26-42d0-bbc1-4d4efab6cc4d", + "name": "odit" + }, + { + "id": "8d5bee71-9ca4-4cba-90b1-a4345a209b15", + "account_id": "b91e575c-8ab0-4114-b11b-d7a06cdbba4f", + "name": "est" + } + ] + }, + "id": "a68b90c0-fb3d-11e7-8f96-c3e7a9bbe7cf" +} +``` + + +[Back](README.md) \ No newline at end of file diff --git a/docs/content-get-course.md b/docs/content-get-course.md new file mode 100644 index 0000000..40bfbfd --- /dev/null +++ b/docs/content-get-course.md @@ -0,0 +1,46 @@ +[Back](README.md) + +### Get Course + +Retrieves a single course by id. A missing course will be designated with a `null` value + +###### Method +``` +Content.GetCourse +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Content.GetCourse", + "params": { + "id": "8d5bee71-9ca4-4cba-90b1-a4345a209b15" + }, + "id": "21184768-fb41-11e7-a503-0f8d1344d13f" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": { + "id": "8d5bee71-9ca4-4cba-90b1-a4345a209b15", + "account_id": "b91e575c-8ab0-4114-b11b-d7a06cdbba4f", + "name": "est" + } + }, + "id": "21184768-fb41-11e7-a503-0f8d1344d13f" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/docs/participation-batch-get-attempt.md b/docs/participation-batch-get-attempt.md new file mode 100644 index 0000000..88791fc --- /dev/null +++ b/docs/participation-batch-get-attempt.md @@ -0,0 +1,60 @@ +[Back](README.md) + +### Batch Get Attempts + +Retrieves a set of 1 to 200 attempts by id. Missing accounts will be designated with a `null` value + +###### Method +``` +Participation.BatchGetAttempt +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Participation.BatchGetAttempt", + "params": { + "ids": [ + "8e73ada3-163c-4bf2-942d-506a9dc007c5", + "4e2d9e89-d13e-4d8e-8be1-293a9ae69cf4" + ] + }, + "id": "7550b72e-fb3d-11e7-b300-0361a11d73f7" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": "8e73ada3-163c-4bf2-942d-506a9dc007c5", + "course_id": "09d3984e-2286-4b1b-95bf-271c7ceaaefe", + "created_at": "2018-01-17T04:16:13.444485198Z", + "trainee_id": "2405658510" + }, + { + "id": "4e2d9e89-d13e-4d8e-8be1-293a9ae69cf4", + "course_id": "8d5bee71-9ca4-4cba-90b1-a4345a209b15", + "created_at": "2018-01-17T04:16:13.444869636Z", + "trainee_id": "0912578565" + } + ] + }, + "id": "7550b72e-fb3d-11e7-b300-0361a11d73f7" +} +``` + + + +[Back](README.md) \ No newline at end of file diff --git a/docs/participation-get-attempt.md b/docs/participation-get-attempt.md new file mode 100644 index 0000000..344cf3b --- /dev/null +++ b/docs/participation-get-attempt.md @@ -0,0 +1,47 @@ +[Back](README.md) + +### Get Attempt + +Retrieves a single attempt by id. A missing attempt will be designated with a `null` value + +###### Method +``` +Participation.GetAttempt +``` + +###### Example Request +```http +POST / HTTP/1.1 +Host: {brainsparkHost} +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "Participation.GetAttempt", + "params": { + "id": "8e73ada3-163c-4bf2-942d-506a9dc007c5" + }, + "id": "5f02c94a-fb41-11e7-aee2-b71def56683b" +} +``` + +###### Example Response +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "result": { + "data": { + "id": "8e73ada3-163c-4bf2-942d-506a9dc007c5", + "course_id": "09d3984e-2286-4b1b-95bf-271c7ceaaefe", + "created_at": "2018-01-17T04:16:13.444485198Z", + "trainee_id": "2405658510" + } + }, + "id": "5f02c94a-fb41-11e7-aee2-b71def56683b" +} +``` + +[Back](README.md) \ No newline at end of file diff --git a/sample-event.json b/docs/sample-event.json similarity index 100% rename from sample-event.json rename to docs/sample-event.json diff --git a/test.json b/test.json new file mode 100644 index 0000000..b5bbf44 --- /dev/null +++ b/test.json @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": "ed3c7c67-a23e-491a-af73-79291726da52", + "course_id": "a9cbbf7f-8c6d-4cf3-883a-1be02bab24bc", + "created_at": "2018-01-16T14:08:29.65499689-07:00", + "progress": 0, + "score": 0, + "status": "INVITED", + "trainee_id": "3782563978" + }, + null, + { + "id": "ed9d4cb7-2d74-4c8a-9bc4-ecb893b548f1", + "course_id": "2de71486-23d9-4ed2-9c25-a61ce6ffbfdb", + "created_at": "2018-01-16T14:09:27.654925074-07:00", + "progress": 0, + "score": 0, + "status": "INVITED", + "trainee_id": "0626833260" + } + ] + }, + "id": "123" +} \ No newline at end of file