Skip to content

Commit

Permalink
final commit
Browse files Browse the repository at this point in the history
  • Loading branch information
phuongthuan committed Aug 1, 2019
1 parent 2de3b17 commit 95dd460
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 3 deletions.
5 changes: 5 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"graphql-cli": "^3.0.11",
"graphql-yoga": "^1.17.4",
"jsonwebtoken": "^8.5.0",
"nodemailer": "^6.1.1",
"prisma": "^1.28.3",
"prisma-client-lib": "^1.28.3",
"signale": "^1.4.0",
Expand Down
27 changes: 27 additions & 0 deletions backend/src/mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const nodemailer = require('nodemailer');

const transport = nodemailer.createTransport({
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
});

const makeANiceEmail = text => `
<div className="email" style="
border: 1px solid black;
padding: 20px;
font-family: sans-serif;
line-height: 2;
font-size: 20px;
">
<h2>Hello There!</h2>
<p>${text}</p>
<p>🐝, Beelap</p>
</div>
`;

exports.transport = transport;
exports.makeANiceEmail = makeANiceEmail;
78 changes: 78 additions & 0 deletions backend/src/resolvers/Mutation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const stripe = require('../stripe');
const { transport, makeANiceEmail } = require('../mail');
const { randomBytes } = require('crypto');
const { promisify } = require('util');

const logger = require('../../logger');
const { hasPermission } = require('../utils');
Expand Down Expand Up @@ -153,6 +156,81 @@ const Mutation = {
return { message: 'Goodbye!' };
},

async requestReset(parent, args, context) {
// 1. Check if this is a real user
const user = await context.prisma.user({ email: args.email });
if (!user) {
throw new Error(`Email ${args.email} not found!`);
}

// 2. Set a reset token and expiry on that user
const randomBytesPromiseified = promisify(randomBytes);
const resetToken = (await randomBytesPromiseified(20)).toString('hex');
const resetTokenExpiry = Date.now() + 3600000; // 1 hour from now
const res = await context.prisma.updateUser({
where: { email: args.email },
data: { resetToken, resetTokenExpiry },
});

// 3. Email them that reset token
const mailRes = await transport.sendMail({
from: 'service@beelap.com',
to: user.email,
subject: 'Your Password Reset Token',
html: makeANiceEmail(`Your Password Reset Token is here!
\n\n
<a href="${process.env
.FRONTEND_URL}/reset?resetToken=${resetToken}">Click Here to Reset</a>`),
});

// 4. Return the message
return { message: 'Thanks!' };

},

async resetPassword(parent, args, context) {
// 1. check if the passwords match
if (args.password !== args.confirmPassword) {
throw new Error(`Your password doesn't match!`);
}

// 2. check if its a legit reset token
// 3. Check if its expired
const [user] = await context.prisma.users({
where: {
resetToken: args.resetToken,
resetTokenExpiry_gte: Date.now() - 3600000,
},
});

if (!user) {
throw new Error('This token is either invalid or expired!');
}

// 4. Hash their new password
const password = await bcrypt.hash(args.password, 10);

// 5. Save the new password to the user and remove old resetToken fields
const updatedUser = await context.prisma.updateUser({
where: { email: user.email },
data: {
password,
resetToken: null,
resetTokenExpiry: null,
},
});

// 6. Generate JWT
const token = jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET);
// 7. Set the JWT cookie
context.response.cookie('token', token, {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 365,
});
// 8. return the new user
return updatedUser;
},

async updatePermissions(parent, args, context) {
// Make sure they're signed in.
const { userId } = context.request;
Expand Down
20 changes: 18 additions & 2 deletions backend/src/resolvers/Query.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { hasPermission } = require('../utils');
const Query = {
items: async (parent, args, context) => {

const { searchTerm, category, orderBy = "createdAt_DESC" } = args;
const { searchTerm, category, orderBy = "createdAt_DESC", first, skip } = args;

const allItems = await context.prisma.items({
orderBy,
Expand All @@ -14,7 +14,9 @@ const Query = {
{ description_contains: searchTerm }
],
category: { id: category }
}
},
first,
skip
}).$fragment(`
fragment ItemWithCategories on Item {
id
Expand All @@ -32,6 +34,18 @@ const Query = {

return allItems;
},
itemsConnection: async (parent, args, context) => {
const count = await context.prisma.itemsConnection().$fragment(`
fragment CountItem on ItemConnection {
aggregate {
count
}
}
`);

return count;
},

item: async (parent, { id }, context) => {
const item = await context.prisma.item({ id }).$fragment(`
fragment ItemWithCategories on Item {
Expand All @@ -50,9 +64,11 @@ const Query = {

return item;
},

categories: async (parent, args, context) => {
return context.prisma.categories();
},

orders: async (parent, args, context) => {

const { userId } = context.request;
Expand Down
16 changes: 15 additions & 1 deletion backend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ type SuccessMessage {

type Query {
item(id: ID!): Item!
items(searchTerm: String, category: String, orderBy: String): [Item]!
items(searchTerm: String, category: String, orderBy: String, first: Int, skip: Int): [Item]!
itemsConnection: ItemConnection!
users: [User]!
categories: [Category!]!
me: User
Expand All @@ -22,6 +23,8 @@ type Mutation {
signin(email: String!, password: String!): User!
updatePermissions(permissions: [Permission], userId: ID!): User
signout: SuccessMessage
requestReset(email: String!): SuccessMessage
resetPassword(resetToken: String!, password: String!, confirmPassword: String!): User!
addToCart(id: ID!): CartItem
removeFromCart(id: ID!): CartItem
createOrder(token: String!): Order!
Expand Down Expand Up @@ -102,6 +105,17 @@ input CategoryWhereUniqueInput {
name: String
}

type ItemConnection {
aggregate: AggregateItem!
}

type AggregateItem {
count: Int!
}







Expand Down

0 comments on commit 95dd460

Please sign in to comment.