{"id":18303125,"url":"https://github.com/monzo/web-exercise","last_synced_at":"2025-04-09T10:14:42.493Z","repository":{"id":78419215,"uuid":"241601289","full_name":"monzo/web-exercise","owner":"monzo","description":"Take-home exercise for web engineer hiring 🚀","archived":false,"fork":false,"pushed_at":"2020-02-20T14:02:39.000Z","size":6,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-15T04:26:11.350Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monzo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-19T11:09:07.000Z","updated_at":"2024-01-28T21:00:09.000Z","dependencies_parsed_at":"2023-04-24T09:14:13.319Z","dependency_job_id":null,"html_url":"https://github.com/monzo/web-exercise","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monzo%2Fweb-exercise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monzo%2Fweb-exercise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monzo%2Fweb-exercise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monzo%2Fweb-exercise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monzo","download_url":"https://codeload.github.com/monzo/web-exercise/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018070,"owners_count":21034048,"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":[],"created_at":"2024-11-05T15:24:17.816Z","updated_at":"2025-04-09T10:14:42.474Z","avatar_url":"https://github.com/monzo.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Build a developer portal!\n\nYour task is to build a browser-based developer portal in JavaScript against a dummy API that we've created! 🎉\n\nYour portal must meet the following requirements:\n\n- The user can log into the portal using email and password\n- If the user's access token (obtained during login) expires at any point during a session, the user should be asked to re-authenticate via email and password\n- The user can list and update their apps\n- For each app, the user can page through a sub-list of the app's users\n\nOther than that, you can build the app **however you want** :) Feel free to host your submission on GitHub or send it in zipped up via email.\n\nThe data model is designed to resemble the kinds of data many of our internal tools as well as our [actual developer tools](https://monzo.com/blog/2016/02/03/mondo-api/) use.\n\nYou shouldn't spend more than 4 hours on this and not everything has to work. The main objective is to see how you approach things and have something to talk about in our interview :)\n\nPlease note down any changes that you would make if you would have more time.\n\n# API\n\nWe've put up a mock API server at https://guarded-thicket-22918.herokuapp.com/.\n\n### Authentication\n\nAll API requests (except for logins) are authenticated by passing an access token in via the `\"Authorization\"` HTTP header. If the token is missing, invalid or expired the API returns `401 Unauthorized`.\n\nAccess tokens expire after a certain amount of time (defaults is 30m) but you can override this during login.\n\nYou can check whether or not an access token is valid and not expired by hitting `GET /`.\n\n### Obtaining an access token\n\nYou obtain an access token by making JSON-encoded POST request to `/login` with the following properties:\n- `email`: You can put whatever you want here. Each `email` gets its own little database of mock data.\n- `password`: If the password is \"hunter2\", the login will succeed. Otherwise it will fail with status 401.\n- `expiry`: Optionally, you can pass in an expiration timespan in [rauchg/ms](https://github.com/rauchg/ms.js) format (e.g. `\"60s\"`, `\"10h\"`, etc). This is useful for testing your re-authentication code.\n\nFor example:\n```bash\n# Obtain an access token\ncurl -H \"Content-Type: application/json\" -X POST -d '{\"email\":\"mondo@example.com\",\"password\":\"hunter2\"}' https://guarded-thicket-22918.herokuapp.com/login\n# Status: 200\n# {\n#     \"accessToken\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTMzMDc4LCJleH# AiOjE0NTQ1MzQ4Nzh9.9nnNyJaR-oZeOjlGFUrimSuLzRUJ3kfzuxbQwTuODBg\"\n# }\n\n# Test your access token\ncurl -H \"Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTMzMDc4LCJleHAiOjE0NTQ1MzQ4Nzh9.9nnNyJaR-oZeOjlGFUrimSuLzRUJ3kfzuxbQwTuODBg\" https://guarded-thicket-22918.herokuapp.com/\n# Status: 401\n# {\n#     \"message\": \"The API is alive and your access token is valid :)\",\n#     \"token\": {\n#         \"email\": \"mondo@example.com\",\n#         \"iat\": 1454533078,\n#         \"exp\": 1454534878\n#     }\n# }\n\n# Login failure\n$ curl -H \"Content-Type: application/json\" -X POST -d '{\"email\":\"mondo@example.com\",\"password\":\"not hunter2\"}' https://guarded-thicket-22918.herokuapp.com/login\n# Status: 200\n# {\n#     \"error\": \"Cannot log in with the given email and password.\"\n# }\n\n# Get a short-lived access token to test re-authentication\ncurl -H \"Content-Type: application/json\" -X POST -d '{\"email\":\"mondo@example.com\",\"password\":\"hunter2\",\"expiry\":\"10s\"}' https://guarded-thicket-22918.herokuapp.com/login\n```\n\n### Apps and their users!\n\nThe developer portal deals with two domain models: `apps` and their `users`. The API has three endpoints:\n\n`GET /apps` returns a list of all apps. This list doesn't include the apps' users.\n\n`PUT /apps/$appId` updates an app and returns the updated app. You can change the `name` and `logo` properties.\n\n`GET /apps/$appId/users?limit=25\u0026offset=0` returns a list of users for a given app. This list can be quite long so you'll need to implement paging using the limit and offset parameters. The API doesn't support returning more than 25 results at once.\n\nExample calls and data model:\n```bash\n# List all apps (after obtaining an access token as described above)\ncurl -H \"Authorization: $token\" https://guarded-thicket-22918.herokuapp.com/apps\n# {\n#     \"apps\": [\n#         {\n#             \"id\": \"ebdb9723-39ba-4157-9d36-aa483581aa13\",\n#             \"name\": \"Intelligent Steel Car\",\n#             \"created\": \"2016-01-25T03:57:53.873Z\",\n#             \"logo\": \"http://lorempixel.com/400/400/animals\"\n#         },\n#         // and so on...\n#     ]\n# }\n\n# Update an app\ncurl -H \"Content-Type: application/json\" -H \"Authorization: $token\" -X PUT -d '{\"name\":\"New Name\"}' https://guarded-thicket-22918.herokuapp.com/apps/ebdb9723-39ba-4157-9d36-aa483581aa13\n# {\n#     \"app\": {\n#         \"id\": \"ebdb9723-39ba-4157-9d36-aa483581aa13\",\n#         \"name\": \"New Name\",\n#         \"created\": \"2016-01-25T03:57:53.873Z\",\n#         \"logo\": \"http://lorempixel.com/400/400/animals\"\n#     }\n# }\n\n# List users of an app (first page of 25 users)\ncurl -H \"Authorization: $token\" https://guarded-thicket-22918.herokuapp.com/apps/ebdb9723-39ba-4157-9d36-aa483581aa13/users\n# {\n#     \"users\": [\n#         {\n#             \"id\": \"6b09a204-0653-4303-9370-222b06c478a8\",\n#             \"name\": \"Madeline Runte\",\n#             \"email\": \"Viviane.Beatty58@yahoo.com\",\n#             \"avatar\": \"https://s3.amazonaws.com/uifaces/faces/twitter/chrisstumph/128.jpg\"\n#         },\n#         {\n#             \"id\": \"f73b5837-6035-40d8-8008-c0e71605670b\",\n#             \"name\": \"Marlin Goodwin\",\n#             \"email\": \"Zechariah.Fisher@yahoo.com\",\n#             \"avatar\": \"https://s3.amazonaws.com/uifaces/faces/twitter/HenryHoffman/128.jpg\"\n#         },\n#         // and so on...\n#     ]\n# }\n\n# List users of an app (second page of 25 users)\ncurl -H \"Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTM1MDg4LCJleHAiOjE0NTQ1MzY4ODh9.7ehzJgS_OojT37j076I05l1ZNKc62AKOpL-aeqR0GkM\" https://guarded-thicket-22918.herokuapp.com/apps/ebdb9723-39ba-4157-9d36-aa483581aa13/users?offset=25\n```\n\n# What we look for\n\nWe picked this code test because it allows us to understand a number of different things:\n\n- How do you structure your code?\n- Are you comfortable implementing more technical bits such as authentication and pagination?\n- Does that app look all right visually? We're not looking for any fancy designs but we're a product-driven company and like it when things look nice.\n\nLastly, we'll use the code-test as a basis to discuss some aspects of web frontend development in more detail later in the interview process :)\n\nHappy hacking!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonzo%2Fweb-exercise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonzo%2Fweb-exercise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonzo%2Fweb-exercise/lists"}