From ef9d5e3ec41a31f5e922b61f05278c1bfc60f626 Mon Sep 17 00:00:00 2001 From: jjjjjjjjjjjjjjjjjjjj Date: Wed, 20 Sep 2023 16:30:57 +0200 Subject: [PATCH] Fix build and clarify set up instructions --- .env.example | 2 ++ .gitignore | 5 +++- README.md | 30 +++++++++++++++---- config/default.js | 2 +- dump.sql => insert-dummy-data.sql | 4 +-- packages/domains/user/formatter.ts | 4 +-- packages/domains/user/routes/v1-get-user.ts | 4 +-- packages/domains/user/tests/formatter.test.ts | 15 +++++----- server.ts | 8 +++-- test/utils/jest/config-injector.ts | 4 --- 10 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 .env.example rename dump.sql => insert-dummy-data.sql (95%) diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e87af64 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +PGUSER=postgres +PGPASSWORD=postgres diff --git a/.gitignore b/.gitignore index 355aaec..f9da712 100644 --- a/.gitignore +++ b/.gitignore @@ -67,4 +67,7 @@ package-lock.json # MacOS .DS_Store -bin \ No newline at end of file +# Environment variables +.env + +bin diff --git a/README.md b/README.md index 9da3b6f..7e4c812 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 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 a fictional 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. @@ -16,7 +16,7 @@ Fork this repo with your solution. Ideally, we'd like to see your progression th 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 +## Installing Make sure that you have a modern version of `yarn` that supports workspaces (`>= 1.0`), then run: @@ -24,13 +24,31 @@ 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`: +Start a Postgres Docker container, or alternatively run postgres outside docker. ```bash -psql challenge < dump.sql +docker run --name pleo-postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres ``` -## Start +Copy the `.env.example` file as `.env` and optionally update it to reflect the proper authentication for your locally running Postgres instance. + +```bash +cp .env.example .env +``` + +Create a `pleo_node_challenge` database in your local instance. The following two steps assume your local postgres instance is running and accessible on 0.0.0.0:5432 as user `postgres` with password `postgres`. Please update below connection string accordingly if that's not the case. + +```bash +psql -d "postgresql://postgres:postgres@0.0.0.0" -c 'CREATE DATABASE pleo_node_challenge' +``` + +Insert some dummy data: + +```bash +psql -d "postgresql://postgres:postgres@0.0.0.0/pleo_node_challenge" < insert-dummy-data.sql +``` + +## Running To enable logs, use the standard `NODE_DEBUG` flag with the value `DEBUG` @@ -38,7 +56,7 @@ To enable logs, use the standard `NODE_DEBUG` flag with the value `DEBUG` NODE_DEBUG=DEBUG yarn start ``` -## Test +## Testing Make sure that you have a modern version of `yarn` that supports workspaces, then run: diff --git a/config/default.js b/config/default.js index f82ce16..9481bf3 100644 --- a/config/default.js +++ b/config/default.js @@ -5,7 +5,7 @@ module.exports = { db: { host: '0.0.0.0', port: 5432, - database: 'challenge', + database: 'pleo_node_challenge', }, debug: { stackSize: 4, diff --git a/dump.sql b/insert-dummy-data.sql similarity index 95% rename from dump.sql rename to insert-dummy-data.sql index 86c53eb..b420595 100644 --- a/dump.sql +++ b/insert-dummy-data.sql @@ -72,8 +72,8 @@ f3f34c29-274a-414d-988f-711802eeac25 BRUS 5000 DKK 3d16547a-79f6-4f62-9034-d3bfb COPY public.users (id, first_name, last_name, company_name, ssn) FROM stdin; da140a29-ae80-4f0e-a62d-6c2d2bc8a474 jeppe rindom pleo 1 -e17825a6-ad80-41bb-a76b-c5ee17b2f29d petr janda pleo 2 -3d16547a-79f6-4f62-9034-d3bfb31fb37c olov eriksson pleo 3 +e17825a6-ad80-41bb-a76b-c5ee17b2f29d nicco perra pleo 2 +3d16547a-79f6-4f62-9034-d3bfb31fb37c jane doe pleo 3 \. diff --git a/packages/domains/user/formatter.ts b/packages/domains/user/formatter.ts index f18c7c9..26b9dd0 100644 --- a/packages/domains/user/formatter.ts +++ b/packages/domains/user/formatter.ts @@ -7,8 +7,8 @@ export function capitalize(word) { return str[0].toUpperCase() + str.slice(1); } -export function secureTrim(user: User): string { - return JSON.stringify(user, publicFields); +export function redactSensitiveFields(user: User): Pick { + return JSON.parse(JSON.stringify(user, publicFields)); } export function format(rawUser): User { diff --git a/packages/domains/user/routes/v1-get-user.ts b/packages/domains/user/routes/v1-get-user.ts index a8e6723..71d22d3 100644 --- a/packages/domains/user/routes/v1-get-user.ts +++ b/packages/domains/user/routes/v1-get-user.ts @@ -1,7 +1,7 @@ import { ApiError } from '@nc/utils/errors'; import { getUserDetails } from '../model'; +import { redactSensitiveFields } from '../formatter'; import { Router } from 'express'; -import { secureTrim } from '../formatter'; import { to } from '@nc/utils/async'; export const router = Router(); @@ -17,5 +17,5 @@ router.get('/get-user-details', async (req, res, next) => { return res.json({}); } - return res.json(secureTrim(userDetails)); + return res.json(redactSensitiveFields(userDetails)); }); diff --git a/packages/domains/user/tests/formatter.test.ts b/packages/domains/user/tests/formatter.test.ts index 799d128..c1cfe7f 100644 --- a/packages/domains/user/tests/formatter.test.ts +++ b/packages/domains/user/tests/formatter.test.ts @@ -1,4 +1,4 @@ -import { capitalize, format, secureTrim } from '../formatter'; +import { capitalize, format, redactSensitiveFields } from '../formatter'; describe('[Packages | User-domain | Formatter] capitalize', () => { test('capitalize should make the first character as a capital letter', () => { @@ -18,18 +18,19 @@ describe('[Packages | User-domain | Formatter] capitalize', () => { }); }); -describe('[Packages | User-domain | Formatter] secureTrim', () => { - test('secureTrim should remove fields that are not defined in the list of public fields', () => { - return expect(secureTrim({ +describe('[Packages | User-domain | Formatter] redactSensitiveFields', () => { + test('redactSensitiveFields should remove fields that are not defined in the list of public fields', () => { + return expect(redactSensitiveFields({ + id: '18d60d19-e747-4365-92e8-d8951eb47904', first_name: 'John', last_name: 'Smith', company_name: 'Pleo', - ssn: 1, - })).toEqual(JSON.stringify({ + ssn: '1230', + })).toEqual({ first_name: 'John', last_name: 'Smith', company_name: 'Pleo', - })); + }); }); }); diff --git a/server.ts b/server.ts index e9ae5c8..c37f2d0 100644 --- a/server.ts +++ b/server.ts @@ -11,7 +11,7 @@ import { createServer as createHTTPSServer, Server as SecureServer } from 'https const logger = Logger('server'); const app = express(); -const server: Server | SecureServer = (config.https.enabled === true) ? createHTTPSServer(config.https, app as any) : createHTTPServer(app as any); +const server: (Server | SecureServer) & {ready?: boolean} = (config.https.enabled === true) ? createHTTPSServer(config.https, app as any) : createHTTPServer(app as any); server.ready = false; gracefulShutdown(server); @@ -31,7 +31,11 @@ app.use(security); app.use('/user', userRoutes); -app.use(function(err, req, res) { +app.use(function(err, req, res, next) { + if (res.headersSent) { + return next(err); + } + res.status(500).json(err); }); diff --git a/test/utils/jest/config-injector.ts b/test/utils/jest/config-injector.ts index 0e3fd0c..1b8bfa2 100644 --- a/test/utils/jest/config-injector.ts +++ b/test/utils/jest/config-injector.ts @@ -1,7 +1,3 @@ -import config from 'config'; - -config.auth.jwtSecret = 'some-fake-key'; - process.env.TEST_MODE = 'test'; process.on('unhandledRejection', (err: Error) => process.stderr.write(`unhandledRejection: ${err.stack}\n`));