diff --git a/app.js b/app.js
index 86ad9ce3..6b7a0e4a 100644
--- a/app.js
+++ b/app.js
@@ -6,6 +6,7 @@ const errorHandlerMiddleware = require('./middlewares/error-handler');
const userRoutes = require('./routes/users');
const orgRoutes = require('./routes/orgRoutes');
const lunchRoutes = require('./routes/lunchRoutes');
+const authRoutes = require('./routes/auth.route');
const sequelize = require('./db/db');
const app = express();
@@ -15,13 +16,14 @@ app.use(express.json());
app.use(helmet());
const PORT = process.env.PORT || 4000;
-app.use('/api/organization', orgRoutes);
app.use('/api/', userRoutes);
+app.use('/api/auth', authRoutes);
+app.use('/api/organization', orgRoutes);
app.use('/api/lunch', lunchRoutes);
// Middlewares
-app.use(notFound);
app.use(errorHandlerMiddleware);
+app.use(notFound);
sequelize.sync().then(() => {
// Remove console.log() before production
diff --git a/controllers/authController.js b/controllers/authController.js
new file mode 100644
index 00000000..00c91e7c
--- /dev/null
+++ b/controllers/authController.js
@@ -0,0 +1,143 @@
+/* eslint-disable camelcase */
+const jwt = require('jsonwebtoken');
+const bcrypt = require('bcrypt');
+const User = require('../models/user.model');
+const { createCustomError } = require('../errors/custom-errors');
+const Invite = require('../models/organisation_invite.model');
+
+const secretKey = process.env.JWT_SECRET_KEY;
+
+async function createUser(req, res, next) {
+ try {
+ const {
+ first_name,
+ last_name,
+ email,
+ phone,
+ password,
+ is_admin,
+ profile_pic,
+ org_id,
+ lunch_credit_balance,
+ refresh_token,
+ bank_code,
+ bank_name,
+ bank_number,
+ token,
+ } = req.body;
+
+ // Validate input data
+
+ if (!first_name || !last_name || !email || !password || !token) {
+ // TODO: truly validate data
+ throw createCustomError('Missing required fields', 400);
+ }
+
+ // Check if the token is valid and retrieve org_id
+ const invite = await Invite.findOne({ where: { token } });
+
+ if (!invite || new Date() > invite.ttl) {
+ throw createCustomError('Invalid or expired invitation token', 400);
+ }
+
+ const salt = await bcrypt.genSalt(10);
+ const hashedPassword = await bcrypt.hash(password, salt);
+
+ const user = {
+ first_name,
+ last_name,
+ email,
+ phone,
+ password_hash: hashedPassword,
+ is_admin,
+ profile_pic,
+ org_id,
+ lunch_credit_balance,
+ refresh_token,
+ bank_code,
+ bank_name,
+ bank_number,
+ };
+
+ const newUser = await User.create(user);
+ delete newUser.password_hash;
+
+ const userWithoutPassword = Object.assign(newUser.toJSON);
+ delete userWithoutPassword.password_hash;
+ console.log(userWithoutPassword);
+
+ return res.status(200).json({
+ success: true,
+ message: 'User registered successfully',
+ data: {
+ user: userWithoutPassword,
+ },
+ });
+ } catch (error) {
+ if (error.name === 'SequelizeUniqueConstraintError') {
+ // Unique constraint violation (duplicate email)
+ let errorMessage = error.errors[0].message;
+ errorMessage = errorMessage[0].toUpperCase() + errorMessage.slice(1);
+ next(createCustomError(errorMessage, 400));
+ }
+ next(error.message);
+ }
+}
+
+const loginUser = async (req, res, next) => {
+ const { email, password } = req.body;
+
+ try {
+ if (!email || !password) {
+ throw createCustomError('Fill all required fields', 400);
+ }
+
+ console.log(1);
+ const user = await User.findOne({ where: { email } });
+ if (!user) {
+ throw createCustomError('Invalid credentials', 404);
+ }
+
+ const isPasswordValid = await bcrypt.compare(password, user.password_hash);
+
+ if (!isPasswordValid) {
+ throw createCustomError('Invalid credentials', 401);
+ }
+
+ const token = jwt.sign({ id: user.id }, secretKey, {
+ expiresIn: '1h',
+ });
+
+ // Sending the token in the response
+
+ return res.status(200).json({
+ message: 'User authenticated successfully',
+ statusCode: 200,
+ data: {
+ access_token: token,
+ email: user.email,
+ id: user.id,
+ isAdmin: user.is_admin,
+ },
+ });
+ } catch (error) {
+ next(error);
+ }
+};
+
+const logoutUser = (req, res) => {
+ try {
+ const token = req.header('Authorization').replace('Bearer ', '');
+
+ jwt.verify(token, process.env.JWT_SECRET_KEY);
+ if (!token) {
+ createCustomError('User is not logged in.', 401);
+ }
+
+ return res.status(200).json({ message: 'User logged out successfully.' });
+ } catch (error) {
+ return res.status(200).json({ message: 'User logged out successfully.' });
+ }
+};
+
+module.exports = { createUser, loginUser, logoutUser };
diff --git a/controllers/index.js b/controllers/index.js
deleted file mode 100644
index e69de29b..00000000
diff --git a/controllers/mailController.js b/controllers/mailController.js
new file mode 100644
index 00000000..c4473716
--- /dev/null
+++ b/controllers/mailController.js
@@ -0,0 +1,94 @@
+// import bcrypt from "bcryptjs"
+const dotenv = require('dotenv');
+// const { transport } = require('../config/nodemailerConfig.js');
+
+const { transport } = {};
+
+dotenv.config();
+// const teamMail = process.env.TEAM_MAIL;
+
+const issueOtp = async () => {
+ const otp = `${Math.floor(1000 + Math.random() * 9000)}`;
+ // const saltRounds = 12; //This should be in environment variable
+
+ // const hashedOTP = await bcrypt.hash(otp, saltRounds);
+
+ //Save hased otp with userId and email for confirmation purposes
+ //Hased OTP should be saved to db for confirmation later,then deleted upon successful authentication
+
+ return {
+ userOtp: otp,
+ timeLeft: `1 hour`,
+ };
+};
+
+const otpMessage = (otp, timeLeft) => {
+ const template = `
+
+
Welcome to XXXX 2.0
+
+
Your OTP is ${otp}
+ It expires in ${timeLeft}
+
+
+ `;
+ return template;
+};
+
+// Function to send email with otp code
+const sendEmail = async (email, message) => {
+ const mailOptions = {
+ from: 'team.lightning.hng@gmail.com', //This should be in environement variable
+ subject: 'Verify your email',
+ to: email,
+ html: message,
+ };
+
+ return new Promise((resolve, reject) => {
+ transport.sendMail(mailOptions, (err, info) => {
+ if (err) reject(err);
+ resolve(info);
+ });
+ });
+};
+
+const sendUserOtp = async (userId, email) => {
+ try {
+ if (!userId || !email) {
+ return {
+ status: false,
+ message: `User details cannot be empty`,
+ };
+ }
+
+ //generate a new otp
+ const otp = await issueOtp(userId, email);
+ const message = otpMessage(otp.userOtp, otp.timeLeft);
+
+ //send mail with otp details
+ await sendEmail(email, message);
+
+ return {
+ status: true,
+ message: 'otp sent successfully',
+ data: null,
+ };
+ } catch (error) {
+ console.log(error);
+ return {
+ status: false,
+ message: `internal server error`,
+ };
+ }
+};
+
+module.exports = {
+ issueOtp,
+ otpMessage,
+ sendEmail,
+ sendUserOtp,
+};
+
+//call this function to test controller
+// sendUserOtp('1','test@example.com')
diff --git a/controllers/organizationController.js b/controllers/organizationController.js
index 5a51e373..5206b039 100644
--- a/controllers/organizationController.js
+++ b/controllers/organizationController.js
@@ -3,10 +3,11 @@ const Organization = require('../models/organization.model');
const LunchWallet = require('../models/org_lunch_wallet.model');
const { createCustomError } = require('../errors/custom-errors');
const orgInvites = require('../models/organisation_invite.model');
-const {
- generateUniqueToken,
- sendInvitationEmail,
-} = require('../utils/sendOTP');
+
+const { generateUniqueToken, sendInvitationEmail } = {
+ generateUniqueToken: '',
+ sendInvitationEmail: '',
+};
// Create a new organization and user (Admin user only)
const createOrganization = async (req, res, next) => {
@@ -40,7 +41,7 @@ const createOrganization = async (req, res, next) => {
}
};
-async function sendInvitation(req, res, next) {
+async function sendInvite(req, res, next) {
try {
const { email, organizationId } = req.body;
@@ -67,4 +68,4 @@ async function sendInvitation(req, res, next) {
}
}
-module.exports = { sendInvitation, createOrganization };
+module.exports = { sendInvite, createOrganization };
diff --git a/controllers/userController.js b/controllers/userController.js
index b9740e37..afdcd09a 100644
--- a/controllers/userController.js
+++ b/controllers/userController.js
@@ -1,9 +1,8 @@
/* eslint-disable camelcase */
-const bcrypt = require('bcrypt');
-const User = require('../models/user.model');
-const Invite = require('../models/organisation_invite.model');
+const User = require('../models/user.model'); //import user model
+const { createCustomError } = require('../errors/custom-errors');
-async function getMe(req, res) {
+async function getMe(req, res, next) {
try {
const user = await User.findOne({ where: { id: req.user.id } });
@@ -23,17 +22,13 @@ async function getMe(req, res) {
}
}
-async function getUserById(req, res) {
+async function getUserById(req, res, next) {
try {
const userId = req.params.id;
const user = await User.findOne({ where: { id: userId } });
if (!user) {
- return res.status(404).json({
- success: false,
- message: 'User not found',
- data: null,
- });
+ throw createCustomError('User not found', 404);
}
res.status(200).json({
@@ -44,102 +39,12 @@ async function getUserById(req, res) {
},
});
} catch (error) {
- return res.status(500).json({
- success: false,
- message: 'Internal Server Error',
- data: null,
- });
+ next(error);
}
}
// Controllers Function to register new user
-async function createUser(req, res) {
- try {
- const {
- first_name,
- last_name,
- email,
- phone,
- password,
- is_admin,
- profile_pic,
- org_id,
- launch_credit_balance,
- refresh_token,
- bank_code,
- bank_name,
- bank_number,
- token,
- } = req.body;
-
- // Validate input data
- if (!first_name || !last_name || !email || !password || !token) {
- return res.status(400).json({
- success: false,
- message: 'Missing required fields',
- data: null,
- });
- }
-
- // Check if the token is valid and retrieve org_id
- const invite = await Invite.findOne({ where: { token } });
-
- if (!invite || new Date() > invite.ttl) {
- return res.status(400).json({
- success: false,
- message: 'Invalid or expired invitation token',
- data: null,
- });
- }
-
- const salt = await bcrypt.genSalt(10);
- const hashedPassword = await bcrypt.hash(password, salt);
-
- const user = {
- first_name,
- last_name,
- email,
- phone,
- password_hash: hashedPassword,
- is_admin,
- profile_pic,
- org_id,
- launch_credit_balance,
- refresh_token,
- bank_code,
- bank_name,
- bank_number,
- };
-
- const newUser = await User.create(user);
- delete newUser.password_hash;
-
- res.status(200).json({
- success: true,
- message: 'User registered successfully',
- data: {
- user: newUser,
- },
- });
- } catch (error) {
- if (error.name === 'SequelizeUniqueConstraintError') {
- // Unique constraint violation (duplicate email)
- return res.status(400).json({
- success: false,
- message: 'Email already exists',
- data: null,
- });
- }
-
- return res.status(500).json({
- success: false,
- message: error.message,
- data: null,
- });
- }
-}
-
-async function getAllUsers(req, res) {
+async function getAllUsers(req, res, next) {
try {
const users = await User.findAll({
where: { org_id: req.user.org_id },
@@ -160,7 +65,7 @@ async function getAllUsers(req, res) {
});
}
}
-async function deleteUser(req, res) {
+async function deleteUser(req, res, next) {
try {
const userId = req.params.id;
@@ -191,7 +96,7 @@ async function deleteUser(req, res) {
}
}
-async function updateUser(req, res) {
+async function updateUser(req, res, next) {
try {
const userId = req.params.id;
const {
@@ -244,7 +149,6 @@ async function updateUser(req, res) {
module.exports = {
getMe,
getUserById,
- createUser,
getAllUsers,
updateUser,
deleteUser,
diff --git a/controllers/userLoginController.js b/controllers/userLoginController.js
deleted file mode 100644
index 6eff515a..00000000
--- a/controllers/userLoginController.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const jwt = require('jsonwebtoken');
-const bcrypt = require('bcrypt');
-const User = require('../models/user.model');
-const { createCustomError } = require('../errors/custom-errors');
-
-const secretKey = process.env.JWT_SECRET_KEY;
-
-const loginController = async (req, res) => {
- const { email, password } = req.body;
-
- try {
- const user = await User.findOne({ where: { email } });
-
- if (!user) {
- return res.status(401).json({ message: 'Invalid credentials' });
- }
-
- const isPasswordValid = await bcrypt.compare(password, user.password_hash);
-
- if (!isPasswordValid) {
- throw createCustomError('Invalid credentials', 401);
- }
-
- const token = jwt.sign({ id: user.id }, secretKey, {
- expiresIn: '1h',
- });
-
- res.status(200).json({
- success: true,
- message: 'User logged in successfully',
- data: { user, token },
- });
- } catch (error) {
- res.status(500).json({ message: 'Internal server error' });
- }
-};
-
-module.exports = loginController;
diff --git a/controllers/userLogoutController.js b/controllers/userLogoutController.js
deleted file mode 100644
index cd205d61..00000000
--- a/controllers/userLogoutController.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const jwt = require('jsonwebtoken');
-const { createCustomError } = require('../errors/custom-errors');
-
-const logoutController = (req, res) => {
- try {
- const token = req.header('Authorization').replace('Bearer ', '');
-
- jwt.verify(token, process.env.JWT_SECRET_KEY);
- if (!token) {
- createCustomError('User is not logged in.', 401);
- }
-
- return res.status(200).json({ message: 'User logged out successfully.' });
- } catch (error) {
- return res.status(200).json({ message: 'User logged out successfully.' });
- }
-};
-
-module.exports = logoutController;
diff --git a/db/db.js b/db/db.js
index b45933ce..2425066a 100644
--- a/db/db.js
+++ b/db/db.js
@@ -5,4 +5,11 @@ const URI = process.env.MYSQL_ADDON_URI;
const sequelize = new Sequelize(URI, { dialect: 'mysql', logging: false });
+//The below is for local connection
+
+// const sequelize = new Sequelize('free_lunch_db', 'root', '64632120', {
+// host: 'localhost',
+// dialect: 'mysql'
+// });
+
module.exports = sequelize;
diff --git a/middlewares/auth.js b/middlewares/auth.js
index c06ea4ca..ce634e76 100644
--- a/middlewares/auth.js
+++ b/middlewares/auth.js
@@ -1,5 +1,6 @@
const jwt = require('jsonwebtoken');
const User = require('../models/user.model');
+const { createCustomError } = require('../errors/custom-errors');
async function auth(req, res, next) {
try {
@@ -11,14 +12,13 @@ async function auth(req, res, next) {
if (!user) {
//this should be updated after custom errors have been implemented
- throw new Error('User not Authenticated');
+ throw createCustomError('Access Denied', 401);
}
req.user = user.dataValues;
req.token = token;
} catch (error) {
- console.log(error.message);
- //switch to next(error) after error middleware have been created
+ next(error);
}
}
diff --git a/middlewares/error-handler.js b/middlewares/error-handler.js
index 0cdcb34a..23eb17ce 100644
--- a/middlewares/error-handler.js
+++ b/middlewares/error-handler.js
@@ -1,6 +1,7 @@
-const { CustomErrorClass } = require('../Errors/custom-errors');
+const { CustomErrorClass } = require('../errors/custom-errors');
const errorHandlerMiddleware = (err, req, res, next) => {
+ console.log(err.message);
if (err instanceof CustomErrorClass) {
return res
.status(err.statusCode)
diff --git a/models/lunches.model.js b/models/lunches.model.js
index f57477d9..608445ec 100644
--- a/models/lunches.model.js
+++ b/models/lunches.model.js
@@ -42,13 +42,13 @@ const Lunch = sequelize.define(
{ tableName: 'lunches', createdAt: 'created_at', updatedAt: false },
);
// foreign key to user from receiver to allow usage like
-// launch.user
+// lunch.user
Lunch.belongsTo(User, {
foreignKey: 'recieverId',
});
// foreign key to user from sender to allow usage like
-// launch.user
+// lunch.user
Lunch.belongsTo(User, {
foreignKey: 'senderId',
});
diff --git a/models/organization.model.js b/models/organization.model.js
index d1dbfe63..cd160b18 100644
--- a/models/organization.model.js
+++ b/models/organization.model.js
@@ -16,7 +16,7 @@ const Organization = sequelize.define(
},
lunch_price: {
type: DataTypes.DECIMAL,
- defaultValue: 2000,
+ defaultValue: 1000,
allowNull: false,
},
currency_code: {
diff --git a/models/user.model.js b/models/user.model.js
index 4122b1ae..0b3cc429 100644
--- a/models/user.model.js
+++ b/models/user.model.js
@@ -36,6 +36,7 @@ const User = sequelize.define(
},
is_admin: {
type: DataTypes.BOOLEAN,
+ defaultValue: false,
},
profile_pic: {
type: DataTypes.STRING,
@@ -44,7 +45,7 @@ const User = sequelize.define(
type: DataTypes.UUID,
references: { model: Organization, key: 'id' },
},
- launch_credit_balance: {
+ lunch_credit_balance: {
type: DataTypes.INTEGER,
defaultValue: 0,
},
@@ -60,6 +61,17 @@ const User = sequelize.define(
bank_number: {
type: DataTypes.STRING,
},
+ bank_region: {
+ type: DataTypes.STRING,
+ },
+ currency_code: {
+ type: DataTypes.STRING,
+ defaultValue: 'NGN',
+ },
+ currency: {
+ type: DataTypes.STRING,
+ defaultValue: 'Naira',
+ },
},
{
tableName: 'users',
@@ -79,4 +91,7 @@ User.prototype.toJSON = function () {
return values;
};
+(async () => {
+ await User.sync({ alter: true });
+})();
module.exports = User;
diff --git a/routes/auth.route.js b/routes/auth.route.js
new file mode 100644
index 00000000..408c57e3
--- /dev/null
+++ b/routes/auth.route.js
@@ -0,0 +1,14 @@
+const express = require('express');
+const {
+ createUser,
+ loginUser,
+ logoutUser,
+} = require('../controllers/authController');
+
+const router = express.Router();
+
+router.post('/auth/signup', createUser);
+router.post('/auth/login', loginUser);
+router.post('/auth/logout', logoutUser);
+
+module.exports = router;
diff --git a/routes/orgRoutes.js b/routes/orgRoutes.js
index bd3ee6bf..4877c669 100644
--- a/routes/orgRoutes.js
+++ b/routes/orgRoutes.js
@@ -1,8 +1,12 @@
const express = require('express');
const router = express.Router();
-const { createOrganization } = require('../controllers/organizationController');
+const {
+ createOrganization,
+ sendInvite,
+} = require('../controllers/organizationController');
router.post('/create', createOrganization);
+router.post('send-invite', sendInvite);
module.exports = router;
diff --git a/routes/users.js b/routes/users.js
index ee7fb43a..8c25e2e4 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -4,17 +4,11 @@ const router = express.Router();
const {
getMe,
getUserById,
- createUser,
getAllUsers,
updateUser,
deleteUser,
} = require('../controllers/userController');
-const loginController = require('../controllers/userLoginController');
-const logoutController = require('../controllers/userLogoutController');
-router.post('/auth/signup', createUser);
-router.post('/auth/login', loginController);
-router.post('/auth/logout', logoutController);
router.get('/users/me', getMe);
router.get('/users/:id', getUserById);
router.get('/users/', getAllUsers);