summaryrefslogtreecommitdiffstats
path: root/finished-application/backend/src/resolvers
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-06-14 16:44:21 -0400
committerWes Bos <wesbos@gmail.com>2018-06-14 16:44:21 -0400
commitb1600adec47a04f60e1da07d94a3ed3906ff5aee (patch)
tree828f786da6806d7237ee5cbc5df61e552353b85b /finished-application/backend/src/resolvers
parenta95e08cd2dd5d7ec0a0412adae02515a5f9cec4f (diff)
starter files
Diffstat (limited to 'finished-application/backend/src/resolvers')
-rw-r--r--finished-application/backend/src/resolvers/Mutation.js354
-rw-r--r--finished-application/backend/src/resolvers/Query.js62
2 files changed, 416 insertions, 0 deletions
diff --git a/finished-application/backend/src/resolvers/Mutation.js b/finished-application/backend/src/resolvers/Mutation.js
new file mode 100644
index 0000000..0f1d9ba
--- /dev/null
+++ b/finished-application/backend/src/resolvers/Mutation.js
@@ -0,0 +1,354 @@
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const { hasPermission } = require('../utils');
+const { randomBytes } = require('crypto');
+const { promisify } = require('util');
+const mail = require('../mail');
+const stripe = require('../stripe');
+
+const mutations = {
+ // Signup Mutations
+ async signup(parent, args, ctx, info) {
+ args.email = args.email.toLowerCase();
+ const password = await bcrypt.hash(args.password, 10);
+ const user = await ctx.db.mutation.createUser(
+ {
+ data: {
+ ...args,
+ password,
+ permissions: { set: ['USER'] },
+ },
+ },
+ info
+ );
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ ctx.response.cookie('token', token, {
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ httpOnly: true,
+ });
+ return user;
+ },
+
+ async signout(parent, args, ctx, info) {
+ ctx.response.clearCookie('token');
+ return { message: 'goodbye!' };
+ },
+
+ async signin(parent, { email, password }, ctx, info) {
+ const user = await ctx.db.query.user({ where: { email } });
+ if (!user) {
+ throw new Error(`No such user found for email: ${email}`);
+ }
+
+ const valid = await bcrypt.compare(password, user.password);
+ if (!valid) {
+ throw new Error('Invalid password');
+ }
+ // set the cookie
+ const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
+ ctx.response.cookie('token', token, {
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ httpOnly: true,
+ });
+ return user;
+ },
+
+ // Create An Item
+ async createItem(parent, args, ctx, info) {
+ if (!ctx.request.userId) {
+ throw new Error('You must be logged in to create an item');
+ }
+
+ const item = await ctx.db.mutation.createItem(
+ {
+ data: {
+ user: {
+ connect: {
+ id: ctx.request.userId,
+ },
+ },
+ ...args,
+ },
+ },
+ info
+ );
+ return item;
+ },
+
+ async deleteItem(parent, args, ctx, info) {
+ const where = {
+ id: args.id,
+ };
+ // 1. find the item
+ const item = await ctx.db.query.item({ where }, `{ user {id}, title, id, description }`);
+ // 2. if they 1. Don't own it AND 2. aren't an admin
+ if (item.user.id !== ctx.request.user.id && !ctx.request.user.permissions.includes('ADMIN')) {
+ throw new Error("You aren't allowed to delete that item!");
+ }
+
+ // 3. remove any orderItems this item is in
+
+ return ctx.db.mutation.deleteItem({ where }, info);
+ },
+
+ async updateItem(parent, args, ctx, info) {
+ const user = ctx.request.user;
+ const item = await ctx.db.query.item({ where: { id: args.id } }, `{ user { id } }`);
+
+ if (item.user.id !== user.id || !hasPermission(user, ['ADMIN'])) {
+ throw new Error('You are not allowed to update that item!');
+ }
+
+ const updates = { ...args };
+ // remove the ID because you can't update that
+ delete updates.id;
+ return ctx.db.mutation.updateItem(
+ {
+ where: { id: args.id },
+ data: {
+ ...updates,
+ },
+ },
+ info
+ );
+ },
+
+ // Send password request
+ async requestReset(parent, args, ctx, info) {
+ // 1. find if there is a user with that email
+ const user = await ctx.db.query.user({ where: { email: args.email } });
+
+ if (!user) {
+ throw new Error(`No user with the email ${args.email}`);
+ }
+ // 2. Set a reset token, and a reset date
+ const resetToken = (await promisify(randomBytes)(20)).toString('hex');
+ const resetTokenExpiry = Date.now() + 3600000; // 1 hour from now
+ console.log({ resetToken, resetTokenExpiry });
+ const res = await ctx.db.mutation.updateUser({
+ where: { email: args.email },
+ data: { resetToken, resetTokenExpiry },
+ });
+
+ // 3. Send them their token via email
+ const mailRes = await mail.transport.sendMail({
+ from: 'wesbos@gmail.com',
+ to: user.email,
+ subject: 'Your password reset token',
+ html: mail.makeANiceEmail(
+ `Your password reset link is here! \n\n<a href="${process.env
+ .FRONTEND_URL}/reset?resetToken=${resetToken}">Click Here to reset</a>`
+ ),
+ });
+ return res.updateUser;
+ },
+
+ async resetPassword(parent, args, ctx, info) {
+ // 1. Check that the passwords match
+ if (args.password !== args.confirmPassword) {
+ throw new Error('Passwords do not match');
+ }
+
+ // 2. Check that this is a legit resetToken
+ // 3. Check that it's not expired
+ // Note: If we didn't need the user here, we could also use db.exists()
+ const [user] = await ctx.db.query.users({
+ where: {
+ resetToken: args.resetToken,
+ resetTokenExpiry_gte: Date.now() - 3600000, // within the last hour
+ },
+ });
+
+ if (!user) {
+ throw new Error('This token is either invalid or expired.');
+ }
+
+ // 4. Hash the password
+ const password = await bcrypt.hash(args.password, 10);
+
+ // 5. Update the users password
+ // clean up the resetToken fields at the same time
+ const updatedUser = await ctx.db.mutation.updateUser({
+ where: { email: user.email },
+ data: {
+ password,
+ resetToken: null,
+ resetTokenExpiry: null,
+ },
+ });
+ const token = jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET);
+ ctx.response.cookie('token', token, {
+ maxAge: 1000 * 60 * 60 * 24 * 365,
+ httpOnly: true,
+ });
+
+ // 6. send back the User for the GraphQL request on the client
+ return updatedUser;
+ },
+ /*
+ Add to cart
+ */
+ async addToCart(parent, args, ctx, info) {
+ const userId = ctx.request.userId;
+
+ if (!userId) {
+ throw new Error('You must be signed in to add to cart!');
+ }
+
+ // 1. Check if there is a CartItem for this user and item already
+ const [existingCartItem] = await ctx.db.query.cartItems({
+ where: {
+ user: { id: userId },
+ item: { id: args.id },
+ },
+ });
+
+ if (existingCartItem) {
+ return ctx.db.mutation.updateCartItem(
+ {
+ where: { id: existingCartItem.id },
+ data: { quantity: existingCartItem.quantity + 1 },
+ },
+ info
+ );
+ }
+
+ // Otherwise create a new cartItem
+ return ctx.db.mutation.createCartItem(
+ {
+ data: {
+ user: {
+ connect: {
+ id: userId,
+ },
+ },
+ item: {
+ connect: { id: args.id },
+ },
+ },
+ },
+ info
+ );
+ },
+
+ // delete that cart item
+ async removeFromCart(parent, args, ctx, info) {
+ console.log(args.id);
+ // 1. Find the CartItem
+ const cartItem = await ctx.db.query.cartItem(
+ {
+ where: { id: args.id },
+ },
+ `{ id, user { id, permissions }}`
+ );
+ // 2. Check they own it
+ if (cartItem.user.id !== ctx.request.userId) {
+ throw new Error("Cheatin' huh");
+ }
+ // 3. Delete it
+ return ctx.db.mutation.deleteCartItem(
+ {
+ where: {
+ id: args.id,
+ },
+ },
+ info
+ );
+ },
+
+ async createOrder(parent, args, ctx, info) {
+ const userId = ctx.request.userId;
+ const user = await ctx.db.query.user(
+ { where: { id: userId } },
+ '{ id, name, email, cart { id, quantity, item { title, price, id, description, image } }}'
+ );
+ // 1. Recalculate the total for the price
+ const amount = user.cart.reduce(
+ (tally, cartItem) => tally + cartItem.item.price * cartItem.quantity,
+ 0
+ );
+ // 2. Create a stripe charge
+ const charge = await stripe.charges.create({
+ amount,
+ currency: 'usd',
+ source: args.token,
+ });
+
+ // 3. convert the items they want to OrderItems
+ const orderItems = user.cart.map(cartItem => {
+ const orderItem = {
+ quantity: cartItem.quantity,
+ // copy all the item details so it's there forever
+ ...cartItem.item,
+ item: {
+ // relationship to the Item incase we need it
+ connect: { id: cartItem.item.id },
+ },
+ user: { connect: { id: user.id } },
+ };
+ // scrub the ID from it because the orderItem will have it's own ID
+ delete orderItem.id;
+ return orderItem;
+ });
+
+ // 4. Create the Order
+ const order = await ctx.db.mutation.createOrder({
+ data: {
+ total: charge.amount,
+ charge: charge.id,
+ items: {
+ create: orderItems,
+ },
+ user: {
+ connect: {
+ id: user.id,
+ },
+ },
+ },
+ });
+
+ // 5. Clean up, clear the users cart and send back { user, order }
+ // Delete the users current cart items
+ const cartItemIds = user.cart.map(cartItem => cartItem.id);
+ await ctx.db.mutation.deleteManyCartItems({
+ where: {
+ id_in: cartItemIds,
+ },
+ });
+
+ // 6. Send the order back to the client
+ return order;
+ },
+
+ async updateUser(parent, args, ctx, info) {
+ const userId = ctx.request.userId;
+ const updatedUser = await ctx.db.mutation.updateUser(
+ {
+ data: args,
+ where: { id: userId },
+ },
+ info
+ );
+ return updatedUser;
+ },
+
+ async updatePermissions(parent, args, ctx, info) {
+ const userId = ctx.request.userId;
+ const currentUser = await ctx.db.query.user({ where: { id: userId } }, info);
+ if (!currentUser) throw new Error('You Must be logged in to updat permissions!');
+ hasPermission(currentUser, ['ADMIN', 'PERMISSIONUPDATE']);
+ return ctx.db.mutation.updateUser(
+ {
+ data: {
+ permissions: {
+ set: args.permissions,
+ },
+ },
+ where: { id: args.userId },
+ },
+ info
+ );
+ },
+};
+
+module.exports = mutations;
diff --git a/finished-application/backend/src/resolvers/Query.js b/finished-application/backend/src/resolvers/Query.js
new file mode 100644
index 0000000..51c2c42
--- /dev/null
+++ b/finished-application/backend/src/resolvers/Query.js
@@ -0,0 +1,62 @@
+const { hasPermission } = require('../utils');
+
+const { forwardTo } = require('prisma-binding');
+
+const Query = {
+ items: forwardTo('db'),
+ itemsConnection: forwardTo('db'),
+
+ async order(parent, args, ctx, info) {
+ // 1. make sure they are signed in
+ if (!ctx.request.userId) {
+ throw new Error('You Must be signed in to view an order');
+ }
+
+ // 2. Create the query
+ const where = {
+ id: args.id,
+ user: {
+ id: ctx.request.userId,
+ },
+ };
+ // 3. Fire off the query
+ const [order] = await ctx.db.query.orders({ where }, info);
+
+ // 4. Check that they are allowed to view the order
+ if (order.user.id !== ctx.request.userId || hasPermission(ctx.request.user, ['ADMIN'])) {
+ throw new Error("You don't have permission");
+ }
+ // 5. If everything checks out, return the order
+ return order;
+ },
+
+ me(parent, args, ctx, info) {
+ if (!ctx.request.userId) {
+ return null; // don't error out, just return nothing
+ }
+
+ return ctx.db.query.user(
+ {
+ where: { id: ctx.request.userId },
+ },
+ info
+ );
+ },
+
+ async orders(parent, args, ctx, info) {
+ const { userId } = ctx.request;
+ if (!userId) {
+ throw new Error('You must be signed in to see your orders');
+ }
+ return ctx.db.query.orders(
+ {
+ where: {
+ user: { id: userId },
+ },
+ },
+ info
+ );
+ },
+};
+
+module.exports = Query;