Skip to content

Commit ccaddbb

Browse files
committed
feat: add endpoint register and authenticate user
1 parent 515b675 commit ccaddbb

File tree

10 files changed

+211
-3
lines changed

10 files changed

+211
-3
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
PORT=9000
22
APP_ENV=dev
33
STORAGE_LOCATION=files
4-
SYSTEM_SHUTDOWN_DELAY=0
4+
SYSTEM_SHUTDOWN_DELAY=0
5+
MONGODB_URL='mongodb://localhost:27017/example?maxPoolSize=10&socketTimeoutMS=10000&connectTimeoutMS=10000&useUnifiedTopology=true'

bin/app/server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class AppServer {
2424
});
2525

2626
this.app.post('/api/users/v1/register', userHandler.registerUser);
27+
this.app.post('/api/users/v1/auth', userHandler.authUser);
2728
}
2829

2930
async listen(port, cb) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const { MongoClient } = require('mongodb');
2+
const configs = require('../../configs/global_config');
3+
const wrapper = require('../../utils/wrapper');
4+
const logger = require('../../utils/logger');
5+
6+
let connectionPool = [];
7+
8+
const fetchConnection = async (config) => {
9+
const checkConnection = connectionPool.findIndex(c => c.config == config);
10+
if(checkConnection === -1) {
11+
try {
12+
const client = new MongoClient(config);
13+
const connection = await client.connect();
14+
const dbName = getDatabaseName(config);
15+
const pool = { config, connection, dbName };
16+
connectionPool.push(pool);
17+
return wrapper.data(pool);
18+
} catch (err) {
19+
logger.log('connection-createConnection', err, 'error');
20+
return wrapper.error(err.message);
21+
}
22+
}
23+
24+
return wrapper.data(connectionPool[checkConnection]);
25+
};
26+
27+
const getDatabaseName = (config) => {
28+
config = config.replace('//', '');
29+
const pattern = new RegExp('/([a-zA-Z0-9-]+)?');
30+
const dbNameFound = pattern.exec(config);
31+
return dbNameFound[1];
32+
}
33+
34+
module.exports = {
35+
fetchConnection,
36+
};

bin/helpers/configs/global_config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const config = {
1010
storage: {
1111
location: process.env.STORAGE_LOCATION,
1212
},
13+
mongodb: {
14+
url: process.env.MONGODB_URL,
15+
},
1316
};
1417

1518
const store = new confidence.Store(config);

bin/modules/user/handlers/api_handler.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ const wrapper = require('../../../helpers/utils/wrapper');
22
const validator = require('../../../helpers/utils/validator');
33

44
const commandValidation = require('../services/commands/command_validation');
5+
const commandDomain = require('../services/commands/command_domain');
6+
7+
const commandUser = new commandDomain();
58

