Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pdekerret committed Feb 27, 2024
1 parent f7429cb commit 100d018
Show file tree
Hide file tree
Showing 27 changed files with 15,616 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_TEST_PORT=5433
DATABASE_USERNAME=user
DATABASE_PASSWORD=pwd
DATABASE_NAME=database
MIGRATIONS_RUN=true
NODE_ENV=development
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"editor.formatOnSave": true,
"typescript.preferences.importModuleSpecifier": "relative",
"editor.codeActionsOnSave": [
"source.fixAll",
"source.organizeImports",
"source.addMissingImports"
],
}

96 changes: 94 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,94 @@
# public-hiring-test
Technical tests for hiring software engineering @Greenly
# How to use

Stack: NestJs + TypeORM + Postgres

## Requirements

- Docker / Docker Compose
- Node.js 18

## Installation

```bash
$ yarn
```

## Running the app

First you need to start, migrate and seed the db :

```bash
$ yarn init-project
```

you can then start the server:

```bash
# development
$ yarn start

# watch mode
$ yarn start:dev
```

## Test

To run unit tests:

```bash
# unit tests
$ yarn test

# e2e tests
$ yarn test:e2e

# test coverage
$ yarn test:cov
```

## Database Migration

When the data schema is updated, the database needs to be synchronised with the code. This is done by creating a migration with Typeorm using the following command:

```bash
migrationName=<name> yarn migration:generate
```

##################################################################################################################################

# Hiring Test

##################################################################################################################################

When working on the following exercise, in addition to answering the product need, to give particular attention to the following points:

- Readability
- Maintainability
- Unit testing
- Handling of corner cases
- Error-handling

We want to compute the Agrybalise carbonfootprint of a foodproduct (e.g.: a hamCheesePizza) that we characterize by its ingredients as shown below

```js
const hamCheesePizza = {
ingredients: [
{ name: "ham", quantity: 0.1, unit: "kg" },
{ name: "cheese", quantity: 0.15, unit: "kg" },
{ name: "tomato", quantity: 0.4, unit: "kg" },
{ name: "floor", quantity: 0.7, unit: "kg" },
{ name: "oliveOil", quantity: 0.3, unit: "kg" },
],
};
```

The calculation of the Agrybalise carbon footprint can be described as below:

- The Agrybalise carbon footprint of one ingredient is obtained by multiplying the quantity of the ingredient by the emission of a matching emission factor (same name and same unit).
- The carbon footprint of the food product is then obtained by summing the carbon footprint of all ingredients.
- If the carbon footprint of one ingredient cannot be calculated, then the carbon footprint of the whole product is set to null.

The tasks of this exercice are as follows:
1/ Implement the carbon footprint calculation of a product and persist the results in database.
2/ Implement a GET endpoint to retrieve the result.
3/ Implement a POST endpoint to trigger the calculation and the saving in the database.
63 changes: 63 additions & 0 deletions config/dataSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { registerAs } from "@nestjs/config";
import { config as dotenvConfig } from "dotenv";
import { join } from "path";
import { DataSource, DataSourceOptions } from "typeorm";

dotenvConfig({ path: ".env" });

const dataSourceOptions: DataSourceOptions = {
type: "postgres",
port: parseInt(
`${process.env.NODE_ENV === "test" ? process.env.DATABASE_TEST_PORT : process.env.DATABASE_PORT}`
),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
synchronize: false,
logging: false,
entities: [join(__dirname, "../src/**/*.entity.{js,ts}")],
migrations: [join(__dirname, "../migrations", "*.*")],
migrationsTableName: "migrations",
migrationsRun: process.env.MIGRATIONS_RUN === "true",
};

export const typeorm = registerAs("typeorm", () => dataSourceOptions);

