Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cf34fc1
fix(core): Fix path to test server
MaryLynJuana Nov 20, 2020
a731fb8
test(api): Add tests for server
MaryLynJuana Nov 20, 2020
15ac482
merge(api): Set server_test branch up-to-date with main branch
MaryLynJuana Jan 7, 2021
a7ef99c
test(api): Rewrite server tests to check authentification
MaryLynJuana Jan 7, 2021
9ba3bde
refactor(tests): Change array of arrays to object factory
MaryLynJuana Jan 7, 2021
81a2d47
build(api): Make test server exportable and move server starting to a…
MaryLynJuana Jan 8, 2021
33916fa
test(api): Enable test file to start and stop server
MaryLynJuana Jan 8, 2021
67e81bd
fix(api): Correct error codes and messages returned from server
MaryLynJuana Jan 8, 2021
96141b3
refactor(api): Get rid of using HTTP responce in routing module
MaryLynJuana Jan 8, 2021
eb3a702
refactor(api): Replace explicit object creation with factory call
MaryLynJuana Jan 8, 2021
2b96d61
fix(api): Check error field instead of checking HTTP code
MaryLynJuana Jan 8, 2021
a1dce77
fix(tests): Replace setTimeout with checking counter of unfinished re…
MaryLynJuana Jan 8, 2021
8e7c0b6
fix(tests): Remove unnecessary Atomics
MaryLynJuana Jan 9, 2021
c15cd82
refactor(tests): Move testing of one request into separate function
MaryLynJuana Jan 9, 2021
94cda13
refactor(tests): Move checking credentials into getOptions function
MaryLynJuana Jan 9, 2021
7b34d6a
fix(core): Add 'use strict'
MaryLynJuana Jan 14, 2021
cb02baf
test(api): Add checking response data for requests with code 200
MaryLynJuana Mar 22, 2021
b3466f2
refactor(tests): Remove unnecessary second parameter to createServer
MaryLynJuana Mar 22, 2021
fdd6e1b
Merge branch 'main' into server_test
MaryLynJuana Mar 22, 2021
0a2a86e
refactor(tests): Add newline at the end of file
MaryLynJuana Mar 23, 2021
f4dddc0
Merge branch 'server_test' of https://github.com/AlmostGreatBand/lemo…
MaryLynJuana Mar 23, 2021
444b249
Merge branch 'main' into server_test
MaryLynJuana Jun 2, 2021
b207ffa
fix(api): Correct expected field names in routing and tests
MaryLynJuana Jun 2, 2021
ab78f80
refactor(api) Make mono datasource eslint compatible
Hud-Volodymyr Jun 3, 2021
0d75073
refactor(tests) Make tests eslint compatible
Hud-Volodymyr Jun 3, 2021
29f100e
refactor(tests) Make tests eslint compatible
Hud-Volodymyr Jun 3, 2021
4f9aa9a
Provide detailed db interface
Hud-Volodymyr Jun 3, 2021
e260dcf
Implement some Repository functions
Hud-Volodymyr Jun 3, 2021
e7a8853
feat(api): Add repository
MaryLynJuana Jun 3, 2021
a503357
refactor(tests): implement additional db interface
Hud-Volodymyr Jun 3, 2021
39c729b
build(api) Implement additional repository functionality
Hud-Volodymyr Jun 3, 2021
b6a40ca
feat(api): Implement main server with routing and calls to repository
MaryLynJuana Jun 3, 2021
2071750
fix(api): Return errors instead of throwing
MaryLynJuana Jun 3, 2021
a78872d
refactor(api): Change error message
MaryLynJuana Jun 3, 2021
250596c
Merge branch 'main' into main_server
MaryLynJuana Jun 3, 2021
8f9006e
Remove npm run start from yml (#30)
MaryLynJuana Jun 3, 2021
422c163
Heroku multiapp setup
ALEGATOR1209 Jun 3, 2021
8ed5b92
refactor(api): Return errors instead of throwing
MaryLynJuana Jun 3, 2021
c96360e
fix(api): Change endpoint for setting cards for bank account
MaryLynJuana Jun 3, 2021
11680d3
refactor(api): Move setCards call inside setBanks function
MaryLynJuana Jun 3, 2021
3fbc855
Merge branch 'main_server' of github.com:AlmostGreatBand/lemon-server…
ALEGATOR1209 Jun 4, 2021
9416f09
fix(api): Fix getTransaction function behavior
MaryLynJuana Jun 4, 2021
924fbe5
feat(api): Add endpoint for logging in
MaryLynJuana Jun 4, 2021
e172a1c
Merge branch 'main_server' of github.com:AlmostGreatBand/lemon-server…
ALEGATOR1209 Jun 4, 2021
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
3 changes: 1 addition & 2 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run start
- run: npm test


3 changes: 2 additions & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
web: npm run testServer
web: npm start
web-test: npm run testServer
41 changes: 26 additions & 15 deletions src/datasources/LemonRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class LemonRepository {
if (!profile) {
return { profile: null, error: new Error('Account Not Found') };
}
if (user.password !== profile.password) {
return { profile: null, error: new Error('Wrong password') };
}
return { profile, error: null };
}

Expand All @@ -25,7 +28,9 @@ class LemonRepository {
if (!profile) {
return { ok: false, error: new Error('Account Not Found') };
}

if (user.password !== profile.password) {
return { ok: false, error: new Error('Wrong password') };
}
const result = this.db.updateAccount(user);

return { ok: result, error: null };
Expand All @@ -49,9 +54,12 @@ class LemonRepository {
if (!profile) {
return { ok: false, error: new Error('Account Not Found') };
}

if (user.password !== profile.password) {
return { ok: false, error: new Error('Wrong password') };
}
const ok = this.db.setBank(profile.account_id, bank);
return { ok, error: null };
if (!ok) return { ok, error: new Error('Database error') };
return setCards(bank);
}

/** @return Array<Card>, error */
Expand All @@ -60,21 +68,15 @@ class LemonRepository {
if (!profile) {
return { cards: null, error: new Error('Account Not Found') };
}

if (user.password !== profile.password) {
return { cards: null, error: new Error('Wrong password') };
}
return { cards: this.db.getCards(profile.account_id), error: null };
}

/** @return boolean, error */
async setCard(user) {
async setCards(bank) {
try {
const profile = this.db.getAccount(user.login);
if (!profile.token) {
return { ok: false, error: new Error('Account Not Found') };
}
const bank = dbInterface.getMonobank(profile);
if (!bank) {
return { ok: false, error: new Error('No monobank cards present') };
}
const monoCards = await this.monoDS.getAccounts(bank.token);
const monoAccounts = monoCards.accounts;
const ok = this.db.setMonoCards(monoAccounts, profile);
Expand All @@ -98,10 +100,19 @@ class LemonRepository {
async getTransactions(user) {
try {
const profile = this.db.getAccount(user.login);
if (!profile.token) {
return { transactions: null, error: new Error('Account Has No Token') };
if (!profile) {
return { transactions: null, error: new Error('Account Not Found') };
}
if (user.password !== profile.password) {
return { transactions: null, error: new Error('Wrong password') };
}
const bank = this.db.getMonobank(profile);
if (!bank) {
return { transactions: null, error: new Error('No cards present') };
}
if (!bank.token) {
return { transactions: null, error: new Error('Account Has No Token') };
}
const cards = this.db.getCards(profile.account_id);
const accounts = this._formMonoTrRequest(cards);
const monoTransactions = this
Expand Down
37 changes: 34 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
'use strict';

// TODO: Add implementation of Lemon Server
// Lemon server can be run by 'npm start' command (see package.json).
console.log('Lemon🍋 server goes brrrrrrrrrrrrrrrrr');
const https = require('https');
const http = require('http');
const fs = require('fs');
const createServer = require('./server.js');

http.name = 'http';
https.name = 'https';

const key = (() => {
try {
return fs.readFileSync('key.pem');
} catch (e) {
return undefined;
}
})();

const cert = (() => {
try {
return fs.readFileSync('cert.pem');
} catch (e) {
return undefined;
}
})();

const options = { key, cert };

const protocol = key && cert ? https : http;

const server = createServer(protocol, options);

server.listen(process.env.PORT || 8000);
console.log('Test server goes brrrrrrrrrrrr');
const address = { ...server.address(), protocol: protocol.name };
console.dir(address);
106 changes: 106 additions & 0 deletions src/routing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict';

const repository = require('./datasources/LemonRepository.js');

const makeResponse = (code, data) => (
{ code, data }
);

const authentificate = user => user.login && user.password;

const badRequestResponse = makeResponse(401, { error: '&#127819Login and password should be specified'});
const userErrorResponse = makeResponse(403, { error: '&#127819Permission denied' });
const serverErrorResponse = makeResponse(501, { error:'&#127819Lemon server is feeling bad' });

const expectedUserErrors = [
'Account Not Found',
'Account Has No Token',
'Wrong password',
'No cards present',
'Card Not Found',
];

const getProfile = user => {
if (!authentificate(user)) return badRequestResponse;
const { profile, error } = repository.getUserInfo(user);
if (!error) return makeResponse(200, profile);
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const signIn = user => {
if (!authentificate(user)) return badRequestResponse;
const { profile, error } = repository.getUserInfo(user);
if (!error) return makeResponse(200, '&#127819Login successful');
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const getCards = user => {
if (!authentificate(user)) return badRequestResponse;
const { cards, error } = repository.getCards(user);
if (!error) return makeResponse(200, { cards });
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const getTransactions = async user => {
if (!authentificate(user)) return badRequestResponse;
const { transactions, error } = await repository.getTransactions(user);
if (!error) return makeResponse(200, { transactions });
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const registerUser = user => {
if (!authentificate(user)) return badRequestResponse;
const { ok, error } = repository.setUser(user);
const accExistsMsg = `Account with login ${user.login} already exists`;
if (error.message === accExistsMsg) {
return makeResponse(401, accExistsMsg);
};
return serverErrorResponse;
};

const changeUserInfo = (user, newInfo) => {
if (!authentificate(user)) return badRequestResponse;
const { ok, error } = repository.changeUserInfo(newInfo);
if (!error) return makeResponse(200, { ok });
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const setCards = user => {
if (!authentificate(user)) return badRequestResponse;
const { ok, error } = repository.setCard(user);
if (!error) return makeResponse(200, { ok });
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const addTransaction = (user, transaction) => {
if (!authentificate(user)) return badRequestResponse;
const { ok, error } = repository.setTransaction(transaction);
if (!error) return makeResponse(200, { ok });
if (expectedUserErrors.includes(error.message)) return userErrorResponse;
return serverErrorResponse;
};

const routing = {
'GET': {
'/': () => makeResponse(200, 'Welcome to Lemon&#127819 Server!'),
'/profile/': user => getProfile(user),
'/cards/': user => getCards(user),
'/transactions/': user => getTransactions(user),
'/login/': user => signIn(user),
},
'POST': {
'/': () => makeResponse(200, 'Welcome to Lemon&#127819 Server!'),
'/profile/': (user, newInfo) => changeUserInfo(user, newInfo),
'/banks/add/': async (user, bank) => setBank(user, bank),
'/transactions/': (user, transaction) => addTransaction(user, transaction),
'/registration/': (_, user) => registerUser(user),
},
};

module.exports = routing;
61 changes: 61 additions & 0 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const routing = require('./routing.js');

const normalizePath = pathname => (
pathname.endsWith('/') ? pathname : pathname + '/'
);

const makeData = string => (
JSON.stringify({ error: `&#127819${string}` })
);

const authorizeUser = (req, res) => {
const { authorization } = req.headers;
if (!authorization) {
res.setHeader(
'WWW-Authenticate',
[ 'Basic', 'realm="Lemon"', 'charset="UTF-8"' ]
);
res.writeHead(401);
res.end(makeData('You should authorize to access the site'));
return null;
}
const credentialsBase64 = authorization.split(' ')[1];
const credentialsASCII = Buffer.from(credentialsBase64, 'base64')
.toString('ascii');
return credentialsASCII.split(':');
};

const createServer = (protocol, options) => (
protocol.createServer(options, async (req, res) => {
const credentials = authorizeUser(req, res);
if (!credentials) return;
const handler = routing[req.method][normalizePath(req.url)];
if (!handler) {
res.writeHead(404);
return res.end(makeData('Page not found :('));
}
const user = {
login: credentials[0],
password: credentials[1],
};
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', async () => {
const { code, data } = await handler(user, body);
res.writeHead(code);
res.end(JSON.stringify(data));
})
} else {
const { code, data } = await handler(user);
res.writeHead(code);
res.end(JSON.stringify(data));
}
})
);

module.exports = createServer;
6 changes: 3 additions & 3 deletions test/server/serverTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ const checkMsg = (msg, expected, path, credentials) => {

const checkResponseData = (data, path) => {
const expected = JSON.stringify(expectedResults[path]);
assert.strictEqual(data, expected, `Wrong response data, expected:
${expected},
got:
assert.strictEqual(data, expected, `Wrong response data, expected:
${expected},
got:
${data}
Path: ${path}`);
};
Expand Down