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

Pleo-challenge updates #10

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 142 additions & 153 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,159 +1,148 @@
{
"extends": "airbnb-base",
"ignorePatterns": [
"node_modules/"
"extends": "airbnb-base",
"ignorePatterns": [
"node_modules/"
],
"parser": "@typescript-eslint/parser",
"plugins": [
"import",
"@typescript-eslint",
"simple-import-sort"
],
"settings": {
"import/resolver": {
"node": {
"paths": [
"middleware",
"packages",
"test"
],
"extensions": [
".js",
".json",
".ts"
]
}
}
},
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/tests/**/*.ts",
"**/test/**/*.ts"
]
}
],
"parser": "@typescript-eslint/parser",
"plugins": [
"import",
"@typescript-eslint"
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"no-async-promise-executor": "error",
"no-return-await": "error",
"require-await": "error",
"yoda": "off",
"no-restricted-properties": "off",
"no-cond-assign": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": false
}
],
"settings": {
"import/resolver": {
"node": {
"paths": [
"middleware",
"packages",
"test"
],
"extensions": [
".js",
".json",
".ts"
]
}
"@typescript-eslint/type-annotation-spacing": "error",
"no-await-in-loop": "error",
"no-useless-computed-key": "off",
"import/no-unresolved": "error",
"no-bitwise": "off",
"strict": "off",
"no-continue": "off",
"arrow-body-style": "off",
"camelcase": "off",
"class-methods-use-this": "off",
"operator-linebreak": "off",
"lines-between-class-members": "off",
"comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}
},
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/tests/**/*.ts",
"**/test/**/*.ts"
]
}
],
"no-async-promise-executor": "error",
"no-return-await": "error",
"require-await": "error",
"yoda": "off",
"no-restricted-properties": "off",
"no-cond-assign": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": false
}
],
"@typescript-eslint/type-annotation-spacing": "error",
"no-await-in-loop": "error",
"no-useless-computed-key": "off",
"import/no-unresolved": "error",
"no-bitwise": "off",
"strict": "off",
"no-continue": "off",
"arrow-body-style": "off",
"camelcase": "off",
"class-methods-use-this": "off",
"operator-linebreak": "off",
"lines-between-class-members": "off",
"comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline",
"functions": "never"
}
],
"no-multi-assign": "off",
"default-case": "off",
"consistent-return": "off",
"func-names": "off",
"import/order": "off",
"import/extensions": "off",
"import/first": "off",
"import/newline-after-import": "off",
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/prefer-default-export": "off",
"max-len": "off",
"no-console": "error",
"no-mixed-operators": "off",
"no-param-reassign": "off",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-prototype-builtins": "off",
"no-restricted-syntax": "off",
"no-underscore-dangle": "off",
"no-useless-escape": "off",
"no-use-before-define": [
"error",
{
"functions": false,
"classes": true,
"variables": true
}
],
"no-var": "off",
"object-property-newline": "off",
"operator-assignment": "off",
"prefer-arrow-callback": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"object-curly-newline": "off",
"prefer-destructuring": "off",
"no-restricted-globals": "off",
"radix": "off",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never"
}
],
"sort-imports": [
"error",
{
"ignoreCase": true,
"ignoreDeclarationSort": false,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": [
"none",
"all",
"single",
"multiple"
]
}
],
"linebreak-style": "off",
"no-lonely-if": "off",
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "none",
"requireLast": false
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
],
"no-multi-assign": "off",
"default-case": "off",
"consistent-return": "off",
"func-names": "off",
"import/order": "off",
"import/extensions": "off",
"import/first": "off",
"import/newline-after-import": "off",
"import/no-named-as-default": "off",
"import/no-named-as-default-member": "off",
"import/prefer-default-export": "off",
"max-len": "off",
"no-console": "error",
"no-mixed-operators": "off",
"no-param-reassign": "off",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-prototype-builtins": "off",
"no-restricted-syntax": "off",
"no-underscore-dangle": "off",
"no-useless-escape": "off",
"no-use-before-define": [
"error",
{
"functions": false,
"classes": true,
"variables": true
}
],
"no-var": "off",
"object-property-newline": "off",
"operator-assignment": "off",
"prefer-arrow-callback": "off",
"prefer-rest-params": "off",
"prefer-spread": "off",
"prefer-template": "off",
"object-curly-newline": "off",
"prefer-destructuring": "off",
"no-restricted-globals": "off",
"radix": "off",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never"
}
],
"linebreak-style": "off",
"no-lonely-if": "off",
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "none",
"requireLast": false
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
]
},
"env": {
"node": true,
"jest": true
}
}
]
},
"env": {
"node": true,
"jest": true
}
}
57 changes: 50 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@ Take home test for Node.js developers.