69
const registerUser = async (req, res) => {
710
const message = 'Register User';
811
const payload = req.body;
912
const validatePayload = validator.isValidPayload(payload, commandValidation.registerUser);
1013
const postRequest = async (result) => {
11-
return result.err ? result : wrapper.data({ isRegistered: true });
14+
return result.err ? result : commandUser.registerUser(result.data);
1215
};
13-
1416
const sendResponse = async (result) => {
1517
(result.err)
1618
? wrapper.response(res, 'fail', result, message)
@@ -19,7 +21,22 @@ const registerUser = async (req, res) => {
1921
sendResponse(await postRequest(validatePayload));
2022
};
2123

24+
const authUser = async (req, res) => {
25+
const message = 'Authenticate User';
26+
const payload = req.body;
27+
const validatePayload = validator.isValidPayload(payload, commandValidation.authUser);
28+
const postRequest = async (result) => {
29+
return result.err ? result : commandUser.authUser(result.data);
30+
};
31+
const sendResponse = async (result) => {
32+
(result.err)
33+
? wrapper.response(res, 'fail', result, message)
34+
: wrapper.response(res, 'success', result, message);
35+
};
36+
sendResponse(await postRequest(validatePayload));
37+
};
2238

2339
module.exports = {
2440
registerUser,
41+
authUser,
2542
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const QueryRepository = require('../queries/query_repository');
2+
const CommandRepository = require('./command_repository');
3+
4+
const configs = require('../../../../helpers/configs/global_config');
5+
const wrapper = require('../../../../helpers/utils/wrapper');
6+
const logger = require('../../../../helpers/utils/logger');
7+
const createError = require('http-errors');
8+
const { createHash } = require('crypto');
9+
10+
class User {
11+
constructor () {
12+
this.query = new QueryRepository(configs.get('/mongodb/url'));
13+
this.command = new CommandRepository(configs.get('/mongodb/url'));
14+
}
15+
16+
async registerUser(payload) {
17+
const ctx = 'cmd-domain-registerUser';
18+
let { email, password } = payload;
19+
const checkUser = await this.query.getUserByEmail(email);
20+
if(checkUser.err && checkUser.err.message != 'Data not found') return checkUser;
21+
else if(checkUser.data) return wrapper.error(new createError.InternalServerError('User already exists'));
22+
23+
password = createHash('sha256').update(password).digest('hex');
24+
const resAddUser = await this.command.addUser(email, password);
25+
if(resAddUser.err) {
26+
logger.error(ctx, 'Failed Register User', 'addUser', resAddUser);
27+
return resAddUser;
28+
}
29+
30+
return wrapper.data('User registration has been successful');
31+
}
32+
33+
async authUser(payload) {
34+
const ctx = 'cmd-domain-authUser';
35+
let { email, password } = payload;
36+
const checkUser = await this.query.getUserByEmail(email);
37+
if(checkUser.err) {
38+
logger.error(ctx, 'Invalid Email', 'check-user');
39+
return wrapper.error(new createError.Unauthorized('Invalid Email or Password'));
40+
}
41+
42+
password = createHash('sha256').update(password).digest('hex');
43+
if(password != checkUser.data.password) {
44+
logger.error(ctx, 'Invalid Password', 'check-password');
45+
return wrapper.error(new createError.Unauthorized('Invalid Email or Password'));
46+
}
47+
48+
return wrapper.data({ userId: checkUser.data.userId });
49+
}
50+
};
51+
52+
module.exports = User;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const mongodbConn = require('../../../../helpers/components/mongodb/connection');
2+
const wrapper = require('../../../../helpers/utils/wrapper');
3+
const logger = require('../../../../helpers/utils/logger');
4+
const createError = require('http-errors');
5+
const { v4: uuidv4 } = require('uuid');
6+
const moment = require('moment');
7+
8+
class CommandRepository {
9+
constructor (config) {
10+
this.config = config;
11+
this.collectionName = 'users';
12+
}
13+
14+
async getDB() {
15+
const ctx = 'cmd-repo-getDB';
16+
try {
17+
const {data: { connection: cacheConnection, dbName }} = await mongodbConn.fetchConnection(this.config);
18+
return wrapper.data(cacheConnection.db(dbName).collection(this.collectionName));
19+
} catch (err) {
20+
logger.error(ctx, err.message, 'Error get db connection');
21+
return wrapper.error(err);
22+
}
23+
};
24+
25+
async addUser(email, password) {
26+
const ctx = 'cmd-repo-addUser';
27+
try {
28+
const { data:db } = await this.getDB();
29+
const document = {
30+
userId: uuidv4(),
31+
email,
32+
password,
33+
createdAt: moment().format(),
34+
};
35+
const recordset = await db.insertOne(document);
36+
if (!recordset.acknowledged)
37+
return wrapper.error(new createError.InternalServerError(`Failed insert data to mongodb`));
38+
39+
return wrapper.data(document);
40+
} catch (err) {
41+
logger.log(ctx, err.message, 'Failed insert data to mongodb');
42+
return wrapper.error(new createError.InternalServerError(`Failed insert data to mongodb: ${err.message}`));
43+
}
44+
}
45+
};
46+
47+
module.exports = CommandRepository;

bin/modules/user/services/commands/command_validation.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ const joi = require('joi');
22

33
const registerUser = joi.object({
44
email: joi.string().required(),
5+
name: joi.string().required(),
6+
password: joi.string().required(),
7+
});
8+
9+
const authUser = joi.object({
10+
email: joi.string().required(),
511
password: joi.string().required(),
612
});
713

814
module.exports = {
915
registerUser,
16+
authUser,
1017
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const mongodbConn = require('../../../../helpers/components/mongodb/connection');
2+
const wrapper = require('../../../../helpers/utils/wrapper');
3+
const logger = require('../../../../helpers/utils/logger');
4+
const createError = require('http-errors');
5+
const validate = require('validate.js');
6+
7+
class QueryRepository {
8+
constructor (config) {
9+
this.config = config;
10+
this.collectionName = 'users';
11+
}
12+
13+
async getDB() {
14+
const ctx = 'cmd-repo-getDB';
15+
try {
16+
const {data: { connection: cacheConnection, dbName }} = await mongodbConn.fetchConnection(this.config);
17+
return wrapper.data(cacheConnection.db(dbName).collection(this.collectionName));
18+
} catch (err) {
19+
logger.error(ctx, err.message, 'Error get db connection');
20+
return wrapper.error(err);
21+
}
22+
};
23+
24+
async getUserByEmail(email) {
25+
const ctx = 'qry-repo-getUserByEmail';
26+
try {
27+
const { data: db } = await this.getDB();
28+
const parameter = { email: email };
29+
const recordset = await db.findOne(parameter);
30+
if (validate.isEmpty(recordset)) {
31+
return wrapper.error(new createError.NotFound('Data not found'));
32+
}
33+
return wrapper.data(recordset);
34+
} catch (err) {
35+
logger.error(ctx, err.message, 'Error get data from mongodb');
36+
return wrapper.error(new createError.InternalServerError(`Error get data from mongodb: ${err.message}`));
37+
}
38+
}
39+
};
40+
41+
module.exports = QueryRepository;

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
"http-errors": "^2.0.0",
2020
"http-status-codes": "^2.3.0",
2121
"joi": "^17.12.1",
22+
"moment": "^2.30.1",
23+
"mongodb": "5.9.2",
2224
"multer": "^1.4.5-lts.1",
2325
"nodemon": "^3.0.3",
26+
"uuid": "^9.0.1",
2427
"validate.js": "^0.13.1",
2528
"winston": "^3.11.0"
2629
}

0 commit comments

Comments
 (0)