Skip to content

Commit

Permalink
Merge pull request #2 from felipe-loka/feature/limit-max-number-of-ch…
Browse files Browse the repository at this point in the history
…oices

limit max number of choices
  • Loading branch information
felipe-loka authored Nov 9, 2023
2 parents a37673e + 005f621 commit faffbe9
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 13 deletions.
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Poll API

The code in this repository corresponds to an API responsible for managing a basic poll system.

## Stack

The code is written in Typescript using NodeJS as the JavaScript Runtime environment. The following packages are used in this project:

- [ExpressJS](https://expressjs.com/): Famous web framework used to build the API.
- [mysql2](https://github.com/sidorares/node-mysql2): MySQL driver used to connect to the MySQL database.
- [zod](https://zod.dev/): Schema validation tool to validate user input.
Expand All @@ -16,25 +19,25 @@ The architecture of this project is simple: an API that uses MySQL as the databa

![Architecture](/docs/architecture.png)


## Getting Started

This project uses [pnpm](https://pnpm.io/) as the package manager, so this must be installed in your machine in order to start contributing to this project.

Several helpful script are present in the `package.json` file, such as: create the infrastructure needed (MySQL database), run tests, run locally the code, build the code...

A step-by-step guide on how to run this project is the following:

1) Clone this repository
1. Clone this repository

2) [Install](https://pnpm.io/installation) `pnpm` package manager
2. [Install](https://pnpm.io/installation) `pnpm` package manager

3) Install all the packages
3. Install all the packages

```
pnpm install
```

4) Set `.env` file
4. Set `.env` file

You can check all the environment variables in the `src/config/environments.ts` file. Here is an example of a valid `.env` file:

Expand All @@ -49,7 +52,7 @@ DB_PORT="3306"
DATABASE_URL="mysql://user:password123@localhost:3306/api"
```

5) Setup database infrastructure
5. Setup database infrastructure

In order to setup the infrastructure the following steps are executed: MySQL Docker container is created, migrations are execute, database is seeded. In order to simplify all these steps a script was created for you, so you can simply run:

Expand All @@ -59,23 +62,23 @@ pnpm infra:up

We are using [dbmate](https://github.com/amacneil/dbmate), an agnostic tool used for database migration. For MySQL databases it expect the [DATABASE_URL](https://github.com/amacneil/dbmate#mysql) environment variable to be set with the string URL used to connect to the database, that is why we are creating this environment variable in the `.env` file given above.

6) Run the code
6. Run the code

You can run the code in a watch mode, which means the API will auto-refresh with new changes everytime you modify and save any files, this helps you increase your productivity. You can run the API with the following script:

```
pnpm start:dev
```

7) Run tests
7. Run tests

Always create tests for new features. The tests are not mocking the database, so it will run tests against the MySQL docker database created! For it to work perfectly it expects the database to be seeded!

```
pnpm test
```

8) Clean up
8. Clean up

You can destroy all the created infrastructure with the following command:

Expand All @@ -87,23 +90,24 @@ pnpm infra:down

![Database Modeling](/docs/database.png)


## User Requirements

This project fullfills the following requirements:

### Poll Creation

- User should be able to create a poll and get a link to share with people
- User can choose if the poll allows more than one choice in the same vote (multi choice poll)

### Poll Voting System

- User should be able to vote in a given poll
- User should be able to see the vote results of a given poll after voting

## Next Steps

The following requirements should be implemented in next versions:

- Add observability to the application: logs, metrics, traces
- Add constraints:
- Max number of options to create in the poll
- Add cache layer (e.g. Redis) to minimize latency time
- Validate the need of using a pool of connections to manage the connection to the database

3 changes: 3 additions & 0 deletions src/config/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export const DB_PASSWORD = process.env.DB_PASSWORD
export const DB_NAME = process.env.DB_NAME
export const DB_HOST = process.env.DB_HOST
export const DB_PORT = process.env.DB_PORT ?? '3306'

// BUSINESS RULES
export const MAX_NUMBER_OF_CHOICES = process.env.MAX_NUMBER_OF_CHOICES === undefined ? 5 : Number(process.env.MAX_NUMBER_OF_CHOICES)
12 changes: 12 additions & 0 deletions src/controllers/pollController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ import { createPoll, getPoll } from '../repositories/pollRepository'
import { createChoices, getChoices } from '../repositories/choiceRepository'
import { buildErrorResponse, buildSuccessResponse } from '../utils/response'
import { type INewPoll } from '../validators/pollValidators'
import { MAX_NUMBER_OF_CHOICES } from '../config/environments'

export const create = async (req: Request, res: Response): Promise<void> => {
const uuid = uuidv4()
const body: INewPoll = req.body
if (body.choice.length > MAX_NUMBER_OF_CHOICES) {
res.status(400).send(buildErrorResponse(
`Max number of choices in a poll is ${MAX_NUMBER_OF_CHOICES}`
))
return
} else {
console.log('xd')
}
console.log(body.choice.length)
console.log(MAX_NUMBER_OF_CHOICES)

await createPoll(uuid, body.question, body.multiChoice)
await createChoices(uuid, body.choice)
res.send(buildSuccessResponse(
Expand Down
15 changes: 15 additions & 0 deletions test/poll.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MAX_NUMBER_OF_CHOICES } from '../src/config/environments'
import app from './common'

describe('GET /poll', () => {
Expand Down Expand Up @@ -90,6 +91,20 @@ describe('POST /poll', () => {
})
})

test('Create an invalid poll - too many choices', async () => {
const response = await app.post('/poll').send({
question: 'How many choices are allowed to be created?',
multiChoice: false,
choice: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
})

expect(response.statusCode).toBe(400)
expect(response.body).toStrictEqual({
message: `Max number of choices in a poll is ${MAX_NUMBER_OF_CHOICES}`,
status: 'failed'
})
})

test('Create an invalid poll - missing multiChoice', async () => {
const response = await app.post('/poll').send({
question: 'Did you forget to add the multiChoice key?',
Expand Down

0 comments on commit faffbe9

Please sign in to comment.