diff --git a/server/.env b/server/.env index 42ad804..84d1a42 100644 --- a/server/.env +++ b/server/.env @@ -1 +1,3 @@ -SECRET=ToMakeNewTokenForNewUser \ No newline at end of file +SECRET=ToMakeNewTokenForNewUser +SECRET_KEY_XENDIT=xnd_development_S1YRENVA6FA1ELZerCvKR3NaBY1J7Jm2JLLhawkAzF7R5gTg4SvcAZfaXPIKA0n +API_KEY_DAILY_CO=29609d3d8928f72e9c0d09e17160a43f85642e2f7a651e540f35a9395c5517c8 \ No newline at end of file diff --git a/server/coba.js b/server/coba.js new file mode 100644 index 0000000..f4459c9 --- /dev/null +++ b/server/coba.js @@ -0,0 +1,10 @@ +const tanggal = new Date(); +console.log(tanggal); +console.log(tanggal.getTime(), "milisecond"); +console.log(tanggal.getDay(), "day"); +console.log(tanggal.getHours(), "hour"); +console.log(tanggal.getMinutes(), "minutes"); +console.log(tanggal.toLocaleString("id-ID"), "Indo"); +console.log(tanggal.toUTCString(), "pusat"); +console.log(tanggal.getUTCHours(), "pusat"); +console.log(5 * 60 * 1000); diff --git a/server/controllers/loanController.js b/server/controllers/loanController.js index 145a960..47dee4b 100644 --- a/server/controllers/loanController.js +++ b/server/controllers/loanController.js @@ -1,16 +1,30 @@ -// const {} = require("") +const {Loan, Borrower} = require("../models/index"); const { XenditInvoice, XenditDisbursement } = require('../helpers/Xendit'); class LoanController { - static async CreateInvoice(req, res, next) { - const { email } = req.body; - const loanID = '123456'; + static async CreateInvoiceLender(req, res, next) { + // const { userID, email } = req.user.email; //tunggu auth + const lenderID = 1 + const email = "dharmasatrya10@gmail.com" + const {amount, tenor} = req.body + const randomID = Math.random().toString(36).slice(2) try { + const loanData = { + externalID: randomID, + lenderID: lenderID, + borrowerID: null, + status: "pending", + initialLoan: amount, + tenor: tenor + } + + const createLoan = await Loan.create(loanData) + const invoice = await XenditInvoice.createInvoice({ - externalID: 'EXTERNAL_ID_KITA', - payerEmail: 'pinjamkuproject@gmail.com', - description: `Invoice for loan ${loanID}`, - amount: 100000, + externalID: `invoice-lender-${randomID}`, + payerEmail: email, + description: `Invoice for loan ${randomID}`, + amount: amount, shouldSendEmail: true }); res.status(200).json({ @@ -22,16 +36,33 @@ class LoanController { } } - // static async CreateLoan(req, res, next) { - // const {} = req.body; - // try { - // } catch (error) { - // next(error); - // } - // } + static async CreateInvoiceBorrower(req, res, next) { + // const {email} = req.user.email + const email = "dharmasatrya10@gmail.com" + const { loanID } = req.body; + try { + const loanData = await Loan.findOne({where: {id: loanID}}) + + const amountWithInterest = loanData.initialLoan + loanData.initialLoan * 0.07 + + const invoice = await XenditInvoice.createInvoice({ + externalID: `invoice-borrower-${loanData.externalID}`, + payerEmail: email, + description: `Invoice for loan ${loanData.externalID}`, + amount: amountWithInterest, + shouldSendEmail: true + }); + res.status(200).json({ + externalID: invoice.external_id, + invoiceURL: invoice.invoice_url + }); + } catch (error) { + console.log(error); + } + } - static async CreateWithdrawal(req, res, next) { - const {} = req.body; + static async CreateWithdrawal(req, res, next) { //disburse ke lender + const {lenderID} = req.body; try { const loan = { @@ -60,29 +91,28 @@ class LoanController { } } - static async CreateDisbursement(req, res, next) { - const {} = req.body; + static async CreateDisbursement(req, res, next) { //disburse ke borrower + const {loanID} = req.body; + // const borrowerID = req.userID + const borrowerID = 1 try { - const loan = { - id: '123', - initialLoan: 100000, - borrower: { - bankCode: 'BCA', - accountHolderName: 'Test', - accountNumber: '1234567890' - } + const loanData = await Loan.findOne({where: {id:loanID}}) + const borrowerData = await Borrower.findOne({where: {id: borrowerID}}) + + const amountWithInterest = loanData.initialLoan + loanData.initialLoan * 0.07 + + const loanDataInput = { + externalID: `disburse-borrower-${loanData.externalID}`, + bankCode: borrowerData.bankCode, + accountHolderName: borrowerData.holderName, + accountNumber: `${borrowerData.accountNumber}`, + description: `loan disbursement for ${loanData.externalID}`, + amount: amountWithInterest }; - const withdrawalInterest = loan.initialLoan + loan.initialLoan * 0.05; + console.log(loanDataInput) - const disbursement = await XenditDisbursement.create({ - externalID: `disburse-${loan.id}`, - bankCode: loan.lender.bankCode, - accountHolderName: loan.lender.accountHolderName, - accountNumber: loan.lender.accountNumber, - description: `withdrawal for ${loan.id}`, - amount: withdrawalInterest - }); + const disbursement = await XenditDisbursement.create(loanDataInput); res.status(200).json(disbursement); } catch (error) { console.log(error); @@ -90,12 +120,20 @@ class LoanController { } static async InvoiceEndpoint(req, res, next) { - const { external_id, status } = req.body; + const { external_id, status, amount } = req.body; try { if (status === 'FAILED') { res.status(200).json({ status: 'failed' }); } else if (status === 'PAID') { - res.status(200).json({ status: 'paid' }); + if(external_id.includes("borrower")){ + const loanID = external_id.split("-")[2] + const loanData = await Loan.update({status: "complete"}, {where: {externalID: loanID}}) + res.status(200).json({ status: 'complete', id: loanID }); + } else { + const loanID = external_id.split("-")[2] + const loanData = await Loan.update({ status: "active" }, {where: {externalID: loanID}, returning: true}) + res.status(200).json({ status: 'active', id: loanID }); + } } } catch (error) { console.log(error); @@ -108,20 +146,20 @@ class LoanController { if (status === 'FAILED') { res.status(200).json({ status: 'failed' }); } else if (status === 'COMPLETED') { - res.status(200).json({ status: 'paid' }); + if(external_id.includes("borrower")){ + const loanID = external_id.split("-")[2] + const loanData = await Loan.update({status: "borrowed"}, {where: {externalID: loanID}}) + res.status(200).json({ status: 'borrower', id: loanID }); + } else { + const loanID = external_id.split("-")[2] + const loanData = await Loan.update({status: "withdrawn"}, {where: {externalID: loanID}}) + res.status(200).json({ status: 'lender', id: loanID }); + } } } catch (error) { console.log(error); } } - - static Test(req, res, next) { - try { - res.status(200).json({ msg: 'ok' }); - } catch (error) { - res.status(500); - } - } } module.exports = LoanController; diff --git a/server/controllers/userController.js b/server/controllers/userController.js index 1c79ecf..206db02 100644 --- a/server/controllers/userController.js +++ b/server/controllers/userController.js @@ -1,8 +1,18 @@ const { User } = require("../models"); const { comparePassword } = require("../helpers/bcrypt"); const { generateToken } = require("../helpers/jwt"); +const createRoom = require("../helpers/dailyCo"); class UserController { + static async getAll(req, res, next) { + try { + const result = await User.findAll(); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } + } + static async registerUser(req, res, next) { try { const { @@ -35,7 +45,17 @@ class UserController { role, }; const result = await User.create(newUser); - res.status(200).json(result); + const { password: resultPassword, ...toSend } = result; + if (result) { + let email = result.email; + let proceed = await createRoom(`${result.id}test`, email); + if (proceed) { + console.log("ahay"); + } else { + console.log("ahuy"); + } + res.status(200).json(toSend); + } } catch (error) { res.status(500).json(error); } @@ -45,10 +65,39 @@ class UserController { try { const { email, password } = req.body; const result = await User.findOne({ where: { email } }); - const correctPassword = comparePassword(password, result.password); + const { + id, + firstName, + lastName, + email: userEmail, + password: userPassword, + phoneNumber, + address, + birthDate, + bankCode, + holderName, + accountNumber, + occupation, + role, + } = result; + const correctPassword = comparePassword(password, userPassword); if (correctPassword) { - const { id, firstName, lastName } = result; - const token = generateToken(result); + const userPayload = { + id, + firstName, + lastName, + email: userEmail, + password: userPassword, + phoneNumber, + address, + birthDate, + bankCode, + holderName, + accountNumber, + occupation, + role, + }; + const token = generateToken(userPayload); res.status(200).json({ access_token: token, id, firstName, lastName }); } else { res.status(404).json({ message: "salah password" }); @@ -57,6 +106,127 @@ class UserController { res.status(500).json(error); } } + + static async updateUser(req, res, next) { + try { + const { id } = req.user; + const { + firstName, + lastName, + email, + password, + phoneNumber, + address, + birthDate, + bankCode, + holderName, + accountNumber, + occupation, + role, + } = req.body; + let updatedUser = {}; + if (firstName !== "" && typeof firstName !== "undefined") { + updatedUser = { + ...updatedUser, + firstName, + }; + } + if (lastName !== "" && typeof lastName !== "undefined") { + updatedUser = { + ...updatedUser, + lastName, + }; + } + if (email !== "" && typeof email !== "undefined") { + updatedUser = { + ...updatedUser, + email, + }; + } + if (password !== "" && typeof password !== "undefined") { + updatedUser = { + ...updatedUser, + password, + }; + } + if (phoneNumber !== "" && typeof phoneNumber !== "undefined") { + updatedUser = { + ...updatedUser, + phoneNumber, + }; + } + if (address !== "" && typeof address !== "undefined") { + updatedUser = { + ...updatedUser, + address, + }; + } + if (birthDate !== "" && typeof birthDate !== "undefined") { + updatedUser = { + ...updatedUser, + birthDate, + }; + } + if (bankCode !== "" && typeof bankCode !== "undefined") { + updatedUser = { + ...updatedUser, + bankCode, + }; + } + if (holderName !== "" && typeof holderName !== "undefined") { + updatedUser = { + ...updatedUser, + holderName, + }; + } + if (accountNumber !== "" && typeof accountNumber !== "undefined") { + updatedUser = { + ...updatedUser, + accountNumber, + }; + } + if (occupation !== "" && typeof occupation !== "undefined") { + updatedUser = { + ...updatedUser, + occupation, + }; + } + if (role !== "" && typeof role !== "undefined") { + updatedUser = { + ...updatedUser, + role, + }; + } + const result = await User.update(updatedUser, { where: { id } }); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } + } + + static async updateUserStatus(req, res, next) { + const { userId } = req.params; + const { status } = req.body; + try { + const userStatus = { + status, + }; + const result = await User.update(userStatus, { where: { id: userId } }); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } + } + + static async deleteUser(req, res, next) { + const { userId } = req.params; + try { + const result = await User.destroy({ where: { id: userId } }); + res.status(200).json(result); + } catch (error) { + res.status(500).json(error); + } + } } module.exports = UserController; diff --git a/server/helpers/NodeMailer.js b/server/helpers/NodeMailer.js new file mode 100644 index 0000000..d3aee6c --- /dev/null +++ b/server/helpers/NodeMailer.js @@ -0,0 +1,33 @@ +const nodeMailer = require("nodemailer"); + +function sendMail(roomLink, email) { + let userMail = email; + let room = roomLink.data; + let transporter = nodeMailer.createTransport({ + service: "gmail", + auth: { + user: "pinjamkuproject@gmail.com", + pass: "Na1robi!", + }, + }); + let message = { + from: "pinjamkuproject@gmail.com", + to: [userMail, "muttaqinimam76@gmail.com", "dharmasatrya10@gmail.com"], + subject: "test", + html: `
${room.url}
`, + }; + transporter.sendMail(); + transporter + .sendMail(message, (err, info) => { + if (err) { + console.log(err); + console.log("failed"); + return err; + } else { + console.log("success"); + return null; + } + }) + .catch((err) => console.log(err)); +} +module.exports = sendMail; diff --git a/server/helpers/Xendit.js b/server/helpers/Xendit.js index e3f772d..f8f9cef 100644 --- a/server/helpers/Xendit.js +++ b/server/helpers/Xendit.js @@ -1,6 +1,6 @@ -const Xendit = require('xendit-node'); +const Xendit = require("xendit-node"); const x = new Xendit({ - secretKey: 'xnd_development_S1YRENVA6FA1ELZerCvKR3NaBY1J7Jm2JLLhawkAzF7R5gTg4SvcAZfaXPIKA0n', + secretKey: process.env.SECRET_KEY_XENDIT, }); const { Invoice, Disbursement } = x; const invoiceSpecificOptions = {}; @@ -8,5 +8,4 @@ const disbursementSpecificOptions = {}; const XenditInvoice = new Invoice(invoiceSpecificOptions); const XenditDisbursement = new Disbursement(disbursementSpecificOptions); - -module.exports = {XenditInvoice, XenditDisbursement} \ No newline at end of file +module.exports = { XenditInvoice, XenditDisbursement }; diff --git a/server/helpers/dailyCo.js b/server/helpers/dailyCo.js new file mode 100644 index 0000000..591fc18 --- /dev/null +++ b/server/helpers/dailyCo.js @@ -0,0 +1,34 @@ +const axios = require("axios"); +const sendMail = require("./NodeMailer"); + +function createRoom(roomName, userEmail) { + const currentDate = new Date(); + axios({ + method: "post", + url: "https://api.daily.co/v1/rooms", + headers: { + authorization: `Bearer ${process.env.API_KEY_DAILY_CO}`, + }, + data: { + properties: { + owner_only_broadcast: false, + exp: (currentDate.getTime() + 60000) / 1000, + enable_chat: true, + max_participants: 4, + }, + name: roomName, + privacy: "public", + }, + }) + .then((room) => { + if (!sendMail(room, userEmail)) { + return true; + } else { + return false; + } + }) + .catch((err) => { + return err; + }); +} +module.exports = createRoom; diff --git a/server/migrations/20210918115618-create-history.js b/server/migrations/20210918115618-create-history.js new file mode 100644 index 0000000..9cec331 --- /dev/null +++ b/server/migrations/20210918115618-create-history.js @@ -0,0 +1,39 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Histories', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + UserId: { + type: Sequelize.INTEGER + }, + LoanId: { + type: Sequelize.INTEGER + }, + status: { + type: Sequelize.STRING + }, + remainingDate: { + type: Sequelize.INTEGER + }, + remainingAmount: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Histories'); + } +}; \ No newline at end of file diff --git a/server/migrations/20210918140440-create-lender.js b/server/migrations/20210918140440-create-lender.js new file mode 100644 index 0000000..58231a9 --- /dev/null +++ b/server/migrations/20210918140440-create-lender.js @@ -0,0 +1,51 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Lenders', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + firstName: { + type: Sequelize.STRING + }, + lastName: { + type: Sequelize.STRING + }, + email: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + address: { + type: Sequelize.STRING + }, + birthDate: { + type: Sequelize.DATE + }, + bankCode: { + type: Sequelize.STRING + }, + holderName: { + type: Sequelize.STRING + }, + accountNumber: { + type: Sequelize.INTEGER + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Lenders'); + } +}; \ No newline at end of file diff --git a/server/migrations/20210918140558-create-borrower.js b/server/migrations/20210918140558-create-borrower.js new file mode 100644 index 0000000..40f49ed --- /dev/null +++ b/server/migrations/20210918140558-create-borrower.js @@ -0,0 +1,54 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Borrowers', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + firstName: { + type: Sequelize.STRING + }, + lastName: { + type: Sequelize.STRING + }, + email: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + address: { + type: Sequelize.STRING + }, + birthDate: { + type: Sequelize.DATE + }, + bankCode: { + type: Sequelize.STRING + }, + holderName: { + type: Sequelize.STRING + }, + accountNumber: { + type: Sequelize.INTEGER + }, + status: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Borrowers'); + } +}; \ No newline at end of file diff --git a/server/migrations/20210918140825-create-loan.js b/server/migrations/20210918140825-create-loan.js new file mode 100644 index 0000000..26ef90b --- /dev/null +++ b/server/migrations/20210918140825-create-loan.js @@ -0,0 +1,56 @@ +"use strict"; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable("Loans", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + externalID: { + type: Sequelize.STRING, + }, + lenderID: { + type: Sequelize.INTEGER, + references: { + model: "Lenders", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + borrowerID: { + type: Sequelize.INTEGER, + type: Sequelize.INTEGER, + allowNull: true, + references: { + model: "Borrowers", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + status: { + type: Sequelize.STRING, + }, + initialLoan: { + type: Sequelize.INTEGER, + }, + tenor: { + type: Sequelize.INTEGER, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable("Loans"); + }, +}; diff --git a/server/migrations/20210918145229-create-staff.js b/server/migrations/20210918145229-create-staff.js new file mode 100644 index 0000000..2e3e42a --- /dev/null +++ b/server/migrations/20210918145229-create-staff.js @@ -0,0 +1,33 @@ +'use strict'; +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.createTable('Staffs', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + type: Sequelize.STRING + }, + email: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE + } + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.dropTable('Staffs'); + } +}; \ No newline at end of file diff --git a/server/models/borrower.js b/server/models/borrower.js new file mode 100644 index 0000000..7a5d4e6 --- /dev/null +++ b/server/models/borrower.js @@ -0,0 +1,157 @@ +'use strict'; +const { + Model +} = require('sequelize'); +const { hashPassword } = require("../helpers/bcrypt"); +module.exports = (sequelize, DataTypes) => { + class Borrower extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + Borrower.hasMany(models.Loan, {foreignKey: "borrowerID"}) + } + }; + Borrower.init({ + firstName: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "first name cannot be null", + }, + notEmpty: { + msg: "first name cannot be empty", + }, + }, + }, + lastName: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "last name cannot be null", + }, + notEmpty: { + msg: "last name cannot be empty", + }, + }, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: { + args: true, + msg: "email is already exists", + }, + validate: { + notNull: { + msg: "email cannot be null", + }, + notEmpty: { + msg: "email cannot be empty", + }, + isEmail: { + msg: "invalid email", + }, + }, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "password cannot be null", + }, + notEmpty: { + msg: "password cannot be empty", + }, + }, + }, + address: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "address cannot be null", + }, + notEmpty: { + msg: "address cannot be empty", + }, + }, + }, + birthDate: { + type: DataTypes.DATE, + allowNull: false, + validate: { + notNull: { + msg: "birth date cannot be null", + }, + notEmpty: { + msg: "birth date cannot be empty", + }, + }, + }, + bankCode: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "bank code cannot be null", + }, + notEmpty: { + msg: "bank code cannot be empty", + }, + }, + }, + holderName:{ + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "holder name cannot be null", + }, + notEmpty: { + msg: "holder name cannot be empty", + }, + }, + }, + accountNumber: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "account number cannot be null", + }, + notEmpty: { + msg: "account number cannot be empty", + }, + }, + }, + status: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "status cannot be null", + }, + notEmpty: { + msg: "status cannot be empty", + }, + }, + }, + }, { + //masukin hooks register disini + hooks: { + beforeCreate: (user) => { + user.password = hashPassword(user.password); + }, + }, + sequelize, + modelName: 'Borrower', + }); + return Borrower; +}; \ No newline at end of file diff --git a/server/models/history.js b/server/models/history.js new file mode 100644 index 0000000..4871037 --- /dev/null +++ b/server/models/history.js @@ -0,0 +1,27 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class History extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + }; + History.init({ + UserId: DataTypes.INTEGER, + LoanId: DataTypes.INTEGER, + status: DataTypes.STRING, + remainingDate: DataTypes.INTEGER, + remainingAmount: DataTypes.INTEGER + }, { + sequelize, + modelName: 'History', + }); + return History; +}; \ No newline at end of file diff --git a/server/models/lender.js b/server/models/lender.js new file mode 100644 index 0000000..ce8fd53 --- /dev/null +++ b/server/models/lender.js @@ -0,0 +1,145 @@ +"use strict"; +const { Model } = require("sequelize"); +const { hashPassword } = require("../helpers/bcrypt"); +module.exports = (sequelize, DataTypes) => { + class Lender extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + Lender.hasMany(models.Loan, {foreignKey: "lenderID"}) + } + } + Lender.init( + { + firstName: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "first name cannot be null", + }, + notEmpty: { + msg: "first name cannot be empty", + }, + }, + }, + lastName: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "last name cannot be null", + }, + notEmpty: { + msg: "last name cannot be empty", + }, + }, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: { + args: true, + msg: "email is already exists", + }, + validate: { + notNull: { + msg: "email cannot be null", + }, + notEmpty: { + msg: "email cannot be empty", + }, + isEmail: { + msg: "invalid email", + }, + }, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "password cannot be null", + }, + notEmpty: { + msg: "password cannot be empty", + }, + }, + }, + address: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "address cannot be null", + }, + notEmpty: { + msg: "address cannot be empty", + }, + }, + }, + birthDate: { + type: DataTypes.DATE, + allowNull: false, + validate: { + notNull: { + msg: "birth date cannot be null", + }, + notEmpty: { + msg: "birth date cannot be empty", + }, + }, + }, + bankCode: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "bank code cannot be null", + }, + notEmpty: { + msg: "bank code cannot be empty", + }, + }, + }, + holderName: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "holder name cannot be null", + }, + notEmpty: { + msg: "holder name cannot be empty", + }, + }, + }, + accountNumber: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: "account number cannot be null", + }, + notEmpty: { + msg: "account number cannot be empty", + }, + }, + }, + }, + { + hooks: { + beforeCreate: (user) => { + user.password = hashPassword(user.password); + }, + }, + sequelize, + modelName: "Lender", + } + ); + return Lender; +}; diff --git a/server/models/loan.js b/server/models/loan.js index ec882e8..37c102e 100644 --- a/server/models/loan.js +++ b/server/models/loan.js @@ -1,6 +1,7 @@ -"use strict"; -const { Model } = require("sequelize"); -const { Op } = require("sequelize"); +'use strict'; +const { + Model +} = require('sequelize'); module.exports = (sequelize, DataTypes) => { class Loan extends Model { /** @@ -10,99 +11,20 @@ module.exports = (sequelize, DataTypes) => { */ static associate(models) { // define association here + Loan.belongsTo(models.Lender, {foreignKey: "lenderID"}) + Loan.belongsTo(models.Borrower, {foreignKey: "borrowerID"}) } - } - Loan.init( - { - externalId: { - type: DataTypes.STRING, - allowNull: false, - unique: { - args: true, - msg: "external id is already exists", - }, - validate: { - notNull: { - msg: "external id cannot be null", - }, - notEmpty: { - msg: "external id cannot be empty", - }, - }, - }, - UserId: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - notNull: { - msg: "UserId cannot be null", - }, - notEmpty: { - msg: "UserId cannot be empty", - }, - }, - }, - initialLoan: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - notNull: { - msg: "initial loan cannot be null", - }, - min: { - args: 10000, - msg: "initial loan cannot be lower than 10000", - }, - }, - }, - status: { - type: DataTypes.STRING, - allowNull: false, - validate: { - notNull: { - msg: "status cannot be null", - }, - notEmpty: { - msg: "status cannot be empty", - }, - }, - }, - amountPaid: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - notNull: { - msg: "amount paid cannot be null", - }, - min: { - args: 10000, - msg: "amount paid cannot be lower than 10000", - }, - }, - }, - tenor: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - notNull: { - msg: "tenor cannot be null", - }, - min: { - args: 30, - msg: "tenor cannot be lower than 30 days", - }, - }, - }, - }, - { - hooks: { - beforeCreate: (loan) => { - loan.status = "available"; - }, - }, - sequelize, - modelName: "Loan", - } - ); + }; + Loan.init({ + externalID: DataTypes.STRING, + lenderID: DataTypes.INTEGER, + borrowerID: DataTypes.INTEGER, + status: DataTypes.STRING, + initialLoan: DataTypes.INTEGER, + tenor: DataTypes.INTEGER + }, { + sequelize, + modelName: 'Loan', + }); return Loan; -}; +}; \ No newline at end of file diff --git a/server/models/staff.js b/server/models/staff.js new file mode 100644 index 0000000..1995e14 --- /dev/null +++ b/server/models/staff.js @@ -0,0 +1,25 @@ +'use strict'; +const { + Model +} = require('sequelize'); +module.exports = (sequelize, DataTypes) => { + class Staff extends Model { + /** + * Helper method for defining associations. + * This method is not a part of Sequelize lifecycle. + * The `models/index` file will call this method automatically. + */ + static associate(models) { + // define association here + } + }; + Staff.init({ + name: DataTypes.STRING, + email: DataTypes.STRING, + password: DataTypes.STRING + }, { + sequelize, + modelName: 'Staff', + }); + return Staff; +}; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 1cb3c43..b555c2a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1062,6 +1062,14 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, "babel-jest": { "version": "27.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.0.tgz", @@ -2057,6 +2065,11 @@ "locate-path": "^3.0.0" } }, + "follow-redirects": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -3790,6 +3803,11 @@ "integrity": "sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw==", "dev": true }, + "nodemailer": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz", + "integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew==" + }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", diff --git a/server/package.json b/server/package.json index caa5e38..f8b09ab 100644 --- a/server/package.json +++ b/server/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^0.21.4", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "cros": "^1.0.1", @@ -19,6 +20,7 @@ "ejs": "^3.1.6", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", + "nodemailer": "^6.6.3", "pg": "^8.7.1", "sequelize": "^6.6.5", "xendit-node": "^1.16.1" diff --git a/server/routes/index.js b/server/routes/index.js index ddb52cd..a543928 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -3,7 +3,6 @@ const userRoutes = require("./userRoutes"); const loanRoutes = require("./loanRoutes"); router.use("/users", userRoutes); - router.use("/loans", loanRoutes); module.exports = router; diff --git a/server/routes/loanRoutes.js b/server/routes/loanRoutes.js index 645104e..a991505 100644 --- a/server/routes/loanRoutes.js +++ b/server/routes/loanRoutes.js @@ -1,10 +1,11 @@ const router = require("express").Router(); -const Controller = require("../controllers/LoanController"); +const LoanController = require("../controllers/loanController"); -router.get("/test", Controller.Test); -router.post("/invoice", Controller.CreateInvoice); -router.post("/disburse", Controller.CreateDisbursement); -router.post("/endpoint/invoice", Controller.InvoiceEndpoint); -router.post("/endpoint/disbursement", Controller.DisbursementEndpoint); +router.post("/invoice/borrower", LoanController.CreateInvoiceBorrower); +router.post("/invoice/lender", LoanController.CreateInvoiceLender); +router.post("/disburse/withdrawal", LoanController.CreateWithdrawal); +router.post("/disburse/loan", LoanController.CreateDisbursement); +router.post("/endpoint/invoice", LoanController.InvoiceEndpoint); +router.post("/endpoint/disbursement", LoanController.DisbursementEndpoint); module.exports = router; diff --git a/server/routes/userRoutes.js b/server/routes/userRoutes.js index 46c8d3a..fbe886d 100644 --- a/server/routes/userRoutes.js +++ b/server/routes/userRoutes.js @@ -1,8 +1,11 @@ const router = require("express").Router(); const Controller = require("../controllers/userController"); +router.get("/", Controller.getAll); router.post("/register", Controller.registerUser); - router.post("/login", Controller.loginUser); +router.patch("/", Controller.updateUser); +router.patch("/:userId", Controller.updateUser); +router.delete("/:userId", Controller.deleteUser); module.exports = router; diff --git a/server/seeders/20210918144140-lender.js b/server/seeders/20210918144140-lender.js new file mode 100644 index 0000000..63b11e5 --- /dev/null +++ b/server/seeders/20210918144140-lender.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + const data = [ + { + id: 1, + firstName: "Lender", + lastName: "Lender", + email: "dharmasatrya10@gmail.com", + password: "rahasia", + address: "jakarta", + birthDate: new Date(), + bankCode: "BCA", + holderName: "Dharma Satrya", + accountNumber: 1234567890, + createdAt: new Date(), + updatedAt: new Date() + } + ] + await queryInterface.bulkInsert('Lenders', data, {}); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Lenders', null, {}) + } +}; diff --git a/server/seeders/20210918144145-borrower.js b/server/seeders/20210918144145-borrower.js new file mode 100644 index 0000000..7aa6d04 --- /dev/null +++ b/server/seeders/20210918144145-borrower.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + const data = [ + { + id: 1, + firstName: "Borrower", + lastName: "Borrower", + email: "dharmasatrya10@gmail.com", + password: "rahasia", + address: "jakarta", + birthDate: new Date(), + bankCode: "BRI", + holderName: "Dharma Satrya", + accountNumber: 1234567890, + createdAt: new Date(), + updatedAt: new Date() + } + ] + await queryInterface.bulkInsert('Borrowers', data, {}); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('Borrowers', null, {}) + } +}; diff --git a/server/seeders/20210918144155-loan.js b/server/seeders/20210918144155-loan.js new file mode 100644 index 0000000..f1072a1 --- /dev/null +++ b/server/seeders/20210918144155-loan.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + /** + * Add seed commands here. + * + * Example: + * await queryInterface.bulkInsert('People', [{ + * name: 'John Doe', + * isBetaMember: false + * }], {}); + */ + }, + + down: async (queryInterface, Sequelize) => { + /** + * Add commands to revert seed here. + * + * Example: + * await queryInterface.bulkDelete('People', null, {}); + */ + } +};