diff --git a/README.md b/README.md index e876f97..76e5a3f 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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: @@ -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: @@ -59,7 +62,7 @@ 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: @@ -67,7 +70,7 @@ You can run the code in a watch mode, which means the API will auto-refresh with 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! @@ -75,7 +78,7 @@ Always create tests for new features. The tests are not mocking the database, so pnpm test ``` -8) Clean up +8. Clean up You can destroy all the created infrastructure with the following command: @@ -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 - diff --git a/src/config/environments.ts b/src/config/environments.ts index a2404ec..7edc417 100644 --- a/src/config/environments.ts +++ b/src/config/environments.ts @@ -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) diff --git a/src/controllers/pollController.ts b/src/controllers/pollController.ts index eb8001a..b719dc9 100644 --- a/src/controllers/pollController.ts +++ b/src/controllers/pollController.ts @@ -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 => { 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( diff --git a/test/poll.test.ts b/test/poll.test.ts index fecb4b7..d51e1e1 100644 --- a/test/poll.test.ts +++ b/test/poll.test.ts @@ -1,3 +1,4 @@ +import { MAX_NUMBER_OF_CHOICES } from '../src/config/environments' import app from './common' describe('GET /poll', () => { @@ -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?',