{"id":13416423,"url":"https://github.com/mumuki/mumuki-laboratory","last_synced_at":"2025-12-16T14:42:10.649Z","repository":{"id":24260442,"uuid":"27654303","full_name":"mumuki/mumuki-laboratory","owner":"mumuki","description":" :microscope: Where students practice and receive automated and human feedback","archived":false,"fork":false,"pushed_at":"2023-07-18T23:15:07.000Z","size":17229,"stargazers_count":202,"open_issues_count":134,"forks_count":26,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-04-14T02:23:15.071Z","etag":null,"topics":["assessment","auth0","code-assesment","education","exercises","extensible","laboratory","lms","multitenant","postgresql","programming-exercises","rails","rspec","ruby","saml"],"latest_commit_sha":null,"homepage":"http://mumuki.io","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mumuki.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null},"funding":{"open_collective":"mumuki"}},"created_at":"2014-12-07T00:31:42.000Z","updated_at":"2024-03-24T21:24:41.000Z","dependencies_parsed_at":"2023-01-14T00:40:10.532Z","dependency_job_id":null,"html_url":"https://github.com/mumuki/mumuki-laboratory","commit_stats":{"total_commits":5556,"total_committers":38,"mean_commits":"146.21052631578948","dds":0.6722462203023758,"last_synced_commit":"fce1ede388e7d57065c91b9a621731d49823f32d"},"previous_names":[],"tags_count":246,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mumuki%2Fmumuki-laboratory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mumuki%2Fmumuki-laboratory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mumuki%2Fmumuki-laboratory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mumuki%2Fmumuki-laboratory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mumuki","download_url":"https://codeload.github.com/mumuki/mumuki-laboratory/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246612486,"owners_count":20805355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assessment","auth0","code-assesment","education","exercises","extensible","laboratory","lms","multitenant","postgresql","programming-exercises","rails","rspec","ruby","saml"],"created_at":"2024-07-30T21:00:58.603Z","updated_at":"2025-12-16T14:42:05.598Z","avatar_url":"https://github.com/mumuki.png","language":"Ruby","funding_links":["https://opencollective.com/mumuki","https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=KCZ5AQR53CH26"],"categories":["Ruby","Happy Exploring 🤘"],"sub_categories":[],"readme":"![Build status](https://github.com/mumuki/mumuki-laboratory/workflows/Test%20and%20deploy/badge.svg?branch=master)\n[![Code Climate](https://codeclimate.com/github/mumuki/mumuki-laboratory/badges/gpa.svg)](https://codeclimate.com/github/mumuki/mumuki-laboratory)\n\n\u003cimg width=\"60%\" src=\"https://raw.githubusercontent.com/mumuki/mumuki-laboratory/master/laboratory-screenshot.png\"\u003e\u003c/img\u003e\n\n# Mumuki Laboratory [![btn_donate_lg](https://cloud.githubusercontent.com/assets/1039278/16535119/386d7be2-3fbb-11e6-9ee5-ecde4cef142a.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=KCZ5AQR53CH26)\n\u003e Code assement web application for the Mumuki Platform\n\n## About\n\nLaboratory is a multitenant Rails webapp for solving exercises, organized in terms of chapters and guides.\n\n## Preparing environment\n\n### 1. Install essentials and base libraries\n\n\u003e First, we need to install some software: [PostgreSQL](https://www.postgresql.org) database, [RabbitMQ](https://www.rabbitmq.com/) queue, and some common Ruby on Rails native dependencies\n\n```bash\nsudo apt-get install autoconf curl git build-essential libssl-dev autoconf bison libreadline6 libreadline6-dev zlib1g zlib1g-dev postgresql libpq-dev rabbitmq-server\n```\n\n### 2. Install rbenv\n\u003e [rbenv](https://github.com/rbenv/rbenv) is a ruby versions manager, similar to rvm, nvm, and so on.\n\n```bash\ncurl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash\necho 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' \u003e\u003e ~/.bashrc # or .bash_profile\necho 'eval \"$(rbenv init -)\"' \u003e\u003e ~/.bashrc # or .bash_profile\n```\n\n### 3. Install ruby\n\n\u003e Now we have rbenv installed, we can install ruby and [bundler](http://bundler.io/)\n\n```bash\nrbenv install 2.6.3\nrbenv global 2.6.3\nrbenv rehash\ngem install bundler\n```\n\n### 4. Clone this repository\n\n\u003e Because, err... we need to clone this repostory before developing it :stuck_out_tongue:\n\n```bash\ngit clone https://github.com/mumuki/mumuki-laboratory\ncd mumuki-laboratory\n```\n\n### 5. Install and setup database\n\n\u003e We need to create a PostgreSQL role - AKA a user - who will be used by Laboratory to create and access the database\n\n```bash\n# create db user for linux users\nsudo -u postgres psql \u003c\u003cEOF\n  create role mumuki with createdb login password 'mumuki';\nEOF\n\n# create db user for mac users\npsql postgres\n#once inside postgres server\ncreate role mumuki with createdb login password 'mumuki';\n\n# create schema and initial development data\n./devinit\n```\n\n\n## Installing and Running\n\n### Quick start\n\nIf you want to start the server quickly in developer environment,\nyou can just do the following:\n\n```bash\n./devstart\n```\n\nThis will install your dependencies and boot the server.\n\n### Installing the server\n\nIf you just want to install dependencies, just do:\n\n```\nbundle install\n```\n\n### Running the server\n\nYou can boot the server by using the standard rackup command:\n\n```\n# using defaults from config/puma.rb and rackup default port 9292\nbundle exec rackup\n\n# changing port\nbundle exec rackup -p 8080\n\n# changing threads count\nMUMUKI_LABORATORY_THREADS=30 bundle exec rackup\n\n# changing workers count\nMUMUKI_LABORATORY_WORKERS=4 bundle exec rackup\n```\n\nOr you can also start it with `puma` command, which gives you more control:\n\n```\n# using defaults from config/puma.rb\nbundle exec puma\n\n# changing ports, workers and threads count, using puma-specific options:\nbundle exec puma -w 4 -t 2:30 -p 8080\n\n# changing ports, workers and threads count, using environment variables:\nMUMUKI_LABORATORY_WORKERS=4 MUMUKI_LABORATORY_PORT=8080 MUMUKI_LABORATORY_THREADS=30 bundle exec puma\n```\n\nFinally, you can also start your server using `rails`:\n\n```bash\nrails s\n```\n\n## Running tests\n\n```bash\n# Run all tests\nbundle exec rake\n\n# Run only web tests (i.e. Capybara and Teaspoon)\nbundle exec rake spec:web\n```\n\n## Running Capybara tests with Selenium\n\nThe Capybara config of this project supports running tests on Firefox, Chrome and Safari via Selenium. The [`webdrivers`](https://github.com/titusfortner/webdrivers) gem automatically installs (and updates) all the necessary Selenium webdrivers.\n\nBy default, Capybara tests will run with the default dummy-driver (Rack test). If you want to run on a real browser, you should set `MUMUKI_SELENIUM_DRIVER` variable to `firefox`, `chrome` or `safari`. Also, a Rake task to run just the Capybara tests is available.\n\nSome examples:\n\n```bash\n# Run web tests, using Firefox\nMUMUKI_SELENIUM_DRIVER=firefox bundle exec rake spec:web\n\n# Run Capybara tests on Chrome\nMUMUKI_SELENIUM_DRIVER=chrome bundle exec rake spec:web:capybara\n```\n\n## Running JS tests\n\nThe [`webdrivers`](https://github.com/titusfortner/webdrivers) gem also works with Teaspoon, no need to install anything manually. By default tests run on Firefox, but this behavior can be changed by setting `MUMUKI_SELENIUM_DRIVER` (see section above).\n\n```bash\nbundle exec rake spec:web:teaspoon\n```\n\n## Running `eslint`\n\n```bash\nyarn run lint\n```\n\n## Using a local runner\n\nSometimes you will need to check `laboratory` against a local runner. Run the following code in you `rails console`:\n\n```ruby\nrequire 'mumuki/domain/seed'\n\n# import a new language\nMumuki::Domain::Seed.languages_syncer.locate_and_import!  Language, 'http://localhost:9292'\n\n# update an existing language object\nMumuki::Domain::Seed.languages_syncer.import!  Mumukit::Sync.key(Language, 'http://localhost:9292'), language\n```\n\n## Using a remote content\n\nLikewise, you will sometimes require a guide that is not locally available. Run the following code in `rails console`:\n\n```ruby\nrequire 'mumuki/domain/seed'\n\n# import a new guide\nMumuki::Domain::Seed.contents_syncer.locate_and_import! Guide, slug\n\n# update an existing guide object\nMumuki::Domain::Seed.contents_syncer.import!  Mumukit::Sync.key(Guide, slug), guide\n```\n\nAfter that you will probably to add it somewhere. The easiest way is to create a complement of `central`:\n\n```ruby\no = Organization.central\no.book.complements \u003c\u003c Guide.locate!(slug).as_complement_of(o.book)\no.reindex_usages!\n```\n\nNow you will be able to visit that guide at `http://localhost:3000/central/guides/#{slug}`\n\n## Debugging email sender\n\nThe development environment is configured to \"send\" emails via `mailcatcher`, a mock server, if it is available. Run these commands to install and run it - and do it _before_ the emails are sent, so it can actually _catch_ them:\n\n```bash\ngem install mailcatcher\nmailcatcher\n```\n\nOnce up and running, go to http://localhost:1080/ to see which emails have been sent. Unfortunately, the developers recommend not to install it via Bundler, so it has to be done this way. :woman_shrugging:\n\n## JavaScript API Docs\n\nIn order to be customized by runners, Laboratory exposes the following selectors and methods\nwhich are granted to be safe and stable.\n\n### Public Selectors\n\n* `.mu-final-state`\n* `.mu-initial-state-header`\n* `.mu-initial-state`\n* `.mu-kids-blocks`\n* `.mu-kids-context`\n* `.mu-kids-exercise-description`\n* `.mu-kids-exercise`\n* `.mu-kids-reset-button`\n* `.mu-kids-results-aborted`\n* `.mu-kids-results`\n* `.mu-kids-state-image`\n* `.mu-kids-state`\n* `.mu-kids-states`\n* `.mu-kids-submit-button`\n* `.mu-multiple-scenarios`\n* `.mu-scenarios`\n* `.mu-submit-button`\n* `#mu-actual-state-text`\n* `#mu-${languageName}-custom-editor`\n* `#mu-custom-editor-default-value`\n* `#mu-custom-editor-extra`\n* `#mu-custom-editor-test`\n* `#mu-custom-editor-value`\n* `#mu-initial-state-text`\n\n### Deprecated Selectors\n\n* `.mu-kids-gbs-board-initial`: Use `.mu-initial-state` instead\n* `.mu-state-final`: Use `.mu-final-state` instead\n* `.mu-state-initial`: Use `.mu-initial-state` instead\n* `#kids-results-aborted`: Use `.mu-kids-results-aborted` instead\n* `#kids-results`: Use `.mu-kids-results` instead\n\n### Methods\n\n* `mumuki.bridge.Laboratory`\n  * `.runTests`\n* `mumuki.CustomEditor`\n  * `addSource`\n* `mumuki.editor`\n  * `formatContent`\n  * `reset`\n  * `toggleFullscreen`\n* `mumuki.elipsis`\n  * `replaceHtml`\n* `mumuki.kids`\n  * `registerBlocksAreaScaler`\n  * `registerStateScaler`\n  * `restart`\n  * `scaleBlocksArea`\n  * `scaleState`\n  * `showResult`\n  * `showContext`\n* `mumuki.renderers`\n  * `SpeechBubbleRenderer`\n  * `renderSpeechBubbleResultItem`\n* `mumuki.locale`\n* `mumuki.exercise`\n   * `id`: the `id` of the currently loaded exercise, if any\n   * `layout`: the `layout` of the currently loaded exercise, if any\n* `mumuki.incognitoUser`: whether the current user is an incognito user\n* `mumuki.MultipleScenarios`\n  * `scenarios`\n  * `currentScenarioIndex`\n  * `resetIndicators`\n  * `updateIndicators`\n* `mumuki.multipleFileEditor`\n  * `setUpAddFile`\n  * `setUpDeleteFiles`\n  * `setUpDeleteFile`\n  * `updateButtonsVisibility`\n* `mumuki.submission`\n  * `processSolution`\n  * `sendSolution`\n  * `registerContentSyncer`\n* `mumuki.version`\n\n### Bridge Response Format\n\n```javascript\n{\n  \"status\": \"passed|passed_with_warnings|failed\",\n  \"guide_finished_by_solution\": \"boolean\",\n  \"html\": \"string\",\n  \"remaining_attempts_html\": \"string\",\n  \"current_exp\": \"integer\",\n  \"title_html\": \"string\",                         // kids-only\n  \"button_html\": \"string\",                        // kids-only\n  \"expectations\": [                               // kids-only\n    {\n      \"status\": \"passed|failed\",\n      \"explanation\": \"string\"\n    }\n  ],\n  \"tips\": [ \"string\" ],                           // kids-only\n  \"test_results\": [                               // kids-only\n    {\n      \"title\": \"string\",\n      \"status\": \"passed|failed\",\n      \"result\": \"string\",\n      \"summary\": \"string\"\n    }\n  ]\n}\n```\n\n### Kids Call order\n\n0. Laboratory Kids API Initialization\n1. Runner Editor JS\n2. Laboratory Kids Layout Initialization\n3. Runner Editor HTML\n\n## Generic event system\n\nLaboartory provides the `mumuki.events` object, which acts as minimal, generic event system, which is mostly designed for third party components built on top of laboratory and runners. It does nothing by default.\n\nThis API has two parts: consumers API and producers API.\n\n```javascript\n// ======\n// producer\n// ======\n\n// you need to call this method in order to enable registration of event handlers\n// otherwise, it will  be ignored\nmumuki.events.enable('myEvent');\n\n// fire the event, with an optional event object as payload\nmumuki.events.fire('myEvent', aPlainOldObject);\n\n// clear all the registered event handlers\nmumuki.events.clear('myEvent');\n\n// ========\n// consumer\n// ========\n\n// register an event handler\nmumuki.events.on('myEvent', (anEventObject) =\u003e {\n  // do stuff\n});\n```\n\n\n## Custom editors\n\nMumuki provides several editor types: code editors, multiple choice, file upload, and so on.\nHowever, some runners will require custom editors in order to provide better ways of entering\nsolutions.\n\nThe process to do so is not difficult, but tricky, since there are a few hooks you need to implement. Let's look at them:\n\n### 1. Before state: adding layout assets\n\nIf you need to provide a custom editor, chances are that you also need to provide assets to augment the layout, e.g. providing ways\nto render some custom components on descriptions or corollaries. That code will be included first.\n\nIn order to do that, add to your runner the layout html, css and js code. Layout code has no further requirements. It can customize any public selector previously.\n\nAlthough it is not required, it is recommended that your layout code works with any of the mumuki layouts:\n\n* `input_right`\n* `input_bottom`\n* `input_primary`\n* `input_kindergarten`\n\n:warning: Not all the selectors will be available to all layouts.\n\nThen expose code in the `MetadataHook`:\n\n```ruby\nclass ... \u003c Mumukit::Hook\n  def metadata\n    {\n      layout_assets_urls: {\n        js: [\n          'assets/....'\n        ],\n        css: [\n          'assets/....'\n        ],\n        html: [\n          'assets/....'\n        ]\n      }\n    }\n  end\nend\n```\n\nFinally, it is _recommended_ that you layout code calls `mumuki.assetsLoadedFor('layout')` when fully loaded.\n\nThat's it!\n\n### 2. Adding custom editor assets\n\nThe process for registering custom editors is more involving.\n\n#### 2.1 Add your assets and expose them\n\nAdd your js, css and html assets to your runner, and expose them in `MetadataHook`:\n\n```ruby\nclass ... \u003c Mumukit::Hook\n  def metadata\n    {\n      editor_assets_urls: {\n        js: [\n          'assets/....'\n        ],\n        css: [\n          'assets/....'\n        ],\n        html: [\n          'assets/....'\n        ]\n      }\n    }\n  end\nend\n```\n\nThese assets will only be loaded when the editor `custom` is used.\n\n#### 2.2 Add your components to the custom editor\n\nUsing JavaScript, append your components the custom-editor root, which can be found using the following selectors:\n\n* `mu-${languageName}-custom-editor`\n* `#mu-${languageName}-custom-editor`\n* `.mu-${languageName}-custom-editor`\n\n```javascript\n$('#mu-mylang-custom-editor').append(/* ... */)\n```\n\n#### 2.3 Extract the test\n\nIf necessary, read the test definition from `#mu-custom-editor-test`, and plump into your custom components\n\n```javascript\nconst test = $('#mu-custom-editor-test').val()\n//...use test...\n```\n\n#### 2.4 Exposing your content\n\nBefore sending a submission, mumuki needs to be able to your read you editor components\ncontents. There are two different approaches:\n\n* Register a syncer that writes `#mu-custom-editor-value` or any other custom editor selectors\n* Add one or more content sources\n\n```javascript\n// simplest method - you can register just one\nmumuki.editors.registerContentSyncer(() =\u003e {\n  // ... write here your custom component content...\n  $('#mu-custom-editor-value').val(/* ... */);\n});\n\n// alternate method\n// you can register many sources\nmumuki.editors.addCustomSource({\n  getContent() {\n    return { name: \"solution[content]\", value: /* ... */ } ;\n  }\n});\n```\n\n#### 2.5 Optional: Triggering submission processing programmatically\n\nYour solution will be automatically sent to the client and processed when the submit button is pressed.\nHowever, if you need to trigger the whole submission process programmatically,\ncall `mumuki.submission.processSolution`:\n\n```javascript\nmumuki.submission.processSolution({solution: {content: /* ... */}});\n```\n\n#### 2.6 Optional: Sending your solution to the server programmatically\n\nYour solution will be automatically sent to the client when the submit button is pressed, as part of the\nsolution processing. However, if you just need to send your submission to the server programmatically,\ncall `mumuki.submission.sendSolution`:\n\n```javascript\nmumuki.submission.sendSolution({solution: {content: /* ... */}});\n```\n\n#### 2.7 Optional: customizing your submit button\n\nYou can alternatively override the default submit button UI and behaviour, by replacing it with a custom component. In order to\ndo that, override the `.mu-submit-button` or the kids-specific `.mu-kids-submit-button`:\n\n```javascript\n $(\".mu-submit-button\").html(/* ... */);\n```\n\nHowever, doing this is tricky, since you will need to manually update the UI and connecting to the server. See:\n\n* `mumuki.kids.showResult`\n* `mumuki.bridge.Laboratory.runTests`\n* `mumuki.updateProgressBarAndShowModal`\n\n#### 2.8 Register kids scalers\n\nKids layouts have some special areas:\n\n * _state area_: its display initial and/or final states of the exercise\n * _blocks area_: a workspace that contains the building blocks of the solution - which are not necessary programming or blockly blocks, actually\n\nIf you want to support kids layouts, you **need** to register scalers that will be called when device is resized. Skip this step otherwise.\n\n```javascript\nmumuki.kids.registerStateScaler(($state, fullMargin, preferredWidth, preferredHeight) =\u003e {\n  // ... resize your components ...\n});\n\nmumuki.kids.registerBlocksAreaScaler(($blocks) =\u003e {\n  // ... resize your components ...\n});\n```\n\n#### 2.9 Notify when your assets have been loaded\n\nIn order to remove loading spinners, you will need to call `mumuki.assetsLoadedFor` when your code is ready.\n\n```javascript\nmumuki.assetsLoadedFor('editor');\n```\n\n## Transparent Navigation API Docs\n\nIn order to be able to link content, laboratory exposes slug-based routes that will redirect to the actual\ncontent URL in the current organization transparently:\n\n* `GET \u003corganization-url\u003e/topics/\u003corganization\u003e/\u003crepository\u003e`\n* `GET \u003corganization-url\u003e/guides/\u003corganization\u003e/\u003crepository\u003e`\n* `GET \u003corganization-url\u003e/exercises/\u003corganization\u003e/\u003crepository\u003e/\u003cbibliotheca-id\u003e`\n\n## REST API Docs\n\nBefore using the API, you must create an `ApiClient` using `rails c`, which will generate a private JWT. Use it to authenticate API calls in any Platform application within a `Authorizaion: Bearer \u003cTOKEN\u003e`.\n\nBefore using the API, take a look to the roles hierarchy:\n\n![roles hierarchy](https://yuml.me/diagram/plain/class/[Admin]%5E-[Janitor],[Admin]%5E-[Forum%20Supervisor],[Forum%20Supervisor]%5E-[Moderator],[Janitor]%5E-[Headmaster],[Headmaster]%5E-[Teacher],[Teacher]%5E-[Student],[Student]%5E-[Ex%20Student],[Admin]%5E-[Editor],[Editor]%5E-[Writer],[Owner]%5E-[Admin]).\n\nPermissions are bound to a scope, that states in which context the operation can be performed. Scopes are simply two-level contexts, expressed as slugss `\u003cfirst\u003e/\u003csecond\u003e`, without any explicit semantic. They exact meaning depends on the role:\n\n  * ex_student: `organization/course`\n  * student: `organization/course`\n  * teacher and headmaster: `organization/course`\n  * writer and editor: `organization/content`\n  * janitor: `organization/_`\n  * moderator and forum_supervisor: `organization/_`\n  * admin: `_/_`\n  * owner: `_/_`\n\n### Users\n\n#### Create single user\n\nThis is a generic user creation request.\n\n**Minimal permission**: `janitor`\n\n```\nPOST /users\n```\n\nSample request body:\n\n```json\n{\n  \"first_name\": \"María\",\n  \"last_name\": \"Casas\",\n  \"email\": \"maryK345@foobar.edu.ar\",\n  \"permissions\": {\n     \"student\": \"cpt/*:rte/*\",\n     \"teacher\": \"ppp/2016-2q\"\n  }\n}\n```\n\n#### Update single user\n\nThis is a way of updating user basic data. Permissions are ignored.\n\n**Minimal permission**: `janitor`\n\n```\nPUT /users/:uid\n```\n\nSample request body:\n\n```json\n{\n  \"first_name\": \"María\",\n  \"last_name\": \"Casas\",\n  \"email\": \"maryK345@foobar.edu.ar\",\n  \"uid\": \"maryK345@foobar.edu.ar\"\n}\n```\n\n#### Add student to course\n\nCreates the student if necessary, and updates her permissions.\n\n**Minimal permission**: `janitor`\n\n```\nPOST /courses/:organization/:course/students\n```\n\n```json\n{\n  \"first_name\": \"María\",\n  \"last_name\": \"Casas\",\n  \"email\": \"maryK345@foobar.edu.ar\",\n  \"uid\": \"maryK345@foobar.edu.ar\"\n}\n```\n**Response**\n```json\n{\n  \"uid\": \"maryK345@foobar.edu.ar\",\n  \"first_name\": \"María\",\n  \"last_name\": \"Casas\",\n  \"email\": \"maryK345@foobar.edu.ar\"\n}\n```\n**Forbidden Response**\n```json\n{\n  \"status\": 403,\n  \"error\": \"Exception\"\n}\n```\n\n#### Detach student from course\n\nRemove student permissions from a course.\n\n**Minimal permission**: `janitor`\n\n```\nPOST /courses/:organization/:course/students/:uid/detach\n```\n\n**Response**: status code: 200\n\n\n**Not Found Response**\n```json\n{\n  \"status\": 404,\n  \"error\": \"Couldn't find User\"\n}\n```\n\n#### Attach student to course\n\nAdd student permissions to a course.\n\n**Minimal permission**: `janitor`\n\n```\nPOST /courses/:organization/:course/students/:uid/attach\n```\n**Response**: status code: 200\n\n**Not Found Response**\n```json\n{\n  \"status\": 404,\n  \"error\": \"Couldn't find User\"\n}\n```\n\n\n#### Add teacher to course\n\nCreates the teacher if necessary, and updates her permissions.\n\n**Minimal permission**: `headmaster`, `janitor`\n\n```\nPOST /course/:id/teachers\n```\n\n```json\n{\n  \"first_name\": \"Erica\",\n  \"last_name\": \"Gonzalez\",\n  \"email\": \"egonzalez@foobar.edu.ar\",\n  \"uid\": \"egonzalez@foobar.edu.ar\"\n}\n```\n\n#### Add a batch of users to a course\n\nCreates every user if necesssary, an updates permissions.\n\n**Minimal permission**: `janitor`\n\n```\nPOST /course/:id/batches\n```\n\n```json\n{\n  \"students\": [\n    {\n      \"first_name\": \"Tupac\",\n      \"last_name\": \"Lincoln\",\n      \"email\": \"tliconln@foobar.edu.ar\",\n      \"uid\": \"tliconln@foobar.edu.ar\"\n    }\n  ],\n  \"teachers\": [\n    {\n      \"first_name\": \"Erica\",\n      \"last_name\": \"Gonzalez\",\n      \"email\": \"egonzalez@foobar.edu.ar\",\n      \"uid\": \"egonzalez@foobar.edu.ar\"\n    }\n  ]\n}\n```\n\n#### Detach student from course\n\n**Minimal permission**: `janitor`\n\n```\nDELETE /course/:id/students/:uid\n```\n\n#### Detach teacher from course\n\n**Minimal permission**: `janitor`\n\n```\nDELETE /course/:id/teachers/:uid\n```\n\n#### Destroy single user\n\n**Minimal permission**: `admin`\n\n```\nDELETE /users/:uid\n```\n\n### Courses\n\n#### Create single course\n\n**Minimal permission**: `janitor`\n\n```\nPOST /organization/:id/courses/\n```\n\n```json\n{\n   \"name\": \"....\",\n}\n```\n\n#### Archive single course\n\n**Minimal permission**: `janitor`\n\n```\nDELETE /organization/:id/courses/:id\n```\n\n#### Destroy single course\n\n**Minimal permission**: `admin`\n\n```\nDELETE /courses/:id\n```\n\n\n### Organizations\n\n#### Model\n\n### Mandatory fields\n```json\n{\n  \"name\": \"academy\",\n  \"contact_email\": \"issues@mumuki.io\",\n  \"books\": [\n    \"MumukiProject/mumuki-libro-metaprogramacion\"\n  ],\n  \"locale\": \"es-AR\"\n}\n```\n\n### Optional fields\n```json\n{\n  \"public\": false,\n  \"description\": \"...\",\n  \"login_methods\": [\n    \"facebook\", \"twitter\", \"google\"\n  ],\n  \"logo_url\": \"http://mumuki.io/logo-alt-large.png\",\n  \"terms_of_service\": \"Al usar Mumuki aceptás que las soluciones de tus ejercicios sean registradas para ser corregidas por tu/s docente/s...\",\n  \"theme_stylesheet\": \".theme { color: red }\",\n  \"extension_javascript\": \"doSomething = function() { }\"\n}\n```\n\n- If you set `null` to `public`, `login_methods`, the values will be `false` and `[\"user_pass\"].\n- If you set `null` to `description`, the value will be `null`.\n- If you set `null` to the others, it will be inherited from an organization called `\"base\"` every time you query the API.\n\n\n### Generated fields\n```json\n{\n  \"theme_stylesheet_url\": \"stylesheets/academy-asjdf92j1jd8.css\",\n  \"extension_javascript_url\": \"javascripts/academy-jd912j8jdj19.js\"\n}\n```\n\n#### List all organizations\n\n```\nget /organizations\n```\n\nSample response body:\n\n```json\n{\n  \"organizations\": [\n    { \"name\": \"academy\", \"contact_email\": \"a@a.com\", \"locale\": \"es-AR\", \"login_methods\": [\"facebook\"], \"books\": [\"libro\"], \"public\": true, \"logo_url\": \"http://...\" },\n    { \"name\": \"alcal\", \"contact_email\": \"b@b.com\", \"locale\": \"en-US\", \"login_methods\": [\"facebook\", \"github\"], \"books\": [\"book\"], \"public\": false }\n  ]\n}\n```\n**Minimal permission**: None for public organizations, `janitor` for user's private organizations.\n\n#### Get single organization by name\n\n```\nget /organizations/:name\n```\n\nSample response body:\n\n```json\n{ \"name\": \"academy\", \"contact_email\": \"a@a.com\", \"locale\": \"es-AR\", \"login_methods\": [\"facebook\"], \"books\": [\"libro\"], \"public\": true, \"logo_url\": \"http://...\" }\n```\n**Minimal permission**: `janitor` of the organization.\n\n#### Create organization\n\n```\npost /organizations\n```\n... with at least the required fields.\n\n**Minimal permission**: `admin` of that organization\n\n#### Update organization\n\n```\nput /organizations/:name\n```\n... with a partial update.\n\n**Minimal permission**: `admin` of `:name`\n\n\n## Authentication Powered by Auth0\n\n\u003ca width=\"150\" height=\"50\" href=\"https://auth0.com/\" target=\"_blank\" alt=\"Single Sign On \u0026 Token Based Authentication - Auth0\"\u003e\u003cimg width=\"150\" height=\"50\" alt=\"JWT Auth for open source projects\" src=\"http://cdn.auth0.com/oss/badges/a0-badge-dark.png\"/\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmumuki%2Fmumuki-laboratory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmumuki%2Fmumuki-laboratory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmumuki%2Fmumuki-laboratory/lists"}