diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-02-09 13:31:39 -0500 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-02-09 13:31:39 -0500 |
| commit | e0d0baa75ec0bf617f91b6cd779305d4009cae12 (patch) | |
| tree | 00eb968f49c6586cbc4367053887d0c5e68471b2 /backend/src | |
| parent | 19c9683e2126c68cb9d231293162c756d69c51fb (diff) | |
🆒
Diffstat (limited to 'backend/src')
| -rw-r--r-- | backend/src/generated/prisma.graphql | 160 | ||||
| -rw-r--r-- | backend/src/index.js | 5 | ||||
| -rw-r--r-- | backend/src/mail.js | 12 | ||||
| -rw-r--r-- | backend/src/resolvers/Mutation.js | 129 | ||||
| -rw-r--r-- | backend/src/schema.graphql | 7 |
5 files changed, 303 insertions, 10 deletions
diff --git a/backend/src/generated/prisma.graphql b/backend/src/generated/prisma.graphql index 5e2d471..6132328 100644 --- a/backend/src/generated/prisma.graphql +++ b/backend/src/generated/prisma.graphql @@ -5,6 +5,12 @@ # Model Types # +type CartItem implements Node { + id: ID! + quantity: Int! + item(where: ItemWhereInput): Item! +} + type Item implements Node { id: ID! title: String! @@ -31,6 +37,8 @@ type User implements Node { website: String age: Int resetToken: String + resetTokenExpiry: String + cart(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem!] } @@ -38,6 +46,10 @@ type User implements Node { # Other Types # +type AggregateCartItem { + count: Int! +} + type AggregateItem { count: Int! } @@ -54,6 +66,104 @@ type BatchPayload { count: Long! } +type CartItemConnection { + pageInfo: PageInfo! + edges: [CartItemEdge]! + aggregate: AggregateCartItem! +} + +input CartItemCreateInput { + quantity: Int + item: ItemCreateOneInput! +} + +input CartItemCreateManyInput { + create: [CartItemCreateInput!] + connect: [CartItemWhereUniqueInput!] +} + +type CartItemEdge { + node: CartItem! + cursor: String! +} + +enum CartItemOrderByInput { + id_ASC + id_DESC + quantity_ASC + quantity_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type CartItemPreviousValues { + id: ID! + quantity: Int! +} + +type CartItemSubscriptionPayload { + mutation: MutationType! + node: CartItem + updatedFields: [String!] + previousValues: CartItemPreviousValues +} + +input CartItemSubscriptionWhereInput { + AND: [CartItemSubscriptionWhereInput!] + OR: [CartItemSubscriptionWhereInput!] + mutation_in: [MutationType!] + updatedFields_contains: String + updatedFields_contains_every: [String!] + updatedFields_contains_some: [String!] + node: CartItemWhereInput +} + +input CartItemUpdateInput { + quantity: Int + item: ItemUpdateOneInput +} + +input CartItemUpdateManyInput { + create: [CartItemCreateInput!] + connect: [CartItemWhereUniqueInput!] + disconnect: [CartItemWhereUniqueInput!] + delete: [CartItemWhereUniqueInput!] +} + +input CartItemWhereInput { + AND: [CartItemWhereInput!] + OR: [CartItemWhereInput!] + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + id_lt: ID + id_lte: ID + id_gt: ID + id_gte: ID + id_contains: ID + id_not_contains: ID + id_starts_with: ID + id_not_starts_with: ID + id_ends_with: ID + id_not_ends_with: ID + quantity: Int + quantity_not: Int + quantity_in: [Int!] + quantity_not_in: [Int!] + quantity_lt: Int + quantity_lte: Int + quantity_gt: Int + quantity_gte: Int + item: ItemWhereInput +} + +input CartItemWhereUniqueInput { + id: ID +} + scalar DateTime type ItemConnection { @@ -68,6 +178,11 @@ input ItemCreateInput { price: Int! } +input ItemCreateOneInput { + create: ItemCreateInput + connect: ItemWhereUniqueInput +} + type ItemEdge { node: Item! cursor: String! @@ -118,6 +233,13 @@ input ItemUpdateInput { price: Int } +input ItemUpdateOneInput { + create: ItemCreateInput + connect: ItemWhereUniqueInput + disconnect: ItemWhereUniqueInput + delete: ItemWhereUniqueInput +} + input ItemWhereInput { AND: [ItemWhereInput!] OR: [ItemWhereInput!] @@ -182,21 +304,27 @@ scalar Long type Mutation { createPost(data: PostCreateInput!): Post! createUser(data: UserCreateInput!): User! + createCartItem(data: CartItemCreateInput!): CartItem! createItem(data: ItemCreateInput!): Item! updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User + updateCartItem(data: CartItemUpdateInput!, where: CartItemWhereUniqueInput!): CartItem updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item deletePost(where: PostWhereUniqueInput!): Post deleteUser(where: UserWhereUniqueInput!): User + deleteCartItem(where: CartItemWhereUniqueInput!): CartItem deleteItem(where: ItemWhereUniqueInput!): Item upsertPost(where: PostWhereUniqueInput!, create: PostCreateInput!, update: PostUpdateInput!): Post! upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! + upsertCartItem(where: CartItemWhereUniqueInput!, create: CartItemCreateInput!, update: CartItemUpdateInput!): CartItem! upsertItem(where: ItemWhereUniqueInput!, create: ItemCreateInput!, update: ItemUpdateInput!): Item! updateManyPosts(data: PostUpdateInput!, where: PostWhereInput!): BatchPayload! updateManyUsers(data: UserUpdateInput!, where: UserWhereInput!): BatchPayload! + updateManyCartItems(data: CartItemUpdateInput!, where: CartItemWhereInput!): BatchPayload! updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput!): BatchPayload! deleteManyPosts(where: PostWhereInput!): BatchPayload! deleteManyUsers(where: UserWhereInput!): BatchPayload! + deleteManyCartItems(where: CartItemWhereInput!): BatchPayload! deleteManyItems(where: ItemWhereInput!): BatchPayload! } @@ -393,12 +521,15 @@ input PostWhereUniqueInput { type Query { posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post]! users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! + cartItems(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem]! items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Item]! post(where: PostWhereUniqueInput!): Post user(where: UserWhereUniqueInput!): User + cartItem(where: CartItemWhereUniqueInput!): CartItem item(where: ItemWhereUniqueInput!): Item postsConnection(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): PostConnection! usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! + cartItemsConnection(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): CartItemConnection! itemsConnection(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ItemConnection! node(id: ID!): Node } @@ -406,6 +537,7 @@ type Query { type Subscription { post(where: PostSubscriptionWhereInput): PostSubscriptionPayload user(where: UserSubscriptionWhereInput): UserSubscriptionPayload + cartItem(where: CartItemSubscriptionWhereInput): CartItemSubscriptionPayload item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload } @@ -422,7 +554,9 @@ input UserCreateInput { website: String age: Int resetToken: String + resetTokenExpiry: String posts: PostCreateManyWithoutAuthorInput + cart: CartItemCreateManyInput } input UserCreateOneWithoutPostsInput { @@ -437,6 +571,8 @@ input UserCreateWithoutPostsInput { website: String age: Int resetToken: String + resetTokenExpiry: String + cart: CartItemCreateManyInput } type UserEdge { @@ -459,6 +595,8 @@ enum UserOrderByInput { age_DESC resetToken_ASC resetToken_DESC + resetTokenExpiry_ASC + resetTokenExpiry_DESC updatedAt_ASC updatedAt_DESC createdAt_ASC @@ -473,6 +611,7 @@ type UserPreviousValues { website: String age: Int resetToken: String + resetTokenExpiry: String } type UserSubscriptionPayload { @@ -499,7 +638,9 @@ input UserUpdateInput { website: String age: Int resetToken: String + resetTokenExpiry: String posts: PostUpdateManyWithoutAuthorInput + cart: CartItemUpdateManyInput } input UserUpdateOneWithoutPostsInput { @@ -518,6 +659,8 @@ input UserUpdateWithoutPostsDataInput { website: String age: Int resetToken: String + resetTokenExpiry: String + cart: CartItemUpdateManyInput } input UserUpdateWithoutPostsInput { @@ -626,9 +769,26 @@ input UserWhereInput { resetToken_not_starts_with: String resetToken_ends_with: String resetToken_not_ends_with: String + resetTokenExpiry: String + resetTokenExpiry_not: String + resetTokenExpiry_in: [String!] + resetTokenExpiry_not_in: [String!] + resetTokenExpiry_lt: String + resetTokenExpiry_lte: String + resetTokenExpiry_gt: String + resetTokenExpiry_gte: String + resetTokenExpiry_contains: String + resetTokenExpiry_not_contains: String + resetTokenExpiry_starts_with: String + resetTokenExpiry_not_starts_with: String + resetTokenExpiry_ends_with: String + resetTokenExpiry_not_ends_with: String posts_every: PostWhereInput posts_some: PostWhereInput posts_none: PostWhereInput + cart_every: CartItemWhereInput + cart_some: CartItemWhereInput + cart_none: CartItemWhereInput } input UserWhereUniqueInput { diff --git a/backend/src/index.js b/backend/src/index.js index fefe598..359456f 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -31,3 +31,8 @@ const server = new GraphQLServer({ // }); server.start(() => console.log('Server is running on http://localhost:4000')); + +// overwrite console.log +const chalk = require('chalk'); + +global.console.l = (...butta) => console.log(chalk.bold.yellow(...butta)); diff --git a/backend/src/mail.js b/backend/src/mail.js new file mode 100644 index 0000000..3159fe2 --- /dev/null +++ b/backend/src/mail.js @@ -0,0 +1,12 @@ +const nodemailer = require('nodemailer'); + +const transport = nodemailer.createTransport({ + host: 'smtp.mailtrap.io', + port: 2525, + auth: { + user: 'c41bab08214808', + pass: '04992d4af4bdcf', + }, +}); + +module.exports = transport; diff --git a/backend/src/resolvers/Mutation.js b/backend/src/resolvers/Mutation.js index 95be561..003a563 100644 --- a/backend/src/resolvers/Mutation.js +++ b/backend/src/resolvers/Mutation.js @@ -4,6 +4,7 @@ const { forwardTo } = require('prisma-binding'); const { getUserId, Context } = require('../utils'); const { randomBytes } = require('crypto'); const { promisify } = require('util'); +const mail = require('../mail'); const mutations = { // Signup Mutations @@ -136,16 +137,128 @@ const mutations = { } console.log(user); // 2. Set a reset token, and a reset date - // const resetToken = await promisify(randomBytes)(20); - // 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 }, - // }); + 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 }, + }); - // console.log(res); + console.log(res); // 3. Send them their token via email + const mailRes = await mail.sendMail({ + from: 'wesbos@gmail.com', + to: 'wesbos@gmail.com', + subject: 'Your password reset token', + html: `Here is your reset link: http://localhost:3000/reset?resetToken=${resetToken}`, + }); + + 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, + }, + }); + + // 6. send back the Auth Payload for the GraphQL request on the client + return { + // TODO: This should use sub instead of userId + token: jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET), + user: updatedUser, + }; + }, + /* + Add to cart + */ + async addToCart(parent, args, ctx, info) { + console.l('Add to cart calle'); + const userId = getUserId(ctx); + // get the current user + const currentUser = await ctx.db.query.user( + { + where: { id: userId }, + }, + '{ cart { id, quantity item { id } }}' + ); + + // find out if the user currently has this item in their cart + const existingCartItemIndex = currentUser.cart.findIndex(cartItem => cartItem.item.id === args.id); + + if (existingCartItemIndex >= 0) { + console.l('======This item already exists in the cart============'); + const cartItem = currentUser.cart[existingCartItemIndex]; + return ctx.db.mutation.updateCartItem({ + where: { id: cartItem.id }, + data: { + quantity: cartItem.quantity + 1, + }, + }); + } + + const res = await ctx.db.mutation.updateUser({ + where: { id: userId }, + data: { + cart: { + create: [ + { + item: { + connect: { + id: args.id, + }, + }, + quantity: 1, + }, + ], + }, + }, + }); + console.log(res); + // return res; + }, + async removeFromCart(parent, args, ctx, info) { + // delete that cart item + return ctx.db.mutation.deleteCartItem({ + where: { id: args.id }, + }); + // const userId = getUserId(ctx); + // return ctx.db.query.user({ where: { id: userId } }, '{ id, cart { id, quantity }}'); + // const cartItem = await ctx.db.query.cartItem({ + // where: { id: args.id }, + // }); + + // console.log(cartItem); + // return cartItem; }, }; diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 8a2fa23..b30b096 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -1,4 +1,4 @@ -# import ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput from './generated/prisma.graphql' +# import CartItem, ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput from './generated/prisma.graphql' type Query { feed: [Post!]! @@ -28,6 +28,7 @@ type Mutation { signup(email: String!, password: String!, name: String!): AuthPayload! signin(email: String!, password: String!): AuthPayload! requestReset(email: String!): User + resetPassword(resetToken: String!, password: String!, confirmPassword: String!): AuthPayload! createDraft(title: String!, text: String!): Post! publish(id: ID!): Post! deletePost(id: ID!): Post! @@ -35,6 +36,8 @@ type Mutation { createItem(title: String, description: String, price: Int): Item! deleteItem(id: ID!): Item! updateItem(id: ID!, title: String, description: String, price: Int): Item! + addToCart(id: ID!): CartItem + removeFromCart(id: ID!): CartItem } type AuthPayload { @@ -49,5 +52,5 @@ type User { name: String! posts: [Post!]! age: Int - resetToken: String + cart: [CartItem!]! } |
