diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..de48503 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +APP_NAME = "Scope" + +HOST = +PORT = + +DB_HOST = +DB_USER = +DB_PASSWORD = +DB_DATABASE = +DB_DIALECT = +DB_POOL_MAX = +DB_POOL_MIN = +DB_POOL_ACQUIRE = +DB_POOL_IDLE = + +JWT_ACCESS_TOKEN_SECRET_PRIVATE = +JWT_ACCESS_TOKEN_SECRET_PUBLIC = +JWT_ACCESS_TOKEN_EXPIRATION_MINUTES = + +REFRESH_TOKEN_EXPIRATION_DAYS = +VERIFY_EMAIL_TOKEN_EXPIRATION_MINUTES = +RESET_PASSWORD_TOKEN_EXPIRATION_MINUTES = + +VERIFYCATION_CODE = +GENERATED_TIME = +VALID_DURATION = + +SMTP_HOST = +SMTP_PORT = +SMTP_USERNAME = +SMTP_PASSWORD = +EMAIL_FROM = + +FRONTEND_URL = + +IMAGE_URL = \ No newline at end of file diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..79c081a --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,37 @@ +name: CI/CD Pipeline + +# Controls when the action will run. Triggers the workflow on push events but only for the main branch +on: + push: + branches: + - main # Change this to your deployment branch + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + # Add additional steps here to install dependencies, run tests, etc. + + - name: Deploy + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_USER: ${{ secrets.SSH_USER }} + run: | + echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null + mkdir -p ~/.ssh + touch ~/.ssh/known_hosts + ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts + ssh $SSH_USER@$SSH_HOST << 'EOF' + cd /path/to/your/application # Change this to your application's path on the server + git pull origin main # Ensure you're pulling the right branch, e.g., `main` + # Add commands to restart your service/application if necessary + # Example for Node.js with PM2: + npm install + pm2 restart app_name + EOF diff --git a/README.md b/README.md index f356369..507dab8 100644 --- a/README.md +++ b/README.md @@ -1 +1,22 @@ -# Express + MySQL +# The Backend Server + +This is stage in Developement. + +Run Command Prompt or Terminal in director of project. + +1. `npm install` or `yarn install` + +2. Install XAMPP and Start Apache and MySQL for database in XAMPP Control Panel. + +3. Create the database(`db_scope`) for server in XAMPP Apache server[http://localhost/phpmyadmin/]. + +4. `npm start` or `yarn start` + +5. Add the Service Provider in service-providers table. + +6. The server is running on [http://localhost:5000] + +7. We get the url(https) for server from ngrok[https://dashboard.ngrok.com]. + +8. Final, we use this url for server. + ex: `https://singularly-hot-cod.ngrok-free.app` diff --git a/src/config/db.config.js b/src/config/db.config.js index c9ce03f..c2a0e23 100644 --- a/src/config/db.config.js +++ b/src/config/db.config.js @@ -1,24 +1,29 @@ const dotenv = require("dotenv"); +// Load environment variables from .env file dotenv.config(); +// Parse integer values for database pool configuration from environment variables const max = parseInt(process.env.DB_POOL_MAX); const min = parseInt(process.env.DB_POOL_MIN); const acquire = parseInt(process.env.DB_POOL_ACQUIRE); const idle = parseInt(process.env.DB_POOL_IDLE); +// Database connection configuration object const config = { - HOST: process.env.DB_HOST, - USER: process.env.DB_USER, - PASSWORD: process.env.DB_PASSWORD, - DB: process.env.DB_DATABASE, - dialect: process.env.DB_DIALECT, + HOST: process.env.DB_HOST, // Database host address + USER: process.env.DB_USER, // Database user + PASSWORD: process.env.DB_PASSWORD, // Database user's password + DB: process.env.DB_DATABASE, // Name of the database + dialect: process.env.DB_DIALECT, // SQL dialect (e.g., 'mysql', 'postgres') pool: { - max: max, - min: min, - acquire: acquire, - idle: idle, + // Pool configuration for managing database connections + max: max, // Maximum number of connections in pool + min: min, // Minimum number of connections in pool + acquire: acquire, // Maximum time (ms) that pool will try to get connection before throwing error + idle: idle, // Maximum time (ms) that a connection can be idle before being released }, }; +// Export the configuration object for use in other parts of the application module.exports = config; diff --git a/src/controllers/service.controller.js b/src/controllers/service.controller.js deleted file mode 100644 index 5e30378..0000000 --- a/src/controllers/service.controller.js +++ /dev/null @@ -1,47 +0,0 @@ -// const bcrypt = require("bcrypt"); -// const jwt = require("jsonwebtoken"); -const db = require("../models"); -const ServiceProvider = db.serviceprovider; - -// const SecurityOfKey = process.env.JWT_ACCESS_TOKEN_SECRET_PRIVATE; -// const expiresIn = process.env.JWT_ACCESS_TOKEN_EXPIRATION_MINUTES; - -const test = async (req, res) => { - await res.status(200).json({ msg: "ServiceProvider is running." }); -}; - -const signup = async (req, res) => { - try { - const { - name, - address, - areaOfOperation, - servicesProvided, - email, - } = req.body; - - // Check if all fields are filled - if ( - !name || - !address || - !areaOfOperation || - !servicesProvided || - !email - ) { - return res.status(400).json({ msg: "Please fill in all fields." }); - } - - const newService = await ServiceProvider.create({ - name, - address, - areaOfOperation, - servicesProvided, - contactInfo: email, - }); - res.status(200).json({ msg: "ServiceProvider created successfully.", newService }); - } catch (error) { - res.status(500).json({ msg: error.message }); - } -}; - -module.exports = { test, signup }; diff --git a/src/controllers/transaction.controller.js b/src/controllers/transaction.controller.js index 59b8354..0a9cdfd 100644 --- a/src/controllers/transaction.controller.js +++ b/src/controllers/transaction.controller.js @@ -1,17 +1,17 @@ const db = require("../models"); const Transaction = db.transaction; const User = db.user; -const UserAccount = db.useraccount; const ServiceProvider = db.serviceprovider; const test = async (req, res) => { await res.status(200).json({ msg: "Transaction is running." }); }; +//* POST /create const create = async (req, res) => { try { - const { userId, serviceId } = req.params; - const { content, amount } = req.body; + const userId = req.user.id; + const { content, amount, service } = req.body; // Check if User exists const user = await User.findOne({ where: { id: userId } }); @@ -21,15 +21,20 @@ const create = async (req, res) => { // Check if ServiceProvider exists const serviceProvider = await ServiceProvider.findOne({ - where: { id: serviceId }, + where: { name: service }, }); if (!serviceProvider) { return res.status(404).json({ msg: "ServiceProvider not found." }); } + const serviceProviderId = serviceProvider.id; // Check if transaction exists const transaction = await Transaction.findOne({ - where: { user: userId, service: serviceId }, + where: { + content, + userId: userId, + serviceProviderId: serviceProviderId, + }, }); if (transaction) { return res.status(400).json({ msg: "Transaction already exists." }); @@ -39,8 +44,8 @@ const create = async (req, res) => { const newTransaction = await Transaction.create({ content, amount, - user: userId, - service: serviceId, + userId: userId, + serviceProviderId: serviceProviderId, }); res.status(201).json({ @@ -52,23 +57,37 @@ const create = async (req, res) => { } }; +//* PUT /update const update = async (req, res) => { try { - const { userId, serviceId } = req.params; - const { content, amount } = req.body; + const userId = req.user.id; + const { content, amount, service } = req.body; + + // Check if ServiceProvider exists + const serviceProvider = await ServiceProvider.findOne({ + where: { name: service }, + }); + if (!serviceProvider) { + return res.status(404).json({ msg: "ServiceProvider not found." }); + } + const serviceProviderId = serviceProvider.id; // Check if transaction exists const transaction = await Transaction.findOne({ - where: { user: userId, service: serviceId }, + where: { + content, + userId: userId, + serviceProviderId: serviceProviderId, + }, }); if (!transaction) { return res.status(404).json({ msg: "Transaction not found." }); } // Update transaction - transaction.content = content; transaction.amount = amount; await transaction.save(); + res.status(200).json({ msg: "Transaction updated successfully.", transaction, @@ -78,9 +97,21 @@ const update = async (req, res) => { } }; +//* DELETE /delete const deleteTrans = async (req, res) => { try { - const { userId, serviceId } = req.params; + const userId = req.user.id; + const { service } = req.body; + + // Check if ServiceProvider exists + const serviceProvider = await ServiceProvider.findOne({ + where: { name: service }, + }); + if (!serviceProvider) { + return res.status(404).json({ msg: "ServiceProvider not found." }); + } + + const serviceId = serviceProvider.id; // Check if transaction exists const transaction = await Transaction.findOne({ @@ -90,7 +121,7 @@ const deleteTrans = async (req, res) => { return res.status(404).json({ msg: "Transaction not found." }); } - if(transaction.status) { + if (transaction.status) { return res.status(400).json({ msg: "Transaction already paid." }); } @@ -102,6 +133,7 @@ const deleteTrans = async (req, res) => { } }; +//* PUT /paid const paid = async (req, res) => { try { const { userId, serviceId } = req.params; @@ -113,7 +145,7 @@ const paid = async (req, res) => { if (!transaction) { return res.status(404).json({ msg: "Transaction not found." }); } - + // Check if transaction is paid if (transaction.status) { return res.status(400).json({ msg: "Transaction already paid." }); diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 2edc48b..b5cfb92 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -5,6 +5,7 @@ const db = require("../models"); const User = db.user; const UserAccount = db.useraccount; const ServiceProvider = db.serviceprovider; +const Profile = db.profile; const SecurityOfKey = process.env.JWT_ACCESS_TOKEN_SECRET_PRIVATE; // const expiresIn = process.env.JWT_ACCESS_TOKEN_EXPIRATION_MINUTES; @@ -13,6 +14,7 @@ const test = async (req, res) => { await res.status(200).json({ msg: "User is running" }); }; +//* POST /signup const signup = async (req, res) => { try { const { @@ -29,6 +31,21 @@ const signup = async (req, res) => { securityAnswer, } = req.body; + if ( + !firstName || + !lastName || + !email || + !password || + !address1 || + !city || + !state || + !phone1 || + !securityQuestion || + !securityAnswer + ) { + return res.status(400).json({ msg: "Please fill all data." }); + } + const userAccount = await UserAccount.findOne({ where: { email } }); if (userAccount) { if (userAccount.active !== 1) { @@ -50,7 +67,7 @@ const signup = async (req, res) => { // Create the user await User.create({ - id: newUserAccount.id, + userAccountId: newUserAccount.id, email: newUserAccount.email, firstName, lastName, @@ -68,6 +85,7 @@ const signup = async (req, res) => { } }; +//* POST /signin const signin = async (req, res) => { try { const { email, password } = req.body; @@ -78,6 +96,7 @@ const signin = async (req, res) => { return res.status(404).json({ msg: "User does not exist." }); } + //TODO: use this // Check if the user is already logged in // if (userAccount.loginTracking) { // return res.status(400).json({ msg: "User is already logged in." }); @@ -92,8 +111,16 @@ const signin = async (req, res) => { return res.status(400).json({ msg: "Password is incorrect." }); } + //! remove this const user = await User.findOne({ where: { id: userAccount.id } }); + //TODO: use this + // const user = await User.findOne({ + // // include: {model: UserAccount, as: "user_account", attributes: ["isFirst"]}, + // include: { model: UserAccount, attributes: ["isFirst"] }, + // where: { userAccountId: userAccount.id }, + // }); + // Create JWT Payload const payload = { id: userAccount.id, @@ -103,11 +130,18 @@ const signin = async (req, res) => { userAccount.loginTracking = true; const isFirst = await userAccount.isFirst; + //! remove this // Check if the user is first time login if (isFirst) { userAccount.isFirst = false; } + //TODO: use this + // Check if the user is first time login + // if (userAccount.isFirst) { + // userAccount.isFirst = false; + // } + // Sign Token jwt.sign(payload, SecurityOfKey, (err, token) => { if (err) throw err; @@ -116,7 +150,7 @@ const signin = async (req, res) => { msg: "Successfully signed in.", token, user, - isFirst, + isFirst, //! remove this }); }); }); @@ -125,6 +159,7 @@ const signin = async (req, res) => { } }; +//* POST /signout const signout = async (req, res) => { try { const userAccount = req.user; @@ -137,6 +172,7 @@ const signout = async (req, res) => { } }; +//* PUT /data const updateUser = async (req, res) => { try { const userAccount = req.user; @@ -145,7 +181,6 @@ const updateUser = async (req, res) => { const { firstName, lastName, - // email, address1, address2, phone1, @@ -153,39 +188,19 @@ const updateUser = async (req, res) => { city, state, zip, - securityQuestion, - securityAnswer, } = req.body; - // if (email) { - // // Check if the email is already taken - // const emailTaken = await UserAccount.findOne({ - // where: { email }, - // }); - // if (emailTaken && emailTaken.id !== id) { - // return res.status(400).json({ msg: "Email is already taken." }); - // } - // } - - // Update the userAccount - await UserAccount.update( - { - // email, - securityQuestion, - securityAnswer, - }, - { where: { id } } - ); - - userAccount.isFirst = false; - await userAccount.save(); + // Check if the user exists + const user = await User.findOne({ where: { userAccountId: id } }); + if (!user) { + return res.status(404).json({ msg: "User does not exist." }); + } // Update the user await User.update( { firstName, lastName, - // email, address1, address2, phone1, @@ -194,10 +209,12 @@ const updateUser = async (req, res) => { state, zip, }, - { where: { id } } + { where: { userAccountId: id } } ); - const updatedUser = await User.findOne({ where: { id } }); + const updatedUser = await User.findOne({ + where: { userAccountId: id }, + }); res.status(200).json({ msg: "Successfully.", updatedUser }); } catch (error) { @@ -205,12 +222,12 @@ const updateUser = async (req, res) => { } }; +//* GET /search const search = async (req, res) => { try { const { key } = req.query; - console.log(key); - + // Check if the service exists const searchedService = await ServiceProvider.findAll({ where: { servicesProvided: { @@ -232,6 +249,76 @@ const search = async (req, res) => { } }; +//* GET /all +const allServices = async (req, res) => { + try { + const services = await ServiceProvider.findAll({}); + res.status(200).json({ msg: "Successfully all search.", services }); + } catch (error) { + res.status(500).json({ msg: error.message }); + } +}; + +//* GET /forgotpwd +const forgotPwd = async (req, res) => { + try { + const { email } = req.query; + const userAccount = await UserAccount.findOne({ where: { email } }); + if (!userAccount) { + return res.status(404).json({ msg: "User does not exist." }); + } + res.status(200).json({ + msg: "Please input the answer.", + securityQuestion: userAccount.securityQuestion, + }); + } catch (error) { + res.status(500).json({ msg: error.message }); + } +}; + +//* POST /forgotpwd +const forgotPwdAnswer = async (req, res) => { + try { + const { email, securityQuestion, securityAnswer } = req.body; + const userAccount = await UserAccount.findOne({ + where: { email, securityQuestion }, + }); + if (!userAccount) { + return res.status(404).json({ msg: "User does not exist." }); + } + + const secAnsMatch = await bcrypt.compare( + securityAnswer, + userAccount.securityAnswer + ); + if (!secAnsMatch) { + return res + .status(400) + .json({ msg: "Security answer does not match." }); + } + res.status(200).json({ msg: "User can reset password." }); + } catch (error) { + res.status(500).json({ msg: error.message }); + } +}; + +//* PUT /forgotpwd +const forgotPwdReset = async (req, res) => { + try { + const { email, password } = req.body; + const userAccount = await UserAccount.findOne({ where: { email } }); + if (!userAccount) { + return res.status(404).json({ msg: "User does not exist." }); + } + const newPasswordHash = await bcrypt.hash(password, 10); + userAccount.password = newPasswordHash; + await userAccount.save(); + res.status(200).json({ msg: "Successfully reset password." }); + } catch (error) { + res.status(500).json({ msg: error.message }); + } +}; + module.exports = { test, signup, @@ -239,4 +326,8 @@ module.exports = { signout, updateUser, search, + allServices, + forgotPwd, + forgotPwdAnswer, + forgotPwdReset, }; diff --git a/src/controllers/verify.controller.js b/src/controllers/verify.controller.js index f81aa20..9e4ded8 100644 --- a/src/controllers/verify.controller.js +++ b/src/controllers/verify.controller.js @@ -5,13 +5,13 @@ dotenv.config(); const UserAccount = db.useraccount; +//* POST /verify const sendCode = async (req, res) => { try { const { email } = req.body; - const userAccount = await UserAccount.findOne({ where: { email } }); - // Check if the user exists + const userAccount = await UserAccount.findOne({ where: { email } }); if (!userAccount) { return res.status(404).json({ msg: "User did not signup. Please signup." }); } @@ -36,6 +36,8 @@ const sendCode = async (req, res) => { pass: process.env.SMTP_PASSWORD, }, }); + + // Defined transport object const mailOptions = { from: `${process.env.APP_NAME} <${process.env.EMAIL_FROM}>`, to: email, @@ -46,6 +48,8 @@ const sendCode = async (req, res) => {
Best wishes,
Scope Inc.