Skip to content
This repository was archived by the owner on Jul 18, 2023. It is now read-only.

Commit b36b3af

Browse files
Initial commit: Planning poker challenge
0 parents  commit b36b3af

22 files changed

+15618
-0
lines changed

README.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Workana Hiring challenge
2+
3+
Hi!
4+
5+
We are looking for great PHP and Javascript developers to join our team.
6+
Instead of going through a ~~boring~~ long interview process, we decided that code often speaks for itself.
7+
If you're up to the challenge, please take a couple of hours to play with this challenge and submit your solution.
8+
9+
## The Planning Poker Lobby
10+
11+
[![See demo interface](https://user-images.githubusercontent.com/281727/100144788-13509980-2e76-11eb-8ae4-264f94928225.png)](https://codepen.io/emilioastarita/pen/NWRKWwv)
12+
13+
This time let's build a little [planning poker](https://en.wikipedia.org/wiki/Planning_poker) system. The exercise
14+
will work in multiple levels (while you're
15+
not supposed to work on all of them we hope that you can handle at least one with a good level of expertise).
16+
17+
### At backend layer you can choose among three options:
18+
19+
- **Mocked Service** mock your responses in the client side using async functions (use this option if you are front developer)
20+
- **Node Js Backend** Here you can use express, implement your own server or any framework of your preference. If you go with this option
21+
we expect to see more realtime features.
22+
- **PHP Server** If you go with PHP please pick a light framework. We expect to see more enterprise level software with robust error handling at API level.
23+
Bonus if you use PHP 8 features.
24+
25+
26+
### Backend endpoints to implement
27+
28+
Let's build a REST API with the following endpoints. Feel free to change some things these
29+
descriptions are only for guidance.
30+
31+
##### `POST /issue/{:issue}/join` - Used to join `{:issue}`.
32+
- If issue not exists generate a new one.
33+
- Must receive a payload with the intended name. ie: `{"name": "florencia"}`
34+
- Feel free to use a session or token to keep identified the user in subsequent requests.
35+
36+
##### `POST /issue/{:issue}/vote` - Used to vote `{:issue}`. Must receive a payload with the vote value.
37+
- Reject votes when status of `{:issue}` is not `voting`.
38+
- Reject votes if user not joined `{:issue}`.
39+
- Reject votes if user already `voted` or `passed`.
40+
41+
##### `GET /issue/{:issue}` - Returns the status of issue
42+
Because during `voting` status the votes are secret you must hide each vote until all members voted.
43+
- Issue is `voting`:
44+
````json
45+
{
46+
"status": "voting",
47+
"members": [
48+
{"name": "florencia", "status": "voted"},
49+
{"name": "kut", "status": "waiting"},
50+
{"name": "lucho", "status": "passed"}
51+
]
52+
}
53+
````
54+
- Issue is `reveal` when all users emitted their votes:
55+
````json
56+
{
57+
"status": "reveal",
58+
"members": [
59+
{"name": "florencia", "status": "voted", "value": 20},
60+
{"name": "kut", "status": "voted", "value": 20},
61+
{"name": "lucho", "status": "passed"}
62+
],
63+
"avg": 20
64+
}
65+
````
66+
67+
#### Realtime
68+
69+
If you are implementing backend with node could be nice to have a collection of events notifying clients about new votes
70+
and changes using some kind of realtime technology of your choice as websockets or long-polling.
71+
72+
#### Persistence
73+
74+
For persistence use the `redis` service provided in docker-compose and choose the best combination of operation/data structures
75+
that serves for your solution.
76+
77+
If you don't want to mess too much with the backend, and you are in the node path you can use `in memmory` persistence but please
78+
provide some kind of abstraction around your store.
79+
80+
81+
**Bonus points** if you can provide some hints around horizontal scalability of the backend service using `redis` features.
82+
83+
84+
### Frontend
85+
86+
Provide an interface to use the system.
87+
88+
89+
If you are backend developer and don't want to build a front give us a bunch of `curl`s showing how to query
90+
the status and how to do some votes.
91+
92+
If you want to work on frontend use Vue 2 or Vue 3 to construct an interface:
93+
94+
- Take a look at [our Codepen](https://codepen.io/emilioastarita/pen/NWRKWwv) if you are looking for some inspiration or ideas.
95+
- Create or join an issue by number
96+
- Show board with cards for voting
97+
- Show a list of members and the status of each one
98+
- Allow users to vote, pass or leave the issue
99+
- Bonus points if you handle client side routing (you can use libs)
100+
101+
If you prefer to work only on front side no problem! Just fake the data using a bunch of async local functions and handle
102+
a global state holding your data at the root component.
103+
104+
```javascript
105+
async function getMembers() {
106+
return [
107+
{"name": "florencia", "status": "voted", "value": 20},
108+
{"name": "kut", "status": "voted", "value": 20},
109+
{"name": "lucho", "status": "passed"}
110+
];
111+
}
112+
```
113+
114+
We are interested to know how you work and if you are able to produce quality code, so take some time to think around
115+
details and put some effort to treat errors with robustness.
116+
Feel free to guide us to review your code and explain where you put more effort
117+
or what you were thinking when you take the key design decisions.
118+
119+
#### Some considerations:
120+
- The demo is in a single component, but it's better if you can use many, and demonstrate how would you communicate between them. "Divide and Conquer" :muscle:
121+
- Try to use good conventions and semantically correct names for variables & functions.
122+
- Take advantage of vue reactivity with computed properties and its two-way data-binding :twisted_rightwards_arrows:
123+
124+
## Get up and running
125+
126+
To run this code you need:
127+
- [Docker](https://www.docker.com/get-started) and [docker-compose](https://docs.docker.com/compose/install/) installed
128+
129+
Then:
130+
- Clone this repo: `git clone [email protected]:Workana/hiring_challenge.git`.
131+
- Run `docker-compose up`.
132+
133+
Check if services are up and running:
134+
- Node backend in [localhost:8082](http://localhost:8082/issue/234)
135+
- PHP backend in [localhost:8081](http://localhost:8081/issue/234)
136+
- Front dev server with demo in [localhost:8080](http://localhost:8080/)
137+
138+
139+
## What we would like you to do?
140+
141+
Download this repo. Code it your way. Choose what parts of the system you want to implement and put your best effort doing it.
142+
143+
- Some unit testing is mandatory.
144+
- Although we love other languages too, we prefer if you stick to PHP, Javascript or Typescript, as these are
145+
Workana main languages.
146+
147+
148+
149+
## Submission
150+
151+
Please don't submit Pull Requests. After you're done, please email to [[email protected]](mailto:[email protected])
152+
with the link to your fork, so we can start talking =)
153+
154+
Thanks a lot and happy coding!
155+

backend/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.idea/
2+
.vscode/
3+
/coverage/
4+
/vendor/
5+
/logs/*
6+
!/logs/README.md
7+
.phpunit.result.cache

backend/app/middleware.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
use Slim\App;
3+
use Psr\Http\Message\ResponseInterface as Response;
4+
use Psr\Http\Message\ServerRequestInterface as Request;
5+
use Psr\Http\Server\RequestHandlerInterface;
6+
use Slim\Routing\RouteContext;
7+
8+
return function (App $app) {
9+
$app->add(function (Request $request, RequestHandlerInterface $handler): Response {
10+
$routeContext = RouteContext::fromRequest($request);
11+
$routingResults = $routeContext->getRoutingResults();
12+
$methods = $routingResults->getAllowedMethods();
13+
$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');
14+
$response = $handler->handle($request);
15+
$response = $response->withHeader('Access-Control-Allow-Origin', '*');
16+
$response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
17+
$response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);
18+
return $response;
19+
});
20+
$app->addBodyParsingMiddleware();
21+
$app->addRoutingMiddleware();
22+
};

backend/app/routes.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
use Psr\Http\Message\ResponseInterface as Response;
3+
use Psr\Http\Message\ServerRequestInterface as Request;
4+
5+
return function (Slim\App $app) {
6+
$app->get('/issue/{number}', function (Request $request, Response $response, array $args) {
7+
$number = $args['number'];
8+
$response->getBody()->write(json_encode(['issue' => $number]));
9+
return $response->withHeader('Content-Type', 'application/json');
10+
});
11+
};

backend/composer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "workana/hiring_challenge",
3+
"description": "Workana Dev Challenge",
4+
"require": {
5+
"php": "^7.4",
6+
"ext-json": "*",
7+
"slim/psr7": "^1.2",
8+
"slim/slim": "^4.5"
9+
},
10+
"require-dev": {
11+
"phpunit/phpunit": "^9.4.3"
12+
},
13+
"config": {
14+
"process-timeout": 0,
15+
"sort-packages": true
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"App\\": "app/"
20+
}
21+
},
22+
"autoload-dev": {
23+
"psr-4": {
24+
"Tests\\": "tests/"
25+
}
26+
},
27+
"scripts": {
28+
"test": "phpunit"
29+
}
30+
}

0 commit comments

Comments
 (0)