Skip to content
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
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.spec.ts'],
};
4,527 changes: 4,007 additions & 520 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/node": "^20.10.3",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.2"
}
Expand Down
20 changes: 20 additions & 0 deletions src/currency/getExchangeRate.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { currencyStore } from './repository';
import { getExchangeRate } from "./getExchangeRate";

jest.mock('./repository', () => ({
currencyStore: {
getCurrencyPrice: jest.fn(),
},
}));

describe('getExchangeRate', () => {
it('should return the exchange rate', () => {
(currencyStore.getCurrencyPrice as jest.Mock).mockImplementation((currency: string, date: string) => {
if (currency === 'USD') return { price_pln: '2' };
return { price_pln: '4' };
});

const result = getExchangeRate('USD', 'EUR', '2022-01-01');
expect(result).toEqual(0.5);
});
});
7 changes: 7 additions & 0 deletions src/currency/getExchangeRate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { currencyStore } from "./repository";

export const getExchangeRate = (fromCurrency: string, toCurrency: string, date: string) => {
const fromCurrencyPrice = currencyStore.getCurrencyPrice(fromCurrency, date);
const toCurrencyPrice = currencyStore.getCurrencyPrice(toCurrency, date);
return fromCurrencyPrice && toCurrencyPrice ? +fromCurrencyPrice.price_pln / +toCurrencyPrice.price_pln : null;
};
41 changes: 41 additions & 0 deletions src/currency/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CurrencyPrice } from "./types";

class CurrencyStore {
private store: Map<string, Map<string, CurrencyPrice>>;

constructor() {
this.store = new Map();
}

addCurrencyPrice(currencyPrice: CurrencyPrice) {
let currencyData = this.store.get(currencyPrice.date);
if (!currencyData) {
currencyData = new Map();
this.store.set(currencyPrice.date, currencyData);
}
currencyData.set(currencyPrice.currency, currencyPrice);
}

getCurrencyPrice(currency: string, date: string): CurrencyPrice | undefined {
if (currency === "PLN") {
return {
currency: "PLN",
price_pln: "1",
date: date
};
}
const currencyData = this.store.get(date);
return currencyData ? currencyData.get(currency) : undefined;
}

flush() {
this.store.clear();
}

getCurrencies(): CurrencyPrice[] {
const currencies = Array.from(this.store.values()).flatMap(dateMap => Array.from(dateMap.values()));
return currencies.sort((a, b) => a.currency.localeCompare(b.currency));
}
}

export const currencyStore = new CurrencyStore();
57 changes: 57 additions & 0 deletions src/currency/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import express, { Request, Response } from 'express';
import {
CurrencyExchangeRequest,
CurrencyExchangeResponse,
CurrencyPriceRequest,
CurrencyPriceResponse
} from "./types";
import { currencyStore } from "./repository";
import { getExchangeRate } from "./getExchangeRate";
const router = express.Router();


router.post('/currencyExchange', (req: Request, res: Response<CurrencyExchangeResponse | { message: string, error: true }>) => {
const { from_currency, to_currency, amount, date }: CurrencyExchangeRequest = req.body;

const exchangeRate = getExchangeRate(from_currency, to_currency, date);

if (exchangeRate === null) {
res.status(404).send({ error: true, message: 'Exchange rate for the given date is not available' });
return;
}

const value = +amount * exchangeRate;

res.send({
currency: to_currency,
value: value,
date: date
});
});

router.post('/currency', (req: Request, res: Response) => {
const { currencies }: CurrencyPriceRequest = req.body;
currencies.forEach((currency) => {
currencyStore.addCurrencyPrice(currency);
});

res.status(201).send({ message: 'Currency data has been successfully saved.' });
});

router.get('/currency', (req: Request, res: Response) => {
const currencies = currencyStore.getCurrencies();

const response: CurrencyPriceResponse = {
currencies: currencies
};

res.send(response);
});

router.post('/currencyFlush', (req: Request, res: Response) => {
currencyStore.flush();

res.send({ message: 'Currency store has been successfully flushed.' });
});

export default router;
26 changes: 26 additions & 0 deletions src/currency/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface CurrencyExchangeRequest {
from_currency: string;
to_currency: string;
amount: number;
date: string;
}

export interface CurrencyExchangeResponse {
currency: string;
value: number;
date: string;
}

export interface CurrencyPrice {
currency: string;
price_pln: string;
date: string;
}

export interface CurrencyPriceRequest {
currencies: CurrencyPrice[];
}

export interface CurrencyPriceResponse {
currencies: CurrencyPrice[];
}
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
import express, { Express, Request, Response } from "express";
import dotenv from "dotenv";

import currencyRouter from "./currency/router";

dotenv.config();
const app: Express = express();
app.use(express.json());

const port = process.env.PORT || 3000;

app.get("/health-check", (req: Request, res: Response) => {
res.send("health check OK");
});

app.use(currencyRouter);

/* Start the Express app and listen
for incoming requests on the specified port */
app.listen(port, () => {
Expand Down
18 changes: 16 additions & 2 deletions test.http
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
###
POST http://localhost:3000/currencyFlush
Content-Type: application/json

{}

> {%
client.test('should add new rates to existing rates',
() => {
console.log(response.status);
client.assert(response.status === 200);
});
%}

###
GET http://localhost:3000/health-check

Expand Down Expand Up @@ -145,7 +159,7 @@ GET http://localhost:3000/currency
},
{
"currency": "EUR",
"price_pln": "4.5",
"price_pln": "4.50",
"date": "2023-01-02"
},
{
Expand All @@ -155,7 +169,7 @@ GET http://localhost:3000/currency
},
{
"currency": "USD",
"price_pln": "4",
"price_pln": "4.00",
"date": "2023-01-02"
}
]
Expand Down