Welcome
Thank you for taking part in this coding exercise! We're looking forward to seeing how you complete the tasks.
- Clone this repository or download the source
- Follow the steps in Setup to get the application running on your machine
- Get familiar with the application code - A tour of the application
- Complete the Tasks
- Submit your modified code as a zip file to [email protected]
- We will get back to you with outcomes and feedback as soon as possible
If you have any issues during setup, please contact [email protected] immediately! We know how many issues can arise when trying to get someone else's code running and don't want you getting stuck on it unnecessarily :)
And please reach out to Daniel if any of the tasks are unclear or you need some assistance.
Screenshot of the demo application
Contents of this readme
We have provided a Docker Compose based setup for the frontend and backend of this application.
If you already have docker-compose
installed, it should be really easy to run.
If you don't want to use docker, skip down to without docker and follow the instructions.
These instructions have been tested with Linux (Ubuntu) and OSX. We haven't tested this on Windows, but we hope the Docker Compose setup should work there!
In a terminal in this directory, bring up the docker compose services:
docker-compose up -d
The first time you run this, both services will need to install dependencies. You can watch the logs of this happening with
docker-compose logs -f
When you have seen both these messages, the app will be ready to use:
backend_1 | Laravel development server started: http://0.0.0.0:11111
frontend_1 | App running at:
frontend_1 | - Local: http://localhost:11112/
You can then view the frontend by visiting http://localhost:11112.
- Install PHP (at least 7.1)
- Install Composer
- Install Node and NPM (we recommend Node 12)
The entrypoint.sh
script will perform all first-time setup for you:
cd backend
./entrypoint.sh
Once the script has run, open http://localhost:11111/api/solar_design in your browser to verify the server has installed correctly.
The frontend also has an entrypoint script:
cd frontend
./entrypoint.sh
You'll need to run this in parallel with the backend script (i.e. in a separate terminal). Once the installation has finished, open http://localhost:11112 to verify the application has built correctly.
The example application is a very simple solar project management tool. The application keeps a list of your solar design projects, and a list of your contacts. Contacts might be customers, your salespeople, or installer subcontractors.
Projects and contacts have a many-many relationship between them; it's not uncommon for customers to order multiple commercial solar systems if they manage more than one building, and of course your salespeople and installers might be assigned to more than one project.
The application as it stands can do a few things:
- Show a list of solar projects
- Show the details of a solar project and the contacts assigned to it
- Delete a project
The API is capable of more actions, but the frontend doesn't have everything implemented yet.
The backend server is implemented using Laravel 6.
It is backed by a SQLite database to make it easier to run locally.
(If you want to browse the contents of the database after running the backend, e.g. using DB Browser, the file is created in backend/database/dev.sqlite
.)
We have implemented the backend's API with hyperlinked responses. For example, here's what a response from the /contacts
endpoint might look like:
{
"data": [
{
"type": "contacts",
"id": "fe5c6852-bfa1-3c6d-9fb6-1ce730f45981",
"links": {
"self": {
"href": "http://localhost:11111/api/contacts/fe5c6852-bfa1-3c6d-9fb6-1ce730f45981"
}
}
},
{
"type": "contacts",
"id": "bbd3e9ec-4a67-37b9-baa4-4b210bcf54b9",
"links": {
"self": {
"href": "http://localhost:11111/api/contacts/bbd3e9ec-4a67-37b9-baa4-4b210bcf54b9"
}
}
}
]
}
The self
links can be followed to view the attributes of an individual contact.
If you have a JSON viewer browser addon, you should be able to click through these links and browse the API for yourself.
While this isn't fully HATEOAS, it's a good start!
In order to see the routes defined for the backend API, run this command:
docker-compose exec backend php artisan route:list
(If you're not running the backend in docker, you should of course omit everythng before php
.)
Most of these routes are defined in backend/routes/api.php
.
From there, you can look up the relevant controller classes.
(Files are named after the class they contain, so in order to find e.g. a controller class like ContactsController
, searching for a file with the name ContactsController.php
.)
Route model binding is used extensively in our API controllers. For example, the following method:
public function show(Contact $contact)
Because Contact
is type-hinted, will look for a route variable named contact
(after the parameter's variable name) and try to find this database model and inject it into the controller.
Models (in the backend/app/Models
folder) are used by the Eloquent ORM to interact with database tables.
They contain relationships and other logic related to the database structure.
You'll notice that the controllers use a combination of Models and Resources to serve responses from the API. For example:
$contact = Contact::create($data);
return new ContactResource($contact);
The Resource classes are responsible for rendering a Model in an appropriate way for the API. Think of them as like Blade templates but for JSON responses. Read more about them here.
We have a small test suite that lives in backend/tests/Feature
.
It covers some of the existing API.
Run the tests with the command:
docker-compose exec backend composer test
Tests run against your existing database, so be aware that some of your data may be altered.
If you wish to reset your database with fresh random data, run:
docker-compose exec backend php artisan migrate:fresh
docker-compose exec backend php artisan db:seed
See the backend/database/seeds
and backend/database/factories
directories for the code that generates sample data.
The frontend is a very small Vue application.
Start in frontend/index.js
and follow the imports to discover the components that make the application work.
The application is built and served using the Vue CLI. If everything goes well you won't have to worry about how it does that! Hot reloading should work out of the box.
We estimate these tasks should take 4-5 hours. Task 5 is significantly more complex than 4, so only attempt it if you have time left.
We won't be evaluating you just on your completion rate; we'd like to see quality code and thoughtful comments. If you find yourself working beyond the 4 hour mark, feel free to just write down the way you would approach the tasks even if the code isn't written. This might include method stubs, etc.
The first two tasks (bug fixes) are intended to help you start finding your way around the codebase in a practical way.
Tips:
- If you know you're deliberately skipping something you think is relevant, a complicated edge case, etc., leave a TODO comment.
- Comment your code a little more than you usually would; we'd like to see some of your thought process as you design solutions!
- Don't refactor the existing application structure; try to work within it as much as possible.
- You don't have to add additional tests that aren't mentioned in the requirements.
- On the frontend, don't worry about compatibility with browsers that aren't the latest Chrome or Firefox. Write modern JavaScript and assume code will be transpiled appropriately.
- When attempting to update a single contact using a PUT request, the
last_name
property is never updated - Add a failing test for this scenario, then fix the backend code so the test passes
This API is not used from the frontend until/unless you complete the extended Task 4, so you can proceed with frontend tasks first if you prefer!
- Currently, at http://localhost:11112/contacts, contacts "fill up" the table incrementally. We would prefer if they all appeared together
- Rewrite the
fetchContacts
method inListContacts.vue
so that contacts are only displayed once all contacts have been fetched from the API - Using
await
is not required, but preferred
The frontend has two main list views, the list of projects and the list of contacts. They are similar in that they both fetch a list of objects from the server, making AJAX requests to the API for each item. However, we wish for them to behave slightly differently.
The projects view loads up each item and renders it as soon as possible, resulting in the list "filling up" as AJAX requests finish. The contacts view does the same, but because it is laid out using a table, the rows "filling up" cause the column widths to wiggle in an ugly way. We'd much rather than the contacts view loads all items at the same time, so contacts will appear "as a block" rather than "filling up".
This requires changing the code in the fetchContacts
method in ListContacts.vue
.
We tried to make it wait for all contacts to finish their AJAX requests before assigning them all to this.contacts
on the final line of the method, but for some reason the contacts still "fill up" the table one at a time!
Please fix this method so that the contacts all appear at the same time.
- Implement an API that can "bulk delete" projects with a constant number of AJAX requests (not necessarily 1 request, but fewer than n requests for n projects!)
- Justify your API design with a few brief sentences or bullet points
- Implement a test for this API in the
backend/tests/Features
directory - Don't implement a frontend for this feature
In order to support more convenient API usage, we want to introduce a 'bulk delete' ability. Bulk operations in REST APIs are a contentious issue with no clear solution. We're keen to see what you come up with!
The ideal solution should either:
- take place inside a single database transaction so that partial deletions don't happen,
- or have some method for communicating partial success to the frontend.
The SolarProject
model uses soft deletion, so you don't need to worry about deleting related rows in other tables; the project will remain in the database with its deleted_at
date set so it doesn't appear in ORM queries.
- There is an 'edit project' link from the individual project page; implement the route and component that will allow the user to edit the project's properties
- All the server API endpoints to update projects using PUT (whole resource) or PATCH (partial update) are already implemented
- Note that the
system_size
field must be saved as a number ornull
(allowing the user to remove the size)
- Implement contact editing on the project edit page (edit contact details, remove contact from project, add new contact)
- This must be able to edit all contacts at the same time as the project details, and have a single 'save' button to perform all modifications to the objects
- Don't implement a new 'bulk save' API on the server; use the existing API endpoints