export class GreenlyDataSource {
private static dataSource: DataSource;
private static testDataSource: DataSource;

public static getInstance() {
if (process.env.NODE_ENV === "test") {
if (this.testDataSource) {
console.log(`testDataSource already set for ${process.env.NODE_ENV}`);
return this.testDataSource;
}
this.testDataSource = new DataSource(dataSourceOptions);

return this.testDataSource;
}
if (this.dataSource) {
console.log(`dataSource already set for ${process.env.NODE_ENV}`);
return this.dataSource;
}
this.dataSource = new DataSource(dataSourceOptions);

return this.dataSource;
}

public static async cleanDatabase(): Promise<void> {
try {
const entities = dataSource.entityMetadatas;
const tableNames = entities
.map((entity) => `"${entity.tableName}"`)
.join(", ");

await dataSource.query(`TRUNCATE ${tableNames} CASCADE;`);
} catch (error) {
throw new Error(`ERROR: Cleaning test database: ${error}`);
}
}
}

export const dataSource = GreenlyDataSource.getInstance();
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
db:
image: postgres
restart: always
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
ports:
- "${DATABASE_PORT}:5432"
db_test:
image: postgres
restart: always
environment:
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
# PGPORT
ports:
- "${DATABASE_TEST_PORT}:5432"
9 changes: 9 additions & 0 deletions jest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "./src",
"testEnvironment": "node",
"testRegex": ".test.ts",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
15 changes: 15 additions & 0 deletions migrations/1708367794381-carbonEmissionFactor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class CarbonEmissionFactor1708367794381 implements MigrationInterface {
name = "CarbonEmissionFactor1708367794381";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "carbon_emission_factors" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "unit" character varying NOT NULL, "emissionCO2eInKgPerUnit" float NOT NULL, "source" character varying NOT NULL, CONSTRAINT "PK_e6a201ea58a7b4cdec0ca1c0c61" PRIMARY KEY ("id"))`
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "carbon_emission_factors"`);
}
}
8 changes: 8 additions & 0 deletions nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
113 changes: 113 additions & 0 deletions notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
## Sources
to integrate typeorm and generate migrations

https://dev.to/amirfakour/using-typeorm-migration-in-nestjs-with-postgres-database-3c75




Technical

- Start from a boiler plate with simple database:
-postgre /Typeorm/express//jest

```js
const carbonEmissionFactors = [
{
Name: "ham",
unit: "kg",
valueInKgCO2: 0.12,
source: "Agrybalise",
},
{
name: cheese,
unit: "kg",
valueInKgCO2: 0.12,
source: "Agrybalise",
},
{
name: tomato,
unit: "kg",
valueInKgCO2: 0.12,
source: "Agrybalise",
},
{
name: oliveOil,
unit: l,
valueInKgCO2: 0.12,
source: "Agrybalise",
},
];

```


When working on the following exercise, give particular attention to the following points:
Readability of your code
Unit Testing
Architecture and organization of your functions
Handling of corner cases and errors
Overall performance of your code

1/ Create an endpoint to compute carbonFootprint of food and persist data in database.
```js
const hamCheesePizza = {
ingredients: [
{ name: "ham", value: "2", unit: "g" },
{ name: "cheese", value: "15", unit: "g" },
{ name: "tomato", value: "4", unit: "g" },
{ name: "floor", value: "7", unit: "g" },
{ name: "oliveOil", value: "0.7", unit: "l" },
],
};
```
2/Agrybalise has updated its coefficients and we want to update accordingly our knowledge base. In order to do that, we need to develop and endpoint allowing to update and/or insert new values in our referential of emission factors.

In particular, we want to add these values:














Test Criterion for technical test:
Problem solving solution: should solve efficiently the problem
Clean code aspects: readability (naming, architecture), test
Performance of code & algorithmic
Knowledge of database
Error handling & exceptions

Test Criterion for technical test:
Clarity of explanation of approach and database
General engineering culture
Reactivity on advices & inputs




Questions:



2/

Fonction buggé


Performance

Asynchronous feature
Utiliser un ORM
Rest API
Base de donnée

Bonus:
authentification
Loading

0 comments on commit 100d018

Please sign in to comment.