├── .babelrc ├── .dockerignore ├── .eslintrc ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── config ├── aws.js └── database.js ├── controllers ├── auth │ ├── forgot-password.js │ ├── index.js │ ├── reset-password.js │ ├── sign-in.js │ └── sign-up.js ├── leads │ ├── add-leads-record.js │ ├── get-leads-record.js │ ├── index.js │ └── remove-leads-record.js └── stripe │ ├── cancel-subscription.js │ ├── create-customer.js │ ├── create-subscription.js │ ├── get-coupon.js │ ├── handle-stripe-webhook.js │ ├── index.js │ ├── list-plans.js │ ├── retrieve-subscription.js │ ├── update-customer.js │ └── update-subscription.js ├── dockerfile ├── index.js ├── middlewares ├── auth.js └── index.js ├── models ├── index.js ├── lead.js └── user.js ├── package-lock.json ├── package.json ├── routes ├── auth.js ├── index.js ├── leads.js └── stripe.js ├── services ├── aws-s3.js └── stripe.js └── utils ├── catch-response.js └── constants.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "12" 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": ["@babel/plugin-proposal-export-default-from"] 13 | } 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jest": true 5 | }, 6 | "extends": [ 7 | "airbnb/base" 8 | ], 9 | "rules": { 10 | "comma-dangle": [ 11 | "error", 12 | "never" 13 | ], 14 | "import/no-named-as-default": 0, 15 | "import/no-named-as-default-member": 0, 16 | "import/extensions": 0, 17 | "no-useless-escape": 0, 18 | "import/prefer-default-export": 0, 19 | "no-underscore-dangle": 0, 20 | "no-return-await": 0, 21 | "consistent-return": 0, 22 | "no-unused-vars": 0, 23 | "func-names": 0, 24 | "import/no-extraneous-dependencies": 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # name: Node API 2 | 3 | # on: 4 | # push: 5 | # branches: ["master"] 6 | # pull_request: 7 | # branches: ["master"] 8 | 9 | # jobs: 10 | # build: 11 | # strategy: 12 | # matrix: 13 | # node-version: [14.x] 14 | # runs-on: ubuntu-latest 15 | # steps: 16 | # - name: Checkout 17 | # uses: actions/checkout@v2 18 | # - name: Set up Docker Buildx 19 | # uses: docker/setup-buildx-action@v1 20 | # - name: Login to private registry 21 | # uses: docker/login-action@v1 22 | # with: 23 | # username: ${{ secrets.DOCKER_USERNAME }} 24 | # password: ${{ secrets.DOCKER_PASSWORD }} 25 | # - name: Build and push 26 | # uses: docker/build-push-action@v2 27 | # with: 28 | # context: . 29 | # push: true 30 | # tags: tanveeritdev26/node-sample-api 31 | # build-args: | 32 | # MONGO_URL=${{ secrets.MONGO_URL }} 33 | # NODE_ENV=${{ secrets.NODE_ENV }} 34 | # PORT=${{ secrets.PORT }} 35 | # JWT_SECRET=${{ secrets.JWT_SECRET }} 36 | # DB_PASSWORD=${{ secrets.DB_PASSWORD }} 37 | # DB_USER=${{ secrets.DB_USER }} 38 | # deploy: 39 | # needs: build 40 | # runs-on: [self-hosted] 41 | # steps: 42 | # - name: Pull image from docker hub 43 | # run: sudo docker pull tanveeritdev26/node-sample-api:latest 44 | # - name: Delete old container 45 | # run: sudo docker rm -f nodejs-sample-api 46 | # - name: Run docker container 47 | # run: sudo docker run -d -p 90:90 --name nodejs-sample-api tanveeritdev26/node-sample-api 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # production 5 | /dist 6 | /deploy 7 | .env 8 | # misc 9 | .DS_Store 10 | .vscode 11 | yarn.lock 12 | yarn-error 13 | npm-debug.log* 14 | -------------------------------------------------------------------------------- /config/aws.js: -------------------------------------------------------------------------------- 1 | import AWS from "aws-sdk"; 2 | 3 | const { 4 | AWS_CONFIG_ACCESS_KEY_ID, 5 | AWS_CONFIG_SECRET_KEY, 6 | AWS_CONFIG_REGION, 7 | AWS_BUCKET_NAME, 8 | } = process.env; 9 | 10 | AWS.config.update({ 11 | accessKeyId: AWS_CONFIG_ACCESS_KEY_ID, 12 | secretAccessKey: AWS_CONFIG_SECRET_KEY, 13 | region: AWS_CONFIG_REGION, 14 | }); 15 | 16 | const s3 = new AWS.S3(); 17 | 18 | const getBucketsList = async () => { 19 | try { 20 | const bucketList = await s3.listBuckets().promise(); 21 | return bucketList; 22 | } catch (err) { 23 | return err.message; 24 | } 25 | }; 26 | 27 | const getListObjectsOfBucket = async () => { 28 | try { 29 | const listS3Objects = await s3 30 | .listObjects({ Bucket: AWS_BUCKET_NAME }) 31 | .promise(); 32 | return listS3Objects; 33 | } catch (err) { 34 | return err; 35 | } 36 | }; 37 | 38 | const uploadToS3Bucket = async ({ uploadParams }) => { 39 | try { 40 | const data = await s3.upload(uploadParams).promise(); 41 | return data.Location; 42 | } catch (err) { 43 | return err.message; 44 | } 45 | }; 46 | 47 | const getS3Object = async ({ Key }) => { 48 | try { 49 | const s3Object = await s3 50 | .getObject({ Bucket: AWS_BUCKET_NAME, Key }) 51 | .promise(); 52 | return s3Object.Location; 53 | } catch (err) { 54 | return err.message; 55 | } 56 | }; 57 | 58 | const deleteFileFromS3 = async ({ Key }) => { 59 | try { 60 | const data = s3.deleteObject({ Bucket: AWS_BUCKET_NAME, Key }).promise(); 61 | return data; 62 | } catch (err) { 63 | return err.message; 64 | } 65 | }; 66 | 67 | export { 68 | getBucketsList, 69 | getListObjectsOfBucket, 70 | uploadToS3Bucket, 71 | getS3Object, 72 | deleteFileFromS3, 73 | }; 74 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const { MONGO_URL } = process.env; 4 | 5 | const options = { 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | useFindAndModify: false, 9 | }; 10 | 11 | const setupDatabase = () => { 12 | if ( 13 | mongoose.connection.readyState !== 1 || 14 | mongoose.connection.readyState !== 2 15 | ) { 16 | mongoose 17 | .connect(MONGO_URL, options) 18 | .then(() => { 19 | console.info("INFO - MongoDB Database connected."); 20 | }) 21 | .catch((err) => 22 | console.log("ERROR - Unable to connect to the database:", err) 23 | ); 24 | } 25 | }; 26 | 27 | export default setupDatabase; 28 | -------------------------------------------------------------------------------- /controllers/auth/forgot-password.js: -------------------------------------------------------------------------------- 1 | import { GenerateTokenResponse } from "../../middlewares/auth"; 2 | 3 | const ForgotPassword = async ({ user, email }) => { 4 | const { _id: userId } = user; 5 | 6 | const token = GenerateTokenResponse({ userId, email }); 7 | 8 | return { message: "Token Sent", token }; 9 | }; 10 | 11 | export default ForgotPassword; 12 | -------------------------------------------------------------------------------- /controllers/auth/index.js: -------------------------------------------------------------------------------- 1 | import ForgotPassword from "./forgot-password"; 2 | import ResetPassword from "./reset-password"; 3 | import SignIn from "./sign-in"; 4 | import SignUp from "./sign-up"; 5 | 6 | export { ForgotPassword, ResetPassword, SignIn, SignUp }; 7 | -------------------------------------------------------------------------------- /controllers/auth/reset-password.js: -------------------------------------------------------------------------------- 1 | import User from "../../models/user"; 2 | 3 | export const ResetPassword = async ({ email, password }) => { 4 | const user = await User.findOne({ email }); 5 | 6 | user.password = password; 7 | 8 | await user.save(); 9 | return { 10 | message: "Password updated succesfully", 11 | }; 12 | }; 13 | 14 | export default ResetPassword; 15 | -------------------------------------------------------------------------------- /controllers/auth/sign-in.js: -------------------------------------------------------------------------------- 1 | import { GenerateTokenResponse } from "../../middlewares/auth"; 2 | 3 | const SignIn = async ({ userId, email }) => { 4 | const response = GenerateTokenResponse({ 5 | userId, 6 | email, 7 | }); 8 | return { 9 | token: response.token, 10 | user: { userId, email }, 11 | }; 12 | }; 13 | 14 | export default SignIn; 15 | -------------------------------------------------------------------------------- /controllers/auth/sign-up.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | import User from "../../models/user"; 4 | import { GenerateTokenResponse } from "../../middlewares/auth"; 5 | 6 | const SignUp = async ({ name, email, password }) => { 7 | const user = new User({ 8 | _id: mongoose.Types.ObjectId().toHexString(), 9 | name, 10 | email, 11 | password, 12 | }); 13 | const result = await user.save(); 14 | const { token } = GenerateTokenResponse({ ...result }) || {}; 15 | return { 16 | token, 17 | user: result, 18 | }; 19 | }; 20 | 21 | export default SignUp; 22 | -------------------------------------------------------------------------------- /controllers/leads/add-leads-record.js: -------------------------------------------------------------------------------- 1 | import { Types } from "mongoose"; 2 | 3 | import Lead from "../../models/lead"; 4 | 5 | const AddLeadsRecord = async ({ data }) => { 6 | const leadData = await Lead.create({ 7 | _id: Types.ObjectId().toHexString(), 8 | ...data, 9 | }); 10 | 11 | return { 12 | message: "Leads Record Saved Successfully", 13 | data: leadData, 14 | }; 15 | }; 16 | 17 | export default AddLeadsRecord; 18 | -------------------------------------------------------------------------------- /controllers/leads/get-leads-record.js: -------------------------------------------------------------------------------- 1 | import { extend } from "lodash"; 2 | 3 | import Lead from "../../models/lead"; 4 | 5 | const GetLeadsRecord = async ({ filter, sort, skip, limit, userId }) => { 6 | const { pageId, groupId, postId, keyword } = filter; 7 | 8 | const selector = { userId, isBlocked: false }; 9 | 10 | if (keyword) { 11 | if (keyword.includes(" ")) { 12 | const regex = new RegExp( 13 | keyword.replace(/(\S+)/g, (s) => `\\b${s}.*`).replace(/\s+/g, "") 14 | ); 15 | extend(selector, { 16 | $or: [ 17 | { name: { $regex: regex, $options: "i" } }, 18 | { email: { $regex: `.*${keyword}.*`, $options: "i" } }, 19 | { contact: { $regex: `.*${keyword}.*`, $options: "i" } }, 20 | { tag: { $regex: regex, $options: "i" } }, 21 | ], 22 | }); 23 | } else { 24 | extend(selector, { 25 | $or: [ 26 | { name: { $regex: `.*${keyword}.*`, $options: "i" } }, 27 | { email: { $regex: `.*${keyword}.*`, $options: "i" } }, 28 | { contact: { $regex: `.*${keyword}.*`, $options: "i" } }, 29 | { tag: { $regex: `.*${keyword}.*`, $options: "i" } }, 30 | ], 31 | }); 32 | } 33 | } else if (pageId && pageId !== "All") { 34 | extend(selector, { pageId }); 35 | } else if (groupId && groupId !== "All") { 36 | extend(selector, { groupId }); 37 | } else if (postId && postId !== "All") { 38 | extend(selector, { postId }); 39 | } 40 | 41 | const totalLeads = await Lead.find({ userId, isBlocked: false }); 42 | 43 | const leadsRecord = await Lead.find(selector) 44 | .sort(sort) 45 | .skip(skip) 46 | .limit(limit); 47 | 48 | return { 49 | total: totalLeads.length, 50 | leadsRecord, 51 | }; 52 | }; 53 | 54 | export default GetLeadsRecord; 55 | -------------------------------------------------------------------------------- /controllers/leads/index.js: -------------------------------------------------------------------------------- 1 | import AddLeadsRecord from "./add-leads-record"; 2 | import GetLeadsRecord from "./get-leads-record"; 3 | import RemoveLeadsRecord from "./remove-leads-record"; 4 | 5 | export { AddLeadsRecord, GetLeadsRecord, RemoveLeadsRecord }; 6 | -------------------------------------------------------------------------------- /controllers/leads/remove-leads-record.js: -------------------------------------------------------------------------------- 1 | import Lead from "../../models/lead"; 2 | 3 | const RemoveLeadsRecord = async ({ userId, leadIds }) => { 4 | await Lead.deleteMany({ userId, leadId: { $in: leadIds } }); 5 | 6 | return { message: "Leads Deleted Successfully" }; 7 | }; 8 | 9 | export default RemoveLeadsRecord; 10 | -------------------------------------------------------------------------------- /controllers/stripe/cancel-subscription.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const CancelSubscription = ({ subscriptionId }) => { 4 | stripe.subscriptions.del(subscriptionId); 5 | }; 6 | 7 | export default CancelSubscription; 8 | -------------------------------------------------------------------------------- /controllers/stripe/create-customer.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const CreateCustomer = ({ name, email, source, metaData }) => 4 | stripe.customers.create({ 5 | description: name, 6 | name, 7 | email, 8 | source, 9 | metadata: metaData, 10 | }); 11 | 12 | export default CreateCustomer; 13 | -------------------------------------------------------------------------------- /controllers/stripe/create-subscription.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const CreateSubscription = ({ priceId, customerId, coupon }) => { 4 | const setObj = { 5 | customer: customerId, 6 | items: [ 7 | { 8 | price: priceId, 9 | }, 10 | ], 11 | // trial_period_days: 7, 12 | trial_from_plan: true, 13 | coupon, 14 | }; 15 | 16 | // if (trialExpiredAt) { 17 | // extend(setObj, { trial_end: 'now' }); 18 | // } 19 | 20 | return stripe.subscriptions.create(setObj); 21 | }; 22 | 23 | export default CreateSubscription; 24 | -------------------------------------------------------------------------------- /controllers/stripe/get-coupon.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const GetCoupon = (coupon) => stripe.coupons.retrieve(coupon); 4 | 5 | export default GetCoupon; 6 | -------------------------------------------------------------------------------- /controllers/stripe/handle-stripe-webhook.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-trailing-spaces */ 2 | /* eslint-disable brace-style */ 3 | /* eslint-disable no-empty */ 4 | /* eslint-disable camelcase */ 5 | 6 | const HandleStripeWebhook = async ({ data, type }) => { 7 | if (type === "payment_method.attached") { 8 | } else if (type === "invoice.payment_succeeded") { 9 | } else if (type === "invoice.payment_failed") { 10 | } else if (type === "customer.subscription.deleted") { 11 | } 12 | 13 | return { 14 | message: "Success", 15 | }; 16 | }; 17 | 18 | export default HandleStripeWebhook; 19 | -------------------------------------------------------------------------------- /controllers/stripe/index.js: -------------------------------------------------------------------------------- 1 | import CreateCustomer from "./create-customer"; 2 | import CreateSubscription from "./create-subscription"; 3 | import CancelSubscription from "./cancel-subscription"; 4 | import GetSubscriptionPlans from "./get-subscription-plans"; 5 | import GetCoupon from "./get-coupon"; 6 | import HandleStripeWebhook from "./handle-stripe-webhook"; 7 | import ListPlans from "./list-plans"; 8 | import RetrieveSubscription from "./retrieve-subscription"; 9 | import UpdateCustomer from "./update-customer"; 10 | import UpdateSubscription from "./update-subscription"; 11 | 12 | export { 13 | CreateCustomer, 14 | CreateSubscription, 15 | CancelSubscription, 16 | GetCoupon, 17 | GetSubscriptionPlans, 18 | HandleStripeWebhook, 19 | ListPlans, 20 | RetrieveSubscription, 21 | UpdateCustomer, 22 | UpdateSubscription, 23 | }; 24 | -------------------------------------------------------------------------------- /controllers/stripe/list-plans.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const product = process.env.PRODUCT_ID; 4 | const ListPlans = () => stripe.plans.list({ product }); 5 | 6 | export default ListPlans; 7 | -------------------------------------------------------------------------------- /controllers/stripe/retrieve-subscription.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const RetrieveSubscription = ({ subscriptionId }) => 4 | stripe.subscriptions.retrieve(subscriptionId); 5 | 6 | export default RetrieveSubscription; 7 | -------------------------------------------------------------------------------- /controllers/stripe/update-customer.js: -------------------------------------------------------------------------------- 1 | import { extend } from "lodash"; 2 | import stripe from "../../services/stripe"; 3 | 4 | const UpdateCustomer = (customerId, { token, coupon }) => { 5 | const customerOptions = {}; 6 | 7 | if (token) extend(customerOptions, { source: token.id }); 8 | if (coupon) extend(customerOptions, { coupon }); 9 | 10 | return stripe.customers.update(customerId, { 11 | ...customerOptions, 12 | }); 13 | }; 14 | 15 | export default UpdateCustomer; 16 | -------------------------------------------------------------------------------- /controllers/stripe/update-subscription.js: -------------------------------------------------------------------------------- 1 | import stripe from "../../services/stripe"; 2 | 3 | const UpdateSubscription = ({ subscriptionId, itemId, planId }) => 4 | stripe.subscriptions.update(subscriptionId, { 5 | items: [ 6 | { 7 | id: itemId, 8 | plan: planId, 9 | }, 10 | ], 11 | prorate: true, 12 | trial_end: "now", 13 | }); 14 | 15 | export default UpdateSubscription; 16 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | WORKDIR ../NodeJS-API/ 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | RUN npm install @babel/core @babel/node @babel/plugin-proposal-export-default-from @babel/preset-env babel-loader babel-polyfill 10 | 11 | COPY . . 12 | 13 | ARG MONGO_URL 14 | ARG NODE_ENV 15 | ARG PORT 16 | ARG JWT_SECRET 17 | ARG DB_PASSWORD 18 | ARG DB_USER 19 | 20 | ENV MONGO_URL ${MONGO_URL} 21 | ENV NODE_ENV ${NODE_ENV} 22 | ENV PORT ${PORT} 23 | ENV JWT_SECRET ${JWT_SECRET} 24 | ENV DB_PASSWORD ${DB_PASSWORD} 25 | ENV DB_USER ${DB_USER} 26 | 27 | EXPOSE 90 28 | 29 | CMD ["npx", "babel-node", "index.js"] 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import express from "express"; 3 | 4 | import setupDatabase from "./config/database"; 5 | import applyMiddlewares from "./middlewares"; 6 | import router from "./routes"; 7 | 8 | const app = express(); 9 | 10 | setupDatabase(); 11 | applyMiddlewares(app); 12 | 13 | app.use("/api", router); 14 | 15 | app.listen(process.env.PORT, () => { 16 | console.log(`app is listening to port ${process.env.PORT}`); 17 | }); 18 | 19 | export default app; 20 | -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | import passport from "passport"; 2 | import jwt from "jsonwebtoken"; 3 | import LocalStrategy from "passport-local"; 4 | import { Strategy as JWTstrategy, ExtractJwt } from "passport-jwt"; 5 | import User from "../models/user"; 6 | 7 | const { JWT_SECRET } = process.env; 8 | 9 | export const GenerateTokenResponse = ({ userId, email }, verify = false) => { 10 | const expiryTime = "365d"; 11 | return { 12 | token: jwt.sign({ userId, email }, JWT_SECRET, { 13 | expiresIn: expiryTime, 14 | }), 15 | }; 16 | }; 17 | 18 | export const authenticateAuthToken = passport.authenticate("jwt", { 19 | session: false, 20 | }); 21 | 22 | // ============================ Local Login Strategy ============================ // 23 | 24 | export const LocalLoginStrategy = new LocalStrategy( 25 | { 26 | usernameField: "email", 27 | passReqToCallback: true, 28 | }, 29 | async (req, email, password, done) => { 30 | try { 31 | const user = await User.findOne({ email }); 32 | if (!user) { 33 | return done(null, false, { 34 | error: "Your login details could not be verified. Please try again.", 35 | }); 36 | } 37 | const isValid = user.validatePassword(password); 38 | if (!isValid) { 39 | return done(null, false, { 40 | error: "Your login details could not be verified. Please try again.", 41 | }); 42 | } 43 | return done(null, user); 44 | } catch (err) { 45 | done(err); 46 | } 47 | } 48 | ); 49 | 50 | // ============================ JWT Strategy ============================ // 51 | 52 | export const AuthenticationStrategy = new JWTstrategy( 53 | { 54 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 55 | secretOrKey: JWT_SECRET, 56 | }, 57 | async (jwtPayload, done) => { 58 | try { 59 | const user = await User.findById(jwtPayload.userId); 60 | if (!user) return done(null, false); 61 | 62 | return done(null, user); 63 | } catch (err) { 64 | done(err, false); 65 | } 66 | } 67 | ); 68 | -------------------------------------------------------------------------------- /middlewares/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import cors from "cors"; 3 | import passport from "passport"; 4 | import logger from "morgan"; 5 | 6 | import { LocalLoginStrategy, AuthenticationStrategy } from "./auth"; 7 | 8 | const applyMiddlewares = (app) => { 9 | app.use(cors()); 10 | app.use(express.json({ limit: "50mb" })); 11 | app.use(express.json()); 12 | app.use(logger("common")); 13 | app.use(passport.initialize()); 14 | passport.use(LocalLoginStrategy); 15 | passport.use(AuthenticationStrategy); 16 | }; 17 | 18 | export default applyMiddlewares; 19 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 2 | import './lead'; 3 | import './user'; 4 | -------------------------------------------------------------------------------- /models/lead.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose'; 2 | 3 | const schema = new Schema( 4 | { 5 | _id: { type: String }, 6 | leadId: { type: String }, 7 | name: { type: String }, 8 | userId: { type: String }, 9 | tag: { type: String }, 10 | image: { type: String }, 11 | gender: { type: String }, 12 | dob: { type: Date }, 13 | contacts: { type: Number }, 14 | email: { type: String }, 15 | notes: { type: String }, 16 | }, { 17 | timestamps: true 18 | } 19 | ); 20 | 21 | const Lead = mongoose.model('lead', schema); 22 | 23 | export default Lead; 24 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose'; 2 | import bcrypt from 'bcryptjs'; 3 | 4 | const schema = new Schema( 5 | { 6 | _id: { type: String }, 7 | name: { type: String }, 8 | email: { type: String }, 9 | facebookId: { type: String }, 10 | password: { type: String }, 11 | status: { type: String } 12 | }, { 13 | timestamps: true 14 | } 15 | ); 16 | 17 | schema.pre('save', function (next) { 18 | const user = this; 19 | if (!user.isModified('password')) return next(); 20 | user.password = bcrypt.hashSync(this.password, 12); 21 | return next(); 22 | }); 23 | 24 | schema.methods.validatePassword = function (candidatePassword) { 25 | return bcrypt.compareSync(candidatePassword, this.password); 26 | }; 27 | const User = mongoose.model('user', schema); 28 | 29 | export default User; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs_sample_api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon --exec babel-node index.js", 8 | "build": "npx --exec babel-node index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@babel/core": "^7.11.6", 14 | "@babel/node": "^7.10.5", 15 | "@babel/plugin-proposal-export-default-from": "^7.10.4", 16 | "@babel/preset-env": "^7.11.5", 17 | "aws-sdk": "^2.1379.0", 18 | "axios": "^1.4.0", 19 | "babel-loader": "^9.1.2", 20 | "babel-polyfill": "^6.26.0", 21 | "bcryptjs": "^2.4.3", 22 | "cors": "^2.8.5", 23 | "dotenv": "^10.0.0", 24 | "express": "^4.17.1", 25 | "https": "^1.0.0", 26 | "jsonwebtoken": "^8.5.1", 27 | "lodash": "^4.17.21", 28 | "mongoose": "^5.13.7", 29 | "morgan": "^1.10.0", 30 | "passport": "^0.4.1", 31 | "passport-custom": "^1.1.1", 32 | "passport-jwt": "^4.0.0", 33 | "passport-local": "^1.0.0", 34 | "stripe": "^12.5.0" 35 | }, 36 | "devDependencies": { 37 | "babel-eslint": "^8.2.5", 38 | "eslint": "^5.0.1", 39 | "eslint-config-airbnb": "^17.0.0", 40 | "eslint-plugin-import": "^2.13.0", 41 | "nodemon": "^2.0.6" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import passport from 'passport'; 4 | 5 | import { 6 | SignIn, 7 | SignUp, 8 | ForgotPassword, 9 | ResetPassword 10 | } from '../controllers/auth'; 11 | 12 | import User from '../models/user'; 13 | 14 | import { authenticateAuthToken } from '../middlewares/auth'; 15 | 16 | import catchResponse from '../utils/catch-response'; 17 | 18 | const router = express.Router(); 19 | 20 | const loginCheck = (req, res, next) => { 21 | passport.authenticate('local', (err, user, info) => { 22 | if (!user) { 23 | req.error = info.error; 24 | } else req.user = user; 25 | next(); 26 | })(req, res, next); 27 | }; 28 | 29 | router.post('/sign-in', loginCheck, async (req, res) => { 30 | try { 31 | if (req.error) { 32 | const err = new Error(); 33 | err.error = 'Email or Password is incorrect'; 34 | err.statusCode = 401; 35 | throw err; 36 | } 37 | 38 | const { 39 | user: { 40 | _id: userId, 41 | email, 42 | name, 43 | role 44 | } 45 | } = req; 46 | 47 | const response = await SignIn({ 48 | userId, 49 | email 50 | }); 51 | 52 | const { 53 | token, 54 | user 55 | } = response; 56 | 57 | res.status(200).json({ 58 | token, 59 | user: { 60 | ...user, 61 | name, 62 | role 63 | } 64 | }); 65 | } catch (err) { 66 | await catchResponse({ 67 | res, 68 | err 69 | }); 70 | } 71 | }); 72 | 73 | router.post('/register', async (req, res) => { 74 | try { 75 | const { 76 | body: { name, email, password } 77 | } = req; 78 | 79 | if (!name || !email || !password) { 80 | return res 81 | .status(400) 82 | .json({ error: 'Name, Email and Password Are Required' }); 83 | } 84 | 85 | const userExists = await User.findOne({ email }); 86 | if (userExists) return res.status(400).json({ error: 'Email Already Exists' }); 87 | 88 | const response = await SignUp({ name, email, password }); 89 | 90 | const { token, user } = response; 91 | return res.status(200).json({ 92 | token, 93 | user 94 | }); 95 | } catch (err) { 96 | await catchResponse({ 97 | res, 98 | err 99 | }); 100 | } 101 | }); 102 | 103 | router.post('/forgot-password', async (req, res) => { 104 | try { 105 | const { body: { email } } = req; 106 | 107 | const user = await User.findOne({ email }); 108 | 109 | if (!user) { 110 | return res.status(400).json('email not found'); 111 | } 112 | 113 | const response = await ForgotPassword({ email, user }); 114 | 115 | return res.status(200).json(response); 116 | } catch (err) { 117 | await catchResponse({ 118 | res, 119 | err 120 | }); 121 | } 122 | }); 123 | router.put('/reset-password', authenticateAuthToken, async (req, res) => { 124 | try { 125 | const { user: { email }, body: { password } } = req; 126 | 127 | const response = await ResetPassword({ email, password }); 128 | 129 | return res.status(200).json({ 130 | message: response.message 131 | }); 132 | } catch (err) { 133 | await catchResponse({ 134 | res, 135 | err 136 | }); 137 | } 138 | }); 139 | 140 | export default router; 141 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import auth from './auth'; 4 | import leads from './leads'; 5 | 6 | const router = express.Router(); 7 | 8 | router.use('/auth', auth); 9 | router.use('/leads', leads); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /routes/leads.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import { 4 | AddLeadsRecord, 5 | GetLeadsRecord, 6 | RemoveLeadsRecord, 7 | } from "../controllers/leads"; 8 | 9 | import { authenticateAuthToken } from "../middlewares/auth"; 10 | 11 | import catchResponse from "../utils/catch-response"; 12 | 13 | const router = express.Router(); 14 | 15 | router.post("/add-record", authenticateAuthToken, async (req, res) => { 16 | try { 17 | const { 18 | user: { _id: userId }, 19 | body: data, 20 | } = req; 21 | 22 | const response = await AddLeadsRecord({ 23 | userId, 24 | data, 25 | }); 26 | 27 | return res.status(200).json(response); 28 | } catch (err) { 29 | await catchResponse({ 30 | res, 31 | err, 32 | }); 33 | } 34 | }); 35 | 36 | router.get("/leads-record", authenticateAuthToken, async (req, res) => { 37 | try { 38 | const { 39 | query: { filter, sort, skip, limit }, 40 | user: { _id: userId }, 41 | } = req; 42 | 43 | const response = await GetLeadsRecord({ 44 | filter: JSON.parse(filter), 45 | sort: JSON.parse(sort), 46 | skip: JSON.parse(skip), 47 | limit: JSON.parse(limit), 48 | userId, 49 | }); 50 | 51 | return res.status(200).json(response); 52 | } catch (err) { 53 | await catchResponse({ 54 | res, 55 | err, 56 | }); 57 | } 58 | }); 59 | 60 | router.delete("/leads-record", authenticateAuthToken, async (req, res) => { 61 | try { 62 | const { 63 | query: { leadIds }, 64 | user: { _id: userId }, 65 | } = req; 66 | const response = await RemoveLeadsRecord({ 67 | leadIds: JSON.parse(leadIds), 68 | userId, 69 | }); 70 | 71 | return res.status(200).json(response); 72 | } catch (err) { 73 | await catchResponse({ 74 | res, 75 | err, 76 | }); 77 | } 78 | }); 79 | 80 | export default router; 81 | -------------------------------------------------------------------------------- /routes/stripe.js: -------------------------------------------------------------------------------- 1 | // Route 2 | import express from "express"; 3 | 4 | import User from "../models/user"; 5 | 6 | import { 7 | CreateCustomer, 8 | CreateSubscription, 9 | CancelSubscription, 10 | HandleStripeWebhook, 11 | UpdateCustomer, 12 | } from "../controllers/stripe"; 13 | 14 | import catchResponse from "../utils/catch-response"; 15 | 16 | import { authenticateAuthToken } from "../middlewares/auth"; 17 | 18 | const router = express.Router(); 19 | 20 | router.post( 21 | "/stripe-webhook", 22 | express.raw({ type: "application/json" }), 23 | async (req, res) => { 24 | try { 25 | const webhook = req.body; 26 | const { type, data } = webhook; 27 | console.log("\n\n", "webhook", { 28 | type, 29 | data, 30 | }); 31 | const { message } = await HandleStripeWebhook({ 32 | data, 33 | type, 34 | }); 35 | 36 | return res.send(message); 37 | } catch (err) { 38 | await catchResponse({ 39 | res, 40 | err, 41 | }); 42 | } 43 | } 44 | ); 45 | 46 | // Create Stripe Subscription Controller 47 | const CreateStripeSubscription = async ({ 48 | userId, 49 | token, 50 | userData, 51 | selectedPlan, 52 | planId, 53 | coupon, 54 | }) => { 55 | try { 56 | const user = await User.findOne({ _id: userId }); 57 | const { cardName, addressTitle, address } = userData; 58 | 59 | let stripeUserId = user?.payment?.stripeUserId; 60 | if (!stripeUserId) { 61 | const customer = await CreateCustomer({ 62 | email: user.email, 63 | name: cardName, 64 | source: token.id, 65 | metaData: { 66 | app: "Tweast", 67 | }, 68 | }); 69 | 70 | stripeUserId = customer.id; 71 | 72 | await User.updateOne( 73 | { _id: userId }, 74 | { 75 | $set: { 76 | "payment.stripeUserId": stripeUserId, 77 | }, 78 | } 79 | ); 80 | } else { 81 | await UpdateCustomer(stripeUserId, { token, coupon }); 82 | } 83 | 84 | const subscriptionResponse = await CreateSubscription({ 85 | priceId: planId, 86 | customerId: stripeUserId, 87 | coupon, 88 | }); 89 | 90 | await User.updateOne( 91 | { _id: userId }, 92 | { 93 | $set: { 94 | "payment.subscriptionId": subscriptionResponse.id, 95 | "payment.failedCount": 0, 96 | cardName, 97 | selectedPlan, 98 | addressTitle, 99 | address, 100 | }, 101 | } 102 | ); 103 | 104 | return subscriptionResponse; 105 | } catch (err) { 106 | return err; 107 | } 108 | }; 109 | 110 | // Cancel Stripe Subscription Controller 111 | const CancelStripeSubscription = async ({ 112 | userId, 113 | feedbackMessage, 114 | feedbackPoint, 115 | }) => { 116 | await User.findOneAndUpdate( 117 | { _id: userId }, 118 | { $set: { feedbackMessage, feedbackPoint } } 119 | ); 120 | const user = await User.findOne({ 121 | _id: userId, 122 | }); 123 | 124 | await CancelSubscription({ 125 | subscriptionId: user.payment.subscriptionId, 126 | }); 127 | }; 128 | 129 | // Stripe Card update Controller 130 | const UpdateCustomerCard = async ({ userId, token, coupon }) => { 131 | const user = await User.findOne({ 132 | _id: userId, 133 | }); 134 | 135 | const stripeUserId = user && user.payment && user.payment.stripeUserId; 136 | if (stripeUserId) { 137 | await UpdateCustomer(stripeUserId, { token, coupon }); 138 | } 139 | }; 140 | 141 | const verifyUserStatus = (userId) => 142 | new Promise((res) => { 143 | let retry = 0; 144 | const userStatusInterval = setInterval(async () => { 145 | const userData = await User.findOne({ _id: userId }); 146 | const { status } = userData || {}; 147 | retry += 1; 148 | if (status === "Subscribed") res(clearInterval(userStatusInterval)); 149 | else if (retry >= 10) res(clearInterval(userStatusInterval)); 150 | }, 1000); 151 | }); 152 | 153 | router.post( 154 | "/create-user-subscription", 155 | authenticateAuthToken, 156 | async (req, res) => { 157 | try { 158 | const { 159 | user: { _id: userId }, 160 | body: { token, coupon, planId, userData, selectedPlan }, 161 | } = req; 162 | 163 | const subscriptionData = await CreateStripeSubscription({ 164 | userId, 165 | token, 166 | coupon, 167 | selectedPlan, 168 | planId, 169 | userData, 170 | }); 171 | 172 | await verifyUserStatus(userId); 173 | const user = await User.findOne({ _id: userId }); 174 | const { status } = user; 175 | if (subscriptionData?.id && status === "Subscribed") { 176 | res.status(200).json({ status: true, user, subscriptionData }); 177 | } else 178 | res 179 | .status(400) 180 | .json({ error: "Subscription Not Created, Please Try Again" }); 181 | } catch (err) { 182 | await catchResponse({ 183 | res, 184 | err, 185 | }); 186 | } 187 | } 188 | ); 189 | 190 | router.post("/cancel-subscription", authenticateAuthToken, async (req, res) => { 191 | try { 192 | const { 193 | user: { _id: userId }, 194 | body: { feedbackMessage, feedbackPoint }, 195 | } = req; 196 | 197 | await CancelStripeSubscription({ 198 | userId, 199 | feedbackMessage, 200 | feedbackPoint, 201 | }); 202 | 203 | const user = await User.findOne({ _id: userId }); 204 | return res.json({ status: true, user }); 205 | } catch (err) { 206 | await catchResponse({ 207 | res, 208 | err, 209 | }); 210 | } 211 | }); 212 | 213 | router.post( 214 | "/update-customer-card", 215 | authenticateAuthToken, 216 | async (req, res) => { 217 | try { 218 | const { 219 | user: { _id: userId }, 220 | body: { token, coupon }, 221 | } = req; 222 | 223 | UpdateCustomerCard({ 224 | userId, 225 | token, 226 | coupon, 227 | }); 228 | return res.json({ status: true }); 229 | } catch (err) { 230 | await catchResponse({ 231 | res, 232 | err, 233 | }); 234 | } 235 | } 236 | ); 237 | 238 | export default router; 239 | -------------------------------------------------------------------------------- /services/aws-s3.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | import fs from "fs"; 3 | import path from "path"; 4 | import Axios from "axios"; 5 | 6 | import Lead from "../models/lead"; 7 | import { uploadToS3Bucket } from "../config/aws"; 8 | 9 | const { AWS_BUCKET_NAME } = process.env; 10 | 11 | const downloadImage = async ({ url, filepath }) => { 12 | try { 13 | const response = await Axios({ 14 | url, 15 | method: "GET", 16 | responseType: "stream", 17 | }); 18 | return new Promise((resolve, reject) => { 19 | response.data 20 | .pipe(fs.createWriteStream(filepath)) 21 | .on("error", reject) 22 | .once("close", () => resolve(filepath)); 23 | }); 24 | } catch (err) { 25 | return err; 26 | } 27 | }; 28 | 29 | const saveToS3Bucket = async ({ leadDocs }) => { 30 | try { 31 | const imagesData = []; 32 | for (let i = 0; i < leadDocs.length; i += 1) { 33 | const { image, _id: leadId } = leadDocs[i]; 34 | 35 | const filePath = path.join(__dirname, "..", "images", `${leadId}.jpeg`); 36 | 37 | await downloadImage({ 38 | url: image, 39 | filepath: filePath, 40 | }); 41 | 42 | const uploadParams = { 43 | Bucket: AWS_BUCKET_NAME, 44 | Key: "", 45 | Body: "", 46 | ContentType: "image/jpeg", 47 | ACL: "public-read", 48 | }; 49 | 50 | const fileStream = fs.createReadStream(filePath); 51 | uploadParams.Body = fileStream; 52 | uploadParams.Key = `${leadId}.jpeg`; 53 | 54 | const imageUrl = await uploadToS3Bucket({ uploadParams }); 55 | 56 | imagesData.push({ 57 | updateOne: { 58 | filter: { 59 | _id: leadId, 60 | }, 61 | update: { 62 | $set: { 63 | image: imageUrl, 64 | }, 65 | }, 66 | }, 67 | }); 68 | 69 | fs.unlinkSync(filePath); 70 | } 71 | 72 | await Lead.bulkWrite(imagesData); 73 | } catch (err) { 74 | console.log("\n\n Error While Uploading Image", err); 75 | } 76 | }; 77 | 78 | export { downloadImage, saveToS3Bucket }; 79 | -------------------------------------------------------------------------------- /services/stripe.js: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe"; 2 | 3 | const { STRIPE_SECRET_KEY } = process.env; 4 | 5 | const stripe = new Stripe(STRIPE_SECRET_KEY); 6 | 7 | export default stripe; 8 | -------------------------------------------------------------------------------- /utils/catch-response.js: -------------------------------------------------------------------------------- 1 | const catchResponse = ({ res, err }) => { 2 | let statusCode = 500; 3 | let error = "Server Error"; 4 | 5 | if (err.statusCode) ({ statusCode } = err); 6 | 7 | if (err.message) ({ message: error } = err); 8 | else if (err.error) ({ error } = err); 9 | 10 | res.status(statusCode).json({ 11 | success: false, 12 | error, 13 | }); 14 | }; 15 | 16 | export default catchResponse; 17 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanveerpot/NodeJS_Sample_API/8ea9d0ab3d2cdfa368211dfea0d68fcd89e725d0/utils/constants.js --------------------------------------------------------------------------------