└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Build a developer portal! 2 | 3 | Your task is to build a browser-based developer portal in JavaScript against a dummy API that we've created! 🎉 4 | 5 | Your portal must meet the following requirements: 6 | 7 | - The user can log into the portal using email and password 8 | - 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 9 | - The user can list and update their apps 10 | - For each app, the user can page through a sub-list of the app's users 11 | 12 | Other 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. 13 | 14 | The 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. 15 | 16 | You 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 :) 17 | 18 | Please note down any changes that you would make if you would have more time. 19 | 20 | # API 21 | 22 | We've put up a mock API server at https://guarded-thicket-22918.herokuapp.com/. 23 | 24 | ### Authentication 25 | 26 | All 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`. 27 | 28 | Access tokens expire after a certain amount of time (defaults is 30m) but you can override this during login. 29 | 30 | You can check whether or not an access token is valid and not expired by hitting `GET /`. 31 | 32 | ### Obtaining an access token 33 | 34 | You obtain an access token by making JSON-encoded POST request to `/login` with the following properties: 35 | - `email`: You can put whatever you want here. Each `email` gets its own little database of mock data. 36 | - `password`: If the password is "hunter2", the login will succeed. Otherwise it will fail with status 401. 37 | - `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. 38 | 39 | For example: 40 | ```bash 41 | # Obtain an access token 42 | curl -H "Content-Type: application/json" -X POST -d '{"email":"mondo@example.com","password":"hunter2"}' https://guarded-thicket-22918.herokuapp.com/login 43 | # Status: 200 44 | # { 45 | # "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTMzMDc4LCJleH# AiOjE0NTQ1MzQ4Nzh9.9nnNyJaR-oZeOjlGFUrimSuLzRUJ3kfzuxbQwTuODBg" 46 | # } 47 | 48 | # Test your access token 49 | curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTMzMDc4LCJleHAiOjE0NTQ1MzQ4Nzh9.9nnNyJaR-oZeOjlGFUrimSuLzRUJ3kfzuxbQwTuODBg" https://guarded-thicket-22918.herokuapp.com/ 50 | # Status: 401 51 | # { 52 | # "message": "The API is alive and your access token is valid :)", 53 | # "token": { 54 | # "email": "mondo@example.com", 55 | # "iat": 1454533078, 56 | # "exp": 1454534878 57 | # } 58 | # } 59 | 60 | # Login failure 61 | $ curl -H "Content-Type: application/json" -X POST -d '{"email":"mondo@example.com","password":"not hunter2"}' https://guarded-thicket-22918.herokuapp.com/login 62 | # Status: 200 63 | # { 64 | # "error": "Cannot log in with the given email and password." 65 | # } 66 | 67 | # Get a short-lived access token to test re-authentication 68 | curl -H "Content-Type: application/json" -X POST -d '{"email":"mondo@example.com","password":"hunter2","expiry":"10s"}' https://guarded-thicket-22918.herokuapp.com/login 69 | ``` 70 | 71 | ### Apps and their users! 72 | 73 | The developer portal deals with two domain models: `apps` and their `users`. The API has three endpoints: 74 | 75 | `GET /apps` returns a list of all apps. This list doesn't include the apps' users. 76 | 77 | `PUT /apps/$appId` updates an app and returns the updated app. You can change the `name` and `logo` properties. 78 | 79 | `GET /apps/$appId/users?limit=25&offset=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. 80 | 81 | Example calls and data model: 82 | ```bash 83 | # List all apps (after obtaining an access token as described above) 84 | curl -H "Authorization: $token" https://guarded-thicket-22918.herokuapp.com/apps 85 | # { 86 | # "apps": [ 87 | # { 88 | # "id": "ebdb9723-39ba-4157-9d36-aa483581aa13", 89 | # "name": "Intelligent Steel Car", 90 | # "created": "2016-01-25T03:57:53.873Z", 91 | # "logo": "http://lorempixel.com/400/400/animals" 92 | # }, 93 | # // and so on... 94 | # ] 95 | # } 96 | 97 | # Update an app 98 | curl -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 99 | # { 100 | # "app": { 101 | # "id": "ebdb9723-39ba-4157-9d36-aa483581aa13", 102 | # "name": "New Name", 103 | # "created": "2016-01-25T03:57:53.873Z", 104 | # "logo": "http://lorempixel.com/400/400/animals" 105 | # } 106 | # } 107 | 108 | # List users of an app (first page of 25 users) 109 | curl -H "Authorization: $token" https://guarded-thicket-22918.herokuapp.com/apps/ebdb9723-39ba-4157-9d36-aa483581aa13/users 110 | # { 111 | # "users": [ 112 | # { 113 | # "id": "6b09a204-0653-4303-9370-222b06c478a8", 114 | # "name": "Madeline Runte", 115 | # "email": "Viviane.Beatty58@yahoo.com", 116 | # "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/chrisstumph/128.jpg" 117 | # }, 118 | # { 119 | # "id": "f73b5837-6035-40d8-8008-c0e71605670b", 120 | # "name": "Marlin Goodwin", 121 | # "email": "Zechariah.Fisher@yahoo.com", 122 | # "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/HenryHoffman/128.jpg" 123 | # }, 124 | # // and so on... 125 | # ] 126 | # } 127 | 128 | # List users of an app (second page of 25 users) 129 | curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6Im1vbmRvQGV4YW1wbGUuY29tIiwiaWF0IjoxNDU0NTM1MDg4LCJleHAiOjE0NTQ1MzY4ODh9.7ehzJgS_OojT37j076I05l1ZNKc62AKOpL-aeqR0GkM" https://guarded-thicket-22918.herokuapp.com/apps/ebdb9723-39ba-4157-9d36-aa483581aa13/users?offset=25 130 | ``` 131 | 132 | # What we look for 133 | 134 | We picked this code test because it allows us to understand a number of different things: 135 | 136 | - How do you structure your code? 137 | - Are you comfortable implementing more technical bits such as authentication and pagination? 138 | - 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. 139 | 140 | Lastly, 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 :) 141 | 142 | Happy hacking! 143 | --------------------------------------------------------------------------------