## The challenge

This challenge has been designed to measure your knowledge of Node.js, Express, Typescript and various technologies, like monorepos, databases and testing. For your exercise, you will be enhancing this API which serves as the backend for the Pleo app. Whenever a user of the app navigates to the expenses view, it calls this API to collect the list of expenses for that user.
This challenge has been designed to measure your knowledge of Node.js, Express, Typescript and various technologies,
like monorepos, databases and testing. For your exercise, you will be enhancing this API which serves as the backend for
the Pleo app. Whenever a user of the app navigates to the expenses view, it calls this API to collect the list of
expenses for that user.

Your objective is to write this new route to fetch the list of expenses for a given user. Right now that domain is empty, so you'll have to build everything from scratch- but you can look over at the user domain for inspiration. Please make sure that the endpoint scales adequately and supports paging, sorting and filtering. Additionally, we would also like you to write some tests for your route.
Your objective is to write this new route to fetch the list of expenses for a given user. Right now that domain is
empty, so you'll have to build everything from scratch- but you can look over at the user domain for inspiration. Please
make sure that the endpoint scales adequately and supports paging, sorting and filtering. Additionally, we would also
like you to write some tests for your route.

Finally, as a bonus objective, try to improve any aspect of this API. It could be to add more TS types, better security, tests, add features, graphql support, etc.
Finally, as a bonus objective, try to improve any aspect of this API. It could be to add more TS types, better security,
tests, add features, graphql support, etc.

## Instructions

Fork this repo with your solution. Ideally, we'd like to see your progression through commits, and don't forget to update the README.md to explain your thought process.
Fork this repo with your solution. Ideally, we'd like to see your progression through commits, and don't forget to
update the README.md to explain your thought process.

Please let us know how long the challenge takes you. We're not looking for how speedy or lengthy you are. It's just really to give us a clearer idea of what you've produced in the time you decided to take. Feel free to go as big or as small as you want.
Please let us know how long the challenge takes you. We're not looking for how speedy or lengthy you are. It's just
really to give us a clearer idea of what you've produced in the time you decided to take. Feel free to go as big or as
small as you want.

## Install

Expand All @@ -24,7 +34,8 @@ Make sure that you have a modern version of `yarn` that supports workspaces (`>=
yarn
```

You will also need to [install Postgres](https://www.postgresqltutorial.com/install-postgresql-macos/), create a `challenge` database and load the sql file `dump.sql`:
You will also need to [install Postgres](https://www.postgresqltutorial.com/install-postgresql-macos/), create
a `challenge` database and load the sql file `dump.sql`:

```bash
psql challenge < dump.sql
Expand Down Expand Up @@ -54,5 +65,37 @@ The command above will run the following test suites sequentially:
| Mid-level | `yarn test:mid-level` | Small integration tests that integration of small components together. |
| Acceptances | `yarn test:acceptance` | Large integration tests, system tests, end-to-end tests. |


Happy hacking 😁!

# Change Log

- Added docker-compose for easy development setup.
- Added sequelize (it was sequelize vs typeorm vs prisma) -> I picked sequelize because it supports multiple db's so
read
replicas can be used for data fetching + it is also backwards compatible with raw sql queries.
- Added class-validator - For creating dto's and validating dto's (joi alternative)
- Introduced variation of hexagonal architecture pattern of controller -> dto -> service -> model <- service -> dto ->
controller , validation can be
done on the dto
object
* Essentially any time a data object is transferred from one layer to another we do it via a data transfer object
, and we validate the data before sending it to the next layer
* We define a "validate" method on the dto class
* Inspiration was taken from django's serializers and nestjs validation pipelines

## How to test:

1. Run ```docker-compose up --build```
2. Request the expenses for a userId

```bash
curl --location --request GET 'http://localhost:9001/expense/v1/get-user-expenses/da140a29-ae80-4f0e-a62d-6c2d2bc8a474?limit=1'
```

## Notes:

- I added a single unit test example for one of the DTO objects as a unit-test example
- The service layer for expenses supports receiving the model/entity via dep injection which makes future testing
simpler.
- Please see my "Todo" comments in the project
- I didn't add an OpenAPI spec ... for any real world application this is NB
6 changes: 5 additions & 1 deletion config/default.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require('dotenv').config();
require('dotenv')
.config();
const path = require('path');

module.exports = {
Expand All @@ -22,4 +23,7 @@ module.exports = {
appKill: 1000,
serverClose: 100,
},
auth: {
jwtSecret: process.env.JWT_SECRET || 'fake-secret',
},
};
Loading