summaryrefslogtreecommitdiffstats
path: root/stepped-solutions/52
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-08-22 16:47:49 -0400
committerWes Bos <wesbos@gmail.com>2018-08-22 16:47:49 -0400
commit9876e7dfca6fa2bbf6ac383eeb8e2c82e05c2164 (patch)
tree3307dbf546a8e3ad027074c11069f8658dc36340 /stepped-solutions/52
parent5d4fca10187c8ffff19161ec2b3b02814789a3b2 (diff)
orders
Diffstat (limited to 'stepped-solutions/52')
-rwxr-xr-xstepped-solutions/52/backend/src/resolvers/Mutation.js337
-rwxr-xr-xstepped-solutions/52/frontend/components/TakeMyMoney.js73
2 files changed, 410 insertions, 0 deletions
diff --git a/stepped-solutions/52/backend/src/resolvers/Mutation.js b/stepped-solutions/52/backend/src/resolvers/Mutation.js
new file mode 100755
index 0000000..f0ea6a8
--- /dev/null
+++ b/stepped-solutions/52/backend/src/resolvers/Mutation.js
@@ -0,0 +1,337 @@
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const { randomBytes } = require('crypto');
+const { promisify } = require('util');
+const { transport, makeANiceEmail } = require('../mail');
+const { hasPermission } = require('../utils');
+const stripe = require('../stripe');
+
+const Mutations = {
+ async createItem(parent, args, ctx, info) {
+ if (!ctx.request.userId) {
+ throw new Error('You must be logged in to do that!');
+ }
+
+ const item = await ctx.db.mutation.createItem(
+ {
+ data: {
+ // This is how to create a relationship between the Item and the User
+ user: {
+ connect: {
+ id: ctx.request.userId,
+ },
+ },
+ ...args,
+ },
+ },
+ info
+ );
+
+ console.log(item);
+
+ return item;
+ },
+ updateItem(parent, args, ctx, info) {
+ // first take a copy of the updates
+ const updates = { ...args };
+ // remove the ID from the updates
+ delete updates.id;
+ // run the update method
+ return ctx.db.mutation.updateItem(
+ {
+ data: updates,
+ where: {
+ id: args.id,
+ },
+ },
+ info
+ );
+ },
+ async deleteItem(parent, args, ctx, info) {
+ const where = { id: args.id };
+ // 1. find the item
+ const item = await ctx.db.query.item({ where }, `{ id title user { id }}`);
+ // 2. Check if they own that item, or have the permissions
+ const ownsItem = item.user.id === ctx.request.userId;
+ const hasPermissions = ctx.request.user.permissions.some(permission =>
+ ['ADMIN', 'ITEMDELETE'].includes(permission)
+ );
+
+ if (!ownsItem && hasPermissions) {
+ throw new Error("You don't have permission to do that!");
+ }
+
+ // 3. Delete it!
+ return ctx.db.mutation.deleteItem({ where }, info);
+ },
+ async signup(parent, args, ctx, info) {
+ // lowercase their email
+ args.email = args.email.toLowerCase();
+ // hash their password
+ const password = await bcrypt.hash(args.password, 10);
+ // create the user in the database
+ const user = await ctx.db.mutation.createUser(
+ {
+ data: {
+ ...args,
+ password,
+ permissions: { set: ['USER'] },
+ },
+ },
+ info
+ );
+ // create the JWT token for them
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ // We set the jwt as a cookie on the response
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365, // 1 year cookie
+ });
+ // Finalllllly we return the user to the browser
+ return user;
+ },
+ async signin(parent, { email, password }, ctx, info) {
+ // 1. check if there is a user with that email
+ const user = await ctx.db.query.user({ where: { email } });
+ if (!user) {
+ throw new Error(`No such user found for email ${email}`);
+ }
+ // 2. Check if their password is correct
+ const valid = await bcrypt.compare(password, user.password);
+ if (!valid) {
+ throw new Error('Invalid Password!');
+ }
+ // 3. generate the JWT Token
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ // 4. Set the cookie with the token
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ });
+ // 5. Return the user
+ return user;
+ },
+ signout(parent, args, ctx, info) {
+ ctx.response.clearCookie('token');
+ return { message: 'Goodbye!' };
+ },
+ async requestReset(parent, args, ctx, info) {
+ // 1. Check if this is a real user
+ const user = await ctx.db.query.user({ where: { email: args.email } });
+ if (!user) {
+ throw new Error(`No such user found for email ${args.email}`);
+ }
+ // 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 ctx.db.mutation.updateUser({
+ where: { email: args.email },
+ data: { resetToken, resetTokenExpiry },
+ });
+ // 3. Email them that reset token
+ const mailRes = await transport.sendMail({
+ from: 'wes@wesbos.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, ctx, info) {
+ // 1. check if the passwords match
+ if (args.password !== args.confirmPassword) {
+ throw new Error("Yo Passwords don't match!");
+ }
+ // 2. check if its a legit reset token
+ // 3. Check if its expired
+ const [user] = await ctx.db.query.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 ctx.db.mutation.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
+ ctx.response.cookie('token', token, {
+ httpOnly: true,
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ });
+ // 8. return the new user
+ return updatedUser;
+ },
+ async updatePermissions(parent, args, ctx, info) {
+ // 1. Check if they are logged in
+ if (!ctx.request.userId) {
+ throw new Error('You must be logged in!');
+ }
+ // 2. Query the current user
+ const currentUser = await ctx.db.query.user(
+ {
+ where: {
+ id: ctx.request.userId,
+ },
+ },
+ info
+ );
+ // 3. Check if they have permissions to do this
+ hasPermission(currentUser, ['ADMIN', 'PERMISSIONUPDATE']);
+ // 4. Update the permissions
+ return ctx.db.mutation.updateUser(
+ {
+ data: {
+ permissions: {
+ set: args.permissions,
+ },
+ },
+ where: {
+ id: args.userId,
+ },
+ },
+ info
+ );
+ },
+ async addToCart(parent, args, ctx, info) {
+ // 1. Make sure they are signed in
+ const { userId } = ctx.request;
+ if (!userId) {
+ throw new Error('You must be signed in soooon');
+ }
+ // 2. Query the users current cart
+ const [existingCartItem] = await ctx.db.query.cartItems({
+ where: {
+ user: { id: userId },
+ item: { id: args.id },
+ },
+ });
+ // 3. Check if that item is already in their cart and increment by 1 if it is
+ if (existingCartItem) {
+ console.log('This item is already in their cart');
+ return ctx.db.mutation.updateCartItem(
+ {
+ where: { id: existingCartItem.id },
+ data: { quantity: existingCartItem.quantity + 1 },
+ },
+ info
+ );
+ }
+ // 4. If its not, create a fresh CartItem for that user!
+ return ctx.db.mutation.createCartItem(
+ {
+ data: {
+ user: {
+ connect: { id: userId },
+ },
+ item: {
+ connect: { id: args.id },
+ },
+ },
+ },
+ info
+ );
+ },
+ async removeFromCart(parent, args, ctx, info) {
+ // 1. Find the cart item
+ const cartItem = await ctx.db.query.cartItem(
+ {
+ where: {
+ id: args.id,
+ },
+ },
+ `{ id, user { id }}`
+ );
+ // 1.5 Make sure we found an item
+ if (!cartItem) throw new Error('No CartItem Found!');
+ // 2. Make sure they own that cart item
+ if (cartItem.user.id !== ctx.request.userId) {
+ throw new Error('Cheatin huhhhh');
+ }
+ // 3. Delete that cart item
+ return ctx.db.mutation.deleteCartItem(
+ {
+ where: { id: args.id },
+ },
+ info
+ );
+ },
+ async createOrder(parent, args, ctx, info) {
+ // 1. Query the current user and make sure they are signed in
+ const { userId } = ctx.request;
+ if (!userId) throw new Error('You must be signed in to complete this order.');
+ const user = await ctx.db.query.user(
+ { where: { id: userId } },
+ `{
+ id
+ name
+ email
+ cart {
+ id
+ quantity
+ item { title price id description image largeImage }
+ }}`
+ );
+ // 2. recalculate the total for the price
+ const amount = user.cart.reduce(
+ (tally, cartItem) => tally + cartItem.item.price * cartItem.quantity,
+ 0
+ );
+ console.log(`Going to charge for a total of ${amount}`);
+ // 3. Create the stripe charge (turn token into $$$)
+ const charge = await stripe.charges.create({
+ amount,
+ currency: 'USD',
+ source: args.token,
+ });
+ // 4. Convert the CartItems to OrderItems
+ const orderItems = user.cart.map(cartItem => {
+ const orderItem = {
+ ...cartItem.item,
+ quantity: cartItem.quantity,
+ user: { connect: { id: userId } },
+ };
+ delete orderItem.id;
+ return orderItem;
+ });
+
+ // 5. create the Order
+ const order = await ctx.db.mutation.createOrder({
+ data: {
+ total: charge.amount,
+ charge: charge.id,
+ items: { create: orderItems },
+ user: { connect: { id: userId } },
+ },
+ });
+ // 6. Clean up - clear the users cart, delete cartItems
+ const cartItemIds = user.cart.map(cartItem => cartItem.id);
+ await ctx.db.mutation.deleteManyCartItems({
+ where: {
+ id_in: cartItemIds,
+ },
+ });
+ // 7. Return the Order to the client
+ return order;
+ },
+};
+
+module.exports = Mutations;
diff --git a/stepped-solutions/52/frontend/components/TakeMyMoney.js b/stepped-solutions/52/frontend/components/TakeMyMoney.js
new file mode 100755
index 0000000..6135db5
--- /dev/null
+++ b/stepped-solutions/52/frontend/components/TakeMyMoney.js
@@ -0,0 +1,73 @@
+import React from 'react';
+import StripeCheckout from 'react-stripe-checkout';
+import { Mutation } from 'react-apollo';
+import Router from 'next/router';
+import NProgress from 'nprogress';
+import PropTypes from 'prop-types';
+import gql from 'graphql-tag';
+import calcTotalPrice from '../lib/calcTotalPrice';
+import Error from './ErrorMessage';
+import User, { CURRENT_USER_QUERY } from './User';
+
+const CREATE_ORDER_MUTATION = gql`
+ mutation createOrder($token: String!) {
+ createOrder(token: $token) {
+ id
+ charge
+ total
+ items {
+ id
+ title
+ }
+ }
+ }
+`;
+
+function totalItems(cart) {
+ return cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0);
+}
+
+class TakeMyMoney extends React.Component {
+ onToken = async (res, createOrder) => {
+ console.log('On Token Called!');
+ console.log(res.id);
+ // manually call the mutation once we have the stripe token
+ const order = await createOrder({
+ variables: {
+ token: res.id,
+ },
+ }).catch(err => {
+ alert(err.message);
+ });
+ console.log(order);
+ };
+ render() {
+ return (
+ <User>
+ {({ data: { me } }) => (
+ <Mutation
+ mutation={CREATE_ORDER_MUTATION}
+ refetchQueries={[{ query: CURRENT_USER_QUERY }]}
+ >
+ {createOrder => (
+ <StripeCheckout
+ amount={calcTotalPrice(me.cart)}
+ name="Sick Fits"
+ description={`Order of ${totalItems(me.cart)} items!`}
+ image={me.cart.length && me.cart[0].item && me.cart[0].item.image}
+ stripeKey="pk_test_Vtknn6vSdcZWSG2JWvEiWSqC"
+ currency="USD"
+ email={me.email}
+ token={res => this.onToken(res, createOrder)}
+ >
+ {this.props.children}
+ </StripeCheckout>
+ )}
+ </Mutation>
+ )}
+ </User>
+ );
+ }
+}
+
+export default TakeMyMoney;