mailgun.messages is not a function - node.js

I am trying to get my e-commerce web app to send emails on purchasing but when I try to pay for things I get the mailgun.messages is not a function I've reverted changes and re coded it twice but this is a different error I'm not sure how to resolve this. Is there another way to use mailgun's api? Here is the code below:
orderRoutes.js:
orderRouter.put(
"/:id/pay",
isAuth,
expressAsyncHandler(async (req, res) => {
const order = await Order.findById(req.params.id).populate(
"user",
"email firstName"
);
if (order) {
order.isPaid = true;
order.paidAt = Date.now();
order.paymentResult = {
id: req.body.id,
status: req.body.status,
update_time: req.body.update_time,
email_address: req.body.email_address,
};
const updateOrder = await order.save();
mailgun.messages().send(
{
from: "Sales <sales#cocoTiCosmetics.com>",
to: `${order.user.firstName} <${order.user.email}>`,
subject: `New Order ${order._id}`,
html: payOrderEmailTemplate(order),
},
(error, body) => {
if (error) {
console.log(error);
} else {
console.log(body);
}
}
);
res.send({ message: "Order Paid", order: updateOrder });
} else {
res.status(404).send({ message: "Order Not Found" });
}
})
);
utils.js
export const mailgun = () =>
mg({
apiKey: process.env.MAILGUN_API_KEY,
domain: process.env.MAILGUN_DOMAIN,
});

Related

Struggling to verify new users: Node JS

