├── .DS_Store ├── .gitignore ├── src ├── .DS_Store ├── images │ ├── newrelic.jpg │ ├── pm2-logs.jpg │ ├── pm2-monit.jpg │ ├── logo-small.png │ └── cloudwatch-logs.jpg ├── index.html ├── _includes │ ├── footer.html │ ├── header.html │ └── head.html ├── guides-migrating.md ├── http-personas.md ├── http-persona-attributes.md ├── http-stores.md ├── welcome.md ├── http-queries.md ├── http-activities.md ├── http-exports.md ├── overview-xapi.md ├── css │ ├── tomorrow-night.css │ └── docs.css ├── faqs.md ├── guides-retrieving.md ├── http-downloads.md ├── guides-structuring.md ├── http-roles.md ├── http-dashboards.md ├── http-organisations.md ├── http-visualisations.md ├── guides-sales-statements.md ├── guides-monitoring.md ├── http-clients.md ├── guides-integrating.md ├── http-xapi.md ├── http-persona-identifiers.md ├── _layouts │ └── default.html ├── http-users.md ├── http-statements.md ├── http-statement-forwarding.md ├── http-aggregate.md ├── guides-indexing.md ├── guides-video-statements.md ├── http-journey-progress.md ├── http-metadata.md ├── guides-cli.md ├── overview-architecture.md ├── http-statement-deletion.md ├── guides-custom-installation.md ├── guides-installing.md ├── http-persona-imports.md ├── guides-inserting.md ├── guides-configuring.md ├── guides-assessment-statements.md ├── http-xapi-statements.md ├── http-xapi-states.md ├── http-rest.md └── http-connection.md ├── .editorconfig ├── Gemfile ├── .travis.yml ├── _config.yml ├── contributing.md ├── Gemfile.lock └── readme.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | out 3 | .DS_Store 4 | .idea 5 | .jekyll-cache 6 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/images/newrelic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/images/newrelic.jpg -------------------------------------------------------------------------------- /src/images/pm2-logs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/images/pm2-logs.jpg -------------------------------------------------------------------------------- /src/images/pm2-monit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/images/pm2-monit.jpg -------------------------------------------------------------------------------- /src/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/images/logo-small.png -------------------------------------------------------------------------------- /src/images/cloudwatch-logs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearningLocker/docs/HEAD/src/images/cloudwatch-logs.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | insert_final_newline = true -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem "jekyll", "~> 4.1" 8 | 9 | gem "jekyll-watch", "~> 2.2" 10 | 11 | gem "jekyll-redirect-from", "~> 0.16.0" 12 | -------------------------------------------------------------------------------- /src/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.6.0 4 | 5 | sudo: false 6 | 7 | install: bundle install --jobs=3 --retry=3 8 | script: jekyll build 9 | deploy: 10 | provider: pages 11 | skip_cleanup: true 12 | github_token: $GH_TOKEN 13 | local_dir: ./out 14 | fqdn: docs.learninglocker.net 15 | on: 16 | branch: master 17 | -------------------------------------------------------------------------------- /src/_includes/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: "Learning Locker Documentation" 3 | email: "hello@learninglocker.net" 4 | description: "Documentation for Learning Locker, the open source learning record store." 5 | source: "./src" 6 | destination: "./out" 7 | 8 | # Build settings 9 | markdown: kramdown 10 | permalink: pretty 11 | 12 | # Plugins 13 | plugins: 14 | - jekyll-redirect-from 15 | 16 | # YAML Frontmatter variables 17 | defaults: 18 | - 19 | scope: 20 | path: "" # empty string for all files 21 | values: 22 | layout: "default" 23 | 24 | -------------------------------------------------------------------------------- /src/guides-migrating.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Migrating from V1 Guide 5 | Before migrating from version 1, you will need to [install the current version](../guides-installing) with a blank database. 6 | 7 | Once you have the current version successfully installed and working, you may follow the instructions at [use the migration tool](https://github.com/LearningLocker/v1-to-v2-migrator) which will download your version 1 database, migrate your data to the current version, and upload the migrated data into your current version's database. 8 | 9 | Note that the migration tool will also copy your xAPI documents and attachments, but currently we only provide support for moving files from and to local storage and s3 storage. 10 | 11 | -------------------------------------------------------------------------------- /src/http-personas.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Personas HTTP Interface 5 | 6 | A persona represents a person with many [identifiers](../http-persona-identifiers) and [attributes](../http-persona-attributes) across systems. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/persona. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/persona. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of the persona. 18 | organisation | The id of the [organisation](../http-organisations#schema) that this persona belongs to. 19 | name | The display name of this persona. 20 | 21 | ### Example 22 | 23 | ```json 24 | { 25 | "_id" : "59c1219936229d4ce9634601", 26 | "organisation" : "59c1219936229d4ce9634602", 27 | "name" : "Example Persona", 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /src/http-persona-attributes.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Persona Attributes HTTP Interface 5 | 6 | Represents an attribute of a [persona](../http-personas). 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/personaattribute. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/personaattribute. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this attribute. 18 | organisation | The id of the [organisation](../http-organisations) this attribute belongs to. 19 | personaId | The id of the [persona](../http-personas) this attribute belongs to. 20 | key | The name of the attribute. 21 | value | The value of the attribute. 22 | 23 | ### Example 24 | 25 | ```json 26 | { 27 | "_id" : "59c1219936229d4ce9634601", 28 | "organisation" : "59c1219936229d4ce9634602", 29 | "personaId": "59c1219936229d4ce9634603", 30 | "key": "hair-colour", 31 | "value": "brown" 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /src/http-stores.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Stores HTTP Interface 5 | 6 | A store holds collections of statements. (may be referred to as lrs). 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/lrs. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/lrs. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this store. 18 | Title | The name of this store. 19 | createdAt | When this store was created. 20 | updatedAt | When this was last updated. 21 | description | The description of this store. 22 | owner_id | The id of the [user](../http-users#schema) who created this store. 23 | organisation | The [organisation](../http-organisations#schema) this store belongs to. 24 | statementCount | Number of statements in this store. 25 | 26 | ### Example Model 27 | 28 | ```json 29 | { 30 | "_id" : "59c2371c16bc715f83c34508", 31 | "title" : "Example Store", 32 | "createdAt" : "2017-04-27T09:40:44.920Z", 33 | "updatedAt" : "2017-04-27T09:40:49.139Z", 34 | "organisation" : "59c2371c16bc715f83c34507", 35 | "statementCount" : 500 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /src/welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: 3 | - "/troubleshooting/" 4 | - "/contributing/" 5 | - "/contributors/" 6 | - "/administration/" 7 | --- 8 | 9 | # Welcome 10 | 11 | Learning Locker is a conformant open source Learning Record Store (LRS) started in 2013 by [HT2 Labs](https://www.ht2labs.com) (now [Learning Pool](https://learningpool.com)); a type of data repository designed to store learning activity statements generated by xAPI (Tin Can) compliant learning activities. This website provides technical documentation for Learning Locker, for anything else please see the [main website](https://learninglocker.net/). 12 | 13 | > Learning Locker only supports xAPI v1.0 and above. It is not suitable for platforms still using an earlier version of xAPI. At this time there are no plans to provide backwards compatibility for older versions of the spec. 14 | 15 | In addition to this website, there are several ways to find out more about Learning Locker such as: 16 | 17 | - [Main Website](http://learninglocker.net/) 18 | - [Github Repository](https://github.com/LearningLocker/learninglocker) 19 | - [Gitter Chat Room](https://gitter.im/LearningLocker/learninglocker) 20 | - [Twitter](https://twitter.com/learning_locker) 21 | - [Email via hello@learninglocker.net](mailto:hello@learninglocker.net) 22 | -------------------------------------------------------------------------------- /src/http-queries.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Queries HTTP Interface 5 | 6 | This holds saved statement queries. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/query. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/query. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of the query. 18 | name | The name of this query. 19 | organisation | The id of the [organisation](../http-organisations#schema) that this persona belongs to. 20 | owner | The id of the [user](../http-users#schema) which created this query. 21 | conditions | A JSON encoded [mongo query](https://docs.mongodb.com/manual/tutorial/query-documents/). 22 | isPublic | If false then this visualisation is only available to the owner and users with [org/all/query/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. 23 | 24 | ### Example Model 25 | 26 | ```json 27 | { 28 | "_id" : "59c2371c16bc715f83c34501", 29 | "name" : "All comments", 30 | "owner" : "59c2371c16bc715f83c34502", 31 | "organisation" : "59c2371c16bc715f83c34503", 32 | "isPublic" : false, 33 | "conditions" : "{\"statement.verb.id\":\"http://adlnet.gov/expapi/verbs/commented\"}" 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /src/http-activities.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Activities HTTP Interface 5 | 6 | It is accessible through the following HTTP interfaces: 7 | 8 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/activity. 9 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/activity. 10 | 11 | ### Schema 12 | 13 | Name | Description 14 | --- | --- 15 | _id | The id of the activity. 16 | organisation | The id of the [organisation](../http-organisations#schema) that this activity belongs to. 17 | name | The display name of this activity. 18 | definition | An object holding details of the activity. See [definition](#definition). 19 | 20 | ### Definition 21 | 22 | Name | Description 23 | --- | --- 24 | type | The type of the activity. 25 | interactionType | Possible values are: true-false, choice, fill-in, long-fill-in, matching, performance, sequencing, likert, numeric or other. See [Interaction Types](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#interaction-types) for full description. 26 | 27 | ### Example 28 | 29 | ```json 30 | { 31 | "_id": "59c1219936229d4ce9634601", 32 | "organisation": "59c1219936229d4ce9634602", 33 | "name": "Example Activity", 34 | "definition": { 35 | "type": "http://adlnet.gov/expapi/activities/question", 36 | "interactionType": "choice" 37 | }, 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /src/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | The contribution guidelines can be found at http://docs.learninglocker.net/contribute. 2 | 3 | ### Issue Templates 4 | If you are reporting an issue, please click the links below and your issue will be prefilled with our template. You need to replace the parts of the template where you see `{{some text}}`. 5 | 6 | - [Mistake](../../issues/new?title={{Brief description of the mistake}}&body=**Expected text**%0A{{page}} should state {{expectedText}} because {{reason}}.%0A%0A**Actual text**%0A{{page}} states {{actualText}}.%0A%0A**Additional information**%0A{{additionalInfo}}): reports documentation that is incorrect. 7 | - [Bug](../../issues/new?title={{Brief description of your bug}}&body=**Version**%0A{{branch}} at {{commit}}%0A%0A**Steps to reproduce the bug**%0A{{steps}}%0A%0A**Expected behaviour**%0A{{feature}} should be {{expectedResult}} because {{reason}}.%0A%0A**Actual behaviour**%0A{{feature}} is {{actualResult}}.%0A%0A**Client information**%0AOS: {{operatingSystem}}%0ABrowser: {{browser}} version 1.0.1%0A%0A**Additional information**%0A{{additionalInfo}}): reports a feature that is not working as expected. 8 | - [Enhancement](../../issues/new?title={{Brief description of your enhancement}}&body=**Motive**%0A{{why the enhancement is needed}}%0A%0A**Result**%0A{{what the enhancement is}}%0A%0A**Additional information**%0A{{additionalInfo}}): requests a removal, addition, or change of a feature. 9 | - [Question](../../issues/new?title={{Brief description of your question}}&body={{question}}%3F): asks how a feature should be used or what the feature does. 10 | -------------------------------------------------------------------------------- /src/http-exports.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Exports HTTP Interface 5 | 6 | This holds queries which are then used to export data form learning locker. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/export. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/export. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The unique id of document. 18 | organisation | The organisation id this export template belongs to. 19 | name | Title of this export template. 20 | owner | The id of the [user](../http-users#schema) who created this export template. 21 | projection | An array of stringified json mongo projection queries. [See mongo docs](https://docs.mongodb.com/manual/reference/operator/aggregation/project/) 22 | rawMode | If true, in Learning Locker UI, the projection will be displayed as JSON text, as opposed to field value inputs. 23 | downloads | A list ids of [downloads](../http-downloads#schema) which have used this export template. 24 | isPublic | If false then this dashboard is only available to the owner and users with [org/all/export/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. 25 | 26 | ### Example Model 27 | 28 | ```json 29 | { 30 | "_id" : "59c2371c16bc715f83c34501", 31 | "name" : "Example Export", 32 | "organisation" : "59c2371c16bc715f83c34502", 33 | "downloads" : [ ], 34 | "rawMode" : false, 35 | "projections" : [ 36 | "{\"_id\":1,\"version\":\"$statement.version\"}" 37 | ] 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /src/overview-xapi.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # xAPI Overview 5 | The Experience API (xAPI) is a specification that defines how to store and retrieve learning experiences across various sources for analysis. Recording learning experiences in this way improves the ability to comprehend and compare learning experiences and their outcomes. 6 | 7 | ## Data Models 8 | The xAPI provides four data models for recording elements of a learning experience, these are: 9 | 10 | - [**Statements**](../http-xapi-statements) which are an immutable record of an agent's interaction with an activity. A set of statements can be used to track a complete learning experience. [Voiding](https://vimeo.com/168961267) can be used to invalidate previous statements, henceforth removing them from normal retrieval methods. 11 | - [**Activity Profiles**](../http-xapi-activities) that record additional mutable information about an activity that you wouldn't want to record on every statement. 12 | - [**Agent Profiles**](../http-xapi-agents) that record additional mutable information about an agent that you wouldn't want to record on every statement. 13 | - [**State**](../http-xapi-states) which records mutable information about an agent in relation to an activity, such as how far they've progressed through a activity. 14 | 15 | ## Using the xAPI 16 | To begin using the xAPI in Learning Locker, you can checkout our guides for [integrating with Learning Locker](../guides-integrating), [inserting statements](../guides-inserting), and [retrieving statements](../guides-retrieving). For more detailed documentation of the xAPI conformant HTTP interfaces that Learning Locker provides, you can checkout the [xAPI HTTP interface documentation](../http-xapi). 17 | -------------------------------------------------------------------------------- /src/css/tomorrow-night.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Night Theme */ 2 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | .tomorrow-comment, pre .comment, pre .title { 6 | color: #969896; 7 | } 8 | 9 | .tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { 10 | color: #cc6666; 11 | } 12 | 13 | .tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant { 14 | color: #de935f; 15 | } 16 | 17 | .tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute { 18 | color: #f0c674; 19 | } 20 | 21 | .tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata { 22 | color: #b5bd68; 23 | } 24 | 25 | .tomorrow-aqua, pre .css .hexcolor { 26 | color: #8abeb7; 27 | } 28 | 29 | .tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { 30 | color: #81a2be; 31 | } 32 | 33 | .tomorrow-purple, pre .keyword, pre .javascript .function { 34 | color: #b294bb; 35 | } 36 | 37 | pre code { 38 | display: block; 39 | background: #1d1f21; 40 | color: #c5c8c6; 41 | padding: 0.5em; 42 | } 43 | 44 | pre .coffeescript .javascript, 45 | pre .javascript .xml, 46 | pre .tex .formula, 47 | pre .xml .javascript, 48 | pre .xml .vbscript, 49 | pre .xml .css, 50 | pre .xml .cdata { 51 | opacity: 0.5; 52 | } 53 | -------------------------------------------------------------------------------- /src/faqs.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Frequently Asked Questions 5 | 6 | #### Why can't I use custom extensions in queries? 7 | When Learning Locker stores extensions, the dots in the keys are replaced with `&46;` because Mongo does not allow dots in keys. You can find more information in the [Connection HTTP Interface documentation](../http-connection#filtering-with-extension-keys) and the [Aggregation HTTP Interface documentation](../http-aggregation#projecting-with-extension-keys). 8 | 9 | #### Why are my queries running slowly? 10 | It's likely that you are querying without utilising [Mongo indexes](https://docs.mongodb.com/manual/indexes/). You can improve the performance of your queries by utilising Mongo indexes. More information is available in the [Connection HTTP Interface documentation](../http-connection#filtering-with-improved-performance) and the [Aggregation HTTP Interface documentation](../http-aggregation#matching-with-improved-performance). 11 | 12 | #### Why am I getting an unauthorised error using the xAPI? 13 | It's likely that you've either misconfigured your "Client" in Learning Locker or you're using an incorrect "Authorization" header. To correctly configure your Client and use the correct Authorization header, we recommend that you firstly follow [our documentation for creating a "Store" in Learning Locker](https://ht2ltd.zendesk.com/hc/en-us/articles/115000893009-Managing-your-Learning-Record-Stores#creating-a-new-store). Creating a Store will automatically create a new Client that is enabled with the correct scopes and associated to the Store you created. Therefore, once you've created a Store, go to `Settings > Clients` and find the new Client. The new Client will have a "Basic auth" token which you can use as the value for your "Authorization" header in any HTTP requests to the xAPI (be sure to prepend "Basic " to the start of the token). 14 | -------------------------------------------------------------------------------- /src/guides-retrieving.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: 3 | - "/reporting/" 4 | - "/exporting/" 5 | - "/report_api/" 6 | - "/exports_api/" 7 | --- 8 | 9 | # Retrieving Statements Guide 10 | Learning Locker provides many options for retrieving statements (listed below) to satisfy various needs, from just viewing a quick list of statements to direct database access for business intelligence (BI) tools. 11 | 12 | Option | Description 13 | --- | --- 14 | [Source page](https://ht2ltd.zendesk.com/hc/en-us/sections/115000232469-Filtering-Exploring-Statements) | The source page provides a quick way to list of statements that have been sent to an organisation in Learning Locker. It also allows you to filter this list using the Query Builder. 15 | [Visualisations](https://ht2ltd.zendesk.com/hc/en-us/sections/115000222689-Visualisations) | Visualisations provide a quick visual summary of statements via bar graphs, line graphs, scatter graphs, etc. These are great for quickly exploring potential correlations and trends. 16 | [Exports](https://ht2ltd.zendesk.com/hc/en-us/sections/115000232489-Exporting-Statements) | If you want data from your statements in a spreadsheet, you can download certain parts of your statements to a CSV file using the Export Panel. 17 | [xAPI](../http-xapi) | The xAPI HTTP interface provides an xAPI-conformant API for retrieving statements in your applications. 18 | [Aggregation API](../http-aggregation) | The Aggregation HTTP Interface is more advanced than the xAPI HTTP interface and allows you to access MongoDB's powerful Aggregation API for more custom filtration of statements. 19 | [Connection HTTP Interface](../http-connection) | The Connection HTTP Interface is a slightly more restricted version of the Aggregation API that utilises cursors to provide paginated statements for improved performance. 20 | Direct DB access | If you want to utilise BI Tools or improve performance further, you can simply access the MongoDB database which Learning Locker is using directly. 21 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.7.0) 5 | public_suffix (>= 2.0.2, < 5.0) 6 | colorator (1.1.0) 7 | concurrent-ruby (1.1.6) 8 | em-websocket (0.5.1) 9 | eventmachine (>= 0.12.9) 10 | http_parser.rb (~> 0.6.0) 11 | eventmachine (1.2.7) 12 | ffi (1.13.1) 13 | forwardable-extended (2.6.0) 14 | http_parser.rb (0.6.0) 15 | i18n (1.8.3) 16 | concurrent-ruby (~> 1.0) 17 | jekyll (4.1.1) 18 | addressable (~> 2.4) 19 | colorator (~> 1.0) 20 | em-websocket (~> 0.5) 21 | i18n (~> 1.0) 22 | jekyll-sass-converter (~> 2.0) 23 | jekyll-watch (~> 2.0) 24 | kramdown (~> 2.1) 25 | kramdown-parser-gfm (~> 1.0) 26 | liquid (~> 4.0) 27 | mercenary (~> 0.4.0) 28 | pathutil (~> 0.9) 29 | rouge (~> 3.0) 30 | safe_yaml (~> 1.0) 31 | terminal-table (~> 1.8) 32 | jekyll-redirect-from (0.16.0) 33 | jekyll (>= 3.3, < 5.0) 34 | jekyll-sass-converter (2.1.0) 35 | sassc (> 2.0.1, < 3.0) 36 | jekyll-watch (2.2.1) 37 | listen (~> 3.0) 38 | kramdown (2.3.0) 39 | rexml 40 | kramdown-parser-gfm (1.1.0) 41 | kramdown (~> 2.0) 42 | liquid (4.0.3) 43 | listen (3.2.1) 44 | rb-fsevent (~> 0.10, >= 0.10.3) 45 | rb-inotify (~> 0.9, >= 0.9.10) 46 | mercenary (0.4.0) 47 | pathutil (0.16.2) 48 | forwardable-extended (~> 2.6) 49 | public_suffix (4.0.5) 50 | rb-fsevent (0.10.4) 51 | rb-inotify (0.10.1) 52 | ffi (~> 1.0) 53 | rexml (3.2.4) 54 | rouge (3.20.0) 55 | safe_yaml (1.0.5) 56 | sassc (2.4.0) 57 | ffi (~> 1.9) 58 | terminal-table (1.8.0) 59 | unicode-display_width (~> 1.1, >= 1.1.1) 60 | unicode-display_width (1.7.0) 61 | 62 | PLATFORMS 63 | ruby 64 | 65 | DEPENDENCIES 66 | jekyll (~> 4.1) 67 | jekyll-redirect-from (~> 0.16.0) 68 | jekyll-watch (~> 2.2) 69 | 70 | BUNDLED WITH 71 | 2.1.4 72 | -------------------------------------------------------------------------------- /src/http-downloads.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Downloads HTTP Interface 5 | 6 | Holds records of exports. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/download. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/download. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this download 18 | name | Name of the download. 19 | organisation | The id of the [organisation](../http-organisations#schema) this download happend from. 20 | owner | The id of the [user](../http-users#schema) who downloaded the export. 21 | isReady | Is the download ready. 22 | time | Date and time of the download. 23 | url | The url of this download. 24 | upload | An object holding details of the file to be downloaded. [See upload](#upload). 25 | isPublic | If false then this download is only available to the owner and users with [org/all/download/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. 26 | 27 | ### Upload 28 | 29 | Holds details of the file to be uploaded. 30 | 31 | Name | Description 32 | --- | --- 33 | createdAt | When the content was created. 34 | updatedAt | When the content was last updated. 35 | key | The location of the content to be downloaded. 36 | repo | The repo to where the content is stored, ie local. 37 | mime | mime type. 38 | 39 | ### Example Model 40 | 41 | ```json 42 | { 43 | "_id" : "59c2371c16bc715f83c3450d", 44 | "url" : "/api/downloadexport/5915be242c3a240fb40801e4.csv", 45 | "time" : "2017-05-12T13:52:36.995Z", 46 | "organisation" : "59c2371c16bc715f83c3450e", 47 | "name" : "Example Download", 48 | "upload" : { 49 | "createdAt" : "2017-05-12T13:52:37.016Z", 50 | "updatedAt" : "2017-05-12T13:52:37.070Z", 51 | "mime" : "text/csv", 52 | "key" : "downloads/1494597156987.csv", 53 | "repo" : "local" 54 | }, 55 | "isReady" : true 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /src/guides-structuring.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Structuring your LRS Guide 5 | In Learning Locker you can structure your [organisations](https://learningpool.zendesk.com/hc/en-us/articles/115000904505-Managing-your-Organisations), [stores](https://learningpool.zendesk.com/hc/en-us/articles/115000893009-Managing-your-Learning-Record-Stores), [clients](https://learningpool.zendesk.com/hc/en-us/articles/115000951445-Managing-your-Clients), and [users](https://learningpool.zendesk.com/hc/en-us/articles/115000894529-Managing-your-Users) to manage the visibility of data in your Learning Locker instance. There are few key concepts that you should know before we get started. 6 | 7 | - Organisations contain stores and clients. 8 | - Stores contain xAPI statements and xAPI documents. 9 | - Clients can be used to access data within the organisation via HTTP interfaces. 10 | - Clients can be restricted to only access data within a single store in their organisation. 11 | - Users can be used to access the Browser Interface. 12 | - Users can be added to many organisations. 13 | 14 | Imagine you have a Learning Locker instance with one organisation (Organisation A) and inside that organisation you have one store (Store A) and one client (Client A) that only has access to Store A. Inside your Learning Locker instance you also have two users (User A and User B) and they've both been added to Organisation A. 15 | 16 | Now imagine that you want to store some data that User A is allowed to see, but User B shouldn't be allowed to see. In this scenario, you should create a new organisation (Organisation B) and add only User A to it, leaving User B out so they can't see the data in that organisation. 17 | 18 | Now imagine that you want to store some xAPI statements that User A and User B are both allowed to see, but you don't want to allow applications using Client A to access the xAPI statements. In this scenario, you should create a new store (Store B) and create a new client (Client B) that can access Store B. Note that Client B will be automatically created when you create Store B, as Learning Locker automatically creates a client when you create a store. 19 | -------------------------------------------------------------------------------- /src/http-roles.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Roles HTTP Interface 5 | 6 | Roles contain a set of permissions, of which then users can be assigned. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/role. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/role. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of the role. 18 | createdAt | When this role was created. 19 | updatedAt | When this role was last updated. 20 | title | The name of this role. 21 | description | The description of this role. 22 | owner_id | The id of the [user](../http-users#schema) who created this role. 23 | organisation | The id of the [organisation](../http-organisations#schema) this role is in. 24 | scopes | A list of [organisation scopes](#organisation-scopes) of this role. 25 | 26 | ### Organisation Scopes 27 | 28 | An organisation scope is a scope that is applied to a user per organisation. Available scopes: 29 | 30 | Name | 31 | --- | 32 | all | 33 | org/public/dashboard/view | 34 | org/public/dashboard/edit | 35 | org/all/dashboard/view | 36 | org/all/dashboard/edit | 37 | org/public/visualisation/view | 38 | org/public/visualisation/edit | 39 | org/all/visualisation/view | 40 | org/all/visualisation/edit | 41 | org/public/journey/view | 42 | org/public/journey/edit | 43 | org/all/journey/view | 44 | org/all/journey/edit | 45 | org/public/statementForwarding/view | 46 | org/public/statementForwarding/edit | 47 | org/all/statementForwarding/view | 48 | org/all/statementForwarding/edit | 49 | org/all/query/view | 50 | org/all/query/edit | 51 | org/all/export/view | 52 | org/all/export/edit | 53 | org/all/download/view | 54 | org/all/download/edit | 55 | org/all/persona/manage | 56 | org/all/activity/manage | 57 | org/all/store/manage | 58 | org/all/user/manage | 59 | org/all/client/manage | 60 | org/all/role/manage | 61 | org/all/organisation/manage | 62 | 63 | ### Examples 64 | 65 | ```json 66 | { 67 | "_id" : "59c2371c16bc715f83c34501", 68 | "createdAt" : "2017-09-19T12:58:58.884Z", 69 | "updatedAt" : "2017-09-19T12:58:58.884Z", 70 | "title" : "Example Role", 71 | "owner_id" : "59c2371c16bc715f83c34502", 72 | "organisation" : "59c2371c16bc715f83c34503", 73 | "scopes" : [ 74 | "all" 75 | ] 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This repository is now archived. It previously generated the documentation for https://docs.learninglocker.net which now redirects to https://learninglocker.atlassian.net/wiki/spaces/DOCS/overview hosted by Learning Pool's account on Confluence. 2 | 3 | [![Learning Locker Logo](https://raw.githubusercontent.com/LearningLocker/docs/master/src/images/logo-small.png)](https://learninglocker.net) 4 | > Documentation for Learning Locker. 5 | 6 | [![Build Status](https://travis-ci.org/LearningLocker/docs.svg?branch=master)](https://travis-ci.org/LearningLocker/docs) 7 | [![License](https://poser.pugx.org/learninglocker/learninglocker/license.svg)](http://opensource.org/licenses/GPL-3.0) 8 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/LearningLocker/learninglocker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | *Learning Locker is copyright [Learning Pool](http://learningpool.com)* 11 | 12 | ## Users 13 | Please view our [documentation](http://docs.learninglocker.net) site. 14 | 15 | ## Developers 16 | You may contribute to this project via [issues](/issues) and [pull request](/pulls), however, please see the [guidelines](/contributing.md) before doing so. More information about how you can get involved can be found on the [Learning Locker website](http://learninglocker.net/community/get-involved/). 17 | 18 | ### Getting Started 19 | 1. Open the file you wish to edit on Github. 20 | 2. Edit the file. 21 | 3. Enter a "commit message" (found below the edited file). 22 | 4. Click "Propose file changes" (found below the edited file). 23 | 24 | Alternatively: 25 | 26 | 1. [Fork](/fork) the repository. 27 | 2. Change the markdown documentation files. 28 | 3. Commit and push your changes to Github. 29 | 4. Create a [pull request](/pulls) on Github (ensuring that you follow the [guidelines](/contributing.md)). 30 | 31 | ### Development 32 | 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) 33 | 2. Install [Bundler](https://bundler.io/) 34 | 3. Run `bundler install` 35 | 4. Run `jekyll server --watch` 36 | 5. Go to `http://127.0.0.1:4000` in your browser 37 | 38 | For each change in the src files jekyll will automatically recompile the dist files. 39 | 40 | Note that the documentation site is generated using [Jekyll](http://jekyllrb.com/). To learn more about the repository structure, please view the [Jekyll documentation](http://jekyllrb.com/docs/home/). 41 | -------------------------------------------------------------------------------- /src/http-dashboards.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Dashboards HTTP Interface 5 | 6 | Details of dashboards. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/dashboard. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/dashboard. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this dashboard. 18 | createdAt | When this dashboard was created. 19 | updatedAt | When this dashboard was last updated. 20 | title | String. Title of the dashboard. 21 | widgets | An array of the widgets on this dashboard. See [widgets](#widgets). 22 | organisation | Id of the [organisation](../http-organisations#schema) this dashboard belongs to. 23 | owner | Id of the [user](../http-users#schema) who created this dashboard 24 | visibility | NOWHERE, ANYWHERE, VALID_DOMAINS. The scope of where this dashboard is externally visible. 25 | validDomains | If visibility is VALID_DOMAINS, a string of domains which can view this dashboard. 26 | isPublic | If false then this dashboard is only available to the owner and users with [org/all/dashboard/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. This is unrelated to visibility. 27 | 28 | ### Widgets 29 | 30 | An array of widgets. The properties for each widget are: 31 | 32 | Name | Description 33 | --- | --- 34 | title | The name of the widget. 35 | visualisation | The id of the [visualisation](../http-visualisations#schema) that this widget is displaying. 36 | x | The x position of this widget. 37 | y | The y position of this widget. 38 | h | The height of this widget. 39 | w | The width of this widget. 40 | 41 | ### Example Model 42 | 43 | ```json 44 | { 45 | "_id" : "59c2371c16bc715f83c3450c", 46 | "createdAt" : "2017-04-28T09:03:57.332Z", 47 | "updatedAt" : "2017-05-18T12:55:24.430Z", 48 | "owner" : "59c2371c16bc715f83c34509", 49 | "title" : "Example Dashboard", 50 | "organisation" : "59c2371c16bc715f83c34507", 51 | "visibility" : "NOWHERE", 52 | "widgets" : [ 53 | { 54 | "title" : "Example Widget", 55 | "_id" : "59c2371c16bc715f83c3450a", 56 | "h" : 4, 57 | "w" : 4, 58 | "y" : 0, 59 | "x" : 0, 60 | "visualisation" : "59c2371c16bc715f83c3450b" 61 | } 62 | ], 63 | "filter" : "{}", 64 | "public" : false, 65 | "isPublic" : true 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /src/http-organisations.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Organisations HTTP Interface 5 | 6 | Organisations are a logical grouping of statements. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/organisation. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/organisation. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The unique id of document. 18 | createdAt | When this document was created. 19 | updatedAt | When the document was last updated. 20 | name | The name of the organisation. 21 | parent | The _id of the parent organisation. 22 | owner | The id of the [user](../http-users#schema) which created this organisation. 23 | settings | [JSON settings for this organisation](#settings). 24 | 25 | ### Settings 26 | 27 | Name | Description 28 | ---|--- 29 | PASSWORD_CUSTOM_MESSAGE | The password message to display. 30 | PASSWORD_CUSTOM_REGEX | The regular expression which the password should match. 31 | PASSWORD_USE_CUSTOM_REGEX | Whether to use the custom regular expression. 32 | PASSWORD_REQUIRE_NUMBER | Whether the password requires numbers. 33 | PASSWORD_REQUIRE_ALPHA | Whether the password requires characters. 34 | PASSWORD_MIN_LENGTH | The minimum password length. 35 | PASSWORD_HISTORY_TOTAL | How many previous passwords the user is not allowed to use 36 | PASSWORD_HISTORY_CHECK | Whether to check password history 37 | LOCKOUT_SECONDS | How long to lock out a user after LOCK_ATTEMPTS failed login attempts. 38 | LOCKOUT_ATTEMPTS | How many login tries a user as allowed before triggering the LOCKOUT_SECONDS timeout. 39 | LOCKOUT_ENABLED | Whether the LOCKOUT functionality is enabled. 40 | 41 | ### Example Model 42 | 43 | ```json 44 | { 45 | "_id" : "59c2371c16bc715f83c34501", 46 | "createdAt" : "2017-04-27T15:45:34.298Z", 47 | "updatedAt" : "2017-04-27T15:45:38.138Z", 48 | "parent" : "59c2371c16bc715f83c34502", 49 | "owner" : "59c2371c16bc715f83c34503", 50 | "settings" : { 51 | "PASSWORD_CUSTOM_MESSAGE" : null, 52 | "PASSWORD_CUSTOM_REGEX" : null, 53 | "PASSWORD_USE_CUSTOM_REGEX" : false, 54 | "PASSWORD_REQUIRE_NUMBER" : false, 55 | "PASSWORD_REQUIRE_ALPHA" : true, 56 | "PASSWORD_MIN_LENGTH" : 8, 57 | "PASSWORD_HISTORY_TOTAL" : 3, 58 | "PASSWORD_HISTORY_CHECK" : true, 59 | "LOCKOUT_SECONDS" : 1800, 60 | "LOCKOUT_ATTEMPS" : 5, 61 | "LOCKOUT_ENABLED" : true 62 | }, 63 | "name" : "Example Organisation" 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /src/http-visualisations.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Visualisations HTTP Interface 5 | 6 | Visualisation configuration. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/visualisation. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/visualisation. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this visualisation. 18 | description | The name of this visualisation. 19 | createdAt | When this visualisation was created. 20 | updatedAt | When this visualisation was last updated. 21 | type | The type of this visualisation. 22 | axesgroup | What to group the visualisation data points as. 23 | axesxLabel | The x axes label. 24 | axesyLabel | The y axes label. 25 | axesxValue | A stringified json encode object of the x axes value options. 26 | axesyValue | A stringified json encode object of the y axes value options. 27 | axesvalue | A stringified json encode object of the value for this visualisation. 28 | axesyOperator | The y axes operator, eg uniqueCount. 29 | axesxOperator | The x axes operator, eg uniqueCount. 30 | axesOperator | The visualisation operator, eg uniqueCount. 31 | axesquery | Query for this visualisation. 32 | axesxQuery | Query for the x axes. 33 | axesyQuery | Query for the y axes. 34 | stacked | If this visualisation is stacked. 35 | chart | The type of visualisation. 36 | filter | An array of stringified [mongo filters](https://docs.mongodb.com/manual/reference/operator/aggregation/filter/). 37 | journey | The [journey's](../http-journeys#schema) id if this visualisation is a journey. 38 | organisation | The [organisation](../http-organisations#schema) this visualisation belongs to. 39 | owner | The id of the [user](../http-users#schema) who created this visualisation. 40 | previewPeriod | The time range which is shown in this visualisation. 41 | isPublic | If false then this visualisation is only available to the owner and users with [org/all/visualisation/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. 42 | 43 | ### Example Model 44 | 45 | ```json 46 | { 47 | "_id" : "59c1087dfd869741959c5701", 48 | "createdAt" : "2017-09-19T13:13:09.312Z", 49 | "updatedAt" : "2017-09-19T13:22:00.404Z", 50 | "owner" : "59c1087dfd869741959c5702", 51 | "axes" : "{}", 52 | "organisation" : "59c1087dfd869741959c5703", 53 | "isPublic" : false, 54 | "previewPeriod" : "LAST_7_DAYS", 55 | "filters" : [ 56 | "{\"color\":\"#1e8bc3\"}" 57 | ], 58 | "chart" : "LINE", 59 | "stacked" : true, 60 | "axesoperator" : "uniqueCount", 61 | "axesvalue" : "{\"optionKey\":\"statements\",\"searchString\":\"Statements\"}", 62 | "type" : "COUNTER", 63 | "description" : "Example Visualisation" 64 | } 65 | ``` 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/guides-sales-statements.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Sales Statements 5 | 6 | - [User closed sale with Client](#user-closed-sale-with-client) 7 | - [User generated lead with Client](#user-generated-lead-with-client) 8 | - [User called Client](#user-called-client) 9 | 10 | The statements shown here are not currently guided by an existing Profile or Community of Practice. They have been created to define how we should create statements for SalesForce. 11 | 12 | ## User closed sale with Client 13 | A statement that is used to indicate that the User has closed a sale with a Client. 14 | 15 | ### Statement 16 | 17 | ```json 18 | { 19 | "actor": { 20 | "objectType": "Agent", 21 | "mbox": "mailto:user@example.org", 22 | "name": "Example User" 23 | }, 24 | "verb": { 25 | "id": "http://id.tincanapi.com/verb/closed-sale", 26 | "display": { 27 | "en-US": "closed a sale with" 28 | } 29 | }, 30 | "object": { 31 | "objectType": "Group", 32 | "name": "Example Client Name", 33 | "account": { 34 | "homePage": "https://learningpool.force.com/client", 35 | "name": "Example-Client-Account-ID" 36 | } 37 | }, 38 | "context": { 39 | "platform": "SalesForce", 40 | "language": "en" 41 | } 42 | } 43 | ``` 44 | 45 | ## User generated lead with Client 46 | A statement that is used to indicate that the User has genereated a lead with a Client. 47 | 48 | ### Statement 49 | 50 | ```json 51 | { 52 | "actor": { 53 | "objectType": "Agent", 54 | "mbox": "mailto:user@example.org", 55 | "name": "Example User" 56 | }, 57 | "verb": { 58 | "id": "https://w3id.org/xapi/dod-isd/verbs/generated", 59 | "display": { 60 | "en-US": "generated a lead with" 61 | } 62 | }, 63 | "object": { 64 | "objectType": "Group", 65 | "name": "Example Client Name", 66 | "account": { 67 | "homePage": "https://learningpool.force.com/client", 68 | "name": "Example-Client-Account-ID" 69 | } 70 | }, 71 | "context": { 72 | "platform": "SalesForce", 73 | "language": "en" 74 | } 75 | } 76 | ``` 77 | 78 | 79 | ## User called Client 80 | A statement that is used to indicate that the User has called a Client. 81 | 82 | ### Statement 83 | 84 | ```json 85 | { 86 | "actor": { 87 | "objectType": "Agent", 88 | "mbox": "mailto:user@example.org", 89 | "name": "Example User" 90 | }, 91 | "verb": { 92 | "id": "http://id.tincanapi.com/verb/called", 93 | "display": { 94 | "en-US": "called" 95 | } 96 | }, 97 | "object": { 98 | "objectType": "Group", 99 | "name": "Example Client Name", 100 | "account": { 101 | "homePage": "https://learningpool.force.com/client", 102 | "name": "Example-Client-Account-ID" 103 | } 104 | }, 105 | "context": { 106 | "platform": "SalesForce", 107 | "language": "en" 108 | } 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /src/guides-monitoring.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Monitoring your LRS 5 | 6 | ## PM2 / KeyMetrics 7 | 8 | If you have installed Learning Locker using the recommended build script, then the application will come pre-installed onto an instance of the excellent [`pm2`](http://pm2.keymetrics.io/) Node Process Management tool. 9 | 10 | Whilst this tool can be used to monitor the running processes from within each server running LL (refer to [the documentation](http://pm2.keymetrics.io/docs/usage/monitoring/)), it can also be setup to send performance metrics to the [Key Metrics](http://docs.keymetrics.io/) platform (costs may apply). 11 | 12 | ![PM2 Monitoring](../images/pm2-monit.jpg "PM2 Monitoring") 13 | 14 | 15 | ## Logs 16 | 17 | ### Local 18 | 19 | By default logs are captured by pm2 and stored locally on each instance as defined in the pm2 process file ([pm2/all.json.dist](https://github.com/LearningLocker/learninglocker/blob/master/pm2/all.json.dist)). Errors and standard output are separated and can be viewed by running `pm2 logs` from within your instance. By default they are stored in `/var/log/learninglocker/` and are rotated using the [`pm2-logrotate`](https://github.com/pm2-hive/pm2-logrotate) module. 20 | 21 | ![PM2 Logs](../images/pm2-logs.jpg "PM2 Logs") 22 | 23 | ### AWS Cloudwatch 24 | Learning Locker comes with the ability to push your logs to AWS Cloudwatch. To enable this, configure the relevant part of the `.env` files: 25 | 26 | ``` 27 | ####################### 28 | # AWS Cloudwatch logs # 29 | # AWS credentials must be configured for Cloudwatch access 30 | # Ref: http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-identity-based-access-control-cwl.html 31 | ####################### 32 | # Enable cloudwatch logs (false*|true) 33 | WINSTON_CLOUDWATCH_ENABLED= 34 | WINSTON_CLOUDWATCH_LOG_GROUP_NAME= 35 | WINSTON_CLOUDWATCH_LOG_STREAM_NAME= 36 | WINSTON_CLOUDWATCH_ACCESS_KEY_ID= 37 | WINSTON_CLOUDWATCH_SECRET_ACCESS_KEY= 38 | WINSTON_CLOUDWATCH_REGION= 39 | ``` 40 | 41 | Please note that the AWS credentials must have the required permissions to create and push to log streams/groups. 42 | 43 | ![Cloudwatch Logs](../images/cloudwatch-logs.jpg "Cloudwatch Logs") 44 | 45 | ### Other logging 46 | 47 | Learning Locker handles all logging via [Winston](https://www.npmjs.com/package/winston). New "transports" can be added and configured in both the Learning Locker application and xAPI services. 48 | 49 | 50 | ## New Relic 51 | 52 | The Learning Locker application is configured to send performance metrics to New Relic. Simply fill in the following in the `.env` of your application instance 53 | 54 | ``` 55 | ############# 56 | # New Relic # 57 | ############# 58 | 59 | # New Relic License key 60 | NEW_RELIC_LICENSE_KEY= 61 | # APM name for API 62 | NEWRELIC_API_NAME= 63 | # APM name for UI 64 | NEWRELIC_UI_NAME= 65 | ``` 66 | 67 | #### xAPI 68 | In the xAPI you can also add monitoring. 69 | 70 | ``` 71 | # New Relic License key 72 | NEW_RELIC_LICENSE_KEY= 73 | # APM name 74 | NEW_RELIC_APP_NAME= 75 | ``` 76 | 77 | ![New Relic](../images/newrelic.jpg "New Relic") 78 | 79 | ## Others 80 | 81 | Other Node monitoring solutions are available but would need to be added manually to the script or web server. 82 | -------------------------------------------------------------------------------- /src/http-clients.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Clients HTTP Interface 5 | 6 | Details of a clients which will be accessing Learning Locker. It contains details for permissions, authenticating and storing the xapi request. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/client. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/client. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The unique id of the document. 18 | createdAt | When this document was created. 19 | updatedAt | When this document was last updated. 20 | title | String. The title of the client. 21 | api | The client [basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication) details. [See api](#api). 22 | authority | A JSON encoded string of an agent object that will be set on any statements that the client pushes. [See authority](#authority). 23 | scopes | An array of strings. Permission [scopes](#client-scopes) that this client has permission for. 24 | isTrusted | Whether this client is enabled. 25 | organisation | The [organisation](../http-organisations#schema) this client is attached to. 26 | lrs_id | The id of the [store](../http-stores#schema) that the system will put received xapi statements. 27 | 28 | ### api 29 | 30 | Name | Description 31 | --- | --- 32 | basic_key | The basic auth key. 33 | basic_secret | The basic auth secret. 34 | 35 | ### authority 36 | 37 | This is a JSON encoded string, of the following properties: 38 | 39 | Name | Description 40 | --- | --- 41 | objectType | 'Agent'. 42 | name | Name of the agent. 43 | mbox | Optional, of its an mbox 44 | mbox_sha1sum | Optional, mbox sha1 45 | openid | Optional, the open id. 46 | account | Optional, A JSON object. [See Account](#account) 47 | homePage | Optional, the homePage. 48 | 49 | One and only one of mbox, mbox_sha1, openid, Account should be provided 50 | 51 | ### account 52 | 53 | Name | Description 54 | --- | --- 55 | name | The unique id or name used to log into the account. 56 | homePage | The url of the home page. 57 | 58 | ### Client Scopes 59 | 60 | A scope is a specific permission. Available scopes: 61 | 62 | Scope | Description 63 | --- | --- | --- 64 | all | Permission to read and write everything. 65 | all/read | Permission to read everything. 66 | xapi/all | Permission to read and write to the xAPI. 67 | xapi/read | Read all. 68 | statements/read | Read all statements. 69 | statements/write | Write statements (must be used with a read scope). 70 | statements/read/mine | Read my statements. 71 | state | Access state. 72 | profile | Access profiles. 73 | 74 | ### Example Model 75 | 76 | ```json 77 | { 78 | "_id" : "59c2371616bc715f83c34506", 79 | "createdAt" : "2017-09-20T09:40:44.962Z", 80 | "updatedAt" : "2017-09-20T09:40:58.376Z", 81 | "organisation" : "59c2371c16bc715f83c34507", 82 | "lrs_id" : "59c2371c16bc715f83c34508", 83 | "title" : "Example Client", 84 | "scopes" : [ 85 | "xapi/all", 86 | "all" 87 | ], 88 | "isTrusted" : true, 89 | "authority" : "{\"objectType\":\"Agent\",\"name\":\"New Client\",\"mbox\":\"mailto:hello@learninglocker.net\"}", 90 | "api" : { 91 | "basic_secret" : "aaa", 92 | "basic_key" : "bbb" 93 | } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /src/guides-integrating.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Integrating your LRS Guide 5 | If you're planning to integrate a service with Learning Locker, you should firstly consider if you can use an existing integration between the service and the xAPI or Learning Locker. A [list of the existing integrations](#existing-integrations) has been provided below. 6 | 7 | If a tool does not already exist, then you should consider if creating the integration yourself is going to be too costly or challenging to implement. If this is the case, you can contact us via [hello@learninglocker.net](mailto:hello@learninglocker.net) to discuss your needs and gain advice using our experience. 8 | 9 | If you've decided you're going to implement the integration yourself, you're probably most interested in our guides for [inserting statements](../guides-inserting) and/or [retrieving statements](../guides-retrieving). You may also be interested in viewing the documentation for our HTTP interfaces especially our [xAPI HTTP interface](../http-xapi). 10 | 11 | ## Existing Integrations 12 | The below integrations can be implemented via our own internal tool. If you'd like to find out how to access these integrations, click on the links below or contact us at [hello@learninglocker.net](mailto:hello@learninglocker.net) for more information. 13 | 14 | - [Yammer](https://learningpool.com/solutions/learning-record-store-learning-locker/yammer/) 15 | - [Get Abstract](https://learningpool.com/solutions/learning-record-store-learning-locker/get-abstract/) 16 | - [Survey Monkey](https://learningpool.com/solutions/learning-record-store-learning-locker/survey-monkey/) 17 | - [Cornerstone on demand](https://learningpool.com/solutions/learning-record-store-learning-locker/cornerstone-on-demand/) 18 | - [Skillsoft](https://learningpool.com/solutions/learning-record-store-learning-locker/skillsoft/) 19 | - [Stream](https://learningpool.com/solutions/learning-experience-platform-stream/) 20 | 21 | ## Other integrations and Apps 22 | 23 | Below are a list of other integrations and some add-on Apps that might interest you. As mentioned above, click on the links or contact us at [hello@learninglocker.net](mailto:hello@learninglocker.net) for more information. 24 | 25 | - [Waves](https://learningpool.com/solutions/learning-experience-platform-stream/waves/) via our automation tool that uses statement forwards to trigger events such as emails, auto-enrolments and more. 26 | - [Semantic Analysis](https://learningpool.com/solutions/learning-record-store-learning-locker/semantic-analysis/) via our proprietary algorithm to drive insights into the quality of learners' submissions. 27 | - [Launchr](https://learningpool.com/solutions/learning-record-store-learning-locker/launchr/) via our proxy service that securely launch xAPI content packages. 28 | - [GDPR](https://learningpool.com/solutions/learning-record-store-learning-locker/gdpr/) via our standalone app that is a central administration tool for organizations seeking to comply with GDPR. 29 | - [CSV to xAPI](https://learningpool.com/solutions/learning-record-store-learning-locker/csv-to-xapi/) via our proprietary tool that converts CSV data into xAPI and inserts records into your LRS. 30 | - [Moodle](https://moodle.org/) via the [Logstore plugin](https://moodle.org/plugins/logstore_xapi) that we originally developed and released. 31 | - [Blackboard](http://www.blackboard.com/learning-management-system/blackboard-learn.aspx) via [JISC's Blackboard xAPI Plugin](https://github.com/jiscdev/blackboard-xapi-plugin/wiki). 32 | -------------------------------------------------------------------------------- /src/http-xapi.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # xAPI HTTP Interface 5 | Learning Locker's HTTP interface conforms to the xAPI specification. The xAPI defines how to record learning experiences for analysis and serves as a guide to maximise interoperability between services that provide learning experiences. 6 | 7 | The table below describes the routes that the HTTP interface provides, all of the URLs are relative to http://www.example.org/data/xAPI where http://www.example.org is the URL of your Learning Locker instance. To access this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. 8 | 9 | Route | Description 10 | --- | --- 11 | [GET /about](#get-about) | Retrieves information about the LRS. 12 | [PUT /statements](../http-xapi-statements#put-statements) | Stores a single statement. 13 | [POST /statements](../http-xapi-statements#post-statements) | Stores a single statement or multiple statements. 14 | [GET /statements](../http-xapi-statements#get-statements) | Retrieves statements. 15 | [PUT /activities/state](../http-xapi-states#put-activitiesstate) | Creates or overwrites a state document. 16 | [POST /activities/state](../http-xapi-states#post-activitiesstate) | Creates or merges a state document. 17 | [GET /activities/state](../http-xapi-states#get-activitiesstate) | Retrieves a single state document or multiple state identifiers. 18 | [DELETE /activities/state](../http-xapi-states#delete-activitiesstate) | Deletes a single state document or multiple state documents. 19 | [GET /activities](../http-xapi-activities#get-activities) | Retrieves a fully described activity. 20 | [PUT /activities/profile](../http-xapi-activities#put-activitiesprofile) | Creates or overwrites a profile document. 21 | [POST /activities/profile](../http-xapi-activities#post-activitiesprofile) | Creates or merges a profile document. 22 | [GET /activities/profile](../http-xapi-activities#get-activitiesprofile) | Retrieves a single profile document or multiple profile identifiers. 23 | [DELETE /activities/profile](../http-xapi-activities#delete-activitiesprofile) | Deletes a single profile document. 24 | [GET /agents](../http-xapi-agents#get-agents) | Retrieves all of the agents used by a person. 25 | [PUT /agents/profile](../http-xapi-agents#put-agentsprofile) | Creates or overwrites a profile document. 26 | [POST /agents/profile](../http-xapi-agents#post-agentsprofile) | Creates or merges a profile document. 27 | [GET /agents/profile](../http-xapi-agents#get-agentsprofile) | Retrieves a single profile document or multiple profile identifiers. 28 | [DELETE /agents/profile](../http-xapi-agents#delete-agentsprofile) | Deletes a single profile document. 29 | 30 | ## GET /about 31 | This route returns a JSON encoded object containing information about the LRS. A request to this route would look something like the request below. 32 | 33 | ```http 34 | GET http://www.example.org/data/xAPI/about 35 | Authorization: YOUR_BASIC_AUTH 36 | ``` 37 | 38 | A request like the one above, will respond with a 200 response with a response body like the JSON below, which contains an array of the xAPI versions supported by your Learning Locker instance. 39 | 40 | ```http 41 | HTTP/1.1 200 OK 42 | Content-Type: application/json; charset=utf-8 43 | X-Experience-API-Version: 1.0.3 44 | 45 | { "version": ["1.0.1"] } 46 | ``` 47 | 48 | For more information, view the [GET /about route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#28-about-resource). 49 | -------------------------------------------------------------------------------- /src/http-persona-identifiers.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Persona Identifiers HTTP Interface 5 | 6 | Represents a unique identifier for a person. Statements that use these identifiers are linked to the [persona](../http-personas) that the identifier belongs to. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/personaidentifier. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/personaidentifier. 12 | - [Upsert HTTP Interface](#upsert-http-interface) via http://www.example.org/api/v2/personaidentifier/upsert. 13 | 14 | ### Schema 15 | 16 | Name | Description 17 | --- | --- 18 | _id | The id of this identifier. 19 | organisation | The id of the [organisation](../http-organisations) this identifier belongs to. 20 | persona | The id of the [persona](../http-personas) this identifier belongs to. 21 | ifi | A representation of the [inverse functional identifier](#inverse-functional-identifier). 22 | 23 | ### Example 24 | 25 | ```json 26 | { 27 | "_id" : "59c1219936229d4ce9634601", 28 | "organisation" : "59c1219936229d4ce9634602", 29 | "persona": "59c1219936229d4ce9634603", 30 | "ifi": { 31 | "key": "account", 32 | "value": { 33 | "homePage": "http://www.example.org", 34 | "name": "example-user" 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | # Inverse Functional Identifier 41 | According to the xAPI specification, an Inverse Functional Identifier (IFI) is "a value of an Agent or Identified Group that is guaranteed to only ever refer to that Agent or Identified Group". 42 | 43 | ### Schema 44 | 45 | Name | Description 46 | --- | --- 47 | key | The type of IFI ([account](#account-ifi), [mbox](#mbox-ifi), [mbox_sha1sum](#mbox-sha1sum-ifi), or [openid](#openid-ifi)). 48 | value | The value of the IFI. 49 | 50 | ### Account IFI 51 | 52 | ```json 53 | { 54 | "key": "account", 55 | "value": { 56 | "homePage": "http://www.example.org", 57 | "name": "example-user" 58 | } 59 | } 60 | ``` 61 | 62 | ### Mbox IFI 63 | 64 | ```json 65 | { 66 | "key": "mbox", 67 | "value": "mailto:user@example.org" 68 | } 69 | ``` 70 | 71 | ### Mbox Sha1Sum IFI 72 | 73 | ```json 74 | { 75 | "key": "mbox_sha1sum", 76 | "value": "cc1e39b02974c5d21e792d7febcaa6018bb6c574" 77 | } 78 | ``` 79 | 80 | ### OpenID IFI 81 | 82 | ```json 83 | { 84 | "key": "openid", 85 | "value": "http://www.example.org/example-user" 86 | } 87 | ``` 88 | 89 | # Upsert HTTP Interface 90 | This interface creates or updates a persona identifier depending on whether the IFI already exists. 91 | 92 | - If the `persona` property is not set, a persona will be created if the identifier's IFI doesn't exist. 93 | - If the `persona` property is set, the persona must already exist. Otherwise, a 404 response code will be returned. 94 | 95 | A request to the upsert route would look something like this: 96 | 97 | ```http 98 | POST http://www.example.org/api/v2/personaidentifier/upsert 99 | Authorization: YOUR_BASIC_AUTH 100 | Content-Type: application/json; charset=utf-8 101 | 102 | { 103 | "ifi": { 104 | "key": "account", 105 | "value": { 106 | "homePage": "http://www.example.org", 107 | "name": "example-user" 108 | } 109 | } 110 | } 111 | ``` 112 | 113 | The interface will respond with a 200 response code, with detail the created/updated identifier. 114 | 115 | ```http 116 | HTTP/1.1 200 OK 117 | Content-Type: application/json; charset=utf-8 118 | 119 | { 120 | "_id" : "59c1219936229d4ce9634601", 121 | "organisation" : "59c1219936229d4ce9634602", 122 | "persona": "59c1219936229d4ce9634603", 123 | "ifi": { 124 | "key": "account", 125 | "value": { 126 | "homePage": "http://www.example.org", 127 | "name": "example-user" 128 | } 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /src/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 | 78 |
79 | {{ content }} 80 |
81 |
82 |
83 | 84 | {% include footer.html %} 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/http-users.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Users HTTP Interface 5 | 6 | A user of learning locker. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/user. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/user. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The id of this user. 18 | name | Name of the user. 19 | email | The email of the user. 20 | organisations | A list of [organisation](../http-organisations#schema) ids that this user belongs to. 21 | organisationSettings | A list of [organisation settings](#organisation-settings) for this user 22 | imageUrl | A url of an image for this user. 23 | googleId | The user's google id. 24 | ownerOrganisation | The id of the users owning [organisation](../http-organisations#schema) 25 | ownerOrganisationSettings | Duplication of [organisation settings](../http-organisations#schema). 26 | settings | An object of specific users settings. [see settings](#settings) 27 | scopes | Global [scopes](../http-roles#scopes) for this user. 28 | verified | Whether this user has been verified. 29 | resetTokens | A list of reset tokens. [See reset token](#reset-token). 30 | passwordHistory | A list of users previously used password hashes. 31 | authLastAttempt | When the user last attempted to authenticate. 32 | authFailedAttempts | Number of failed authentication attempts. 33 | authLockoutExpiry | When the user can attempt to authenticate again. 34 | 35 | ### Organisation Settings 36 | 37 | A list of organisation settings: 38 | 39 | Name | Description 40 | --- | --- 41 | organisation | The id of the [organisation](organisations#schema) of these settings. 42 | scopes | List of [scopes](../http-roles#scopes) that this user has permissions for in this [organisation](organisations#schema). 43 | roles | List of [role](../http-roles#schema) ids that this user has permissions for in the [organisation](organisations#schema). 44 | filter | A [statement](../http-statements#schema) filter which restricts which statements this user has access too. 45 | 46 | ### Settings 47 | 48 | Name | Description 49 | --- | --- 50 | CONFIRM_BEFORE_DELETE | If true, will prompt user before delete actions. 51 | 52 | ### Reset Token 53 | 54 | A token which is used to reset a user's password. 55 | 56 | Name | Description 57 | --- | --- 58 | token | The token. 59 | expires | When this token expires. 60 | 61 | ### Example Model 62 | 63 | ```json 64 | { 65 | "_id" : "59c2371c16bc715f83c34501", 66 | "createdAt" : "2017-09-19T12:07:25.536Z", 67 | "updatedAt" : "2017-09-19T15:06:54.027Z", 68 | "email" : "example@example.org", 69 | "password" : "aaa", 70 | "authLockoutExpiry" : null, 71 | "authFailedAttempts" : 0, 72 | "authLastAttempt" : "2017-09-19T15:06:54.020Z", 73 | "passwordHistory" : [ 74 | { 75 | "date" : "2017-09-19T12:07:25.505Z", 76 | "hash" : "aaa", 77 | "_id" : "59c2371c16bc715f83c34502" 78 | }, 79 | { 80 | "date" : "2017-09-19T12:47:34.093Z", 81 | "hash" : "bbb", 82 | "_id" : "59c2371c16bc715f83c34503" 83 | } 84 | ], 85 | "resetTokens" : [ ], 86 | "verified" : true, 87 | "scopes" : [ 88 | "site_admin" 89 | ], 90 | "settings" : { 91 | "CONFIRM_BEFORE_DELETE" : true 92 | }, 93 | "ownerOrganisationSettings" : { 94 | "PASSWORD_CUSTOM_MESSAGE" : null, 95 | "PASSWORD_CUSTOM_REGEX" : null, 96 | "PASSWORD_USE_CUSTOM_REGEX" : false, 97 | "PASSWORD_REQUIRE_NUMBER" : true, 98 | "PASSWORD_REQUIRE_ALPHA" : true, 99 | "PASSWORD_MIN_LENGTH" : 8, 100 | "PASSWORD_HISTORY_TOTAL" : 3, 101 | "PASSWORD_HISTORY_CHECK" : true, 102 | "LOCKOUT_SECONDS" : 1800, 103 | "LOCKOUT_ATTEMPS" : 5, 104 | "LOCKOUT_ENABLED" : true 105 | }, 106 | "organisationSettings" : [ 107 | { 108 | "organisation" : "59c2371c16bc715f83c34504", 109 | "_id" : "59c2371c16bc715f83c34505", 110 | "filter" : "{}", 111 | "roles" : [ ], 112 | "scopes" : [ 113 | "all" 114 | ] 115 | } 116 | ], 117 | "organisations" : [ 118 | "59c2371c16bc715f83c34504", 119 | "59c2371c16bc715f83c34506", 120 | "59c2371c16bc715f83c34507" 121 | ], 122 | "ownerOrganisation" : "59c2371c16bc715f83c34504" 123 | } 124 | ``` 125 | -------------------------------------------------------------------------------- /src/http-statements.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Statements HTTP Interface 5 | 6 | Statements hold all of the xAPI records that have been inserted into the database via the [xAPI Statements HTTP interface](../http-xapi-statements). 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/statement. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/statement. 12 | 13 | Whilst statements _can_ be inserted via the Models HTTP interface, this is not recommended as it bypasses all validation, reference and void checking, leading to invalid xAPI records. 14 | 15 | Statements can be deleted by the REST API. See [Statement Deletion API](../http-statement-deletion) for more information. 16 | 17 | Optional metadata can be added to a statement via the [Metadata API](../http-metadata). 18 | We automatically add metadata in cases such as for [question statements](../guides-assessment-statements) (typically coming from Adapt), which makes reporting on assessments much easier than previously possible. 19 | 20 | ### Schema 21 | 22 | Name | Type | Description 23 | --- | --- | --- 24 | _id | Mongo ID | The id of the role _(autogenerated)_ 25 | stored | Mongo Date | When this statement was created _(autogenerated)_ 26 | timestamp | Mongo Date | When this statement's activity occured _(autogenerated)_ 27 | organisation | Mongo ID | The organisation this statement belongs in _(autogenerated)_ 28 | lrs_id | Mongo ID | The store this statement belongs in _(autogenerated)_ 29 | client | Mongo ID | The client that inserted this statement _(autogenerated)_ 30 | person | [Person](#person) | Information about the person associated to the actor in this statement 31 | voided | Boolean | Has the statement been voided 32 | hash | String | A unique hash of the statement used for conflict checks on insert _(autogenerated)_ 33 | statement | [Statement](#statement) | The statement object as constructed and validated by the xAPI service 34 | metadata | [Metadata](../http-metadata#schema) | The metadata related to the statement 35 | refs | Array [[Statement References](#statement-references)] 36 | 37 | ### Person 38 | 39 | Name | Type | Description 40 | --- | --- | --- 41 | _id | Mongo ID | The id of the person _(autogenerated)_ 42 | name | String | The name of the person - this is auto populated for quick reference to avoid lookups _(autogenerated)_ 43 | 44 | ### Statement 45 | 46 | Please refer to the [Statement Properties](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#24-statement-properties) in the xAPI specification for a full list of possible entries 47 | 48 | ### Statement References 49 | 50 | This contains an array of any statements that are referenced as part of this statement structure. 51 | 52 | ### Example GET data 53 | ```json 54 | { 55 | "_id" : "111aaa1111a111111aa11111", 56 | "hasGeneratedId" : false, 57 | "organisation" : "111aaa1111a111111aa11111", 58 | "lrs_id" : "111aaa1111a111111aa11111", 59 | "client" : "111aaa1111a111111aa11111", 60 | "person" : { 61 | _id: "111aaa1111a111111aa11111", 62 | name: "Joe Bloggs" 63 | }, 64 | "voided" : false, 65 | "timestamp" : "2017-09-08T09:41:58.136Z", 66 | "stored" : "2017-09-08T09:41:58.136Z", 67 | "hash" : "05db245f890c4e5f9513658435ddd5e16c579df1", 68 | "refs" : [ ], 69 | "statement" : { 70 | "actor" : { 71 | "objectType" : "Agent", 72 | "name" : "xAPI mbox", 73 | "mbox" : "mailto:xapi@adlnet.gov" 74 | }, 75 | "verb" : { 76 | "id" : "http://adlnet.gov/expapi/verbs/attended", 77 | "display" : { 78 | "en-GB" : "attended", 79 | "en-US" : "attended" 80 | } 81 | }, 82 | "object" : { 83 | "objectType" : "Activity", 84 | "id" : "http://www.example.com/meetings/occurances/34534" 85 | }, 86 | "id" : "3c5f9182-81ba-4e4c-9aa4-93a6b57a61ee", 87 | "timestamp" : "2017-09-08T09:41:58.136Z", 88 | "stored" : "2017-09-08T09:41:58.136Z", 89 | "authority" : { 90 | "mbox" : "mailto:hello@learninglocker.net", 91 | "name" : "New Client", 92 | "objectType" : "Agent" 93 | }, 94 | "version" : "1.0.0" 95 | } 96 | } 97 | ``` 98 | -------------------------------------------------------------------------------- /src/http-statement-forwarding.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Statement Forwarding HTTP Interface 5 | 6 | Details of a statement forwarding configuration. 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/statementforwarding. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/statementforwarding. 12 | 13 | ### Schema 14 | 15 | Name | Description 16 | --- | --- 17 | _id | The unique id of the document. 18 | createdAt | When the document was created. 19 | updatedAt | When the document was updated. 20 | description | The name of this statement forwarder. 21 | lrs_id | The id of the [store](../http-stores#schema) that Learning Locker will forward statements for. 22 | active | If this statement forwarder is currently active. 23 | configuration | The configuration for this statement forwarder [See configuration](#configuration). 24 | owner | Id of the [user](../http-users#schema) who created this statement forwarder. 25 | query | A JSON Mongo query string - only statements which match this query will be forwarded.

e.g. `'{"statement.verb.id":"http://adlnet.gov/expapi/verbs/completed"}'` 26 | isPublic | If false then this statement forwarder is only available to the owner and users with [org/all/statementForwarding/view scope](../http-roles/#organisation-scopes), otherwise it's available to everyone in the organisation with permission. 27 | 28 | ### Configuration 29 | 30 | The configuration for the statement forwarding request. 31 | 32 | Name | Description 33 | --- | --- 34 | protocol | http, https. The protocol to forward statements to. 35 | url | The url to forward statement to. 36 | authType | no auth, token, basic auth. The auth method to use. 37 | secret | If authType is token, this is the token which will be sent with the request. 38 | basicUsername | If authType is basic auth, this is the basic auth username. 39 | basicPassword | If authType is basic auth, this is the basic auth password. 40 | maxRetries | The number of times the statement forwarder will retry before giving up. 41 | headers | A json array encoded as a string which contains additional headers to send with the request. 42 | 43 | ### Example Model 44 | 45 | ```json 46 | { 47 | "_id" : "59c8d14b0d82b3864a450604", 48 | "createdAt" : "2017-09-25T09:50:03.880Z", 49 | "updatedAt" : "2017-11-06T14:07:27.212Z", 50 | "owner" : "59198183d8ea540933227030", 51 | "query" : "{}", 52 | "organisation" : "59c209c4ad95fd50960c0362", 53 | "isPublic" : false, 54 | "configuration" : { 55 | "authType" : "no auth", 56 | "protocol" : "https", 57 | "url" : "example.org/endpoint", 58 | "maxRetries" : 10, 59 | "headers" : "{\"Test-Header-Key\":\"Test-Header-Value\"}", 60 | "secret" : "Dave" 61 | }, 62 | "__v" : 0, 63 | "active" : true, 64 | "description" : "Statement forwarder" 65 | } 66 | ``` 67 | 68 | ### *[Enterprise]*: Forwarding to AWS Kinesis 69 | _Note: Only available in Enterprise editions of Learning Locker_ 70 | 71 | To create a statement forward configured for AWS Kinesis Firehose, configure the record with this modified data structure: 72 | 73 | ```js 74 | "configuration" : { 75 | "protocol" : "Kinesis", 76 | "authType" : "no auth" 77 | }, 78 | "kinesisOptions" : { 79 | "streamName" : "KinesisFirehoseName", // The immutable name of the Kinesis Firehose configured in AWS 80 | "awsClientKey" : "xxxxxxxxxxxx", // AWS client access key with appropriate permission 81 | "awsClientSecret" : "xxxxxxxxxxxx", // AWS client secret key 82 | "awsRegion" : "us-east-1" // AWS Kinesis Firehose region 83 | }, 84 | ``` 85 | 86 | #### Permissions 87 | In order for Learning Locker to successfully write to the Kinesis Firehose, please ensure that the IAM user (attributed to the key/secret) has the minimum permissions in its policy (as shown below). When using the example below, please replace `region`, `account-id`, and `KinesisFirehoseName` with your details in the `Resource` array. 88 | 89 | ```json 90 | { 91 | "Version": "2012-10-17", 92 | "Statement": [ 93 | { 94 | "Effect": "Allow", 95 | "Action": [ 96 | "firehose:PutRecord", 97 | ], 98 | "Resource": [ 99 | "arn:aws:firehose:region:account-id:deliverystream/KinesisFirehoseName" 100 | ] 101 | } 102 | ] 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /src/css/docs.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Thanks to http://getbootstrap.com/examples/blog/ for initial inspiration. 3 | */ 4 | 5 | /* 6 | * Globals 7 | */ 8 | 9 | h1, .h1, 10 | h2, .h2, 11 | h3, .h3, 12 | h4, .h4, 13 | h5, .h5, 14 | h6, .h6 { 15 | margin-top: 0; 16 | font-family: 'Open Sans', Arial, sans-serif; 17 | font-weight: normal; 18 | text-transform: uppercase; 19 | text-decoration: none; 20 | color: #696969; 21 | line-height: 1.2em; 22 | } 23 | 24 | h1 .highlighter-rouge, .h1 .highlighter-rouge, 25 | h2 .highlighter-rouge, .h2 .highlighter-rouge, 26 | h3 .highlighter-rouge, .h3 .highlighter-rouge, 27 | h4 .highlighter-rouge, .h4 .highlighter-rouge, 28 | h5 .highlighter-rouge, .h5 .highlighter-rouge, 29 | h6 .highlighter-rouge, .h6 .highlighter-rouge { 30 | text-transform: none; 31 | } 32 | 33 | body { 34 | font-family: 'Open Sans', Arial, sans-serif; 35 | font-size: 14px; 36 | font-weight: 500; 37 | color: #666; 38 | line-height: 1.7em; 39 | -webkit-font-smoothing: antialiased; 40 | -moz-osx-font-smoothing: grayscale; 41 | } 42 | 43 | a { color: #ed7030; } 44 | a:focus, a:hover { color: #ed7030; } 45 | 46 | h1 { 47 | font-size: 30px; 48 | } 49 | 50 | h2 { 51 | font-size: 26px; 52 | margin-top: 30px; 53 | } 54 | 55 | h3 { 56 | font-size: 22px; 57 | margin-top: 30px; 58 | } 59 | 60 | h4 { 61 | font-size: 16px; 62 | margin-top: 20px; 63 | color: #95989a; 64 | } 65 | 66 | blockquote { 67 | background-color: #f1f1f1; /* #f4f8fa; */ 68 | margin: 20px 0; 69 | padding: 20px; 70 | border-left: 3px solid #f2a942; 71 | } 72 | blockquote p { 73 | font-size:13px; 74 | } 75 | blockquote strong { 76 | color: #f2a942; 77 | font-weight:bold; 78 | font-size:14px; 79 | } 80 | 81 | .docs-sidebar .nav>li>a { 82 | padding: 2px 15px; 83 | } 84 | 85 | .banner{ 86 | background:#e4e7ec; /* url(../img/banner.jpg) no-repeat; */ 87 | margin-bottom:20px; 88 | } 89 | 90 | .logo { 91 | margin-top:4px; 92 | } 93 | 94 | .active { 95 | background:#e4e7ec; 96 | border-radius:6px; 97 | } 98 | 99 | /* 100 | * Masthead for nav 101 | */ 102 | 103 | .docs-masthead { 104 | padding: 5px 0 5px 0; 105 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 106 | -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 107 | -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 108 | } 109 | 110 | .docs-masthead img { 111 | height: 48px; 112 | width: 200px; 113 | } 114 | 115 | /* Nav links */ 116 | .docs-nav-item { 117 | position: relative; 118 | display: inline-block; 119 | padding: 18px; 120 | font-weight: 600; 121 | color: #696969; 122 | } 123 | .docs-nav-item:hover, 124 | .docs-nav-item:focus { 125 | opacity: 0.7; 126 | color: #696969; 127 | text-decoration: none; 128 | transition: all 0.4s ease-in-out; 129 | } 130 | 131 | /* 132 | * docs name and description 133 | */ 134 | 135 | .docs-header { 136 | padding-top: 15px; 137 | padding-bottom: 15px; 138 | } 139 | .docs-title { 140 | margin-top: 30px; 141 | margin-bottom: 10px; 142 | font-size: 40px; 143 | font-weight: normal; 144 | /* color:#fff; */ 145 | } 146 | .docs-description { 147 | font-size: 20px; 148 | color: #999; 149 | } 150 | 151 | .docs-main { 152 | margin-bottom:60px; 153 | margin-top: 20px; 154 | } 155 | 156 | .docs-main img { 157 | width: 100%; 158 | } 159 | 160 | .docs-sidebar { 161 | margin-top: 10px; 162 | margin-bottom:40px; 163 | } 164 | 165 | .docs-sidebar .sidebar-title { 166 | font-weight:bold; 167 | margin:10px 0 5px 0; 168 | } 169 | 170 | .docs-sidebar .sidebar-title .top { 171 | margin:0 0 10px 0; 172 | } 173 | 174 | .docs-sidebar .nav>li>a:hover, 175 | .docs-sidebar .nav>li>a :focus { 176 | background: none; 177 | text-decoration: underline; 178 | } 179 | 180 | /* 181 | * Footer 182 | */ 183 | 184 | .docs-footer { 185 | padding: 40px 0; 186 | color: #999; 187 | text-align: center; 188 | background-color: #f8f9fa; 189 | } 190 | 191 | /* Laravel paste inspired code display 192 | *****************************************************************/ 193 | 194 | pre{ 195 | background:transparent; 196 | padding:0; 197 | font-size:12px; 198 | border:0; 199 | line-height:1.9em; 200 | border-radius:6px; 201 | } 202 | 203 | pre code { 204 | padding:20px; 205 | } 206 | 207 | code { 208 | white-space: pre-wrap !important; 209 | color: #cc7228; 210 | } 211 | 212 | @media (max-width: 770px) { 213 | 214 | .banner { 215 | background:#f1f1f1; /*transparent;*/ 216 | } 217 | 218 | .docs-title { 219 | color:#222; 220 | font-size:30px; 221 | margin-bottom:20px; 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /src/http-aggregate.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Aggregate HTTP Interface 5 | 6 | The Learning Locker Aggregate HTTP interface utilises the [Mongo aggregation API](https://docs.mongodb.com/manual/aggregation/) and is only available for allowed collections. 7 | The Aggregate HTTP Interface is more advanced than the xAPI HTTP interface and allows you to access MongoDB's powerful Aggregation API for more custom filtration of documents. 8 | 9 | ## Allowed Collections 10 | 11 | This interface can be used to aggregate on multiple **allowed only** collections. To check whether a collection is allowed for aggregation or not, we use a regular expression. You can specify your own rule using `AGGREGATE_API_ALLOWED_COLLECTIONS` environment variable with `^rollup` default value that means that by default you can aggregate on collections that prefixed with "rollup" word. 12 | 13 | ## Request Setup 14 | 15 | When using this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. 16 | Your Basic Auth details can be found under **Settings** > **Clients**. The interface can be passed options via a JSON object in the request body (described in the table below). The `collection` property is required. The `collection` is a string that determines the collection name used to execute the aggregation on. The other properties are optional. 17 | 18 | Name | Description | Default Value 19 | --- | --- | --- 20 | pipeline | Array containing a pipeline of stages for documents to pass through before being output from the interface. | `[]`

(in this case, you'll get records from the desired collection without any transformations) 21 | skip | Number that specifies a number of records that should be skipped from the beginning. This parameter will be transformed into `$skip` aggregation stage and executed at the very end. | `0` 22 | limit | Number that specifies a number of records that will be cut from the final results. This parameter will be transformed into `$limit` aggregation stage and executed at the very end. | `10000` 23 | maxTimeMS | Number that specifies the time limit for the query in Mongo. | `0`

(if nothing else was set to `MAX_TIME_MS` environment variable) 24 | batchSize | Specifies the number of documents to return in each batch of the response from the MongoDB instance. Defaults to `100` | `100` 25 | 26 | A simple request to this interface using all of the available parameters looks like the request below. 27 | 28 | ```http 29 | POST http://www.example.org/api/aggregate 30 | Authorization: Basic YOUR_BASIC_AUTH 31 | Content-Type: application/json; charset=utf-8 32 | 33 | { 34 | "collection": "rollupUniqueVerbsByPlatformDaily", 35 | "pipeline": [], 36 | "skip": 0, 37 | "limit": 1, 38 | "maxTimeMs": 100, 39 | "batchSize": 100 40 | } 41 | 42 | ``` 43 | A response to this valid request will return a 200 response like the response below, where the JSON encoded body contains several keys. 44 | 45 | ```http 46 | { 47 | "result": [ 48 | { 49 | "_id" : "111aaa1111a111111aa11111", 50 | "organisationId" : "111aaa1111a111111aa11111", 51 | "organisationName" : [ 52 | "My amazing org" 53 | ], 54 | "lrsId" : "111aaa1111a111111aa11111", 55 | "lrsName" : [ 56 | "My awesome store" 57 | ], 58 | "clientId" : "111aaa1111a111111aa11111", 59 | "clientName" : [ 60 | "My incredible client" 61 | ], 62 | "platform" : "Some Cool Platform", 63 | "verb" : "http://adlnet.gov/expapi/verbs/scored", 64 | "count" : 200, 65 | "date" : "2017-08-01T00:00:00Z", 66 | "storedDate" : "2017-08-01T00:00:00Z" 67 | } 68 | ], 69 | "startedAt": "2020-04-03 14:17:30.924+03:00", 70 | "completedAt": "2020-04-03 14:17:30.943+03:00" 71 | } 72 | ``` 73 | 74 | ## Pipeline Stages 75 | So far we've only seen the `limit` and `project` stages, however, there are many other [stages available in Mongo](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/). The common stages are listed in the table below. 76 | 77 | Name | Description 78 | --- | --- 79 | [project](#project-stage) | Reshapes the records from the previous stage of the pipeline for the next stage. 80 | [match](#match-stage) | Filters the records from the previous stage of the pipeline for the next stage. 81 | [limit](#limit-stage) | Limits the number of records from the previous stage of the pipeline for the next stage. 82 | [skip](#skip-stage) | Skips a number of records from the previous stage of the pipeline for the next stage. 83 | [group](#group-stage) | Groups records by a specified identifier from the previous stage of the pipeline for the next stage. 84 | [sort](#sort-stage) | Sorts the records from the previous stage of the pipeline for the next stage. 85 | -------------------------------------------------------------------------------- /src/guides-indexing.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Indexing 5 | If some of your queries are taking a while to run, you can take advantage of database indexes to improve performance. You can create indexes in your database via [the Mongo shell](https://docs.mongodb.com/manual/mongo/). You can run the code below in your Mongo shell to create our recommended indexes. More information is available about [using indexes via Mongo's documentation](https://docs.mongodb.com/manual/indexes/). If utilising indexes doesn't have the required performance improvement, you can instead utilise [BI tools](../guides-retrieving). 6 | 7 | ```js 8 | // Statements 9 | db.statements.createIndex({ "statement.id": 1, lrs_id: 1 }, { background: true }); 10 | db.statements.createIndex({ stored: -1 }, { background: true }); 11 | 12 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "statement.object.id": 1, "statement.object.objectType": 1 }, { background: true }); 13 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "statement.verb.id": 1, "statement.object.objectType": 1 }, { background: true }); 14 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "statement.verb.id": 1, "statement.object.id": 1 }, { background: true }); 15 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "statement.actor.mbox": 1 }, { background: true }); 16 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "statement.actor.account.name": 1, "statement.actor.account.homePage": 1 }, { background: true }); 17 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "timestamp": -1, _id: -1 }, { background: true }); 18 | db.statements.createIndex({ organisation: 1, lrs_id: 1, "voided": 1, "stored": -1, _id: -1 }, { background: true }); 19 | 20 | db.statements.createIndex({ organisation: 1, lrs_id: 1, hash: 1 }, {unique: true, background: true}); 21 | 22 | db.statements.createIndex({ organisation: 1, lrs_id: 1, activities: 1, timestamp: -1}, {background: true}); 23 | db.statements.createIndex({ organisation: 1, lrs_id: 1, agents: 1, timestamp: -1}, {background: true}); 24 | db.statements.createIndex({ organisation: 1, lrs_id: 1, registrations: 1, timestamp: -1}, {background: true}); 25 | db.statements.createIndex({ organisation: 1, lrs_id: 1, relatedActivities: 1, timestamp: -1}, {background: true}); 26 | db.statements.createIndex({ organisation: 1, lrs_id: 1, relatedAgents: 1, timestamp: -1}, {background: true}); 27 | db.statements.createIndex({ organisation: 1, lrs_id: 1, verbs: 1, timestamp: -1}, {background: true}); 28 | 29 | db.statements.createIndex({ organisation: 1, lrs_id: 1, activities: 1, stored: -1}, {background: true}); 30 | db.statements.createIndex({ organisation: 1, lrs_id: 1, agents: 1, stored: -1}, {background: true}); 31 | db.statements.createIndex({ organisation: 1, lrs_id: 1, registrations: 1, stored: -1}, {background: true}); 32 | db.statements.createIndex({ organisation: 1, lrs_id: 1, relatedActivities: 1, stored: -1}, {background: true}); 33 | db.statements.createIndex({ organisation: 1, lrs_id: 1, relatedAgents: 1, stored: -1}, {background: true}); 34 | db.statements.createIndex({ organisation: 1, lrs_id: 1, verbs: 1, stored: -1}, {background: true}); 35 | 36 | db.statements.createIndex({ organisation: 1, "statement.object.id": 1 }, { background: true }); 37 | db.statements.createIndex({ organisation: 1, "statement.verb.id": 1, "statement.object.id": 1 }, { background: true }); 38 | db.statements.createIndex({ organisation: 1, "timestamp": -1, _id: 1 }, { background: true }); 39 | db.statements.createIndex({ organisation: 1, "stored": -1, _id: 1 }, { background: true }); 40 | db.statements.createIndex({ organisation: 1, "voided": 1 }, { background: true }); 41 | 42 | db.statements.createIndex({ organisation: 1, personaIdentifier: 1 }, { background: true }); 43 | db.statements.createIndex({ organisation: 1, "person._id": 1, timestamp: -1 }, { background: true }); 44 | db.statements.createIndex({ "person._id": 1}, { background: true}); 45 | 46 | // Personas 47 | db.personas.createIndex({organisation: 1}, {background: true}); 48 | db.personaIdentifiers.createIndex({organisation: 1, persona: 1}, {background: true}); 49 | db.personaIdentifiers.createIndex({ organisation: 1, "ifi.key": 1, "ifi.value.homePage": 1, "ifi.value.name": 1}, {background: true}) 50 | db.personaIdentifiers.createIndex({ organisation: 1, "ifi.key": 1, "ifi.value": 1}, {background: true, unique: true}); 51 | db.personaAttributes.createIndex({organisation: 1, personaId: 1, key: 1}, {background: true}); 52 | db.personaAttributes.createIndex({personaId: 1, key: 1}, {background: true}); 53 | 54 | // State API 55 | db.states.createIndex({ "organisation" : 1, "lrs" : 1, "activityId" : 1, "agent.account.homePage" : 1, "agent.account.name" : 1, "stateId" : 1, "registration" : 1}, {background: true}); 56 | db.states.createIndex({ "organisation" : 1, "lrs" : 1, "activityId" : 1, "agent.mbox" : 1, "stateId" : 1}, {background: true}); 57 | db.states.createIndex({ "organisation" : 1, "lrs" : 1, "activityId" : 1, "stateId" : 1}, {background: true}); 58 | 59 | // Others 60 | db.client.createIndex({ "api.basic_key": 1, "api.basic_secret": 1}, {unique: true, background: true}); 61 | db.fullActivities.createIndex({organisation:1, lrs_id: 1, activityId:1}, {unique: true, background:true}); 62 | 63 | ``` 64 | -------------------------------------------------------------------------------- /src/guides-video-statements.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Video Statements 5 | 6 | - [User initialized video](#user-initialized-video) 7 | - [User played video](#user-played-video) 8 | - [User paused video](#user-paused-video) 9 | - [User terminated video](#user-paused-video) 10 | 11 | The statements shown here are guided by the [xAPI Video Profile](https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html) created by the [xAPI Video Community of Practice](https://liveaspankaj.gitbooks.io/xapi-video-profile/content/). All times and lengths represented as numbers below should use seconds as the unit. 12 | 13 | ## User initialized video 14 | An "Initialized" statement is used by the LRP to indicate that the video has been fully initialized or launched/accessed. 15 | 16 | ### Statement 17 | ```json 18 | { 19 | "actor": { 20 | "objectType": "Agent", 21 | "mbox": "mailto:user@example.org", 22 | "name": "Example User" 23 | }, 24 | "verb": { 25 | "id": "http://adlnet.gov/expapi/verbs/initialized", 26 | "display": { 27 | "en-US": "initialized" 28 | } 29 | }, 30 | "object": { 31 | "objectType": "Activity", 32 | "id": "http://www.example.org/video/1", 33 | "definition": { 34 | "type": "https://w3id.org/xapi/video/activity-type/video", 35 | "name": { 36 | "en-US": "Example Video" 37 | }, 38 | "extensions": { 39 | "https://w3id.org/xapi/video/extensions/length": 124.3 40 | } 41 | } 42 | }, 43 | "context": { 44 | "platform": "Brightcove", 45 | "language": "en", 46 | "extensions": { 47 | "https://w3id.org/xapi/video/extensions/session-id": "9a2ce995-c3f5-458a-9fd3-cab07ea005c0" 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | ## User played video 54 | Indicates that the actor started experiencing the recorded media object. 55 | 56 | ### Statement 57 | ```json 58 | { 59 | "actor": { 60 | "objectType": "Agent", 61 | "mbox": "mailto:user@example.org", 62 | "name": "Example User" 63 | }, 64 | "verb": { 65 | "id": "https://w3id.org/xapi/video/verbs/played", 66 | "display": { 67 | "en-US": "played" 68 | } 69 | }, 70 | "object": { 71 | "objectType": "Activity", 72 | "id": "http://www.example.org/video/1", 73 | "definition": { 74 | "type": "https://w3id.org/xapi/video/activity-type/video", 75 | "name": { 76 | "en-US": "Example Video" 77 | } 78 | } 79 | }, 80 | "context": { 81 | "platform": "Brightcove", 82 | "language": "en", 83 | "extensions": { 84 | "https://w3id.org/xapi/video/extensions/session-id": "9a2ce995-c3f5-458a-9fd3-cab07ea005c0" 85 | } 86 | }, 87 | "result": { 88 | "extensions": { 89 | "https://w3id.org/xapi/video/extensions/time": 0 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | ## User paused video 96 | Indicates that the actor temporary or permanently stopped experiencing the recorded media object. 97 | 98 | ### Statement 99 | ```json 100 | { 101 | "actor": { 102 | "objectType": "Agent", 103 | "mbox": "mailto:user@example.org", 104 | "name": "Example User" 105 | }, 106 | "verb": { 107 | "id": "https://w3id.org/xapi/video/verbs/paused", 108 | "display": { 109 | "en-US": "paused" 110 | } 111 | }, 112 | "object": { 113 | "objectType": "Activity", 114 | "id": "http://www.example.org/video/1", 115 | "definition": { 116 | "type": "https://w3id.org/xapi/video/activity-type/video", 117 | "name": { 118 | "en-US": "Example Video" 119 | } 120 | } 121 | }, 122 | "context": { 123 | "platform": "Brightcove", 124 | "language": "en", 125 | "extensions": { 126 | "https://w3id.org/xapi/video/extensions/session-id": "9a2ce995-c3f5-458a-9fd3-cab07ea005c0" 127 | } 128 | }, 129 | "result": { 130 | "extensions": { 131 | "https://w3id.org/xapi/video/extensions/time": 57.1 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | ## User terminated video 138 | Used to express that the actor ended a video. 139 | 140 | ### Metadata 141 | ```json 142 | { 143 | "https://learninglocker&46;net/duration": { 144 | "seconds": 124.3 145 | } 146 | } 147 | ``` 148 | 149 | ### Statement 150 | ```json 151 | { 152 | "actor": { 153 | "objectType": "Agent", 154 | "mbox": "mailto:user@example.org", 155 | "name": "Example User" 156 | }, 157 | "verb": { 158 | "id": "http://adlnet.gov/expapi/verbs/terminated", 159 | "display": { 160 | "en-US": "terminated" 161 | } 162 | }, 163 | "object": { 164 | "objectType": "Activity", 165 | "id": "http://www.example.org/video/1", 166 | "definition": { 167 | "type": "https://w3id.org/xapi/video/activity-type/video", 168 | "name": { 169 | "en-US": "Example Video" 170 | }, 171 | "extensions": { 172 | "https://w3id.org/xapi/video/extensions/length": 124.3 173 | } 174 | } 175 | }, 176 | "context": { 177 | "platform": "Brightcove", 178 | "language": "en", 179 | "extensions": { 180 | "https://w3id.org/xapi/video/extensions/session-id": "9a2ce995-c3f5-458a-9fd3-cab07ea005c0" 181 | } 182 | }, 183 | "result": { 184 | "completion": true, 185 | "duration": "PT124.3S", 186 | "extensions": { 187 | "https://w3id.org/xapi/video/extensions/time": 124.3, 188 | "https://w3id.org/xapi/video/extensions/progress": 1 189 | } 190 | } 191 | } 192 | ``` 193 | -------------------------------------------------------------------------------- /src/http-journey-progress.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Journey Progress HTTP Interface 5 | 6 | > `Please note:` Journeys are only available in our Enterprise hosted version of Learning Locker. For more information about the differences, please see [this page](https://learninglocker.net/faqs/open-source-vs-saas-lrs/). 7 | 8 | An actor/person's progress through a journey. 9 | 10 | It is accessible through the following HTTP interfaces: 11 | 12 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/journeyProgress. 13 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/journeyProgress. 14 | 15 | ### Schema 16 | 17 | Name | Type | Description 18 | --- | --- | --- 19 | _id | Mongo ID | The id of this journey progress _(autogenerated)_ 20 | organisation| [Organisation](../http-organisations#schema) Mongo ID | The id of the organisation 21 | createdAt | Timestamp | When this journey progress was created _(autogenerated)_ 22 | updatedAt | Timestamp | When this journey progress was last updated _(autogenerated)_ 23 | owner | [owner](#owner) | The ident associated to this progress 24 | pendingProgress | [pendingProgress](#pending-progress) | An object of pendingProgress _(autogenerated)_ 25 | completions | [completions](#completions) | An object of completed statements. 26 | startedAt | Timestamp | When the first attempt of a waypoint was registered 27 | isCompleted | Boolean | Has this owner completed this journey? 28 | completedAt | Timestamp | When was the journey first completed 29 | duration | microseconds | Time between `startedAt` and `completedAt` in [microseconds](https://en.wikipedia.org/wiki/Microsecond) 30 | 31 | 32 | ### Owner 33 | 34 | This is a reference to the xAPI actor or Learning Locker Persona associated to this progress. 35 | 36 | Name | Type | Description 37 | --- | --- | --- 38 | trackBy | string | Only `"actor"` currently supported, `"persona"` tracking coming soon 39 | ident | mixed | Either a full xAPI actor object or a [Persona Mongo ID](../http-personas) 40 | 41 | ### Pending Progress 42 | 43 | This is a reference to statements which need processing. 44 | 45 | It is an object whose key is the waypointID: 46 | 47 | Name | Type | Decsription 48 | --- | --- | --- 49 | statement | Array of [Journey Progress Statement](#journey-progress-statement) | The reference to the statement. 50 | 51 | ### Completions 52 | 53 | A journey can be completed multiple times if the journey is set to be repeatable. An array of completed journey attempts is stored in this value. 54 | 55 | Name | Type | Description 56 | --- | --- | --- 57 | waypoints | object | An object of [completed waypoints](#completed-waypoint) whose key are the waypoint ids. 58 | completedAt | Timestamp | When this journey attempt was completed. 59 | isCompleted | boolean | If this journey attempt has been completed. 60 | 61 | 62 | ### Completed Waypoint 63 | 64 | An array of completed waypoints for a journey attempt 65 | 66 | Name | Type | Description 67 | --- | --- | --- 68 | waypoint | Mongo ID | The id of the waypoint 69 | order | Integer | The order of the waypoint 70 | statements | Array of [journey progress statements](#journey-progress-statements) | A reference to the statements 71 | 72 | ### Journey Progress Statements 73 | 74 | Reference and timestamp of a statement. 75 | 76 | Name | Type | Description 77 | --- | --- | --- 78 | statement | Mongo ID | The id of a statement. 79 | timestamp | Timestamp | When this statement was added. 80 | 81 | ## Example GET data 82 | 83 | ``` 84 | { 85 | "_id":"59c23b28f6463f3569446bdc", 86 | "journey":"59c23527bdf9ac67b2ab5ee6", 87 | "organisation":"59198183d8ea540933227033", 88 | "updatedAt":"2017-09-20T09:56:04.309Z", 89 | "createdAt":"2017-09-20T09:55:52.198Z", 90 | "pendingProgress":{ 91 | "59c237bebdf9ac67b2ab5ee9":{ 92 | "statements":[ 93 | ] 94 | }, 95 | "59c237bebdf9ac67b2ab5eea":{ 96 | "statements":[ 97 | 98 | ] 99 | } 100 | }, 101 | "lockKey":"571bf02f-d41b-4b31-972d-f092845af816", 102 | "duration":12133, 103 | "startedAt":"2017-09-20T09:55:50.310Z", 104 | "completions":[ 105 | { 106 | "waypoints":{ 107 | "59c237bebdf9ac67b2ab5ee9":{ 108 | "completedAt":"2017-09-20T09:56:02.443Z", 109 | "isCompleted":true, 110 | "statements":[ 111 | { 112 | "timestamp":"2017-09-20T09:56:02.443Z", 113 | "statement":"59c23b32d138e44d720041a9" 114 | } 115 | ], 116 | "order":1, 117 | "waypoint":"59c237bebdf9ac67b2ab5ee9" 118 | }, 119 | "59c237bebdf9ac67b2ab5eea":{ 120 | "completedAt":"2017-09-20T09:55:53.799Z", 121 | "isCompleted":true, 122 | "statements":[ 123 | { 124 | "statement":"59c23b26d138e44a720041ad", 125 | "timestamp":"2017-09-20T09:55:50.310Z" 126 | }, 127 | { 128 | "timestamp":"2017-09-20T09:55:51.192Z", 129 | "statement":"59c23b27d138e44a720041ae" 130 | }, 131 | { 132 | "statement":"59c23b28d138e44a720041af", 133 | "timestamp":"2017-09-20T09:55:52.313Z" 134 | }, 135 | { 136 | "timestamp":"2017-09-20T09:55:52.880Z", 137 | "statement":"59c23b28d138e44a720041b0" 138 | }, 139 | { 140 | "statement":"59c23b29d138e44a720041b1", 141 | "timestamp":"2017-09-20T09:55:53.799Z" 142 | } 143 | ], 144 | "order":0, 145 | "waypoint":"59c237bebdf9ac67b2ab5eea" 146 | } 147 | }, 148 | "completedAt":"2017-09-20T09:56:02.443Z", 149 | "isCompleted":true 150 | } 151 | ] 152 | } 153 | ``` 154 | -------------------------------------------------------------------------------- /src/http-metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Statement Metadata API HTTP Interface 5 | The [routes table](#routes) below describes the methods that the Statement Metadata HTTP interface provides. Both routes require that you specify an `_id` from the statement record, in order to determine to which statement the metadata should be added. This can be found be found by performing a [GET request on the statement model](#get-statement-_id). 6 | 7 | ``` 8 | POST http://www.example.org/api/v2/statementmetadata/:id 9 | ``` 10 | 11 | To access this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. 12 | 13 | The JSON body of these methods is a key-value pair such as the following. Some examples of where we currently add metadata can be found on [question statements](../guides-assessment-statements) coming from Adapt. 14 | ``` 15 | { 16 | "https://learninglocker.net/true-false-response": "Yes" 17 | } 18 | ``` 19 | 20 | ## Schema 21 | 22 | Name | Description 23 | --- | --- | --- 24 | _id | The unique id of the [Statement](./http-statements#schema/) 25 | metadata | An object containing key-value pairs, where the key is an IRI representing the metadata field with requirements defined by the [xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#31-iri-requirements) and the value can be of any type. 26 | 27 | ## Routes 28 | 29 | Method | Description 30 | --- | --- 31 | [PATCH /:id](#patch-id) | Patches a model. 32 | [POST /:id](#post-id) | Creates or overwrites a model. 33 | 34 | ### PATCH /:id 35 | This route patches the metadata field on a statement that has the specified statement _id from the URL. A request to this route would look something like the request below. 36 | 37 | ```http 38 | PATCH http://www.example.org/api/v2/statementmetadata/111aaa1111a111111aa11112 39 | Authorization: Basic YOUR_BASIC_AUTH 40 | Content-Type: application/json; charset=utf-8 41 | 42 | { 43 | "example_key": "example_value" 44 | } 45 | ``` 46 | 47 | A request like the one above, will respond with a 200 response like the one below containing the model as JSON in the body. 48 | 49 | ```http 50 | HTTP/1.1 200 OK 51 | Content-Type: application/json; charset=utf-8 52 | 53 | { 54 | "_id": "111aaa1111a111111aa11111", 55 | "metadata": { 56 | "example_key": "example_value" 57 | } 58 | } 59 | ``` 60 | 61 | ### POST /:id 62 | This route creates or updates the metadata field on a statement that has the specified statement identifier from the URL. 63 | > `Please note:` It is preferable to use [PATCH](#patch-id) where possible to prevent overwriting metadata set by Learning Locker that is required for certain parts of the UI to work. 64 | 65 | A request to this route would look something like the request below. 66 | 67 | ```http 68 | POST http://www.example.org/api/v2/statementmetadata/111aaa1111a111111aa11112 69 | Authorization: Basic YOUR_BASIC_AUTH 70 | Content-Type: application/json; charset=utf-8 71 | 72 | { 73 | "example_key": "example_value" 74 | } 75 | ``` 76 | 77 | A request like the one above, will respond with a 200 response like the one below containing the model as JSON in the body. 78 | 79 | ```http 80 | HTTP/1.1 200 OK 81 | Content-Type: application/json; charset=utf-8 82 | 83 | { 84 | "_id": "111aaa1111a111111aa11111", 85 | "metadata": { 86 | "example_key": "example_value" 87 | } 88 | } 89 | ``` 90 | 91 | ## GET statement _id 92 | 93 | To get the statement _id for the route above, we can perform a GET request on statements via the [REST API](../http-rest/#get-). 94 | 95 | If you haven't already inserted your statements, they can be added via the [xAPI Statements HTTP Interface](../http-xapi-statements/), for example: 96 | 97 | ```http 98 | POST http://www.example.org/data/xAPI/statements 99 | Authorization: YOUR_BASIC_AUTH 100 | X-Experience-API-Version: 1.0.3 101 | Content-Type: application/json; charset=utf-8 102 | 103 | { 104 | "actor": { "mbox": "mailto:test1@example.org" }, 105 | "verb": { "id": "http://www.example.org/verb" }, 106 | "object": { "id": "http://www.example.org/activity" } 107 | } 108 | ``` 109 | 110 | This route returns a 200 response with an array of statement identifiers when the statements are successfully created. A response from this route would look something like the response below. 111 | 112 | ```http 113 | HTTP/1.1 200 OK 114 | Content-Type: application/json; charset=utf-8 115 | X-Experience-API-Version: 1.0.3 116 | X-Experience-API-Consistent-Through: 2017-08-31T15:16:29.709Z 117 | 118 | ["dfb7218c-0fc9-4dfc-9524-d497097de027"] 119 | ``` 120 | 121 | Then, we can use this statement identifier from the response to query the statement model ([detailed documentation can be found on the HTTP REST page](../http-rest/#get-)), in order to retrieve the `_id` which is used in the statement metadata routes. We can use [URL query parameters](https://florianholzapfel.github.io/express-restify-mongoose/#querying) to get the statement with the specified statement identifier, then return the statement identifier with the `_id`, which is returned by default. 122 | 123 | ```http 124 | GET http://www.example.org/api/v2/statement?query={"statement.id":"dfb7218c-0fc9-4dfc-9524-d497097de027"}&select={"statement.id":1} 125 | Authorization: Basic YOUR_BASIC_AUTH 126 | ``` 127 | 128 | A request like the one above, will respond with a 200 response like the one below containing the statement model as JSON in the body with only the fields defined in the SELECT parameter. 129 | 130 | ```http 131 | HTTP/1.1 200 OK 132 | Content-Type: application/json; charset=utf-8 133 | 134 | [ 135 | { 136 | "_id": "5f32b029f31b0ad6c241f7bb", 137 | "statement": { 138 | "id": "dfb7218c-0fc9-4dfc-9524-d497097de027", 139 | }, 140 | } 141 | ] 142 | ``` 143 | 144 | This `_id` is then used to add statement metadata via the [PATCH](#patch) and [POST](#post) routes. -------------------------------------------------------------------------------- /src/guides-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Command Line Interface 5 | 6 | Learning Locker comes with a range of CLI commands that can be used to execute common administrative tasks. 7 | 8 | > `Note:` In order to use the CLI commands, you must ensure that the CLI server is built. The default install script will have performed this action as part of the `yarn build-all` command, but it can manually be built using `yarn build-cli-server` 9 | 10 | > `Note:` These commands should be run from the Learning Locker working directory. By default, with the install script, this is `/usr/local/learninglocker/current/webapp` 11 | 12 | ## `createSiteAdmin` 13 | 14 | Create a site admin user 15 | 16 | ### Command: 17 | ```sh 18 | node cli/dist/server createSiteAdmin [email] [organisation] [password] 19 | ``` 20 | 21 | Create a new user and organisation for the site. This user will have the Super User privilege (only assignable via this command) and will also automatically be made an admin of the new organisation. 22 | 23 | Additional organisation admins can be made within the platform but other super users need to be made via subsequent calls to this command. 24 | 25 | 26 | ### Arguments: 27 | #### `email` 28 | The email of the user - this is what they will use to login 29 | 30 | #### `organisation` 31 | The name of the new organisation. If the organisation already exists, the new user will be added to it. 32 | 33 | #### `password` 34 | The user's password 35 | 36 | ### Example 37 | ``` 38 | node cli/dist/server createSiteAdmin "user@example.com" "Example" "password123" 39 | ``` 40 | ___ 41 | 42 | ## `migrateMongo` 43 | 44 | Runs any outstanding migrations on the Mongo database 45 | 46 | ### Command: 47 | ```sh 48 | node cli/dist/server migrateMongo 49 | ``` 50 | 51 | Will check for any migrations that have not been run and apply them to the database. You can run migrations "up" or "down". The former will apply migrations, whilst the latter will roll back migrations. 52 | 53 | If you do not pass any arguments, defaults to `-u`; all pending migrations will be performed. 54 | 55 | ### Arguments: 56 | #### `-u, --up [target]` 57 | Runs all pending migrations. 58 | 59 | The optional [target] parameter can be passed to specify a certain migration to run. Pass `next` as [target] to run only the next pending migration, or the name of a certain pending migration to run only that one. 60 | 61 | #### `-d, --down [target]` 62 | Optional. Runs applied migrations down until the migration name specified as [target]. Also accepts `last` as [target] to only down the last applied migration. 63 | 64 | #### `-i, --info [level]` 65 | Display the state of migrations (which have been run, which haven't). Can optionally specify `v` or `verbose` as [level] to output more detailed information. 66 | 67 | ### Example 68 | Apply all outstanding migrations: 69 | 70 | ``` 71 | node cli/dist/server migrateMongo --up 72 | ``` 73 | 74 | Roll back the last applied migration: 75 | 76 | ``` 77 | node cli/dist/server migrateMongo --down last 78 | ``` 79 | ___ 80 | 81 | ## `clearAggregationCache` 82 | Clear the cache of any aggregation data 83 | 84 | ### Command 85 | ```sh 86 | node cli/dist/server clearAggregationCache 87 | ``` 88 | 89 | Will clear down any cached aggregation results. This can be useful if you require an up-to-date result for a particular visualisation or query. 90 | 91 | ### Arguments 92 | 93 | #### `-o --org ` (optional) 94 | An organisation's Mongo ObjectId. 95 | 96 | Filter to only clear down the cache for a particular organisation. 97 | 98 | ### Example 99 | 100 | Clear all caches: 101 | ``` 102 | node cli/dist/server clearAggregationCache 103 | ``` 104 | 105 | Clear a particular organisation's cache: 106 | ``` 107 | node cli/dist/server clearAggregationCache 572cac001bb110583ed76177 108 | ``` 109 | 110 | ___ 111 | 112 | ## `batchJobs` 113 | Batch run the worker across existing statements 114 | 115 | ### Command 116 | ```sh 117 | node cli/dist/server batchJobs 118 | ``` 119 | 120 | Will force statements back through the respective worker queue if they have not already been handled. This is useful if you have migrated statements into the LRS (e.g. if migrating from v1), or if your workers were not enabled at the time your statements were inserted into the LRS. 121 | 122 | Note this can be an intensive task and may be best done on a separate box. 123 | 124 | Currently you can batch process the Query Builder Cache generation (used to populate the items for the query builder) and also the persona generation. 125 | 126 | When a worker job is completed, the appropriate worker queue name will be populated into the `completedQueues` array on each statement document in the database. If you wish to reprocess a set of statements, then clearing the `completedQueues` will allow you to reprocess them. 127 | 128 | 129 | ### Arguments 130 | 131 | #### `-j --job [job]` (optional) 132 | Which worker job to run. `querybuildercache`* and `personas`. 133 | 134 | _* Default job_ 135 | 136 | #### `-o --org [organisation_id]` (optional) 137 | An organisation's Mongo ObjectId. 138 | 139 | Filter to operate on only statements in this organisation. 140 | 141 | #### `-l, --lrs [lrs]` (optional) 142 | An LRS's (store) Mongo ObjectId. 143 | 144 | Filter to operate on only statements in this store. 145 | 146 | #### `-b, --batchSize [batchSize]` (optional) 147 | How many statements to include in each batch. For query builder cache generation, good performance is seen with 1000 per batch. For persona processing we've seen good results with this set to 100. 148 | 149 | 150 | #### `-f, --from [date]` (optional) 151 | ISO8601 formatted date to query the stored date from. 152 | 153 | 154 | #### `-t, --to [date]` (optional) 155 | ISO8601 formatted date to query the stored date to. 156 | 157 | 158 | ### Example 159 | 160 | Process persona data on all statements (100 per batch) personas in a particular store: 161 | ``` 162 | node cli/dist/server batchJobs -j personas -b 100 -lrs 572cac001bb110583ed76177 163 | ``` 164 | 165 | ## `updateStatementCount` 166 | 167 | Recalculates the statement counts on stores 168 | 169 | ### Command: 170 | ```sh 171 | node cli/dist/server updateStatementCount 172 | ``` 173 | 174 | For each store, will count how many statements exist in the database and update the statementCount property on the store document. This may be required if statements have manually been deleted permanently from the database. 175 | 176 | _Note: as of v2.6.3 this process is done in synchronously per store to avoid large concurrency query issues on Learning Locker's with many stores._ 177 | -------------------------------------------------------------------------------- /src/overview-architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Architecture Overview 5 | Learning Locker is divided into two Github repositories, one for [the Learning Locker application](#learning-locker-application) and one for [the xAPI service](#xapi-service). 6 | 7 | In Learning Locker the Browser Interface, HTTP Interface, and xAPI Service use their own HTTP ports, hence we'd recommend that you use a server such as [NGINX](https://www.nginx.com/) to route to the correct port upon receiving HTTP requests. If you utilise the [installation command provided in the installation documentation](../guides-installing), it will attempt to install and setup NGINX for you. The installation command will also use [PM2](https://github.com/Unitech/pm2) to manage your processes and restart them if they exit to ensure uptime. 8 | 9 | ## Learning Locker Application 10 | The Learning Locker application repository is made up of three parts (in the same Github repository), [the browser interface (UI)](#browser-interface-ui), [the HTTP interface (API)](#http-interface-api), and [the workers](#workers). The three parts are ran as their own process to share resources (since JavaScript is single-threaded) and ensure a degree of redundancy. 11 | 12 | ### Browser Interface (UI) 13 | The browser interface is written in JavaScript (ES6 using Webpack and Babel), utilising React to construct views and Redux to manage state. The browser interface utilises the HTTP interface to retrieve and change models in Learning Locker. Note that all models automatically save within 3 seconds after they're changed. For more information and help with the browser interface, you can go to the [Help Centre for Learning Locker](https://ht2ltd.zendesk.com/hc/en-us/categories/115000129989-Learning-Locker). 14 | 15 | ### HTTP Interface (API) 16 | The HTTP interface is also written in JavaScript (ES6 using Webpack and Babel) and uses Express, Restify, Mongo, and Mongoose. Express is used to provide HTTP routes and Restify is used on top of Express to provide [RESTful routes for each of the models in Learning Locker](../http-rest). Mongoose is used on top of Mongo to manage models in the Mongo database used by Learning Locker. 17 | 18 | ### Workers 19 | The workers are also written in JavaScript (ES6 using Webpack and Babel), they utilise Redis and optionally SQS via queue drivers. The workers make use of queues to process long running jobs. Multiple instances of the workers can be used in a cluster to process the queues in parallel across many machines and processors. 20 | 21 | Currently there are workers for managing the [query builder cache](https://ht2ltd.zendesk.com/hc/en-us/articles/115000925249-Query-Builder-Overview), [extracting personas from statements](../http-personas), [exporting statements via CSV](https://ht2ltd.zendesk.com/hc/en-us/articles/115000931369-Exporting-statements-to-CSV), [importing personas via CSV](https://ht2ltd.zendesk.com/hc/en-us/articles/115001223771-Adding-Additional-Data-to-People-via-CSV), and (in [Enterprise](https://www.ht2labs.com/learning-locker)) [recalculating journeys](https://ht2ltd.zendesk.com/hc/en-us/articles/115000857025-Journeys-Overview). 22 | 23 | ## xAPI Service 24 | The xAPI service is made up of four parts: [statements](#statements), [activity profiles](#activity-profiles), [agent profiles](#agent-profiles), and [state](#state). The xAPI service provides access to the [xAPI HTTP Interface](../xapi-http) for interacting with the xAPI as defined in the specification. The service is written in entirely TypeScript and primarily uses Express, Mongo, and Redis. 25 | 26 | If you require more information there are some useful links provided below. 27 | - [xAPI Specification of the HTTP Resources](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#20-resources). 28 | - [Documentation of the xAPI HTTP interface](../http-xapi). 29 | - [Github repository for the xAPI Service](http://github.com/LearningLocker/xapi-service). 30 | 31 | ### Statements 32 | This part provides a [xAPI compliant Statements HTTP interface](../http-xapi-statements) written in TypeScript to use Express, Mongo, and Redis. It also uses local file storage or AWS S3 storage to store attachments. 33 | 34 | The [Activity HTTP Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#25-activities-resource) is also provided as part of this because Learning Locker has to merge activity definitions from inserted statements to provide this resource. You can find out more about this resource in the [Activities HTTP interface documentation](../http-xapi-activities#get-activities). 35 | 36 | If you require more information there are some useful links provided below. 37 | - [xAPI Specification of the Statements Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#stmtres). 38 | - [Documentation of the xAPI Statements HTTP interface](../http-xapi-statements). 39 | 40 | ### Activity Profiles 41 | This part provides a [xAPI compliant Activity Profiles HTTP interface](../http-xapi-activities) written in TypeScript to use Express and Mongo. It also uses local file storage or AWS S3 storage to store documents. 42 | 43 | If you require more information there are some useful links provided below. 44 | - [xAPI Specification of the Activity Profiles Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#actprofres). 45 | - [Documentation of the xAPI Activity Profiles HTTP interface](../http-xapi-activities). 46 | 47 | ### Agent Profiles 48 | This part provides a [xAPI compliant Agent Profiles HTTP interface](../http-xapi-agents) written in TypeScript to use Express and Mongo. It also uses local file storage or AWS S3 storage to store documents. 49 | 50 | The [Agent HTTP Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#24-agents-resource) is also provided as part of this service to retrieve all of the agents that are used by a single person. A person can be created by inserting statements via the [xAPI Statements HTTP interface](../http-xapi-statements) or using the [Persona HTTP interface](../http-personas). Multiple agents can be associated with a person using the [Persona HTTP interface](../http-personas) too. 51 | 52 | If you require more information there are some useful links provided below. 53 | - [xAPI Specification of the Agent Profiles Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#26-agent-profile-resource). 54 | - [Documentation of the xAPI Agent Profiles HTTP interface](../http-xapi-agents). 55 | 56 | ### State 57 | This part provides a [xAPI compliant State HTTP interface](../http-xapi-states) written in TypeScript to use Express and Mongo. It also uses local file storage or AWS S3 storage to store documents. 58 | 59 | If you require more information there are some useful links provided below. 60 | - [xAPI Specification of the State Resource](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#23-state-resource). 61 | - [Documentation of the xAPI State HTTP interface](../http-xapi-states). 62 | -------------------------------------------------------------------------------- /src/http-statement-deletion.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Statement Deletion HTTP Interfaces 5 | 6 | ### _Added in [v3.11.0](https://github.com/LearningLocker/learninglocker/releases)_ 7 | 8 | By default, Learning Locker gives you the ability to delete statements via the API (this can be disabled via the `ENABLE_STATEMENT_DELETION` flag). 9 | 10 | Statements may be deleted individually, using the record's `_id`, or in bulk via a batch delete method. 11 | 12 | ## Authorization 13 | 14 | When using this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. 15 | 16 | The deletion APIs require that the "Delete statements" (`'statements/delete'`) scope is enabled on the client. If a client also has a store (`lrs_id`) attached, this will be used to further filter down deletions only to this store. 17 | 18 | 19 | ## Single Statement Deletion 20 | 21 | This leverages the existing REST API for statements. 22 | 23 | ``` 24 | DELETE http://www.example.org/api/v2/statement/111aaa1111a111111aa11112 25 | Authorization: Basic YOUR_BASIC_AUTH 26 | ``` 27 | 28 | A request like the one above, will respond with a 204 response like the one below. 29 | 30 | ``` 31 | HTTP/1.1 204 NO CONTENT 32 | ``` 33 | 34 | The statement is deleted immediately and the 204 represents a receipt of the deletion event succeeding in the database. 35 | 36 | ## Batch Statement Deletion 37 | 38 | These endpoints allow you to send in deletion jobs to be processed, as well as stop a batch deletion job. 39 | 40 | As the filter passed in may apply to a large amount of data, the batch delete job is split out into batches, each deleting up to 1000 records at a time. Each successive batch will trigger another deletion job to the worker, until no more statements exist in the database matching that filter. 41 | 42 | ### Initialising a batch deletion 43 | 44 | Sending a POST with a JSON body holding the required deletion filter to the following endpoint will create a job to remove all data matching that filter from the respective organisation (or store) that the client is attached to. 45 | 46 | _e.g. Deletes all completions in the client's organisation or store_ 47 | 48 | ``` 49 | POST http://www.example.org/api/v2/batchdelete/initialise 50 | Authorization: Basic YOUR_BASIC_AUTH 51 | Content-Type: application/json; charset=utf-8 52 | 53 | { 54 | "filter": { 55 | "statement.verb.id": "http://adlnet.gov/expapi/verbs/completed" 56 | } 57 | } 58 | ``` 59 | 60 | If the client used to make the request also has a store (`lrs_id`) attached, this will be used to further filter down deletions only to this store. 61 | 62 | An intialise request will return a 200 HTTP response with a JSON version of the batch deletion job (see [Schema](#schema)) 63 | 64 | ### Terminating batch deletions 65 | 66 | You may choose to terminate one or all batch deletions for an organisation using the following commands. 67 | 68 | _Note that if a deletion is currently in progress and a worker job to delete a batch has been issued, up to 1000 records may be deleted before the termination command is respected._ 69 | 70 | _This is due to how we batch the deletions into blocks of 1000. Once a batch has started, it cannot be stopped without manually stopping the Worker's Node process running the deletion job. However, no subsequent batches will be processed once that batch has finished._ 71 | 72 | #### Terminating a single batch deletion 73 | 74 | Stop a specific batch deletion from executing any more batches. 75 | 76 | ``` 77 | POST http://www.example.org/api/v2/batchdelete/terminate/111aaa1111a111111aa11112 78 | Authorization: Basic YOUR_BASIC_AUTH 79 | ``` 80 | 81 | #### Terminating all batch deletions 82 | 83 | Stop all batch deletions from executing any more batches 84 | 85 | ``` 86 | POST http://www.example.org/api/v2/batchdelete/terminate/all 87 | Authorization: Basic YOUR_BASIC_AUTH 88 | ``` 89 | 90 | ### Viewing batch deletions 91 | 92 | #### Schema 93 | 94 | Name | Description 95 | --- | --- 96 | _id | The id of the batch delete job. 97 | organisation | The id of the [organisation](../http-organisations#schema) that this job belongs to. 98 | filter | A stringified JSON Mongo query - records matching this filter are deleted 99 | pageSize | Total records deleted per batch (defaults to 1000, no way to customise outside of code change and rebuild) 100 | deleteCount | How many records have been deleted so far 101 | total | Total number of statements found for deletion at initialise 102 | processing | Boolean value; is there a deletion batch currently being actioned 103 | done | Boolean value; has the job finished or been terminated 104 | createdAt | When this document was created 105 | updatedAt | When this document was last updated 106 | 107 | _The `filter` field is stored as text to account for `.` (dot) characters used in query keys - these are invalid in Mongo JSON structures. 108 | 109 | 110 | #### Example 111 | 112 | ```json 113 | { 114 | "_id": "111aaa1111a111111aa11111", 115 | "organisation": "111aaa1111a111111aa11111", 116 | "filter": "{\"statement.verb.id\": \"http://adlnet.gov/expapi/verbs/completed\"}", 117 | "pageSize": 1000, 118 | "deleteCount": 100000, 119 | "total": 100000, 120 | "processing": false, 121 | "done": true, 122 | "createdAt": "2019-01-01T00:00:00Z", 123 | "updatedAt": "2019-01-01T00:00:00Z" 124 | } 125 | ``` 126 | 127 | The Batch Delete model may be retrieved using the GET [REST](../http-rest) or [Connection APIs](../http-connection) but other HTTP methods are disabled (PUT, PATCH, DELETE) and are instead replaced by the [initialise](#initialising-a-batch-deletion) and [terminate](#terminating-batch-deletions) routes specified above. 128 | 129 | #### Examples: (using the Connection API) 130 | 131 | _Note; query parameters should be URL encoded - these examples have not been, for readability_ 132 | 133 | ##### Fetch a particular job by `_id` 134 | 135 | ``` 136 | GET http://www.example.org/api/connection/batchdelete?filter={"_id":{"$oid":"111aaa1111a111111aa11112"}} 137 | Authorization: Basic YOUR_BASIC_AUTH 138 | ``` 139 | 140 | ##### Fetch the 5 most recently completed/terminated jobs 141 | ``` 142 | GET http://www.example.org/api/connection/batchdelete?filter={"done":true}&sort={"updatedAt":-1, "_id": 1}&first=5 143 | Authorization: Basic YOUR_BASIC_AUTH 144 | ``` 145 | 146 | ##### Fetch the 5 most recently created and unfinished jobs 147 | ``` 148 | GET http://www.example.org/api/connection/batchdelete?filter={"done":false}&sort={"createdAt":-1, "_id": 1}&first=5 149 | Authorization: Basic YOUR_BASIC_AUTH 150 | ``` 151 | 152 | 153 | 154 | ### Deletion time window 155 | Because deletion can be an intensive job for the database, it is possible to configure Learning Locker to only trigger and process batch deletion during a specified window every day. This can be configured to enable deletions during periods of known low activity (e.g. night time). 156 | 157 | To configure this, update the document in the `siteSettings` collection with the required UTC hour, minute and duration: 158 | 159 | ```mongo 160 | db.siteSettings.updateOne( 161 | { 162 | "_id" : ObjectId("111111111111111111111111") 163 | }, 164 | { 165 | $set: { 166 | batchDeleteWindowStartUTCHour: 00, 167 | batchDeleteWindowUTCMinutes: 00, 168 | batchDeleteWindowDurationSeconds: 18000 169 | } 170 | } 171 | ) 172 | ``` 173 | 174 | The configuration above, for example, would allow deletions to be triggered from midnight to 5am (UTC) everyday. Note that the Scheduler process is required to be run in order outstanding jobs at the start of the window everyday. 175 | -------------------------------------------------------------------------------- /src/guides-custom-installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Custom installation 5 | 6 | ## Requirements 7 | 8 | In order to install Learning Locker you will require a machine that has these minimum requirements: 9 | 10 | * [Git](https://git-scm.com/) 11 | * [GCC toolchain](https://en.wikipedia.org/wiki/GNU_toolchain) 12 | * [Node](https://nodejs.org/en/) (v14) 13 | * [Yarn](https://yarnpkg.com/en/) 14 | * Connectivity to a [Mongo](https://www.mongodb.com/) instance (v4+ as of LL v7.0.0) 15 | * Connectivity to a [Redis](https://redis.io/) instance (v2.8.18+) 16 | * Web server _e.g. [Nginx](https://www.nginx.com/resources/wiki/) or [Apache](https://httpd.apache.org/)_ (optional) 17 | * Process management system _e.g. [PM2](http://pm2.keymetrics.io/docs/usage/quick-start/) or [Supervisor](http://supervisord.org/)_ 18 | 19 | ### GCC and Git 20 | 21 | A good minimum set of requirements for Git and the GCC toolchain can be installed with the following commands: 22 | 23 | ### Fedora based systems 24 | ``` 25 | yum update 26 | yum -y install curl git python make automake gcc gcc-c++ kernel-devel xorg-x11-server-Xvfb git-core 27 | ``` 28 | 29 | ### Ubuntu/Debian based systems 30 | ``` 31 | apt-get update && apt-get upgrade 32 | apt-get -y install curl git python build-essential xvfb apt-transport-https 33 | ``` 34 | 35 | ### Node and Yarn installation 36 | 37 | There are multiple ways to install Node and Yarn (a package management system for Node). We would recommend the excellent NVM (Node Version Manager) which can be installed by following instructions here: [https://github.com/creationix/nvm](https://github.com/creationix/nvm) 38 | 39 | We are currently targeting builds on Node 14.* 40 | 41 | Instructions to install Yarn can be found here: [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install) but if being used with `nvm` install via: 42 | ``` 43 | npm install -g yarn 44 | ``` 45 | 46 | ### Process Management 47 | 48 | [PM2](http://pm2.keymetrics.io/docs/usage/quick-start/) is an excellent tool that can be used to manage the Node processes. It also handles log management/rotation and can automatically restart failed services. Learning Locker comes pre-packaged with some [pm2 configuration scripts](https://github.com/LearningLocker/learninglocker/tree/master/pm2). 49 | 50 | To [install PM2](http://pm2.keymetrics.io/docs/usage/quick-start/#installation), run the following command: 51 | 52 | ``` 53 | npm install -g pm2 54 | ``` 55 | 56 | We also recommend installing the `pm2-logrotate` module, which can handle rotating and compressing your logs. 57 | 58 | ``` 59 | pm2 install pm2-logrotate 60 | pm2 set pm2-logrotate:compress true 61 | ``` 62 | 63 | ## User best practice 64 | 65 | It is always preferable that you do not run your Node processes as the `root` user. For this reason we would always suggest creating a new system user under which installation, builds and services can be run. 66 | 67 | ## Setup 68 | 69 | The Learning Locker application is divided into two logically separate codebases, each of which can be configured to talk to the same Mongo and Redis instances. 70 | 71 | Each codebase requires that it is downloaded, built and configured before it can be run. This guide will aim to guide you through manually installing and running a full Learning Locker stack. 72 | 73 | More distribution specific information can be found inside the [install script source code](https://github.com/LearningLocker/deploy/). 74 | 75 | ## Installing and Building the Learning Locker UI, API and Worker 76 | 77 | ### Clone and install 78 | 79 | Clone [the Learning Locker application](https://github.com/LearningLocker/learninglocker) into a new working directory on your server. 80 | 81 | ``` 82 | git clone https://github.com/LearningLocker/learninglocker.git 83 | ``` 84 | 85 | Enter the directory and install the requirements: 86 | 87 | ``` 88 | npm_config_build_from_source=true yarn install --ignore-engines 89 | ``` 90 | 91 | ### Build 92 | 93 | You are now ready to build the code. You have different option here depending on how you wish to deploy the services. The codebase has 5 distinct services that can be built: 94 | 95 | * UI Server - `yarn build-ui-server` 96 | * UI Client - `yarn build-ui-client` 97 | * API Server - `yarn build-api-server` 98 | * Worker - `yarn build-worker-server` 99 | * CLI - `yarn build-cli-server` 100 | 101 | If you wish to run the UI, API and Worker on the same machine and run CLI commands, you will probably want to build all the services in one simple command: 102 | 103 | ``` 104 | yarn build-all 105 | ``` 106 | 107 | ### Configuration 108 | 109 | Copy the `.env.example` into a new `.env` file and [edit as required](http://docs.learninglocker.net/guides-configuring/#learning-locker-application). 110 | 111 | ### Migrations 112 | 113 | The database requires some indexes adding and also when upgrading you will find migrations that take care of mutating your data where required. 114 | 115 | Once your instance is configured, run required migrations using the below command. 116 | 117 | ``` 118 | yarn migrate 119 | ``` 120 | 121 | ## Installing the xAPI service 122 | 123 | ### Clone and install 124 | 125 | Clone [the xAPI Service](https://github.com/LearningLocker/xapi-service) into a new working directory on your server. 126 | 127 | ``` 128 | git clone https://github.com/LearningLocker/xapi-service.git 129 | ``` 130 | 131 | Enter the directory and install the requirements: 132 | 133 | ``` 134 | yarn install --ignore-engines 135 | ``` 136 | 137 | ### Build 138 | 139 | ``` 140 | yarn build 141 | ``` 142 | 143 | ### Configuration 144 | 145 | Copy the `.env.example` into a new `.env` file and [edit as required](http://docs.learninglocker.net/guides-configuring/#xapi-service). 146 | 147 | ## Running the services via PM2 148 | 149 | If PM2 has been installed, you will be able to run the services using the preconfigured pm2 files in each codebase: 150 | 151 | ### Learning Locker UI, API, Worker 152 | 153 | To start all 3 services, navigate to the LL working directory and run: 154 | ``` 155 | pm2 start pm2/all.json 156 | ``` 157 | 158 | ### xAPI Service 159 | 160 | To start the xAPI service, navigate to the xAPI Service working directory and run: 161 | ``` 162 | pm2 start pm2/xapi.json 163 | ``` 164 | 165 | ### Config, status and logs 166 | 167 | Note you may wish to copy and modify these pm2 config files based on your setup. Documentation can be found [here](http://pm2.keymetrics.io/docs/usage/application-declaration/). 168 | 169 | To view the status of your services: 170 | ``` 171 | pm2 status 172 | ``` 173 | 174 | To view logs: 175 | ``` 176 | pm2 logs 177 | ``` 178 | 179 | To restart the services: 180 | ``` 181 | pm2 restart all 182 | ``` 183 | 184 | 185 | ## Running the services manually 186 | 187 | If you wish to use a different process management tool (e.g. Supervisor) or simply wish to run them manually for testing, you can start the services with these commands. 188 | 189 | #### Running the UI 190 | 191 | ``` 192 | node ui/dist/server 193 | ``` 194 | 195 | #### Running the API 196 | 197 | ``` 198 | node api/dist/server 199 | ``` 200 | 201 | 202 | #### Running the worker 203 | 204 | ``` 205 | node worker/dist/server 206 | ``` 207 | 208 | 209 | #### Running the xAPI service 210 | 211 | _In your xAPI service directory_ 212 | 213 | ``` 214 | node dist/server.js 215 | ``` 216 | 217 | ## Server configuration 218 | 219 | The application is accessed through 3 web interfaces, the UI, API and xAPI. Each of these is configured to run on independent ports but it is recommended you setup a server to sit infront of all traffic and route accordingly. An example nginx config can be seen here: [nginx.conf.example](https://github.com/LearningLocker/learninglocker/blob/master/nginx.conf.example) 220 | -------------------------------------------------------------------------------- /src/guides-installing.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: 3 | - "/install/" 4 | - "/installing/" 5 | - "/installation/" 6 | - "/upgrading/" 7 | --- 8 | 9 | # Installing 10 | 11 | ## Via the install script 12 | 13 | To install Learning Locker using our deploy script, please follow the below instructions. 14 | For more information, you can view the [deployment repository's documentation](https://github.com/LearningLocker/deploy). 15 | 16 | 17 | 18 | **You must run this script as root user. Typically this can be done by running `sudo su -`** 19 | 20 | **Install with cURL** 21 | ```sh 22 | curl -o- -L https://raw.githubusercontent.com/LearningLocker/deploy/master/deployll.sh > deployll.sh && bash deployll.sh 23 | ``` 24 | **Install with Wget**: 25 | ```sh 26 | wget -qO deployll.sh https://raw.githubusercontent.com/LearningLocker/deploy/master/deployll.sh && bash deployll.sh 27 | ``` 28 | 29 | ### Upgrading 30 | 31 | You may choose to upgrade your Learning Locker to take advantage of new features and bug fixes. To make this process easier, it is strongly recommended that any Learning Locker running for production use has the database (Mongo) running on different servers to that of the application. This means you can seamlessly update your application without having to move your data. 32 | 33 | You can run the install script below on your existing EC2 server and that will grab and rebuild the code directly on the server. **Please note: this requires an EC2 instance with at least 2GB of RAM**. You will be asked if you wish to "upgrade" any existing instance, or perform a fresh install. 34 | 35 | If you plan on keeping the Mongo database on the same server as the application, you will need to perform a [backup and restoration](https://docs.mongodb.com/manual/tutorial/backup-and-restore-tools/) of your Mongo data between upgrades. For this reason we strongly recommend placing your database separate to your application. 36 | 37 | ### Logs 38 | 39 | By default, logs are written to `/var/log/learninglocker/` 40 | 41 | #### Install log 42 | 43 | If there is a problem installing the script, you can view the full install log output here: 44 | `/var/log/learninglocker/install.log` 45 | 46 | #### Service logs 47 | 48 | Individual logs for the different services outputs (stdout) and errors (stderr) are available in this directory under the following names: 49 | 50 | **stdout** 51 | * `xapi_stdout***.log` 52 | * `api_stdout***.log` 53 | * `ui_stdout***.log` 54 | * `worker_stdout***.log` 55 | 56 | **stderr** 57 | * `xapi_stderr***.log` 58 | * `api_stderr***.log` 59 | * `ui_stderr***.log` 60 | * `worker_stderr***.log` 61 | 62 | `***`: _Logs may have slightly different names due to rotation_ 63 | 64 | 65 | ### Restarting the services 66 | To restart the services, simply run the following command: 67 | ``` 68 | service pm2-learninglocker restart 69 | ``` 70 | Where `learninglocker` is the system user you chose to install with in the script (defaults to `learninglocker`) 71 | 72 | ### Managing the services 73 | 74 | The PM2 service manages the 4 micro-services that Learning Locker requires. This is installed by default with the install script under the system user you chose. 75 | 76 | **In order to use the pm2 service, first ensure you are in as the correct system user:** 77 | 78 | (using the default `learninglocker` system user): 79 | ``` 80 | sudo su learninglocker 81 | ``` 82 | 83 | #### Service status 84 | To view the status of your processes (using the default `learninglocker` system user): 85 | ``` 86 | pm2 status 87 | ``` 88 | 89 | Output: 90 | ``` 91 | ┌──────────┬─────────┬────────┬───┬──────┬────────────┐ 92 | │ Name │ mode │ status │ ↺ │ cpu │ memory │ 93 | ├──────────┼─────────┼────────┼───┼──────┼────────────┤ 94 | │ API │ cluster │ online │ 0 │ 0% │ 144.0 MB │ 95 | │ UIServer │ cluster │ online │ 0 │ 0% │ 105.8 MB │ 96 | │ Worker │ cluster │ online │ 0 │ 0% │ 109.5 MB │ 97 | │ xAPI │ cluster │ online │ 0 │ 0% │ 81.8 MB │ 98 | └──────────┴─────────┴──-─────┴───┴──────┴────────────┘ 99 | ``` 100 | 101 | #### Service logs 102 | 103 | You can view a tail of the logs by running: 104 | ``` 105 | pm2 logs 106 | ``` 107 | 108 | Or view the logs for a particular service (by name or ID): 109 | ``` 110 | pm2 logs xAPI 111 | ``` 112 | 113 | To view more lines: 114 | ``` 115 | pm2 logs --lines 1000 116 | ``` 117 | 118 | ### Restarting the services manually 119 | You can restart all the services by running: 120 | ``` 121 | pm2 restart all 122 | ``` 123 | 124 | Or individual services by their name or ID: 125 | ``` 126 | pm2 restart UIServer 127 | ``` 128 | 129 | 130 | ### Application Structure 131 | 132 | There are two main repositories that are installed as part of a fresh Learning Locker installation, the [Learning Locker application](https://github.com/LearningLocker/learninglocker) and [xAPI service](https://github.com/LearningLocker/xapi-service). An in depth look at what both these packages do can be read in the [Architecture Overview](../overview-architecture). 133 | 134 | When installing your LL instance using the install script, these packages will be (by default) installed to `/usr/local/learninglocker/current` (as a symlink to a directory inside `/usr/local/learninglocker/release/...`). 135 | 136 | Inside `current/webapp/` lives the Learning Locker application which controls the User Interface, API and worker. 137 | 138 | We also install the xAPI Service here, inside the `current/xapi/` directory. 139 | 140 | 141 | ### Configuration & Environment Variables 142 | 143 | Each of these applications has their own `.env`. These hold all the configurations that the applications require in order to run, from database settings to logging configuration. 144 | 145 | By default the install script will copy the (.env.example) from [both](https://github.com/LearningLocker/learninglocker/blob/master/.env.example) repos [respectively](https://github.com/LearningLocker/xapi-service/blob/master/.env.example). 146 | 147 | It is likely you will wish to configure your application to connect to external databases, and whilst setup and configuration of these is beyond the scopes of this documentation, you will need to ensure that both `.env` files contain the same configuration values where appropriate. 148 | 149 | A full description of all configuration values in both repositories is available in the [Configuration Guide](../guides-configuring) 150 | 151 | ___ 152 | 153 | ## Production Installations 154 | For production installations, we recommend the following setup**:** 155 | 156 | * 1 Load balancer (e.g. [AWS Elastic Load Balancing](https://aws.amazon.com/elasticloadbalancing/)); 157 | * 2 Learning Locker servers (e.g. [AWS EC2](https://aws.amazon.com/ec2/)); 158 | * 3 Mongo servers in a replica set (e.g. [Atlas](https://www.mongodb.com/cloud/atlas)); 159 | * 1 Redis server (e.g. [AWS ElastiCache](https://aws.amazon.com/elasticache/)). 160 | 161 | This setup ensures good performance and a reasonable degree of redundancy in case of failures in some parts. We'd also recommend that you back up your Mongo database quite regularly depending on your own data requirements. If this sounds too costly or challenging, you may wish to consider using [our Software as a Service (SaaS) enterprise solution](https://learningpool.com/solutions/learning-record-store-learning-locker/). If you require more advice for your setup, please get in touch via [hello@learninglocker.net](mailto:hello@learninglocker.net). 162 | 163 | ___ 164 | 165 | ## Custom installations 166 | 167 | Please follow instructions [here](../guides-custom-installation) if you wish to install Learning Locker manually. 168 | -------------------------------------------------------------------------------- /src/http-persona-imports.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Persona Imports HTTP Interface 5 | 6 | Represents a CSV import (typically from a HR system) containing [persona](../http-personas) [identifiers](../http-persona-identifiers) and their [attributes](../http-persona-attributes). 7 | 8 | It is accessible through the following HTTP interfaces: 9 | 10 | - [Connection HTTP Interface](../http-connection) via http://www.example.org/api/connection/personasImport. 11 | - [REST HTTP Interface](../http-rest) via http://www.example.org/api/v2/personasImport. 12 | 13 | To import a CSV via the API, you need to make the three requests below in order. 14 | 15 | 1. Create the persona import model via the [REST HTTP Interface](../http-rest) (http://www.example.org/api/v2/personasImport). 16 | 2. Upload the CSV via the [Upload HTTP interface](#upload-http-interface). 17 | 3. Process the persona import model via the [Process HTTP interface](#process-http-interface). 18 | 19 | Alternatively, you can create/update personas via the [Persona Upsert HTTP Interface](#persona-upsert-http-interface). 20 | 21 | ### Schema 22 | 23 | Name | Description 24 | --- | --- 25 | _id | The id of this persona import model. 26 | organisation | The id of the [organisation](../http-organisations) this persona import belongs to. 27 | owner | The id of the [user](../http-users) that created the persona import. 28 | title | The title of this persona import. 29 | csvHeaders | An array of the header names from the CSV. 30 | structure | The [structure](#structure) of the CSV. 31 | importedAt | The date and time that the persona import was imported. 32 | csvHandle | The location of the CSV file in the storage provider. 33 | csvErrorHandle | The location of the CSV file with its errors in the storage provider. 34 | totalCount | Total number of data rows in the CSV. 35 | processedCount | Total number of processed data rows in the CSV. 36 | importErrors | An array of the errors in the CSV. 37 | result | The result of the import in terms of personas created and merged. 38 | 39 | ### Example 40 | 41 | ```json 42 | { 43 | "_id" : "59c1219936229d4ce9634601", 44 | "organisation" : "59c1219936229d4ce9634602", 45 | "owner": "59c1219936229d4ce9634603", 46 | "title": "Example Persona Import", 47 | "csvHeaders": ["Full Name", "Moodle User ID", "Moodle Home Page", "Age"], 48 | "structure": { 49 | "Full Name": { 50 | "columnName": "Full Name", 51 | "columnType": "COLUMN_NAME", 52 | "relatedColumn": "", 53 | "primary": null 54 | }, 55 | "Moodle User ID": { 56 | "columnName": "Moodle User ID", 57 | "columnType": "COLUMN_ACCOUNT_VALUE", 58 | "relatedColumn": "Moodle Home Page", 59 | "primary": null 60 | }, 61 | "Moodle Home Page": { 62 | "columnName": "Moodle Home Page", 63 | "columnType": "COLUMN_ACCOUNT_KEY", 64 | "relatedColumn": "Moodle User ID", 65 | "primary": 1 66 | }, 67 | "Age": { 68 | "columnName": "Age", 69 | "columnType": "COLUMN_ATTRIBUTE_DATA", 70 | "relatedColumn": "", 71 | "primary": null 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | # Structure 78 | 79 | Defines the structure of the CSV and how it should be parsed. The structure property is stored as an object where the keys are the CSV column names and the values are objects that define the column structure (value object schema defined below). 80 | 81 | ### Schema 82 | 83 | Name | Description 84 | --- | --- 85 | columnName | The name of the column in the CSV (CSV header). 86 | [columnType](#column-types) | The type of the column or how it should be parsed. 87 | relatedColumn | For account names this is the account home page column, for account home pages this is the account name column. 88 | primary | Defines the order in which identifiers should be used. 89 | 90 | # Column Types 91 | 92 | Column Type | Description 93 | --- | --- 94 | COLUMN_NAME | Persona name. 95 | COLUMN_ACCOUNT_KEY | Persona identifier account home page. 96 | COLUMN_ACCOUNT_VALUE | Persona identifier account name. 97 | COLUMN_ATTRIBUTE_DATA | Persona attribute value. 98 | COLUMN_MBOX | Persona identifier mbox. 99 | COLUMN_MBOXSHA1SUM | Persona identifier mbox_sha1sum. 100 | COLUMN_OPENID | Persona identifier openid. 101 | 102 | # Upload HTTP Interface 103 | This interface uploads the CSV for the persona import model. Requests to this interface should look something like the request below. 104 | 105 | ```http 106 | POST http://www.example.org/api/uploadpersonas 107 | Authorization: YOUR_BASIC_AUTH 108 | Content-Type: multipart/form-data; boundary=YOUR_FORM_BOUNDARY 109 | Content-Length: YOUR_CONTENT_LENGTH 110 | 111 | --YOUR_FORM_BOUNDARY 112 | Content-Disposition form-data; name="id" 113 | Content-Length: YOUR_PERSONA_IMPORT_MODEL_ID_LENGTH 114 | 115 | YOUR_PERSONA_IMPORT_MODEL_ID 116 | 117 | --YOUR_FORM_BOUNDARY 118 | Content-Disposition form-data; name="file"; filename="import.csv" 119 | Content-Length: YOUR_CSV_CONTENT_LENGTH 120 | Content-Type: text/csv 121 | 122 | YOUR_CSV_CONTENT 123 | 124 | --YOUR_FORM_BOUNDARY-- 125 | ``` 126 | 127 | The interface will respond with a 200 response code when the CSV is successfully uploaded. 128 | 129 | # Process HTTP Interface 130 | This interface starts the processing of the persona import. Requests to this interface should look something like the request below. 131 | 132 | ```http 133 | POST http://www.example.org/api/importpersonas 134 | Authorization: YOUR_BASIC_AUTH 135 | Content-Type: application/json; charset=utf-8 136 | 137 | { 138 | "id": "YOUR_PERSONA_IMPORT_MODEL_ID" 139 | } 140 | ``` 141 | 142 | The interface will respond with a 200 response code, processing of the persona import will start shortly after the response is received. 143 | 144 | # Persona Upsert HTTP Interface 145 | This interface provides API access to the function we use to process individual CSV rows in Persona Imports. Requests to this interface should look something like the request below. 146 | 147 | ```http 148 | POST http://www.example.org/api/uploadpersona 149 | Content-Type: application/json 150 | Authorization: 151 | 152 | { 153 | "personaName": "Sam Jackson", 154 | "ifis": [ 155 | { 156 | "key": "account", 157 | "value": { 158 | "homePage": "https://sso.example.org", 159 | "name": "sam_jackson_sso_id" 160 | } 161 | }, 162 | { 163 | "key": "mbox", 164 | "value": "mailto:sam.jackson@example.org" 165 | } 166 | ], 167 | "attributes": [ 168 | { 169 | "key": "Team", 170 | "value": "Avengers" 171 | } 172 | ] 173 | } 174 | ``` 175 | 176 | The interface will respond with a response similar to the one below after it has created/updated a matching persona. 177 | 178 | ```http 179 | HTTP/1.1 200 OK 180 | Content-Type: application/json; charset=utf-8 181 | 182 | { 183 | "merged": true 184 | } 185 | ``` 186 | 187 | ## Retrieving Created/Updated Persona 188 | To retrieve the persona that was created/updated in the previous upsert request, you can make the following request to the [Connection HTTP Interface](/http-connection/) for [Persona Indentifiers](/http-persona-identifiers/). 189 | 190 | ```http 191 | GET http://www.example.org/api/connection/personaidentifier?filter={"ifi.key": "account", "ifi.value.homePage": "https://sso.example.org", "ifi.value.name": "sam_jackson_sso_id"} 192 | Authorization: 193 | ``` 194 | 195 | This will provide a response similar to the one below. 196 | 197 | ``` 198 | HTTP/1.1 200 OK 199 | Content-Type: application/json; charset=utf-8 200 | 201 | { 202 | "edges": [ 203 | { 204 | "cursor": "", 205 | "node": { 206 | "_id": "", 207 | "ifi": { 208 | "key": "account", 209 | "value": { 210 | "homePage": "https://sso.example.org", 211 | "name": "sam_jackson_sso_id" 212 | } 213 | }, 214 | "organisation": "", 215 | "locked": false, 216 | "persona": "" 217 | } 218 | } 219 | ], 220 | "pageInfo": { 221 | "endCursor": "", 222 | "hasNextPage": false, 223 | "hasPreviousPage": false, 224 | "startCursor": "" 225 | } 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /src/guides-inserting.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Inserting Statements Guide 5 | To quickly try out inserting statements into your Learning Locker instance you can use Postman. Postman is a tool for creating and sending HTTP requests. You can [download and install Postman via their website](https://www.getpostman.com/). Once you've installed Postman, you can check out the [documentation for our xAPI HTTP Interface](../http-xapi-statements). 6 | 7 | Before you start designing and inserting your own statements in production, you should consider using [tools that already transmit xAPI statements](../guides-integrating/#existing-integrations). If you can't use any of these existing tools, there are a number of processes and best practices that you can follow to fall into the pit of success when designing and transmitting your own statements. 8 | 9 | - Design Statements 10 | 1. [List the experiences](#list-experiences) you wish to capture for analysis. 11 | 2. [Create a recipe](#create-recipes) (a statement template for an experience) for each experience from the previous step. 12 | 3. [Map the variables](#map-variables) in each recipe to variables in your application. 13 | - Transmit Statements 14 | 1. [Identify the sources](#identify-sources) that will construct and transmit statements. 15 | 2. [Implement the transmission](#implementing-transmission) from the sources to the LRS. 16 | 17 | ## List Experiences 18 | At this stage you should figure out which experiences are important to track (e.g. logging in, completing a quiz, watching a video, etc.) and what data you ideally need to capture for analysis (e.g. quiz score, video duration, etc.). When creating this list, you may wish to consider the data required to: 19 | 20 | - Answer your research questions. 21 | - Produce your stakeholder reports. 22 | - Adapt and improve the experiences of your users. 23 | 24 | ## Create Recipes 25 | At this stage you should create some example statements for each of the experiences you listed earlier ensuring that you include all of the data required for each experience inside the statement. 26 | 27 | Before you create a recipe, consider reusing one of the [recipes in the Tin Can Registry](https://registry.tincanapi.com/#home/profiles) as this will hopefully save you some time, improve your compatibility with existing systems, and ensure that you're using best practices. You may need to adapt these existing recipes to better meet your needs, as you may require additional data or not require some of the data they specify. 28 | 29 | If you can't find an existing recipe, don't worry, it's easy to create your own and we have some guidelines below. 30 | 31 | - Your actors should be identified by an [account](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#2424-account-object), where the `homePage` is an IRI for the application and the `name` is an identifier for the user. Other identifier types are available, but we recommend using an account. 32 | - Try to select an appropriate [verb from the Tin Can Registry](https://registry.tincanapi.com/#home/verbs) before creating your own. 33 | - Try to select an appropriate [activity type from the Tin Can Registry](https://registry.tincanapi.com/#home/activityTypes) before creating your own. 34 | - Only include one language in your languages maps. Preferably this should use the language of the user. 35 | - You should set a [timestamp](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#timestamp) with the correct time zone, otherwise the LRS will generate one that uses the time the statement was stored instead of the time an experience occurred, which is inaccurate for analysis. 36 | - Your verb and object identifiers should preferably resolve to a JSON representation of the verb or object. 37 | - The [statement context](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#context) should contain some minimal debugging information in the [extensions](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#miscext). For example, you might define the version of your statement transmission tool and platform. 38 | 39 | A statement for "viewing an application" using these guidelines might look a bit like the statement below. 40 | 41 | ```json 42 | { 43 | "actor": { 44 | "name": "Example User", 45 | "account": { 46 | "homePage": "http://www.example.org", 47 | "name": "example_user_id" 48 | } 49 | }, 50 | "verb": { 51 | "id": "http://id.tincanapi.com/verb/viewed", 52 | "display": { 53 | "en": "viewed" 54 | } 55 | }, 56 | "object": { 57 | "id": "http://www.example.org", 58 | "definition": { 59 | "type": "http://activitystrea.ms/schema/1.0/application", 60 | "name": { 61 | "en": "Example Application" 62 | } 63 | } 64 | }, 65 | "context": { 66 | "platform": "Example Platform", 67 | "language": "en", 68 | "extensions": { 69 | "http://www.example.org/transition_tool_version": "1.0.0" 70 | }, 71 | }, 72 | "timestamp": "2015-01-01T01:00Z" 73 | } 74 | ``` 75 | 76 | There are [more example statements provided in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#appendix-a-example-statements). For further support and consultation, you can email [hello@learninglocker.net](mailto:hello@learninglocker.net). 77 | 78 | ## Map Variables 79 | Using the recipes from the previous step, you should map the data required in each of the recipes to variables in your application. You might find it useful to use a table like the one below. 80 | 81 | Statement Property | Mapping 82 | --- | --- 83 | `actor.name` | User's full name from our user data model. 84 | `actor.account.homePage` | The URL of the application: "http://www.example.org" 85 | `actor.account.name` | User's identifier from our user data model. 86 | `object.id` | The URL of the application: "http://www.example.org" 87 | ... | ... 88 | 89 | ## Identify Sources 90 | At this stage, you should identify where the statement will be constructed and transmitted for each recipe that you identified earlier. 91 | 92 | You should consider whether you will be sending statements from the [client-side](https://en.wikipedia.org/wiki/Client-side) or the [server-side](https://en.wikipedia.org/wiki/Server-side) for each recipe as this will affect the transmission implementation. Alternatively, you may be creating statements using an external service, so at this stage you may also wish to consider whether you will need to [poll](https://en.wikipedia.org/wiki/Polling_(computer_science)) that service for events or whether you can use [webhooks](https://en.wikipedia.org/wiki/Webhook) or an [event listener](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) (with something like [Redis](https://redis.io/topics/pubsub)). 93 | 94 | ## Implementing Transmission 95 | At this stage, you can start planning and implementing the transmission of statements to your Learning Locker instance via the [xAPI HTTP Interface](../http-xapi-statements). However, there are a number of things listed below that you should consider before you begin. 96 | 97 | - [Batching Statements](#batching-statements) 98 | - [Handling Sending Failures](#handling-sending-failures) 99 | - [Sending Securely](#sending-securely) 100 | 101 | ### Batching Statements 102 | If you're potentially sending a significant number of statements in a short period of time, you should consider sending statements to the LRS in batches to improve response times, reduce HTTP requests, and reduce the elapsed time spent sending statements. For example, on the server-side this may be implemented with the use of a statement log and a cron job (the [Moodle Logstore plugin](https://github.com/xAPI-vle/moodle-logstore_xapi/pull/26) is an example of this). 103 | 104 | ### Handling Sending Failures 105 | If a statement fails to be sent you may want to consider implementing some retry strategies to resend statements that previously failed to be stored (normally because of downtime). We'd recommend that you send these failed statements in [batches](#batching-statements). If you're sending statements from the client-side, we'd recommend that you wait 5-60 seconds between retries and retry a maximum of 3-5 times. If you're sending statements server-side, you may want to consider storing failed statements somewhere and using a Cron job to send them. 106 | 107 | ### Sending Securely 108 | If you're sending statements from the server-side this shouldn't be an issue as the LRS credentials are not exposed. However, if you're sending statements from the client-side, you should consider finding a way to protect the LRS credentials so that they're not exposed to tech-savvy users, since a malicious user may try to read sensitive data from, or write unwanted data to, the LRS. We'd recommend using the [xAPI launch process](https://github.com/adlnet/xapi-launch) with Learning Locker's Launchr, you can email [hello@learninglocker.net](mailto:hello@learninglocker.net) for more information about this. 109 | -------------------------------------------------------------------------------- /src/guides-configuring.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Configuring Learning Locker 5 | 6 | Both the UI/API/Worker and xAPI Service require a `.env` file which will contain all the configuration variables required the application to run. 7 | 8 | Where applicable, both of these files will need to have the same values (e.g. in order to connect to the same database). 9 | 10 | Both repositories contain .env.example templates which can be copied as new `.env` files. If the application has been installed using the install script then this step will be performed for you. 11 | 12 | ## Learning Locker Application 13 | 14 | **Variables in bold are required or strongly recommended** 15 | 16 | _Variables in italics are required for debugging or development only_ 17 | 18 | 19 | Name | Description | Example | Default 20 | --- | --- | --- | --- 21 | **NODE_ENV** | Under what mode is Node running. This should be left as `production` in most circumstances | `production` | - 22 | **SITE_URL** | The host this application is running under, including protocol | `https://mylrs.com` | `127.0.0.1` 23 | **UI_PORT** | The port that the UI is attached to | `3000` | - 24 | **API_PORT** | The port that the API is attached to | `8080` | - 25 | _TEST_API_PORT_ | A port to expose the application on when running tests | `808080` | - 26 | **APP_SECRET** | Unique string used for hashing. Recommended length of 256 bits | `pleasechangetounique256bitstring` | - 27 | **MONGODB_PATH** | The [full Mongo connection string](https://docs.mongodb.com/manual/reference/connection-string/). This can include multiple hosts for replicas, and extra configuration values passed through query strings. | `mongodb://localhost:27017/learninglocker_v2` 28 | _MONGODB_TEST_PATH_ | A different Mongo URL to use when running tests | `mongodb://localhost:27017/llv2_tests` | - 29 | MONGO_SOCKET_TIMEOUT_MS | How long does the socket stay open when there is no activity | `300000` | `300000` 30 | MONGO_CONNECTION_POOLSIZE | https://blog.mlab.com/2013/11/deep-dive-into-connection-pooling/ | `20` | `20` 31 | **REDIS_HOST** | The host of the Redis instance | `127.0.0.1` | `127.0.0.1` 32 | **REDIS_PORT** | The port of the Redis instance | `example` | `6379` 33 | REDIS_DB | The database number of the Redis instance | `0` | - 34 | REDIS_PREFIX | A prefix to append to all keys within the Redis database | `learninglocker` | - 35 | ALLOW_AGGREGATION_DISK_USE | Can Mongo use its disks for aggregating | `true` | `true` 36 | AGGREGATE_API_ALLOWED_COLLECTIONS | Rule to check whether is collection allowed for Aggregate API or not | `^rollup` | `^rollup` 37 | AGGREGATION_CACHE_SECONDS | How many seconds are aggregation results cached | `300` | `300` 38 | AGGREGATION_REFRESH_AT_SECONDS | Refresh aggregations when this close to expiry | `120` | `120` 39 | MAX_TIME_MS | [Max time aggregations can run for in milliseconds](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) | `300` | `0` (no limit) 40 | DISABLE_PERSONA_SCORING | Turn off fuzzy scoring on persona matching. This will make persona workers much faster at scale. | `false` | `false` 41 | **LOG_MIN_LEVEL** | Minimum logging level (error\|warning\|info\|debug\|silly) | `debug` | `info` 42 | LOG_DIR | Relative dir to store API access logs | `logs` | `logs` 43 | _TEST_LOG_MIN_LEVEL_ | Logging level for tests | `silly` | - 44 | COLOR_LOGS | Should logs be output using ANSI color | `true` | - 45 | **WINSTON_CLOUDWATCH_ENABLED** | Should logs be sent to AWS Cloudwatch?

[AWS credentials must be configured for Cloudwatch access](http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-identity-based-access-control-cwl.html) | `true` | `false` 46 | WINSTON_CLOUDWATCH_LOG_GROUP_NAME | The Cloudwatch Logs group name | `llv2` | `llv2` 47 | WINSTON_CLOUDWATCH_LOG_STREAM_NAME | The Cloudwatch Logs stream name | `application` | The server's hostname 48 | WINSTON_CLOUDWATCH_ACCESS_KEY_ID | AWS Access Key with suitable privileges | `12345678901` | - 49 | WINSTON_CLOUDWATCH_SECRET_ACCESS_KEY | AWS Secret Access Key | `12345678901` | - 50 | WINSTON_CLOUDWATCH_REGION | The region the logs will be sent to | `us-west-1` | - 51 | **SMTP_HOST** | The SMTP mailbox host | `smtp.mailserver.com` | - 52 | **SMTP_PORT** | The SMTP port | `1234` | - 53 | **SMTP_SECURED** | Use SSL for SMTP? | `true` | - 54 | **SMTP_USER** | The SMTP username | `username` | - 55 | **SMTP_PASS** | The SMTP password | `password` | - 56 | **QUEUE_PROVIDER** | Which queue provider should be used?

Options are `REDIS` or `SQS`

When using Redis, queues are held in the Redis database

When using SQS, queues are held and managed by the AWS Simple Queue Service | `SQS` | - 57 | QUEUE_NAMESPACE | A queue prefix for SQS | `example` | - 58 | AWS_SQS_ACCESS_KEY_ID | An AWS Access Key ID with privileges to read/write to SQS queue jobs | `12567890` | - 59 | AWS_SQS_SECRET_ACCESS_KEY | An AWS Secret Access Key for SQS | `example` | - 60 | AWS_SQS_DEFAULT_REGION | The AWS region for SQS | `us-west-1` | - 61 | GOOGLE_ENABLED | Enable OAuth via Google (Requires setup in the Google Developer Console) | `true` | `false` 62 | GOOGLE_CLIENT_ID | Google OAuth Client ID | `12456789` | - 63 | GOOGLE_CLIENT_SECRET | Google OAuth Client Secret | `12356789` | - 64 | **FS_REPO** | Define the storage method (`local` for local storage or `amazon` for AWS S3 storage) | `local` | - 65 | FS_SUBFOLDER | A subfolder for all uploads to live within | `storage` | `storage` 66 | FS_LOCAL_ENDPOINT | An absolute path to storage | `/custom/storage/dir` | Current working directory 67 | FS_AWS_S3_ACCESS_KEY_ID | If using the Amazon repo, an AWS Access Key with permissions to read and write to the specified S3 bucket | `12356789` | - 68 | FS_AWS_S3_SECRET_ACCESS_KEY | AWS Secret Access Key | `12356789` | - 69 | FS_AWS_S3_REGION | AWS Secret Access Key | `us-west-1` | - 70 | FS_AWS_S3_BUCKET | The S3 bucket name | `12356789` | - 71 | NEW_RELIC_LICENSE_KEY | A New Relic license key for monitoring the UI and API | `qwertyuiopsdfghjkl` | - 72 | NEWRELIC_API_NAME | Name for the API in New Relic | `12356789` | - 73 | NEWRELIC_UI_NAME | Name for the UI in New Relic | `12356789` | - 74 | CLAMSCAN_BINARY | Location of Clamscan binary if requiring anti-virus scans on uploaded files (e.g. images) | `/usr/bin/clamscan` | - 75 | **MAX_TRIP_COUNT** | Load control counter for Journey outcomes (Enterprise Only) | `15` | - 76 | 77 | ## xAPI Service 78 | 79 | Please note that some of these variables are slightly different to their Application equivalents. Future updates will bring these inline with each other, with the eventual goal of allowing for a single `.env` file. 80 | 81 | **Variables in bold are required or strongly recommended** 82 | 83 | _Variables in italics are required for debugging or development only_ 84 | 85 | 86 | Name | Description | Example | Default 87 | --- | --- | --- | --- 88 | **EXPRESS_PORT** | The port that the UI is attached to | `8081` | `8081` 89 | _MODELS_REPO_ | Development setting to pick database type (`mongo` or `memory`). Memory only to be used for testing | `mongo` | `mongo` 90 | **MONGO_URL** | The [full Mongo connection string](https://docs.mongodb.com/manual/reference/connection-string/). This can include multiple hosts for replicas, and extra configuration values passed through query strings. | `mongodb://localhost:27017/learninglocker_v2` 91 | **REDIS_URL** | The full URL of the Redis instance including port, database number and authentication if required | `redis://127.0.0.1:6379/0` | `redis://127.0.0.1:6379/0` 92 | REDIS_PREFIX | A prefix to append to all keys within the Redis database | `learninglocker` | - 93 | REDIS_PREFIX | A prefix to append to all keys within the Redis database | `learninglocker` | 'LEARNINGLOCKER' 94 | **STORAGE_REPO** | Define the storage method (`local` for local storage or `s3` for AWS S3 storage) | `local` | - 95 | FS_LOCAL_STORAGE_DIR | An absolute path to storage | `/custom/storage/dir` | Current working directory 96 | FS_S3_ACCESS_KEY_ID | If using the Amazon repo, an AWS Access Key with permissions to read and write to the specified S3 bucket | `12356789` | - 97 | FS_S3_SECRET_ACCESS_KEY | AWS Secret Access Key | `12356789` | - 98 | FS_S3_REGION | AWS Secret Access Key | `us-west-1` | - 99 | WINSTON_CONSOLE_LEVEL | Minimum logging level (error\|warning\|info\|debug\|silly) | `info` | `info` 100 | **WINSTON_CLOUDWATCH_ENABLED** | Should logs be sent to AWS Cloudwatch?

[AWS credentials must be configured for Cloudwatch access](http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-identity-based-access-control-cwl.html) | `true` | `false` 101 | WINSTON_CLOUDWATCH_LOG_GROUP_NAME | The Cloudwatch Logs group name | `llv2` | `llv2` 102 | WINSTON_CLOUDWATCH_LOG_STREAM_NAME | The Cloudwatch Logs stream name | `application` | The server's hostname 103 | WINSTON_CLOUDWATCH_ACCESS_KEY_ID | AWS Access Key with suitable privileges | `12345678901` | - 104 | WINSTON_CLOUDWATCH_SECRET_ACCESS_KEY | AWS Secret Access Key | `12345678901` | - 105 | WINSTON_CLOUDWATCH_REGION | The region the logs will be sent to | `us-west-1` | - 106 | -------------------------------------------------------------------------------- /src/guides-assessment-statements.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Assessment Statements 5 | 6 | - [User completed Assessment](#user-completed-assessment) 7 | - [User answered True/False Question](#user-answered-true-false-question) 8 | - [User answered Numeric Question](#user-answered-numeric-question) 9 | - [User answered Fill-In Question](#user-answered-fill-in-question) 10 | - [User answered Choice Question](#user-answered-choice-question) 11 | - [User answered Sequencing Question](#user-answered-sequencing-question) 12 | - [User answered Likert Question](#user-answered-likert-question) 13 | - [User answered Matching Question](#user-answered-matching-question) 14 | 15 | ## User completed Assessment 16 | 17 | ### Statement 18 | ```json 19 | { 20 | "actor": { 21 | "objectType": "Agent", 22 | "mbox": "mailto:user@example.org", 23 | "name": "Example User" 24 | }, 25 | "verb": { 26 | "id": "http://adlnet.gov/expapi/verbs/completed", 27 | "display": { 28 | "en-US": "completed" 29 | } 30 | }, 31 | "object": { 32 | "objectType": "Activity", 33 | "id": "http://www.example.org/quiz/1", 34 | "definition": { 35 | "type": "http://adlnet.gov/expapi/activities/assessment", 36 | "name": { 37 | "en-US": "Example Assessment" 38 | } 39 | } 40 | }, 41 | "result": { 42 | "completion": true, 43 | "success": true, 44 | "score": { 45 | "scaled": 1.0, 46 | "raw": 7, 47 | "min": 0, 48 | "max": 7 49 | }, 50 | "duration": "PT0H5M2S" 51 | } 52 | } 53 | ``` 54 | 55 | ## User answered True False Question 56 | 57 | ### Metadata 58 | ```json 59 | { 60 | "https://learninglocker&46;net/true-false-response": "Yes" 61 | } 62 | ``` 63 | 64 | ### Statement 65 | ```json 66 | { 67 | "actor": { 68 | "objectType": "Agent", 69 | "name": "Example User", 70 | "mbox": "mailto:user@example.org" 71 | }, 72 | "verb": { 73 | "id": "http://adlnet.gov/expapi/verbs/answered", 74 | "display": { 75 | "en": "answered" 76 | } 77 | }, 78 | "object": { 79 | "objectType": "Activity", 80 | "id": "http://www.example.org/quiz/1/question/1", 81 | "definition": { 82 | "name": {"en":"Is 1 + 1 = 2?"}, 83 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 84 | "interactionType": "true-false", 85 | "correctResponsesPattern": ["Yes"] 86 | } 87 | }, 88 | "result": { 89 | "success": true, 90 | "response": "Yes" 91 | } 92 | } 93 | ``` 94 | 95 | ## User answered Numeric Question 96 | 97 | ### Metadata 98 | ```json 99 | { 100 | "https://learninglocker&46;net/numeric-response": 2 101 | } 102 | ``` 103 | 104 | ### Statement 105 | ```json 106 | { 107 | "actor": { 108 | "objectType": "Agent", 109 | "name": "Example User", 110 | "mbox": "mailto:user@example.org" 111 | }, 112 | "verb": { 113 | "id": "http://adlnet.gov/expapi/verbs/answered", 114 | "display": { 115 | "en": "answered" 116 | } 117 | }, 118 | "object": { 119 | "id": "http://www.example.org/quiz/1/question/2", 120 | "objectType": "Activity", 121 | "definition": { 122 | "name": {"en": "What is 1 + 1?"}, 123 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 124 | "interactionType": "numeric", 125 | "correctResponsesPattern": ["2"] 126 | } 127 | }, 128 | "result": { 129 | "success": true, 130 | "response": "2" 131 | } 132 | } 133 | ``` 134 | 135 | ## User answered Fill-In Question 136 | 137 | ### Metadata 138 | No Metadata 139 | 140 | ### Statement 141 | ```json 142 | { 143 | "actor": { 144 | "objectType": "Agent", 145 | "name": "Example User", 146 | "mbox": "mailto:user@example.org" 147 | }, 148 | "verb": { 149 | "id": "http://adlnet.gov/expapi/verbs/answered", 150 | "display": { 151 | "en": "answered" 152 | } 153 | }, 154 | "object": { 155 | "id": "http://www.example.org/quiz/1/question/3", 156 | "objectType": "Activity", 157 | "definition": { 158 | "name": {"en": "What is Ben often heard saying?"}, 159 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 160 | "interactionType": "fill-in", 161 | "correctResponsesPattern": ["Bob’s your uncle"] 162 | } 163 | }, 164 | "result": { 165 | "success": true, 166 | "response": "Bob's your uncle" 167 | } 168 | } 169 | ``` 170 | 171 | ## User answered Choice Question 172 | 173 | ### Metadata 174 | ```json 175 | { 176 | "https://learninglocker&46;net/choice-response": [ 177 | "hedgehog", 178 | "shark" 179 | ] 180 | } 181 | ``` 182 | 183 | ### Statement 184 | ```json 185 | { 186 | "actor": { 187 | "objectType": "Agent", 188 | "name": "Example User", 189 | "mbox": "mailto:user@example.org" 190 | }, 191 | "verb": { 192 | "id": "http://adlnet.gov/expapi/verbs/answered", 193 | "display": { 194 | "en": "answered" 195 | } 196 | }, 197 | "object": { 198 | "id": "http://www.example.org/quiz/1/question/4", 199 | "objectType": "Activity", 200 | "definition": { 201 | "name": {"en": "Which of these animals have a spine?"}, 202 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 203 | "interactionType": "choice", 204 | "correctResponsesPattern": ["hedgehog[,]shark"], 205 | "choices": [ 206 | {"id": "hedgehog", "description": {"en": "Hedgehog"}}, 207 | {"id": "shark", "description": {"en": "Shark"}}, 208 | {"id": "octopus", "description": {"en": "Octopus"}} 209 | ] 210 | } 211 | }, 212 | "result": { 213 | "success": true, 214 | "response": "hedgehog[,]shark" 215 | } 216 | } 217 | ``` 218 | 219 | ## User answered Sequencing Question 220 | 221 | ### Metadata 222 | ```json 223 | { 224 | "https://learninglocker&46;net/sequencing-response": [ 225 | "12", 226 | "234", 227 | "4561" 228 | ] 229 | } 230 | ``` 231 | 232 | ### Statement 233 | ```json 234 | { 235 | "actor": { 236 | "objectType": "Agent", 237 | "name": "Example User", 238 | "mbox": "mailto:user@example.org" 239 | }, 240 | "verb": { 241 | "id": "http://adlnet.gov/expapi/verbs/answered", 242 | "display": { 243 | "en": "answered" 244 | } 245 | }, 246 | "object": { 247 | "id": "http://www.example.org/quiz/1/question/5", 248 | "objectType": "Activity", 249 | "definition": { 250 | "name": {"en": "Order these numbers from lowest to highest?"}, 251 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 252 | "interactionType": "sequencing", 253 | "correctResponsesPattern": ["12[,]234[,]4561"], 254 | "choices": [ 255 | {"id": "234", "description": {"en": "Two hundred and thirty four"}}, 256 | {"id": "12", "description": {"en": "Twelve"}}, 257 | {"id": "4561", "description": {"en": "Four thousand, five hundred and sixty one"}} 258 | ] 259 | } 260 | }, 261 | "result": { 262 | "success": true, 263 | "response": "12[,]234[,]4561" 264 | } 265 | } 266 | ``` 267 | 268 | ## User answered Likert Question 269 | 270 | ### Metadata 271 | ```json 272 | { 273 | "https://learninglocker&46;net/likert-response": "likert_3" 274 | } 275 | ``` 276 | 277 | ### Statement 278 | ```json 279 | { 280 | "actor": { 281 | "objectType": "Agent", 282 | "name": "Example User", 283 | "mbox": "mailto:user@example.org" 284 | }, 285 | "verb": { 286 | "id": "http://adlnet.gov/expapi/verbs/answered", 287 | "display": { 288 | "en": "answered" 289 | } 290 | }, 291 | "object": { 292 | "id": "http://www.example.org/quiz/1/question/6", 293 | "objectType": "Activity", 294 | "definition": { 295 | "name": {"en": "How awesome is xAPI?"}, 296 | "type": "http://adlnet.gov/expapi/activities/cmi.interaction", 297 | "interactionType": "likert", 298 | "correctResponsesPattern": ["likert_3"], 299 | "scale": [ 300 | {"id": "likert_0", "description": {"en": "It’s OK"}}, 301 | {"id": "likert_1", "description": {"en": "It’s Pretty Cool"}}, 302 | {"id": "likert_2", "description": {"en": "It’s Damn Cool"}}, 303 | {"id": "likert_3", "description": {"en": "It’s Gonna Change the World"}} 304 | ] 305 | } 306 | }, 307 | "result": { 308 | "success": true, 309 | "response": "likert_3" 310 | } 311 | } 312 | ``` 313 | 314 | ## User answered Matching Question 315 | 316 | ### Metadata 317 | ```json 318 | { 319 | "https://learninglocker&46;net/matching-response": [ 320 | ["apple", "fruit"], 321 | ["cheese", "dairy"], 322 | ["chicken", "meat"] 323 | ] 324 | } 325 | ``` 326 | 327 | ### Statement 328 | ```json 329 | { 330 | "actor": { 331 | "objectType": "Agent", 332 | "name": "Example User", 333 | "mbox": "mailto:user@example.org" 334 | }, 335 | "verb": { 336 | "id": "http://adlnet.gov/expapi/verbs/answered", 337 | "display": { 338 | "en": "answered" 339 | } 340 | }, 341 | "object": { 342 | "id": "http://www.example.org/quiz/1/question/7", 343 | "objectType": "Activity", 344 | "definition":{ 345 | "name":{"en":"Match these foods to their food group"}, 346 | "type":"http://adlnet.gov/expapi/activities/cmi.interaction", 347 | "interactionType":"matching", 348 | "correctResponsesPattern":[ 349 | "apple[.]fruit[,]cheese[.]dairy[,]chicken[.]meat" 350 | ], 351 | "source":[ 352 | { 353 | "id":"apple", 354 | "description":{"en":"Apple"} 355 | }, 356 | { 357 | "id":"cheese", 358 | "description":{"en":"Cheese"} 359 | }, 360 | { 361 | "id":"chicken", 362 | "description":{"en":"Chicken"} 363 | } 364 | ], 365 | "target":[ 366 | { 367 | "id":"fruit", 368 | "description":{"en":"Fruit"} 369 | }, 370 | { 371 | "id":"dairy", 372 | "description":{"en":"Dairy"} 373 | }, 374 | { 375 | "id":"meat", 376 | "description":{"en":"Meat"} 377 | } 378 | ] 379 | } 380 | }, 381 | "result": { 382 | "success": true, 383 | "response": "apple[.]fruit[,]cheese[.]dairy[,]chicken[.]meat" 384 | } 385 | } 386 | ``` 387 | -------------------------------------------------------------------------------- /src/http-xapi-statements.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # xAPI Statements HTTP Interface 5 | The table below describes the routes that the HTTP interface provides, all of the URLs are relative to http://www.example.org/data/xAPI where http://www.example.org is the URL of your Learning Locker instance. To access this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. 6 | 7 | If you receive an "unauthorised error" from this API, you can use the three checks below via the Client UI. To remove the need for these checks, Learning Locker will automatically create a new client when a new store is created, the new client is enabled by default, with the "All" scope, and the LRS set to the new store. To avoid unauthorised errors, try to use the new client and remember the checks below if you edit or create a client manually. 8 | 9 | 1. Check that the client is enabled. 10 | 2. Check that the "All" scope is selected under the "xAPI" heading. 11 | 3. Check that an LRS is selected. 12 | 13 | Go to the [xAPI HTTP interface documentation](../http-xapi) to see the rest of the xAPI routes. 14 | 15 | Route | Description 16 | --- | --- 17 | [PUT /statements](../http-xapi-statements#put-statements) | Stores a single statement. 18 | [POST /statements](../http-xapi-statements#post-statements) | Stores a single statement or multiple statements. 19 | [GET /statements](../http-xapi-statements#get-statements) | Retrieves statements. 20 | 21 | ## PUT /statements 22 | This route allows you to create a single statement with a statement identifier in the URL parameters and the statement itself in the JSON body. A request to this API would look something like the request below. 23 | 24 | ```http 25 | PUT http://www.example.org/data/xAPI/statements?statementId=dfb7218c-0fc9-4dfc-9524-d497097de027 26 | Authorization: YOUR_BASIC_AUTH 27 | X-Experience-API-Version: 1.0.3 28 | Content-Type: application/json 29 | 30 | { 31 | "id": "dfb7218c-0fc9-4dfc-9524-d497097de027", 32 | "actor": { "mbox": "mailto:test@example.org" }, 33 | "verb": { "id": "http://www.example.org/verb" }, 34 | "object": { "id": "http://www.example.org/activity" } 35 | } 36 | ``` 37 | 38 | This route returns a 204 response with no content when the statement is successfully created, like the example response below. 39 | 40 | ```http 41 | HTTP/1.1 204 NO CONTENT 42 | X-Experience-API-Version: 1.0.3 43 | X-Experience-API-Consistent-Through: 2017-08-31T15:16:29.709Z 44 | ``` 45 | 46 | For more information, view the [PUT /statements route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#211-put-statements). 47 | 48 | ## POST /statements 49 | This route allows you to create a multiple statements with or without statement identifiers. A request to this API would look something like the request below. 50 | 51 | ```http 52 | POST http://www.example.org/data/xAPI/statements 53 | Authorization: YOUR_BASIC_AUTH 54 | X-Experience-API-Version: 1.0.3 55 | Content-Type: application/json; charset=utf-8 56 | 57 | [{ 58 | "id": "dfb7218c-0fc9-4dfc-9524-d497097de027", 59 | "actor": { "mbox": "mailto:test1@example.org" }, 60 | "verb": { "id": "http://www.example.org/verb" }, 61 | "object": { "id": "http://www.example.org/activity" } 62 | }, { 63 | "actor": { "mbox": "mailto:test2@example.org" }, 64 | "verb": { "id": "http://www.example.org/verb" }, 65 | "object": { "id": "http://www.example.org/activity" } 66 | }] 67 | ``` 68 | 69 | This route returns a 200 response with an array of statement identifiers when the statements are successfully created. A response from this route would look something like the response below. 70 | 71 | ```http 72 | HTTP/1.1 200 OK 73 | Content-Type: application/json; charset=utf-8 74 | X-Experience-API-Version: 1.0.3 75 | X-Experience-API-Consistent-Through: 2017-08-31T15:16:29.709Z 76 | 77 | ["dfb7218c-0fc9-4dfc-9524-d497097de027", "dfb7218c-0fc9-4dfc-9524-d497097de028"] 78 | ``` 79 | 80 | For more information, view the [POST /statements route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#212-post-statements). 81 | 82 | ## GET /statements 83 | This route allows you to retrieve a single statement or multiple statements. If the `statementId` or `voidedStatementId` URL parameters are set, it will [retrieve a single statement](#single-statement) with the given identifier, otherwise it will retrieve [many statements](#many-statements). For more information, view the [GET /statements route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#213-get-statements). 84 | 85 | 86 | 87 | ### Single Statement 88 | In addition to the `statementId` and `voidedStatementId` URL parameters (one of which must be set to retrieve a single statement), there are two additional optional parameters in the form of `format` and `attachments`. The `format` parameter defaults to "exact" if it's not set, the other options are "ids" (only includes minimal information) and "canonical" (only includes one language per language map). The `attachments` parameter is a boolean that determines if the statement's attachments are returned with the statement. 89 | 90 | ```http 91 | GET http://www.example.org/data/xAPI/statements?statementId=dfb7218c-0fc9-4dfc-9524-d497097de027&format=exact&attachments=false 92 | Authorization: YOUR_BASIC_AUTH 93 | X-Experience-API-Version: 1.0.3 94 | ``` 95 | 96 | When getting a single statement successfully, this route will return a 200 response as shown below with the statement as the body of the request. If the single statement cannot be found, then this route will return a 404 response. 97 | 98 | ```http 99 | HTTP/1.1 200 OK 100 | Content-Type: application/json; charset=utf-8 101 | X-Experience-API-Version: 1.0.3 102 | X-Experience-API-Consistent-Through: 2017-08-31T15:16:29.709Z 103 | 104 | { 105 | "id": "dfb7218c-0fc9-4dfc-9524-d497097de027", 106 | "actor": { "objectType": "Agent", "mbox": "mailto:test@example.org" }, 107 | "verb": { "id": "http://www.example.org/verb" }, 108 | "object": { "objectType": "Activity", "id": "http://www.example.org/activity" }, 109 | "version": "1.0.3", 110 | "authority": { 111 | "objectType": "Agent", 112 | "mbox": "mailto:authority@example.org" 113 | }, 114 | "timestamp": "2017-09-05T12:45:31+00:00", 115 | "stored": "2017-09-05T12:45:31+00:00" 116 | } 117 | ``` 118 | 119 | ### Many Statements 120 | When retrieving multiple statements there are a number of optional URL parameters listed below that can be used to filter statements. All of the URL parameters should be URL encoded (after JSON encoding if JSON encoding is required). 121 | 122 | Parameter | Description 123 | --- | --- 124 | agent | JSON encoded object containing an IFI to match an agent or group. 125 | verb | String matching the statement's verb identifier. 126 | activity | String matching the statement's object identifier. 127 | registration | String matching the statement's registration from the context. 128 | related_activities | Applies the activity filter to any activity in the statement when `true`. Defaults to `false`. 129 | related_agents | Applies the activity filter to any agent/group in the statement when `true`. Defaults to `false`. 130 | since | String that returns statements stored after the given timestamp (exclusive). 131 | until | String that returns statements stored before the given timestamp (inclusive). 132 | limit | Number of statements to return. Defaults to `0` which returns the maximum the server will allow. 133 | format | String ("exact"/"ids"/"canonical") determining how much of the statement is returned. Defaults to "exact" to return the full statement. 134 | attachments | Boolean determining if the statements' attachments should be returned. Defaults to `false`. 135 | ascending | Boolean determining if the statements should be returned in ascending stored order. Defaults to `false`. 136 | 137 | Below is an example of a request containing each of the URL parameters. 138 | 139 | ```http 140 | GET http://www.example.org/data/xAPI/statements?agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&verb=http%3A%2F%2Fwww.example.org%2Fverb&activity=http%3A%2F%2Fwww.example.org%2Factivity®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c&related_activities=false&related_agents=false&since=2017-09-04T12:45:31+00:00&until=2017-09-06T12:45:31+00:00&limit=1&format=exact&attachments=false&ascending=false 141 | Authorization: YOUR_BASIC_AUTH 142 | X-Experience-API-Version: 1.0.3 143 | ``` 144 | 145 | This route will return a 200 response as shown below where the JSON body of the response contains a more link and an array of statements that match the URL parameters. The more link can be used to retrieve the next page of statements, if there aren't any more pages of statements the more link will be an empty string. 146 | 147 | ```http 148 | HTTP/1.1 200 OK 149 | Content-Type: application/json; charset=utf-8 150 | X-Experience-API-Version: 1.0.3 151 | X-Experience-API-Consistent-Through: 2017-08-31T15:16:29.709Z 152 | 153 | { 154 | "more": "/data/xAPI/statements?agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&verb=http%3A%2F%2Fwww.example.org%2Fverb&activity=http%3A%2F%2Fwww.example.org%2Factivity®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c&related_activities=false&related_agents=false&since=2017-09-04T12:45:31+00:00&until=2017-09-06T12:45:31+00:00&limit=1&format=exact&attachments=false&ascending=false&cursor=59a8289f399c5b1a19efa60e", 155 | "statements": [{ 156 | "id": "dfb7218c-0fc9-4dfc-9524-d497097de027", 157 | "actor": { "objectType": "Agent", "mbox": "mailto:test@example.org" }, 158 | "verb": { "id": "http://www.example.org/verb" }, 159 | "object": { "objectType": "Activity", "id": "http://www.example.org/activity" }, 160 | "context": { "registration": "361cd8ef-0f6a-40d2-81f2-b988865f640c" }, 161 | "version": "1.0.3", 162 | "authority": { 163 | "objectType": "Agent", 164 | "mbox": "mailto:authority@example.org" 165 | }, 166 | "timestamp": "2017-09-05T12:45:31+00:00", 167 | "stored": "2017-09-05T12:45:31+00:00" 168 | }] 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /src/http-xapi-states.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # xAPI State HTTP Interface 5 | The table below describes the routes that the HTTP interface provides, all of the URLs are relative to http://www.example.org/data/xAPI where http://www.example.org is the URL of your Learning Locker instance. To access this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. Go to the [xAPI HTTP interface documentation](../http-xapi) to see the rest of the xAPI routes. 6 | 7 | Route | Description 8 | --- | --- 9 | [PUT /activities/state](../http-xapi-states#put-activitiesstate) | Creates or overwrites a state document. 10 | [POST /activities/state](../http-xapi-states#post-activitiesstate) | Creates or merges a state document. 11 | [GET /activities/state](../http-xapi-states#get-activitiesstate) | Retrieves a single state document or multiple state identifiers. 12 | [DELETE /activities/state](../http-xapi-states#delete-activitiesstate) | Deletes a single state document or multiple state documents. 13 | 14 | 15 | 16 | ## PUT /activities/state 17 | This route allows you to create a single state document if it doesn't exist or overwrite an existing state document if it does exist. The route has 3 required URL parameters, an `activityId` (an IRI representing the activity), an `agent` (a JSON encoded object representing the agent the state belongs to), and a `stateId` (a string representing an identifier for the state). There is also an optional URL parameter for the `registration`. A request to this route would look something like the request below. 18 | 19 | ```http 20 | PUT http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 21 | Authorization: YOUR_BASIC_AUTH 22 | X-Experience-API-Version: 1.0.3 23 | Content-Type: application/json; charset=utf-8 24 | 25 | { 26 | "example_key": "example_value" 27 | } 28 | ``` 29 | 30 | This route returns a 204 response with no content when the state document is successfully created/overwritten, like the example response below. 31 | 32 | ```http 33 | HTTP/1.1 204 NO CONTENT 34 | X-Experience-API-Version: 1.0.3 35 | ``` 36 | 37 | For more information, view the [PUT /activities/state route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#single-document-put--post--get--delete). 38 | 39 | ## POST /activities/state 40 | This route allows you to create a single state document if it doesn't exist or merge an existing state document if it does exist. The route allows the same URL parameters as the [PUT /activities/state route](#put-activitiesstate). The state document is merged when the state document exists, the existing state document is a JSON encoded object, and the posted state document is a JSON encoded object. When the two JSON encoded documents are merged, only the top-level properties are merged. The example requests below demonstrate merging state documents. For more information, view the [POST /activities/state route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#single-document-put--post--get--delete). 41 | 42 | ### POST Initial State 43 | To POST an initial state, the request should be something like the request below. 44 | 45 | ```http 46 | POST http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 47 | Authorization: YOUR_BASIC_AUTH 48 | X-Experience-API-Version: 1.0.3 49 | Content-Type: application/json; charset=utf-8 50 | 51 | { 52 | "key_to_keep": "value_to_keep", 53 | "key_to_change": "value_before_change" 54 | } 55 | ``` 56 | 57 | The response to the request above would be something similar to the response below. 58 | 59 | ```http 60 | HTTP/1.1 204 NO CONTENT 61 | X-Experience-API-Version: 1.0.3 62 | ``` 63 | 64 | ### POST Merge State 65 | To POST a state for merging, the request should be something like the request below. 66 | 67 | ```http 68 | POST http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 69 | Authorization: YOUR_BASIC_AUTH 70 | X-Experience-API-Version: 1.0.3 71 | Content-Type: application/json; charset=utf-8 72 | 73 | { 74 | "key_to_change": "value_after_change", 75 | "key_to_add": "value_to_add" 76 | } 77 | ``` 78 | 79 | The response to the request above would be something similar to the response below. 80 | 81 | ```http 82 | HTTP/1.1 204 NO CONTENT 83 | X-Experience-API-Version: 1.0.3 84 | ``` 85 | 86 | ### GET Merged State 87 | To GET the merged state, the request should be something like the request below. 88 | 89 | ```http 90 | GET http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 91 | Authorization: YOUR_BASIC_AUTH 92 | X-Experience-API-Version: 1.0.3 93 | ``` 94 | 95 | The response to the request above would be something similar to the response below. 96 | 97 | ```http 98 | HTTP/1.1 200 OK 99 | X-Experience-API-Version: 1.0.3 100 | Content-Type: application/json; charset=utf-8 101 | 102 | { 103 | "key_to_keep": "value_to_keep", 104 | "key_to_change": "value_after_change", 105 | "key_to_add": "value_to_add" 106 | } 107 | ``` 108 | 109 | ## GET /activities/state 110 | This route allows you to retrieve a single state document or multiple state identifiers. If the `stateId` URL parameter is set, it will [retrieve a single state document](#retrieve-single-state-document) with the state identifier, otherwise it will [retrieve many state identifiers](#retrieve-many-state-identifiers). For more information, view the [GET /activities/state route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#single-document-put--post--get--delete). 111 | 112 | ### Retrieve Single State Document 113 | The route allows the same URL parameters as the [PUT /activities/state route](#put-activitiesstate). A request to this route should be similar to the request below. 114 | 115 | ```http 116 | GET http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 117 | Authorization: YOUR_BASIC_AUTH 118 | X-Experience-API-Version: 1.0.3 119 | ``` 120 | 121 | The response to the request above would be something similar to the response below. If the state document cannot be found, a 404 response will be returned instead of a 200. 122 | 123 | ```http 124 | HTTP/1.1 200 OK 125 | X-Experience-API-Version: 1.0.3 126 | Content-Type: application/json; charset=utf-8 127 | 128 | { 129 | "example_key": "example_value" 130 | } 131 | ``` 132 | 133 | ### Retrieve Many State Identifiers 134 | The route allows the same URL parameters as the [PUT /activities/state route](#put-activitiesstate) except for the `stateId` parameter and with the addition of the optional `since` parameter. The `since` URL parameter is an ISO timestamp that ensures only state identifiers stored since the timestamp (exclusive) are returned. A request to this route should be similar to the request below. 135 | 136 | ```http 137 | GET http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c&since=2017-09-04T12:45:31+00:00 138 | Authorization: YOUR_BASIC_AUTH 139 | X-Experience-API-Version: 1.0.3 140 | ``` 141 | 142 | The response to the request above would be something similar to the response below, where the JSON encoded response body contains an array of state identifiers that match the URL parameters. 143 | 144 | ```http 145 | HTTP/1.1 200 OK 146 | X-Experience-API-Version: 1.0.3 147 | Content-Type: application/json; charset=utf-8 148 | 149 | ["example_state_id"] 150 | ``` 151 | 152 | ## DELETE /activities/state 153 | This route allows you to delete a single state document or multiple state documents. If the `stateId` URL parameter is set, it will [delete a single state document](#delete-single-state-document) with the state identifier, otherwise it will [delete many state documents](#delete-many-state-documents). For more information, view the [DELETE /activities/state route in the xAPI specification](https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#single-document-put--post--get--delete). 154 | 155 | ### Delete Single State Document 156 | The route allows the same URL parameters as the [PUT /activities/state route](#put-activitiesstate). A request to this route should be similar to the request below. 157 | 158 | ```http 159 | DELETE http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D&stateId=example_state_id®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 160 | Authorization: YOUR_BASIC_AUTH 161 | X-Experience-API-Version: 1.0.3 162 | ``` 163 | 164 | The response to the request above would be something similar to the response below. If the state document cannot be found, a 404 response will be returned instead of a 204. 165 | 166 | ```http 167 | HTTP/1.1 204 NO CONTENT 168 | X-Experience-API-Version: 1.0.3 169 | ``` 170 | 171 | ### Delete Many State Documents 172 | The route allows the same URL parameters as the [PUT /activities/state route](#put-activitiesstate) except for the `stateId` parameter. A request to this route should be similar to the request below. 173 | 174 | ```http 175 | DELETE http://www.example.org/data/xAPI/activities/state?activityId=http%3A%2F%2Fwww.example.org%2Factivity&agent=%7B%22mbox%22%3A%20%22mailto%3Atest%40example.org%22%7D®istration=361cd8ef-0f6a-40d2-81f2-b988865f640c 176 | Authorization: YOUR_BASIC_AUTH 177 | X-Experience-API-Version: 1.0.3 178 | ``` 179 | 180 | The response to the request above would be something similar to the response below. 181 | 182 | ```http 183 | HTTP/1.1 204 NO CONTENT 184 | X-Experience-API-Version: 1.0.3 185 | ``` 186 | -------------------------------------------------------------------------------- /src/http-rest.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: 3 | - "/http-models/" 4 | --- 5 | 6 | # REST API HTTP Interface 7 | The table below describes the routes that the HTTP interface provides. This HTTP interface is available for all models in Learning Locker. 8 | 9 | ``` 10 | GET http://www.example.org/api/v2/MODEL_NAME 11 | ``` 12 | 13 | For example, to get a count of stores via this API, you'd use the following route. 14 | 15 | ``` 16 | GET http://www.example.org/api/v2/lrs 17 | ``` 18 | 19 | To access this interface, you must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. 20 | 21 | Method | Description 22 | --- | --- 23 | [GET /count](#get-count) | Gets a count of the models. 24 | [GET /](#get-) | Gets a subset of the models. 25 | [POST /](#post-) | Creates a model. 26 | [GET /:id](#get-id) | Gets a single model. 27 | [PUT /:id](#put-or-post-id) | Creates or overwrites a model. 28 | [POST /:id](#put-or-post-id) | Creates or overwrites a model. 29 | [PATCH /:id](#patch-id) | Patches a model. 30 | [DELETE /:id](#delete-id) | Deletes a model. 31 | 32 | ## Models 33 | The table below lists the models supported by this interface, you can view the model schemas by clicking the model names. 34 | 35 | Name | API Model Name | Description 36 | --- | --- | --- 37 | [Activity](../http-activities#schema) | `activity` | Activity with many identifiers. 38 | [Client](../http-clients#schema) | `client` | Credentials that access HTTP Interfaces. 39 | [Dashboard](../http-dashboards#schema) | `dashboard` | Customisable grid of visualisations. 40 | [Download](../http-downloads#schema) | `download` | Record of downloaded exports. 41 | [Export](../http-exports#schema) | `export` | Template for exporting statements. 42 | [Journey](../http-journeys#schema) | `journey` | Journeys visualisation. 43 | [Journey Progress](../http-journey-progress#schema) | `journeyprogress` | Journey progress. 44 | [Organisation](../http-organisations#schema) | `organisation` | Container of clients and stores that a subset of users can access. 45 | [Persona](../http-personas#schema) | `persona` | Person with many [identifiers](../http-persona-identifiers) and [attributes](../http-persona-attributes) across systems. 46 | [Persona Identifier](../http-persona-identifiers#schema) | `personaidentifier` | Unique xAPI identifier for a persona. 47 | [Persona Attribute](../http-persona-attributes#schema) | `personaattribute` | Attribute of a persona. 48 | [Query](../http-queries#schema) | `query` | Saved filter for statements. 49 | [Role](../http-roles#schema) | `role` | Group of permissions for accessing organisation data via users. 50 | [Store](../http-stores#schema) | `store` | Container for xAPI data (statements, documents, and attachments). 51 | [User](../http-users#schema) | `user` | Login details for accessing the UI. 52 | [Visualisation](../http-visualisations#schema) | `visualisation` | Graphical view of statements. 53 | 54 | ## Routes 55 | ### GET /count 56 | This route returns a count of the models. A request to this route would look something like the request below. 57 | 58 | ```http 59 | GET http://www.example.org/api/v2/lrs/count 60 | Authorization: Basic YOUR_BASIC_AUTH 61 | ``` 62 | 63 | A request like the one above, will respond with a 200 response like the one below containing a count of the models in the body. 64 | 65 | ```http 66 | HTTP/1.1 200 OK 67 | Content-Type: application/json; charset=utf-8 68 | 69 | { 70 | "count": 3 71 | } 72 | ``` 73 | 74 | For more information about the acceptable URL query parameters, view the [Restify documentation](https://florianholzapfel.github.io/express-restify-mongoose/#querying). 75 | 76 | **Important:** In case of **User** route, [query](https://florianholzapfel.github.io/express-restify-mongoose/#querying) parameter will be ignored and `search` parameter should be used instead (only available in Enterprise). `search` query parameter is a simple string and will be transformed into filter shown below. 77 | 78 | ```json 79 | { 80 | "$or": [ 81 | { "name": { "$regex": "exampleSearchString", "$options": "i" } }, 82 | { "email": { "$regex": "exampleSearchString", "$options": "i" } } 83 | ] 84 | } 85 | ``` 86 | 87 | ### GET / 88 | This route returns an array of models. A request to this route would look something like the request below. 89 | 90 | ```http 91 | GET http://www.example.org/api/v2/lrs 92 | Authorization: Basic YOUR_BASIC_AUTH 93 | ``` 94 | 95 | A request like the one above, will respond with a 200 response like the one below containing the models as JSON in the body. Different models will respond with a different schema, you can view the schemas by clicking the model names in the [model table above](#models). 96 | 97 | ```http 98 | HTTP/1.1 200 OK 99 | Content-Type: application/json; charset=utf-8 100 | 101 | [ 102 | { 103 | "createdAt": "2017-08-08T14:35:18.400Z", 104 | "organisation": "111aaa1111a111111aa11111", 105 | "statementCount": 987, 106 | "title": "Example Store", 107 | "__v": 0, 108 | "updatedAt": "2017-08-08T14:35:33.721Z", 109 | "_id": "111aaa1111a111111aa11112" 110 | } 111 | ] 112 | ``` 113 | 114 | For more information about the acceptable URL query parameters, view the [Restify documentation](https://florianholzapfel.github.io/express-restify-mongoose/#querying). 115 | 116 | **Important:** In case of **User** route, [query](https://florianholzapfel.github.io/express-restify-mongoose/#querying) parameter will be ignored and `search` parameter should be used instead. `search` query parameter is a simple string and will be transformed into filter shown below. 117 | 118 | ```json 119 | { 120 | "$or": [ 121 | { "name": { "$regex": "exampleSearchString", "$options": "i" } }, 122 | { "email": { "$regex": "exampleSearchString", "$options": "i" } } 123 | ] 124 | } 125 | ``` 126 | 127 | ### POST / 128 | This route creates a model. A request to this route would look something like the request below. Different models will require and respond with a different schema, you can view the schemas by clicking the model names in the [model table above](#models). 129 | 130 | ```http 131 | POST http://www.example.org/api/v2/lrs 132 | Authorization: Basic YOUR_BASIC_AUTH 133 | Content-Type: application/json; charset=utf-8 134 | 135 | { 136 | "title": "Example Store" 137 | } 138 | ``` 139 | 140 | A request like the one above, will respond with a 201 response like the one below containing the created model in the JSON body. 141 | 142 | ```http 143 | HTTP/1.1 201 CREATED 144 | Content-Type: application/json; charset=utf-8 145 | 146 | { 147 | "createdAt": "2017-08-08T14:35:18.400Z", 148 | "organisation": "111aaa1111a111111aa11111", 149 | "statementCount": 0, 150 | "title": "Example Store", 151 | "updatedAt": "2017-08-08T14:35:33.721Z", 152 | "_id": "111aaa1111a111111aa11112", 153 | "__v": 0 154 | } 155 | ``` 156 | 157 | ### GET /:id 158 | This route returns a single model that has the specified identifier from the URL. A request to this route would look something like the request below. 159 | 160 | ```http 161 | GET http://www.example.org/api/v2/lrs/111aaa1111a111111aa11112 162 | Authorization: Basic YOUR_BASIC_AUTH 163 | ``` 164 | 165 | A request like the one above, will respond with a 200 response like the one below containing the model as JSON in the body. Different models will respond with a different schema, you can view the schemas by clicking the model names in the [model table above](#models). Note that the request will return a 404 response if the model doesn't exist. 166 | 167 | ```http 168 | HTTP/1.1 200 OK 169 | Content-Type: application/json; charset=utf-8 170 | 171 | { 172 | "createdAt": "2017-08-08T14:35:18.400Z", 173 | "organisation": "111aaa1111a111111aa11111", 174 | "statementCount": 987, 175 | "title": "Example Store", 176 | "__v": 0, 177 | "updatedAt": "2017-08-08T14:35:33.721Z", 178 | "_id": "111aaa1111a111111aa11112" 179 | } 180 | ``` 181 | 182 | For more information about the acceptable URL query parameters, view the [Restify documentation](https://florianholzapfel.github.io/express-restify-mongoose/#querying). 183 | 184 | ### PUT or POST /:id 185 | This route creates or updates a single model that has the specified identifier from the URL. A request to this route would look something like the request below. Different models will require and respond with a different schema, you can view the schemas by clicking the model names in the [model table above](#models). 186 | 187 | ```http 188 | PUT http://www.example.org/api/v2/lrs/111aaa1111a111111aa11112 189 | Authorization: Basic YOUR_BASIC_AUTH 190 | Content-Type: application/json; charset=utf-8 191 | 192 | { 193 | "createdAt": "2017-08-08T14:35:18.400Z", 194 | "organisation": "111aaa1111a111111aa11111", 195 | "statementCount": 987, 196 | "title": "Updated Title", 197 | "updatedAt": "2017-08-08T14:35:33.721Z", 198 | "_id": "111aaa1111a111111aa11112" 199 | } 200 | ``` 201 | 202 | A request like the one above, will respond with a 200 response like the one below containing the model as JSON in the body. 203 | 204 | ```http 205 | HTTP/1.1 200 OK 206 | Content-Type: application/json; charset=utf-8 207 | 208 | { 209 | "createdAt": "2017-08-08T14:35:18.400Z", 210 | "organisation": "111aaa1111a111111aa11111", 211 | "statementCount": 987, 212 | "title": "Updated Title", 213 | "__v": 0, 214 | "updatedAt": "2017-08-08T14:35:33.721Z", 215 | "_id": "111aaa1111a111111aa11112" 216 | } 217 | ``` 218 | 219 | ### PATCH /:id 220 | This route patches a single model that has the specified identifier from the URL. A request to this route would look something like the request below. Different models will require and respond with a different schema, you can view the schemas by clicking the model names in the [model table above](#models). 221 | 222 | ```http 223 | PATCH http://www.example.org/api/v2/lrs/111aaa1111a111111aa11112 224 | Authorization: Basic YOUR_BASIC_AUTH 225 | Content-Type: application/json; charset=utf-8 226 | 227 | { 228 | "title": "Patched Title" 229 | } 230 | ``` 231 | 232 | A request like the one above, will respond with a 200 response like the one below containing the model as JSON in the body. 233 | 234 | ```http 235 | HTTP/1.1 200 OK 236 | Content-Type: application/json; charset=utf-8 237 | 238 | { 239 | "createdAt": "2017-08-08T14:35:18.400Z", 240 | "organisation": "111aaa1111a111111aa11111", 241 | "statementCount": 987, 242 | "title": "Patched Title", 243 | "__v": 0, 244 | "updatedAt": "2017-08-08T14:35:33.721Z", 245 | "_id": "111aaa1111a111111aa11112" 246 | } 247 | ``` 248 | 249 | ### DELETE /:id 250 | This route deletes a single model that has the specified identifier from the URL. A request to this route would look something like the request below. 251 | 252 | ```http 253 | DELETE http://www.example.org/api/v2/lrs/111aaa1111a111111aa11112 254 | Authorization: Basic YOUR_BASIC_AUTH 255 | ``` 256 | 257 | A request like the one above, will respond with a 204 response like the one below. 258 | 259 | ```http 260 | HTTP/1.1 204 NO CONTENT 261 | ``` 262 | -------------------------------------------------------------------------------- /src/http-connection.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: 3 | - "/postman/" 4 | --- 5 | 6 | # Connection HTTP Interface 7 | 8 | The Learning Locker Connection API is a HTTP interface that [utilises cursors to provide paginated models](#pagination-example). The API is inspired by [GraphQL's connections](https://facebook.github.io/relay/graphql/connections.htm). The API is available for all models in Learning Locker, for example, to receive paginated statements via this API, you'd use the following URL. 9 | 10 | ``` 11 | http://www.example.org/api/connection/statement 12 | ``` 13 | 14 | You must additionally supply your Basic Auth details with each request in the `Authorization` header. Your Basic Auth details can be found under **Settings** > **Clients**. The API also accepts the following *optional* URL parameters for filtering the models returned. 15 | 16 | - [sort](#sort-parameter) (required - we recommend sorting by `_id` if nothing else) 17 | - [search](#search-parameter) (available only for user connection in Enterprise) 18 | - [filter](#filter-parameter) (not available for User connection) 19 | - [project](#project-parameter) 20 | - [hint](#hint-parameter) 21 | - [first](#first-parameter) 22 | - [after](#after-parameter) 23 | 24 | ## URL Parameters 25 | All of the URL parameters should be URL encoded (after JSON encoding if JSON encoding is required). For example, if you were using the [example sort parameter](#sort-parameter), your request would look something like the request below. 26 | 27 | ```http 28 | GET http://www.example.org/api/connection/statement?sort=%7b%22timestamp%22%3a-1%2c%22statement.id%22%3a1%7d 29 | Authorization: Basic YOUR_BASIC_AUTH 30 | ``` 31 | 32 | ### Sort Parameter 33 | The sort parameter is a JSON encoded object. The keys of the object represent the names of the properties you wish to sort. The values of the object represent the order in which you want to sort the properties. To sort in ascending order, use the number 1; to sort in descending order, use the number -1. 34 | 35 | For example, to sort statements in descending order of their timestamp and ascending order of their Mongo ObjectId, you can use the following sort parameter. 36 | 37 | ```json 38 | { 39 | "timestamp": -1, 40 | "_id": 1 41 | } 42 | ``` 43 | 44 | In the above example, we've included the `_id` because it should be unique and the sort parameter should always contain a unique property in order for pagination to work correctly with cursors. The order of the keys in the object determines which property is sorted first, so always include a unique property at the end such as the `_id` property. 45 | 46 | #### Sorting With Extension Keys 47 | Note that when using extension keys, you need to replace any dots with `&46;` because Mongo does not allow dots in keys. For example, when you have an extension key like `http://www.example.com/extension` you can sort by it using `http://www&46;example&46;com/extension` instead, so a sort parameter using this extension key might look something like the sort parameter below. 48 | 49 | ```json 50 | { 51 | "statement.context.extensions.http://www&46;example&46;com/extension": 1 52 | } 53 | ``` 54 | 55 | #### Sorting With Improved Performance 56 | You may find that changing the sort parameter can vary the time it takes a query to run, especially when you have a large number of models. You can take advantage of database indexes to improve performance, more information is available about [using indexes via Mongo's documentation](https://docs.mongodb.com/manual/indexes/). If utilising indexes doesn't have the required performance improvement, you can instead utilise [BI tools](../guides-retrieving). 57 | 58 | ### Search Parameter 59 | The search parameter is a simple string and **available only for User connection**. This parameter is gonna search for the match over `name` and `email` fields of User schema. So if the value for this parameter is `exampleSearchString`, as shown in the request below, it will be transformed into filter as shown below. 60 | 61 | ```http 62 | GET http://www.example.org/api/connection/user?search=exampleSearchString 63 | Authorization: Basic YOUR_BASIC_AUTH 64 | ``` 65 | 66 | ```json 67 | { 68 | "$or": [ 69 | { "name": { "$regex": "exampleSearchString", "$options": "i" } }, 70 | { "email": { "$regex": "exampleSearchString", "$options": "i" } } 71 | ] 72 | } 73 | ``` 74 | 75 | ### Filter Parameter 76 | The filter parameter is a JSON encoded object. The keys of the object represent the names of the properties or operators. The values of the object represent the value you wish to filter by. 77 | 78 | For example, to filter statements by actor or verb, you can use the following filter parameter. 79 | 80 | ```json 81 | { 82 | "$or": [{ 83 | "statement.actor.account.name": "123", 84 | "statement.actor.account.homePage": "http://www.example.org/user" 85 | }, { 86 | "statement.verb.id": "http://www.example.org/verb" 87 | }] 88 | } 89 | ``` 90 | 91 | In the example above, [`$or`](https://docs.mongodb.com/manual/reference/operator/query/or/#op._S_or) is a operator, all operators start with a dollar (`$`). You can find a [list of the available operators in the Mongo documentation](https://docs.mongodb.com/manual/reference/operator/query/). The most common operators are the comparison operators ([`$eq`](https://docs.mongodb.com/manual/reference/operator/query/eq/#op._S_eq), [`$gt`](https://docs.mongodb.com/manual/reference/operator/query/gt/#op._S_gt), [`$gte`](https://docs.mongodb.com/manual/reference/operator/query/gte/#op._S_gte), [`$in`](https://docs.mongodb.com/manual/reference/operator/query/in/#op._S_in), [`$lt`](https://docs.mongodb.com/manual/reference/operator/query/lt/#op._S_lt), [`$lte`](https://docs.mongodb.com/manual/reference/operator/query/lte/#op._S_lte), [`$ne`](https://docs.mongodb.com/manual/reference/operator/query/ne/#op._S_ne), and [`$nin`](https://docs.mongodb.com/manual/reference/operator/query/nin/#op._S_nin)) and the logical operators ([`$and`](https://docs.mongodb.com/manual/reference/operator/query/and/#op._S_and), [`$not`](https://docs.mongodb.com/manual/reference/operator/query/not/#op._S_not), [`$nor`](https://docs.mongodb.com/manual/reference/operator/query/nor/#op._S_nor), and [`$or`](https://docs.mongodb.com/manual/reference/operator/query/or/#op._S_or)). 92 | 93 | #### Filtering With Extension Keys 94 | Note that when using extension keys, you need to replace any dots with `&46;` because Mongo does not allow dots in keys. For example, when you have an extension key like `http://www.example.com/extension` you can filter it using `http://www&46;example&46;com/extension` instead, so a filter parameter using this extension key might look something like the filter parameter below. 95 | 96 | ```json 97 | { 98 | "statement.context.extensions.http://www&46;example&46;com/extension": { 99 | "$ne": "example_value" 100 | } 101 | } 102 | ``` 103 | 104 | #### Filtering With Improved Performance 105 | You may find that changing the filter parameter can vary the time it takes a query to run, especially when you have a large number of models. You can take advantage of database indexes to improve performance, more information is available about [using indexes via Mongo's documentation](https://docs.mongodb.com/manual/indexes/). If utilising indexes doesn't have the required performance improvement, you can instead utilise [BI tools](../guides-retrieving). 106 | 107 | ### Project Parameter 108 | The project parameter is a JSON encoded object. The keys of the object usually represent the names you want to give to the projected properties. The values of the object usually determine whether the property is included/excluded or the name of the property to project from the model. 109 | 110 | For example, to project the actor's account name as a user's identifier, the verb without a display, and the object's identifier you can use the following project parameter. 111 | 112 | ```json 113 | { 114 | "userId": "$statement.actor.account.name", 115 | "statement.verb": { 116 | "display": 0 117 | }, 118 | "statement.object.id": 1 119 | } 120 | ``` 121 | 122 | In the example above, the value `0` is used to exclude the verb's display property. Similarly, the value `1` is used to include the object's identifier. You can find out more about [projections via the Mongo documentation](https://docs.mongodb.com/manual/reference/operator/aggregation/project/). 123 | 124 | #### Projecting With Extension Keys 125 | Note that when using extension keys, you need to replace any dots with `&46;` because Mongo does not allow dots in keys. For example, when you have an extension key like `http://www.example.com/extension` you can project it using `http://www&46;example&46;com/extension` instead, so a project parameter using this extension key might look something like the project parameter below. 126 | 127 | ```json 128 | { 129 | "statement.context.extensions.http://www&46;example&46;com/extension": 1 130 | } 131 | ``` 132 | 133 | ### Hint Parameter 134 | The hint parameter is a JSON encoded object that represents a Mongo index and is similar to the [sort parameter](#sort-parameter). A hint overrides Mongo's default index selection and query optimisation process. 135 | 136 | For example, to use an index you've created in Mongo for verb identifiers in ascending order, you can use the following hint parameter. 137 | 138 | ```json 139 | { 140 | "statement.verb.id": 1 141 | } 142 | ``` 143 | 144 | For more information about hints, you can checkout [Mongo's hint documentation](https://docs.mongodb.com/manual/reference/method/cursor.hint/index.html). 145 | 146 | ### First Parameter 147 | The first parameter is a number that represents the number of models to be returned after the [after cursor parameter](#after-parameter) or from the very first model in Mongo. 148 | 149 | ### After Parameter 150 | The after parameter is a string that represents a cursor used for getting models after a specified point in the Mongo collection. 151 | 152 | ## Pagination Example 153 | To demonstrate pagination with this API, you can insert two statements using a request like the one below. 154 | 155 | ```http 156 | POST http://www.example.org/data/xAPI/statements 157 | Authorization: Basic YOUR_BASIC_AUTH 158 | X-Experience-API-Version: 1.0.3 159 | Content-Type: application/json 160 | 161 | [{ 162 | "actor": { "mbox": "mailto:test1@example.org" }, 163 | "verb": { "id": "http://www.example.org/verb" }, 164 | "object": { "id": "http://www.example.org/activity" }, 165 | }, { 166 | "actor": { "mbox": "mailto:test2@example.org" }, 167 | "verb": { "id": "http://www.example.org/verb" }, 168 | "object": { "id": "http://www.example.org/activity" }, 169 | }] 170 | ``` 171 | 172 | The request above should return you a statement identifier for each of the statements in an array. 173 | 174 | ### Retrieving Page One 175 | Once you've inserted two statements, you can make a request for the first statement, using a request like the one below. 176 | 177 | ```http 178 | GET http://www.example.org/api/connection/statement?first=1 179 | Authorization: Basic YOUR_BASIC_AUTH 180 | ``` 181 | 182 | The request above should return you a connection, consisting of edges (which contain the models) and page info (which contains the cursors). For example, the above request would return something like the response below. 183 | 184 | ```json 185 | { 186 | "edges": { 187 | "cursor": "Zmlyc3RTdGF0ZW1lbnQ=", 188 | "node": { 189 | "_id": "59ad59a40334c1bd23322c3a", 190 | "statement": { 191 | "id": "0f748889-8d6c-4423-9919-189a14484d2f", 192 | "actor": { "objectType": "Agent", "mbox": "mailto:test1@example.org" }, 193 | "verb": { "id": "http://www.example.org/verb" }, 194 | "object": { "objectType": "Activity", "id": "http://www.example.org/activity" }, 195 | "version": "1.0.3", 196 | "authority": { 197 | "objectType": "Agent", 198 | "mbox": "mailto:authority@example.org" 199 | } 200 | } 201 | } 202 | }, 203 | "pageInfo": { 204 | "endCursor": "Zmlyc3RTdGF0ZW1lbnQ=", 205 | "hasNextPage": true, 206 | "hasPreviousPage": true, 207 | "startCursor": "Zmlyc3RTdGF0ZW1lbnQ=" 208 | } 209 | } 210 | ``` 211 | 212 | ### Retrieving Subsequent Pages 213 | Once you've received a page, you can use the cursors in the `pageInfo` from the previous response to retrieve the next page. For example, we can use a request like the one below to retrieve the second of the inserted statements from earlier in this example. 214 | 215 | ```http 216 | GET http://www.example.org/api/connection/statement?first=1&after=Zmlyc3RTdGF0ZW1lbnQ= 217 | Authorization: Basic YOUR_BASIC_AUTH 218 | ``` 219 | 220 | The request above should return you another connection, again this will consist of edges (which contain the models) and page info (which contains the cursors). For example, the request for page two would return something like the response below. 221 | 222 | ```json 223 | { 224 | "edges": { 225 | "cursor": "c2Vjb25kU3RhdGVtZW50", 226 | "node": { 227 | "_id": "59ad5b9480fe0205abbd0aec", 228 | "statement": { 229 | "id": "3c44a187-ad17-41c1-bc73-fed40fdbb200", 230 | "actor": { "objectType": "Agent", "mbox": "mailto:test2@example.org" }, 231 | "verb": { "id": "http://www.example.org/verb" }, 232 | "object": { "objectType": "Activity", "id": "http://www.example.org/activity" }, 233 | "version": "1.0.3", 234 | "authority": { 235 | "objectType": "Agent", 236 | "mbox": "mailto:authority@example.org" 237 | } 238 | } 239 | } 240 | }, 241 | "pageInfo": { 242 | "endCursor": "c2Vjb25kU3RhdGVtZW50", 243 | "hasNextPage": true, 244 | "hasPreviousPage": true, 245 | "startCursor": "c2Vjb25kU3RhdGVtZW50" 246 | } 247 | } 248 | ``` 249 | --------------------------------------------------------------------------------