I am trying to implement a mechanism to allow new users to verify their account with a link sent to their email address.
Here's is my subscribe route in routes/user/index.js, which is working to send the email correctly:
require('dotenv').config();
const { v4 } = require('uuid');
const nodemailer = require('nodemailer');
const sibApiV3Sdk = require('sib-api-v3-sdk');
const express = require('express');
const stripe = require('../middlewares/stripe')
const db = require("../models");
const cors = require('cors');
const User = db.user;
const Feedback = db.feedback;
const defaultClient = sibApiV3Sdk.ApiClient.instance;
// Configure API key authorization: api-key
const apiKey = defaultClient.authentications['api-key'];
apiKey.apiKey = process.env.SENDINBLUE_API_KEY;
const transactionalEmailsApi = new sibApiV3Sdk.TransactionalEmailsApi();
// Configure nodemailer
const transporter = nodemailer.createTransport({
host: "smtp-relay.sendinblue.com",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: "noreply#redacted.ai",
pass: "redacted"
}
});
// Define generateVerificationToken function
function generateVerificationToken() {
return v4();
}
// Prepare Core Router
let app = express.Router()
app.post('/stripe/subscribe', async (req, res) => {
const domainURL = process.env.DOMAIN;
const { priceId, trial } = req.body
try {
let user = await User.findOne({ _id: req.user._id });
let customer = user.customerId ? { customer: user.customerId } : {customer_email: user.email};
const subscription_data = trial ? { trial_period_days: 7 } : {};
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
...customer,
line_items: [
{
price: priceId,
quantity: 1,
},
],
subscription_data,
success_url: `${domainURL}/index.html`,
cancel_url: `${domainURL}/signup/failed`,
});
let credits = 0;
if (priceId === process.env.STRIPE_PRODUCT_FREE) {
credits = 0;
} else if (priceId === process.env.STRIPE_PRODUCT_ENTRY) {
credits = 0;
} else if (priceId === process.env.STRIPE_PRODUCT_PRO) {
credits = 0;
}
// Check if user already has a verification_token
if (!user.verification_token) {
const token = generateVerificationToken();
console.log("generated token: ", token);
const updatedUser = await User.findOneAndUpdate({ _id: req.user._id },
{
credits: credits,
verification_token: token,
isVerified: false
},
{
new: true
});
console.log("updatedUser: ", updatedUser);
const verificationLink = `${domainURL}/verify?token=${updatedUser.verification_token}`;
const mailOptions = {
from: 'noreply#redacted.ai',
to: user.email,
subject: 'Verify your email address',
html: `Please click on the following link to verify your email address: ${verificationLink}`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
} else {
console.log('User already has a verification token.');
}
const updateResult = await User.updateOne({_id: req.user._id}, {verification_token: user.verification_token}, {upsert: true});
console.log("updateResult: ", updateResult);
// Send verification email after stripe step completes
if (!user.isVerified) {
const verificationLink = `${domainURL}/verify?token=${user.verification_token}`;
const mailOptions = {
from: 'noreply#redacted.ai',
to: user.email,
subject: 'Verify your email address',
html: `Please click on the following link to verify your email address: ${verificationLink}`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
}
// Redirect user to the verify route
res.redirect(303, session.url);
} catch (e) {
res.status(400);
return res.send({
error: {
message: e.message,
}
});
}
});
Immediately after this, I then have a verify route:
app.get('/verify', cors(), async (req, res) => {
const { token } = req.query;
try {
// Find user with matching token
let user = await User.findOne({ verification_token: token });
if (!user) {
throw new Error('Invalid token');
}
// Update user verification status
user.isVerified = true;
const savedUser = await user.save();
// Send verification email
if (savedUser.isVerified) {
const mailOptions = {
from: 'noreply#redacted.ai',
to: savedUser.email,
subject: 'Email Verified!',
html: `Your email has been verified! Welcome to redacted.ai`
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
}
// Redirect user to success page
res.redirect('/index.html#/verify/success');
} catch (e) {
res.status(400);
return res.send({
error: {
message: e.message,
}
});
}
});
I also have a file called src/verify.js to deal with things on the front-end:
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
function Verify(props) {
const [isVerified, setIsVerified] = useState(false);
const { token } = props.match.params;
const history = useHistory();
useEffect(() => {
// logic to process token and set isVerified to true/false
fetch(`/verify/${token}`)
.then(res => res.json())
.then(data => {
if (data.isVerified) {
setIsVerified(true);
}
});
}, [token]);
useEffect(() => {
// redirect to success page if isVerified is true
if (isVerified) {
history.push('/index.html#/verify/success');
}
}, [isVerified, history]);
return (
<div>
{isVerified ? (
<div>Your email has been verified! Welcome to redacted.ai</div>
) : (
<div>Invalid token.</div>
)}
</div>
);
}
export default Verify;
However, when the user clicks on the link in their email, the verify route does not initiate. Rather, nothing happens at all in the terminal except for profile refresh, and I'm not sure why.
As an aside, I can see in the database that the new user has a verification_token and isVerfied is set to false. That verification_token matches the one sent in the email.
I'm very new to Node JS so suspect I'm missing something obvious! Thanks so much in advance.
PS.
As a work around, I attempted to try a different approach, and created a form that the user could go to and copy and paste in their verification_token: the form looked like this:
import React, { useState } from 'react';
import TOOLS from './tools'
import config from './config'
import Header from './Header'
let baseURL = config.baseURL
const VerifyPage = () => {
const [verificationToken, setVerificationToken] = useState('');
const [error, setError] = useState('');
const [responseData, setResponseData] = useState({});
const handleSubmit = async (event) => {
event.preventDefault();
const token = window.localStorage.getItem('token');
console.log('Token: ', token);
console.log('Authorization header: ', `Bearer ${token}`);
// Make a request to your API to verify the token
try {
const response = await fetch(`${baseURL}user/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ verificationToken })
});
console.log('Request URL: ', `${baseURL}user/verify`);
console.log('Request body: ', JSON.stringify({ verificationToken }));
const responseJson = await response.json();
setResponseData(responseJson);
console.log('Response status: ', response.status);
console.log('Response text: ', await response.text());
if (!response.ok) {
throw new Error(responseJson.message);
}
// Show a success message or redirect to another page
} catch (error) {
setError(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="verificationToken">Verification Token:</label>
<input
type="text"
id="verificationToken"
value={verificationToken}
onChange={(event) => setVerificationToken(event.target.value)}
/>
</div>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Verify</button>
{responseData && JSON.stringify(responseData)}
</form>
);
};
export default VerifyPage;
And with this in place, I altered my verify route to looks like this:
app.post("/verify", (req, res) =\> {
console.log('Received request to /verify');
const { token } = req.body;
console.log('Received verification token:', token);
User.findOne({ token }, (err, user) => {
if (err) {
console.error('Error finding user:', err);
return res.status(500).send({ error: "Error verifying account" });
}
if (!user) {
console.error('No user found with provided verification token');
return res.status(400).send({ error: "Invalid Token" });
}
user.isVerified = true;
user.verificationToken = undefined;
user.save((err) => {
if (err) {
console.error('Error saving user:', err);
return res.status(500).send({ error: "Error verifying account" });
}
console.log('User verified successfully');
return res.send({ message: "Account verified successfully" });
});
});
});`
But whenever the user attempted to put in their valid token, they would get something like this:
"Unexpected token 'F', "Forbidden" is not valid JSON"
Moreover, this is all that would show up in the terminal:
`"OPTIONS /api/user/verify 204 0.285 ms - 0 POST /api/user/verify 403 1.558 ms - 9" `
And, under the JWT code, this would show up in the console: "Failed to load resource: the server responded with a status of 403 (Forbidden)"
Truth be told, I'd be happy to get this working either via the form, or by having users click the link in the email. The latter is preferable - but either would work.

API endpoint not found (404), but there (next js)

I'm sending (nodemailer) a link to the user to verify his account. The email is sent successfully.
The problem: The link doesn't work. I get a 404 error "Not found".
Here's what I'm doing backend to send the email with the link:
// send email with verification link
handler.post(async (req, res) => {
const { email, id } = req.body;
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL_BASE_URL;
if (email && validator.isEmail(email)) {
const dataMail = {
from: " ... ",
to: " ... ",
attachments: [
{
filename: "logo.png",
path: "public/assets/logo.png",
cid: "/logo.png",
},
],
subject: "... ",
html: `<p>Please verify your account:</p><a
href="${baseUrl}/api/verification/${id}" target="_blank">ACTIVATE
ACCOUNT</a>`,
};
try {
await emailService.sendMail(dataMail);
await knex("User")
.update("status", "pending")
.where("id", id);
} catch (error) {
return res.status(400).json({
message: "Something went wrong",
});
}
And here's what should happen backend after the user clicks on the link:
// /api/verification/[id].js
handler.post(async (req, res) => {
try {
const user = await knex("User").where("id", req.query.id);
if (!user) {
res.sendStatus(401).json({ message: "Something went wrong." });
} else {
await knex("User")
.update({ status: "active", date: new Date() })
.where("id", req.query.id);
const redirectPath = process.env.NEXT_PUBLIC_REDIRECT_VERIFICATION;
res.redirect(redirectPath);
}
} catch (err) {
res.status(500).json({ message: "Something went wrong" });
}
});
My folder structure looks as follows:
api/verification/[id].js
Any ideas what's wrong with this approach?

how to send email confirmation in nodejs and SendGrid?

I have a NodeJs and ReactJs project, where a user can register and after the user is registered they will get an email to confirm their account.
so now when I register the email is working well. but it works with an email that I set in like this.
function sendMail() {
const msg = {
to: "someoneemail#gmail.com",
from: "myemail#gmail.com",
subject: "a subject",
text: "some text herer",
html: "<strong>and easy to do anywhere, even with Node.js</strong>",
};
sgMail
.send(msg)
.then(() => {
console.log("Email sent");
})
.catch((error) => {
console.error(error);
});
}
module.exports = { sendMail };
I need to remove this to: "someoneemail#gmail.com" a*
nd instead set the user email, the user who to register on this
system
and instead of text: i have to send the token.
so here is the registration part:
router.post("/register", async (req, res) => {
const { fullName, emailAddress, password } = req.body;
const user = await Users.findOne({
where: {
[Op.and]: [{ fullName: fullName }, { emailAddress: emailAddress }],
},
});
if (user) {
res.status(400).send({
error: `some message.`,
});
} else {
bcrypt
.hash(password, 10)
.then((hash) => {
return {
fullName: fullName,
emailAddress: emailAddress,
password: hash,
isVerified: false,
};
})
.then((user) => {
const token = TokenGenerator.generate();
const creator = Verifications.belongsTo(Users, { as: "user" });
return Verifications.create(
{
token,
user: user,
},
{
include: [creator],
}
);
})
.then((verification) => {
console.log("verification", verification);
sendMail();
})
.then(() => res.json("User, Successmessage "));
}
});
but the codes are not in the same file.
Just add the parameters you need to the sendMail function:
function sendMail(user, token) {
const msg = {
to: user.emailAddress,
from: "myemail#gmail.com",
subject: "Sending with SendGrid is Fun",
text: token,
html: `<strong>${token}</strong>`,
};
sgMail
.send(msg)
.then(() => {
console.log("Email sent");
})
.catch((error) => {
console.error(error);
});
}
Also inject the needed parameters in the promises:
.then(async (user) => {
const token = TokenGenerator.generate();
const creator = Verifications.belongsTo(Users, { as: "user" });
await Verifications.create(
{
token,
user: user,
},
{
include: [creator],
}
);
return {user, token};
})
.then(({user, token}) => {
sendMail(user, token);
})
.then(() => res.json("User, Successmessage "));

NextJs - Function in API folder in pages gives 'res.status is not a function' error

I'm using NextJs and I'm trying to create a subscription form that sends data to MailChimp. However, I'm getting error which says
res.status is not a function
This file is inside my pages/api directory. What might be going wrong?
import { subscribe } from "../../lib/api";
const request = require("request");
export default async function subscribeWithEmail(req, res) {
const { email, js } = req.body;
const mcData = {
members: [
{
email_address: email,
status: "pending",
},
],
};
const mcDataPost = JSON.stringify(mcData);
const options = {
url: "https://us6.api.mailchimp.com/3.0/lists/SECRET",
method: "POST",
headers: {
Authorization: "auth APIKEY",
},
body: mcDataPost,
};
if (email) {
request(options, (err, res, body) => {
console.log(res);
if (err) {
res.json({ error: err });
} else {
if (js) {
res.status(200).send({ message: "yay" });
} else {
res.redirect("/");
}
}
});
} else {
res.status(404).send({ message: "Failed" });
}
// res.status(200).json(data);
return res.status(200);
}
You are shadowing your initial res variable.
// you have another res here, which has nothing to do with Next.js res, but it is overriding it
// So you need to rename it to something else, for example to "response"
request(options, (err, response, body) => {
console.log(response);
if (err) {
res.json({ error: err });
} else {
if (js) {
res.status(200).send({ message: "yay" });
} else {
res.redirect("/");
}
}
});

Cannot set headers after they are sent to the client - Entries saved to MongoDb - Express.Js

I know this question gets asked a lot but I cannot tell where I am sending multiple headers. The data is getting stored in the database and then it crashes. I am fairly new to Node/Express and I think I might be missing something fundamental here.
I have tried reading what I could find on stackoverflow and figured out the reason I am getting this error is because it is sending multiple header requests. Tried updating the code with little tweaks but nothing has worked so far.
Thanks for the help.
Dashboard Controller -
exports.getGymOwnerMembersAdd = (req, res, next) => {
let message = req.flash('error');
if(message.length > 0) {
message = message[0];
} else {
message = null;
}
const oldInput = {
...
};
Membership
.find()
.then(memberships => {
res.render('gym-owner/members-add', {
memberships: memberships,
oldInput: oldInput,
errorMessage: message,
pageTitle: 'Add Members',
path: '/gym-owner-dashboard/members-add',
validationErrors: []
});
})
.catch(err => {
console.log(err);
});
}
exports.postGymOwnerMembersAdd = (req, res, next) => {
const membershipId = req.body.membershipLevel;
const errors = validationResult(req);
let message = req.flash('error');
if(message.length > 0) {
message = message[0];
} else {
message = null;
}
if(!errors.isEmpty()) {
Membership
.find()
.then(memberships => {
return res.status(422).render('gym-owner/members-add', {
pageTitle: 'Add Members',
path: '/gym-owner-dashboard/members-add',
errorMessage: errors.array()[0].msg,
message: message,
memberships: memberships,
oldInput: {
...
},
validationErrors: errors.array()
});
})
.catch(next);
}
bcrypt
.hash(password, 12)
.then(hashedPassword => {
const user = new User({
...
});
return user.save();
})
.then(result => {
res.redirect('/gym-owner-dashboard/members');
})
.catch(err=> {
console.log(err);
});
}
Dashboard Routes And Validation-
router.get('/gym-owner-dashboard/members-add', isAuth, isGymOwner, dashboardController.getGymOwnerMembersAdd);
router.post(
'/gym-owner-dashboard/members-add',
isAuth, isGymOwner,
[
check('name')
.isAlpha().withMessage('Names can only contain letters.')
.isLength({ min: 2 }).withMessage('Please enter a valid name')
.trim(),
check('email')
.isEmail().withMessage('Please enter a valid email.')
.custom((value, { req }) => {
return User.findOne({
email: value
}).then(userDoc => {
console.log('Made it here!');
if(userDoc) {
return Promise.reject('E-mail already exists, please pick a different one.');
};
});
})
.normalizeEmail(),
...
check(
'password',
'Please enter a password at least 5 characters.'
)
.isLength({ min: 5 })
.trim(),
check('confirmPassword')
.trim()
.custom((value, { req }) => {
if(value !== req.body.password) {
throw new Error('Passwords have to match!');
}
return true;
})
],
dashboardController.postGymOwnerMembersAdd
);
Expected Results
Create a new user while passing validation.
Actual Results
A new user is created and saved to Mongodb. The user gets redirected back to the user creation page with an error that the user is undefined. The server crashes with the error "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client"
I understand you have a bug in "postGymOwnerMembersAdd".
if(!errors.isEmpty()) {
Membership
.find()
.then(memberships => {
return res.status(422).render('gym-owner/members-add', { // this return refers to cb but not to middleware
pageTitle: 'Add Members',
path: '/gym-owner-dashboard/members-add',
errorMessage: errors.array()[0].msg,
message: message,
memberships: memberships,
oldInput: {
...
},
validationErrors: errors.array()
});
})
.catch(next);
}
bcrypt
.hash(password, 12)
.then(hashedPassword => {
const user = new User({
...
});
return user.save();
})
.then(result => {
res.redirect('/gym-owner-dashboard/members');
})
.catch(err=> {
console.log(err);
});
Thus, both the "return res.status(422).render()" and the "res.redirect('/gym-owner-dashboard/members')" will be executed, and this trigger error (header set after they are sent).
I mean two solutions to the problem
First: use async/await
exports.postGymOwnerMembersAdd = async (req, res, next) => {
const membershipId = req.body.membershipLevel;
const errors = validationResult(req);
let message = req.flash('error');
if(message.length > 0) {
message = message[0];
} else {
message = null;
}
if(!errors.isEmpty()) {
try {
const memberships = await Membership.find();
return res.status(422).render('gym-owner/members-add', {
pageTitle: 'Add Members',
path: '/gym-owner-dashboard/members-add',
errorMessage: errors.array()[0].msg,
message: message,
memberships: memberships,
oldInput: {
...
},
validationErrors: errors.array()
};
} catch (err) {
next(err);
}
}
const hashedPassword = await bcrypt.hash(password, 12);
const user = new User({
...
});
await user.save();
return res.redirect('/gym-owner-dashboard/members');
};
Second: use else
exports.postGymOwnerMembersAdd = (req, res, next) => {
const membershipId = req.body.membershipLevel;
const errors = validationResult(req);
let message = req.flash('error');
if(message.length > 0) {
message = message[0];
} else {
message = null;
}
if(!errors.isEmpty()) {
Membership
.find()
.then(memberships => {
return res.status(422).render('gym-owner/members-add', {
pageTitle: 'Add Members',
path: '/gym-owner-dashboard/members-add',
errorMessage: errors.array()[0].msg,
message: message,
memberships: memberships,
oldInput: {
...
},
validationErrors: errors.array()
});
})
.catch(next);
} else {
bcrypt
.hash(password, 12)
.then(hashedPassword => {
const user = new User({
...
});
return user.save();
})
.then(result => {
res.redirect('/gym-owner-dashboard/members');
})
.catch(err=> {
console.log(err);
});
}
}

Resources