diff options
Diffstat (limited to 'stepped-solutions/51')
| -rwxr-xr-x | stepped-solutions/51/backend/datamodel.graphql | 55 | ||||
| -rwxr-xr-x | stepped-solutions/51/backend/src/generated/prisma.graphql | 1805 | ||||
| -rwxr-xr-x | stepped-solutions/51/backend/src/resolvers/Mutation.js | 312 | ||||
| -rwxr-xr-x | stepped-solutions/51/backend/src/schema.graphql | 36 | ||||
| -rwxr-xr-x | stepped-solutions/51/frontend/components/TakeMyMoney.js | 72 |
5 files changed, 2280 insertions, 0 deletions
diff --git a/stepped-solutions/51/backend/datamodel.graphql b/stepped-solutions/51/backend/datamodel.graphql new file mode 100755 index 0000000..b556d36 --- /dev/null +++ b/stepped-solutions/51/backend/datamodel.graphql @@ -0,0 +1,55 @@ +enum Permission { + ADMIN + USER + ITEMCREATE + ITEMUPDATE + ITEMDELETE + PERMISSIONUPDATE +} + +type User { + id: ID! @unique + name: String! + email: String! @unique + password: String! + resetToken: String + resetTokenExpiry: String + permissions: [Permission] + cart: [CartItem!]! +} + +type Item { + id: ID! @unique + title: String! + description: String! + image: String + largeImage: String + price: Int! + user: User! +} + +type CartItem { + id: ID! @unique + quantity: Int! @default(value: 1) + item: Item # relationship to Item + user: User! # relationship to User +} + +type OrderItem { + id: ID! @unique + title: String! + description: String! + image: String! + largeImage: String! + price: Int! + quantity: Int! @default(value: 1) + user: User +} + +type Order { + id: ID! @unique + items: [OrderItem!]! + total: Int! + user: User! + charge: String! +} diff --git a/stepped-solutions/51/backend/src/generated/prisma.graphql b/stepped-solutions/51/backend/src/generated/prisma.graphql new file mode 100755 index 0000000..c9dd405 --- /dev/null +++ b/stepped-solutions/51/backend/src/generated/prisma.graphql @@ -0,0 +1,1805 @@ +# source: https://us1.prisma.sh/wesbos/siccccccccck-fits/dev +# timestamp: Wed Aug 22 2018 14:34:48 GMT-0400 (EDT) + +type AggregateCartItem { + count: Int! +} + +type AggregateItem { + count: Int! +} + +type AggregateOrder { + count: Int! +} + +type AggregateOrderItem { + count: Int! +} + +type AggregateUser { + count: Int! +} + +type BatchPayload { + """The number of nodes that have been affected by the Batch operation.""" + count: Long! +} + +type CartItem implements Node { + id: ID! + quantity: Int! + item(where: ItemWhereInput): Item + user(where: UserWhereInput): User! +} + +"""A connection to a list of items.""" +type CartItemConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [CartItemEdge]! + aggregate: AggregateCartItem! +} + +input CartItemCreateInput { + quantity: Int + item: ItemCreateOneInput + user: UserCreateOneWithoutCartInput! +} + +input CartItemCreateManyWithoutUserInput { + create: [CartItemCreateWithoutUserInput!] + connect: [CartItemWhereUniqueInput!] +} + +input CartItemCreateWithoutUserInput { + quantity: Int + item: ItemCreateOneInput +} + +"""An edge in a connection.""" +type CartItemEdge { + """The item at the end of the edge.""" + node: CartItem! + + """A cursor for use in pagination.""" + 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 { + """Logical AND on all given filters.""" + AND: [CartItemSubscriptionWhereInput!] + + """Logical OR on all given filters.""" + OR: [CartItemSubscriptionWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [CartItemSubscriptionWhereInput!] + + """ + The subscription event gets dispatched when it's listed in mutation_in + """ + mutation_in: [MutationType!] + + """ + The subscription event gets only dispatched when one of the updated fields names is included in this list + """ + updatedFields_contains: String + + """ + The subscription event gets only dispatched when all of the field names included in this list have been updated + """ + updatedFields_contains_every: [String!] + + """ + The subscription event gets only dispatched when some of the field names included in this list have been updated + """ + updatedFields_contains_some: [String!] + node: CartItemWhereInput +} + +input CartItemUpdateInput { + quantity: Int + item: ItemUpdateOneInput + user: UserUpdateOneWithoutCartInput +} + +input CartItemUpdateManyWithoutUserInput { + create: [CartItemCreateWithoutUserInput!] + connect: [CartItemWhereUniqueInput!] + disconnect: [CartItemWhereUniqueInput!] + delete: [CartItemWhereUniqueInput!] + update: [CartItemUpdateWithWhereUniqueWithoutUserInput!] + upsert: [CartItemUpsertWithWhereUniqueWithoutUserInput!] +} + +input CartItemUpdateWithoutUserDataInput { + quantity: Int + item: ItemUpdateOneInput +} + +input CartItemUpdateWithWhereUniqueWithoutUserInput { + where: CartItemWhereUniqueInput! + data: CartItemUpdateWithoutUserDataInput! +} + +input CartItemUpsertWithWhereUniqueWithoutUserInput { + where: CartItemWhereUniqueInput! + update: CartItemUpdateWithoutUserDataInput! + create: CartItemCreateWithoutUserInput! +} + +input CartItemWhereInput { + """Logical AND on all given filters.""" + AND: [CartItemWhereInput!] + + """Logical OR on all given filters.""" + OR: [CartItemWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [CartItemWhereInput!] + id: ID + + """All values that are not equal to given value.""" + id_not: ID + + """All values that are contained in given list.""" + id_in: [ID!] + + """All values that are not contained in given list.""" + id_not_in: [ID!] + + """All values less than the given value.""" + id_lt: ID + + """All values less than or equal the given value.""" + id_lte: ID + + """All values greater than the given value.""" + id_gt: ID + + """All values greater than or equal the given value.""" + id_gte: ID + + """All values containing the given string.""" + id_contains: ID + + """All values not containing the given string.""" + id_not_contains: ID + + """All values starting with the given string.""" + id_starts_with: ID + + """All values not starting with the given string.""" + id_not_starts_with: ID + + """All values ending with the given string.""" + id_ends_with: ID + + """All values not ending with the given string.""" + id_not_ends_with: ID + quantity: Int + + """All values that are not equal to given value.""" + quantity_not: Int + + """All values that are contained in given list.""" + quantity_in: [Int!] + + """All values that are not contained in given list.""" + quantity_not_in: [Int!] + + """All values less than the given value.""" + quantity_lt: Int + + """All values less than or equal the given value.""" + quantity_lte: Int + + """All values greater than the given value.""" + quantity_gt: Int + + """All values greater than or equal the given value.""" + quantity_gte: Int + item: ItemWhereInput + user: UserWhereInput +} + +input CartItemWhereUniqueInput { + id: ID +} + +type Item implements Node { + id: ID! + title: String! + description: String! + image: String + largeImage: String + price: Int! + user(where: UserWhereInput): User! +} + +"""A connection to a list of items.""" +type ItemConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [ItemEdge]! + aggregate: AggregateItem! +} + +input ItemCreateInput { + title: String! + description: String! + image: String + largeImage: String + price: Int! + user: UserCreateOneInput! +} + +input ItemCreateOneInput { + create: ItemCreateInput + connect: ItemWhereUniqueInput +} + +"""An edge in a connection.""" +type ItemEdge { + """The item at the end of the edge.""" + node: Item! + + """A cursor for use in pagination.""" + cursor: String! +} + +enum ItemOrderByInput { + id_ASC + id_DESC + title_ASC + title_DESC + description_ASC + description_DESC + image_ASC + image_DESC + largeImage_ASC + largeImage_DESC + price_ASC + price_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type ItemPreviousValues { + id: ID! + title: String! + description: String! + image: String + largeImage: String + price: Int! +} + +type ItemSubscriptionPayload { + mutation: MutationType! + node: Item + updatedFields: [String!] + previousValues: ItemPreviousValues +} + +input ItemSubscriptionWhereInput { + """Logical AND on all given filters.""" + AND: [ItemSubscriptionWhereInput!] + + """Logical OR on all given filters.""" + OR: [ItemSubscriptionWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [ItemSubscriptionWhereInput!] + + """ + The subscription event gets dispatched when it's listed in mutation_in + """ + mutation_in: [MutationType!] + + """ + The subscription event gets only dispatched when one of the updated fields names is included in this list + """ + updatedFields_contains: String + + """ + The subscription event gets only dispatched when all of the field names included in this list have been updated + """ + updatedFields_contains_every: [String!] + + """ + The subscription event gets only dispatched when some of the field names included in this list have been updated + """ + updatedFields_contains_some: [String!] + node: ItemWhereInput +} + +input ItemUpdateDataInput { + title: String + description: String + image: String + largeImage: String + price: Int + user: UserUpdateOneInput +} + +input ItemUpdateInput { + title: String + description: String + image: String + largeImage: String + price: Int + user: UserUpdateOneInput +} + +input ItemUpdateOneInput { + create: ItemCreateInput + connect: ItemWhereUniqueInput + disconnect: Boolean + delete: Boolean + update: ItemUpdateDataInput + upsert: ItemUpsertNestedInput +} + +input ItemUpsertNestedInput { + update: ItemUpdateDataInput! + create: ItemCreateInput! +} + +input ItemWhereInput { + """Logical AND on all given filters.""" + AND: [ItemWhereInput!] + + """Logical OR on all given filters.""" + OR: [ItemWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [ItemWhereInput!] + id: ID + + """All values that are not equal to given value.""" + id_not: ID + + """All values that are contained in given list.""" + id_in: [ID!] + + """All values that are not contained in given list.""" + id_not_in: [ID!] + + """All values less than the given value.""" + id_lt: ID + + """All values less than or equal the given value.""" + id_lte: ID + + """All values greater than the given value.""" + id_gt: ID + + """All values greater than or equal the given value.""" + id_gte: ID + + """All values containing the given string.""" + id_contains: ID + + """All values not containing the given string.""" + id_not_contains: ID + + """All values starting with the given string.""" + id_starts_with: ID + + """All values not starting with the given string.""" + id_not_starts_with: ID + + """All values ending with the given string.""" + id_ends_with: ID + + """All values not ending with the given string.""" + id_not_ends_with: ID + title: String + + """All values that are not equal to given value.""" + title_not: String + + """All values that are contained in given list.""" + title_in: [String!] + + """All values that are not contained in given list.""" + title_not_in: [String!] + + """All values less than the given value.""" + title_lt: String + + """All values less than or equal the given value.""" + title_lte: String + + """All values greater than the given value.""" + title_gt: String + + """All values greater than or equal the given value.""" + title_gte: String + + """All values containing the given string.""" + title_contains: String + + """All values not containing the given string.""" + title_not_contains: String + + """All values starting with the given string.""" + title_starts_with: String + + """All values not starting with the given string.""" + title_not_starts_with: String + + """All values ending with the given string.""" + title_ends_with: String + + """All values not ending with the given string.""" + title_not_ends_with: String + description: String + + """All values that are not equal to given value.""" + description_not: String + + """All values that are contained in given list.""" + description_in: [String!] + + """All values that are not contained in given list.""" + description_not_in: [String!] + + """All values less than the given value.""" + description_lt: String + + """All values less than or equal the given value.""" + description_lte: String + + """All values greater than the given value.""" + description_gt: String + + """All values greater than or equal the given value.""" + description_gte: String + + """All values containing the given string.""" + description_contains: String + + """All values not containing the given string.""" + description_not_contains: String + + """All values starting with the given string.""" + description_starts_with: String + + """All values not starting with the given string.""" + description_not_starts_with: String + + """All values ending with the given string.""" + description_ends_with: String + + """All values not ending with the given string.""" + description_not_ends_with: String + image: String + + """All values that are not equal to given value.""" + image_not: String + + """All values that are contained in given list.""" + image_in: [String!] + + """All values that are not contained in given list.""" + image_not_in: [String!] + + """All values less than the given value.""" + image_lt: String + + """All values less than or equal the given value.""" + image_lte: String + + """All values greater than the given value.""" + image_gt: String + + """All values greater than or equal the given value.""" + image_gte: String + + """All values containing the given string.""" + image_contains: String + + """All values not containing the given string.""" + image_not_contains: String + + """All values starting with the given string.""" + image_starts_with: String + + """All values not starting with the given string.""" + image_not_starts_with: String + + """All values ending with the given string.""" + image_ends_with: String + + """All values not ending with the given string.""" + image_not_ends_with: String + largeImage: String + + """All values that are not equal to given value.""" + largeImage_not: String + + """All values that are contained in given list.""" + largeImage_in: [String!] + + """All values that are not contained in given list.""" + largeImage_not_in: [String!] + + """All values less than the given value.""" + largeImage_lt: String + + """All values less than or equal the given value.""" + largeImage_lte: String + + """All values greater than the given value.""" + largeImage_gt: String + + """All values greater than or equal the given value.""" + largeImage_gte: String + + """All values containing the given string.""" + largeImage_contains: String + + """All values not containing the given string.""" + largeImage_not_contains: String + + """All values starting with the given string.""" + largeImage_starts_with: String + + """All values not starting with the given string.""" + largeImage_not_starts_with: String + + """All values ending with the given string.""" + largeImage_ends_with: String + + """All values not ending with the given string.""" + largeImage_not_ends_with: String + price: Int + + """All values that are not equal to given value.""" + price_not: Int + + """All values that are contained in given list.""" + price_in: [Int!] + + """All values that are not contained in given list.""" + price_not_in: [Int!] + + """All values less than the given value.""" + price_lt: Int + + """All values less than or equal the given value.""" + price_lte: Int + + """All values greater than the given value.""" + price_gt: Int + + """All values greater than or equal the given value.""" + price_gte: Int + user: UserWhereInput +} + +input ItemWhereUniqueInput { + id: ID +} + +""" +The `Long` scalar type represents non-fractional signed whole numeric values. +Long can represent values between -(2^63) and 2^63 - 1. +""" +scalar Long + +type Mutation { + createCartItem(data: CartItemCreateInput!): CartItem! + createOrder(data: OrderCreateInput!): Order! + createItem(data: ItemCreateInput!): Item! + createOrderItem(data: OrderItemCreateInput!): OrderItem! + createUser(data: UserCreateInput!): User! + updateCartItem(data: CartItemUpdateInput!, where: CartItemWhereUniqueInput!): CartItem + updateOrder(data: OrderUpdateInput!, where: OrderWhereUniqueInput!): Order + updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item + updateOrderItem(data: OrderItemUpdateInput!, where: OrderItemWhereUniqueInput!): OrderItem + updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User + deleteCartItem(where: CartItemWhereUniqueInput!): CartItem + deleteOrder(where: OrderWhereUniqueInput!): Order + deleteItem(where: ItemWhereUniqueInput!): Item + deleteOrderItem(where: OrderItemWhereUniqueInput!): OrderItem + deleteUser(where: UserWhereUniqueInput!): User + upsertCartItem(where: CartItemWhereUniqueInput!, create: CartItemCreateInput!, update: CartItemUpdateInput!): CartItem! + upsertOrder(where: OrderWhereUniqueInput!, create: OrderCreateInput!, update: OrderUpdateInput!): Order! + upsertItem(where: ItemWhereUniqueInput!, create: ItemCreateInput!, update: ItemUpdateInput!): Item! + upsertOrderItem(where: OrderItemWhereUniqueInput!, create: OrderItemCreateInput!, update: OrderItemUpdateInput!): OrderItem! + upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! + updateManyCartItems(data: CartItemUpdateInput!, where: CartItemWhereInput): BatchPayload! + updateManyOrders(data: OrderUpdateInput!, where: OrderWhereInput): BatchPayload! + updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput): BatchPayload! + updateManyOrderItems(data: OrderItemUpdateInput!, where: OrderItemWhereInput): BatchPayload! + updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload! + deleteManyCartItems(where: CartItemWhereInput): BatchPayload! + deleteManyOrders(where: OrderWhereInput): BatchPayload! + deleteManyItems(where: ItemWhereInput): BatchPayload! + deleteManyOrderItems(where: OrderItemWhereInput): BatchPayload! + deleteManyUsers(where: UserWhereInput): BatchPayload! +} + +enum MutationType { + CREATED + UPDATED + DELETED +} + +"""An object with an ID""" +interface Node { + """The id of the object.""" + id: ID! +} + +type Order implements Node { + id: ID! + items(where: OrderItemWhereInput, orderBy: OrderItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [OrderItem!] + total: Int! + user(where: UserWhereInput): User! + charge: String! +} + +"""A connection to a list of items.""" +type OrderConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [OrderEdge]! + aggregate: AggregateOrder! +} + +input OrderCreateInput { + total: Int! + charge: String! + items: OrderItemCreateManyInput + user: UserCreateOneInput! +} + +"""An edge in a connection.""" +type OrderEdge { + """The item at the end of the edge.""" + node: Order! + + """A cursor for use in pagination.""" + cursor: String! +} + +type OrderItem implements Node { + id: ID! + title: String! + description: String! + image: String! + largeImage: String! + price: Int! + quantity: Int! + user(where: UserWhereInput): User +} + +"""A connection to a list of items.""" +type OrderItemConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [OrderItemEdge]! + aggregate: AggregateOrderItem! +} + +input OrderItemCreateInput { + title: String! + description: String! + image: String! + largeImage: String! + price: Int! + quantity: Int + user: UserCreateOneInput +} + +input OrderItemCreateManyInput { + create: [OrderItemCreateInput!] + connect: [OrderItemWhereUniqueInput!] +} + +"""An edge in a connection.""" +type OrderItemEdge { + """The item at the end of the edge.""" + node: OrderItem! + + """A cursor for use in pagination.""" + cursor: String! +} + +enum OrderItemOrderByInput { + id_ASC + id_DESC + title_ASC + title_DESC + description_ASC + description_DESC + image_ASC + image_DESC + largeImage_ASC + largeImage_DESC + price_ASC + price_DESC + quantity_ASC + quantity_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type OrderItemPreviousValues { + id: ID! + title: String! + description: String! + image: String! + largeImage: String! + price: Int! + quantity: Int! +} + +type OrderItemSubscriptionPayload { + mutation: MutationType! + node: OrderItem + updatedFields: [String!] + previousValues: OrderItemPreviousValues +} + +input OrderItemSubscriptionWhereInput { + """Logical AND on all given filters.""" + AND: [OrderItemSubscriptionWhereInput!] + + """Logical OR on all given filters.""" + OR: [OrderItemSubscriptionWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [OrderItemSubscriptionWhereInput!] + + """ + The subscription event gets dispatched when it's listed in mutation_in + """ + mutation_in: [MutationType!] + + """ + The subscription event gets only dispatched when one of the updated fields names is included in this list + """ + updatedFields_contains: String + + """ + The subscription event gets only dispatched when all of the field names included in this list have been updated + """ + updatedFields_contains_every: [String!] + + """ + The subscription event gets only dispatched when some of the field names included in this list have been updated + """ + updatedFields_contains_some: [String!] + node: OrderItemWhereInput +} + +input OrderItemUpdateDataInput { + title: String + description: String + image: String + largeImage: String + price: Int + quantity: Int + user: UserUpdateOneInput +} + +input OrderItemUpdateInput { + title: String + description: String + image: String + largeImage: String + price: Int + quantity: Int + user: UserUpdateOneInput +} + +input OrderItemUpdateManyInput { + create: [OrderItemCreateInput!] + connect: [OrderItemWhereUniqueInput!] + disconnect: [OrderItemWhereUniqueInput!] + delete: [OrderItemWhereUniqueInput!] + update: [OrderItemUpdateWithWhereUniqueNestedInput!] + upsert: [OrderItemUpsertWithWhereUniqueNestedInput!] +} + +input OrderItemUpdateWithWhereUniqueNestedInput { + where: OrderItemWhereUniqueInput! + data: OrderItemUpdateDataInput! +} + +input OrderItemUpsertWithWhereUniqueNestedInput { + where: OrderItemWhereUniqueInput! + update: OrderItemUpdateDataInput! + create: OrderItemCreateInput! +} + +input OrderItemWhereInput { + """Logical AND on all given filters.""" + AND: [OrderItemWhereInput!] + + """Logical OR on all given filters.""" + OR: [OrderItemWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [OrderItemWhereInput!] + id: ID + + """All values that are not equal to given value.""" + id_not: ID + + """All values that are contained in given list.""" + id_in: [ID!] + + """All values that are not contained in given list.""" + id_not_in: [ID!] + + """All values less than the given value.""" + id_lt: ID + + """All values less than or equal the given value.""" + id_lte: ID + + """All values greater than the given value.""" + id_gt: ID + + """All values greater than or equal the given value.""" + id_gte: ID + + """All values containing the given string.""" + id_contains: ID + + """All values not containing the given string.""" + id_not_contains: ID + + """All values starting with the given string.""" + id_starts_with: ID + + """All values not starting with the given string.""" + id_not_starts_with: ID + + """All values ending with the given string.""" + id_ends_with: ID + + """All values not ending with the given string.""" + id_not_ends_with: ID + title: String + + """All values that are not equal to given value.""" + title_not: String + + """All values that are contained in given list.""" + title_in: [String!] + + """All values that are not contained in given list.""" + title_not_in: [String!] + + """All values less than the given value.""" + title_lt: String + + """All values less than or equal the given value.""" + title_lte: String + + """All values greater than the given value.""" + title_gt: String + + """All values greater than or equal the given value.""" + title_gte: String + + """All values containing the given string.""" + title_contains: String + + """All values not containing the given string.""" + title_not_contains: String + + """All values starting with the given string.""" + title_starts_with: String + + """All values not starting with the given string.""" + title_not_starts_with: String + + """All values ending with the given string.""" + title_ends_with: String + + """All values not ending with the given string.""" + title_not_ends_with: String + description: String + + """All values that are not equal to given value.""" + description_not: String + + """All values that are contained in given list.""" + description_in: [String!] + + """All values that are not contained in given list.""" + description_not_in: [String!] + + """All values less than the given value.""" + description_lt: String + + """All values less than or equal the given value.""" + description_lte: String + + """All values greater than the given value.""" + description_gt: String + + """All values greater than or equal the given value.""" + description_gte: String + + """All values containing the given string.""" + description_contains: String + + """All values not containing the given string.""" + description_not_contains: String + + """All values starting with the given string.""" + description_starts_with: String + + """All values not starting with the given string.""" + description_not_starts_with: String + + """All values ending with the given string.""" + description_ends_with: String + + """All values not ending with the given string.""" + description_not_ends_with: String + image: String + + """All values that are not equal to given value.""" + image_not: String + + """All values that are contained in given list.""" + image_in: [String!] + + """All values that are not contained in given list.""" + image_not_in: [String!] + + """All values less than the given value.""" + image_lt: String + + """All values less than or equal the given value.""" + image_lte: String + + """All values greater than the given value.""" + image_gt: String + + """All values greater than or equal the given value.""" + image_gte: String + + """All values containing the given string.""" + image_contains: String + + """All values not containing the given string.""" + image_not_contains: String + + """All values starting with the given string.""" + image_starts_with: String + + """All values not starting with the given string.""" + image_not_starts_with: String + + """All values ending with the given string.""" + image_ends_with: String + + """All values not ending with the given string.""" + image_not_ends_with: String + largeImage: String + + """All values that are not equal to given value.""" + largeImage_not: String + + """All values that are contained in given list.""" + largeImage_in: [String!] + + """All values that are not contained in given list.""" + largeImage_not_in: [String!] + + """All values less than the given value.""" + largeImage_lt: String + + """All values less than or equal the given value.""" + largeImage_lte: String + + """All values greater than the given value.""" + largeImage_gt: String + + """All values greater than or equal the given value.""" + largeImage_gte: String + + """All values containing the given string.""" + largeImage_contains: String + + """All values not containing the given string.""" + largeImage_not_contains: String + + """All values starting with the given string.""" + largeImage_starts_with: String + + """All values not starting with the given string.""" + largeImage_not_starts_with: String + + """All values ending with the given string.""" + largeImage_ends_with: String + + """All values not ending with the given string.""" + largeImage_not_ends_with: String + price: Int + + """All values that are not equal to given value.""" + price_not: Int + + """All values that are contained in given list.""" + price_in: [Int!] + + """All values that are not contained in given list.""" + price_not_in: [Int!] + + """All values less than the given value.""" + price_lt: Int + + """All values less than or equal the given value.""" + price_lte: Int + + """All values greater than the given value.""" + price_gt: Int + + """All values greater than or equal the given value.""" + price_gte: Int + quantity: Int + + """All values that are not equal to given value.""" + quantity_not: Int + + """All values that are contained in given list.""" + quantity_in: [Int!] + + """All values that are not contained in given list.""" + quantity_not_in: [Int!] + + """All values less than the given value.""" + quantity_lt: Int + + """All values less than or equal the given value.""" + quantity_lte: Int + + """All values greater than the given value.""" + quantity_gt: Int + + """All values greater than or equal the given value.""" + quantity_gte: Int + user: UserWhereInput +} + +input OrderItemWhereUniqueInput { + id: ID +} + +enum OrderOrderByInput { + id_ASC + id_DESC + total_ASC + total_DESC + charge_ASC + charge_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type OrderPreviousValues { + id: ID! + total: Int! + charge: String! +} + +type OrderSubscriptionPayload { + mutation: MutationType! + node: Order + updatedFields: [String!] + previousValues: OrderPreviousValues +} + +input OrderSubscriptionWhereInput { + """Logical AND on all given filters.""" + AND: [OrderSubscriptionWhereInput!] + + """Logical OR on all given filters.""" + OR: [OrderSubscriptionWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [OrderSubscriptionWhereInput!] + + """ + The subscription event gets dispatched when it's listed in mutation_in + """ + mutation_in: [MutationType!] + + """ + The subscription event gets only dispatched when one of the updated fields names is included in this list + """ + updatedFields_contains: String + + """ + The subscription event gets only dispatched when all of the field names included in this list have been updated + """ + updatedFields_contains_every: [String!] + + """ + The subscription event gets only dispatched when some of the field names included in this list have been updated + """ + updatedFields_contains_some: [String!] + node: OrderWhereInput +} + +input OrderUpdateInput { + total: Int + charge: String + items: OrderItemUpdateManyInput + user: UserUpdateOneInput +} + +input OrderWhereInput { + """Logical AND on all given filters.""" + AND: [OrderWhereInput!] + + """Logical OR on all given filters.""" + OR: [OrderWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [OrderWhereInput!] + id: ID + + """All values that are not equal to given value.""" + id_not: ID + + """All values that are contained in given list.""" + id_in: [ID!] + + """All values that are not contained in given list.""" + id_not_in: [ID!] + + """All values less than the given value.""" + id_lt: ID + + """All values less than or equal the given value.""" + id_lte: ID + + """All values greater than the given value.""" + id_gt: ID + + """All values greater than or equal the given value.""" + id_gte: ID + + """All values containing the given string.""" + id_contains: ID + + """All values not containing the given string.""" + id_not_contains: ID + + """All values starting with the given string.""" + id_starts_with: ID + + """All values not starting with the given string.""" + id_not_starts_with: ID + + """All values ending with the given string.""" + id_ends_with: ID + + """All values not ending with the given string.""" + id_not_ends_with: ID + total: Int + + """All values that are not equal to given value.""" + total_not: Int + + """All values that are contained in given list.""" + total_in: [Int!] + + """All values that are not contained in given list.""" + total_not_in: [Int!] + + """All values less than the given value.""" + total_lt: Int + + """All values less than or equal the given value.""" + total_lte: Int + + """All values greater than the given value.""" + total_gt: Int + + """All values greater than or equal the given value.""" + total_gte: Int + charge: String + + """All values that are not equal to given value.""" + charge_not: String + + """All values that are contained in given list.""" + charge_in: [String!] + + """All values that are not contained in given list.""" + charge_not_in: [String!] + + """All values less than the given value.""" + charge_lt: String + + """All values less than or equal the given value.""" + charge_lte: String + + """All values greater than the given value.""" + charge_gt: String + + """All values greater than or equal the given value.""" + charge_gte: String + + """All values containing the given string.""" + charge_contains: String + + """All values not containing the given string.""" + charge_not_contains: String + + """All values starting with the given string.""" + charge_starts_with: String + + """All values not starting with the given string.""" + charge_not_starts_with: String + + """All values ending with the given string.""" + charge_ends_with: String + + """All values not ending with the given string.""" + charge_not_ends_with: String + items_every: OrderItemWhereInput + items_some: OrderItemWhereInput + items_none: OrderItemWhereInput + user: UserWhereInput +} + +input OrderWhereUniqueInput { + id: ID +} + +"""Information about pagination in a connection.""" +type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: String + + """When paginating forwards, the cursor to continue.""" + endCursor: String +} + +enum Permission { + ADMIN + USER + ITEMCREATE + ITEMUPDATE + ITEMDELETE + PERMISSIONUPDATE +} + +type Query { + cartItems(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem]! + orders(where: OrderWhereInput, orderBy: OrderOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Order]! + items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Item]! + orderItems(where: OrderItemWhereInput, orderBy: OrderItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [OrderItem]! + users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]! + cartItem(where: CartItemWhereUniqueInput!): CartItem + order(where: OrderWhereUniqueInput!): Order + item(where: ItemWhereUniqueInput!): Item + orderItem(where: OrderItemWhereUniqueInput!): OrderItem + user(where: UserWhereUniqueInput!): User + cartItemsConnection(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): CartItemConnection! + ordersConnection(where: OrderWhereInput, orderBy: OrderOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): OrderConnection! + itemsConnection(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ItemConnection! + orderItemsConnection(where: OrderItemWhereInput, orderBy: OrderItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): OrderItemConnection! + usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection! + + """Fetches an object given its ID""" + node( + """The ID of an object""" + id: ID! + ): Node +} + +type Subscription { + cartItem(where: CartItemSubscriptionWhereInput): CartItemSubscriptionPayload + order(where: OrderSubscriptionWhereInput): OrderSubscriptionPayload + item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload + orderItem(where: OrderItemSubscriptionWhereInput): OrderItemSubscriptionPayload + user(where: UserSubscriptionWhereInput): UserSubscriptionPayload +} + +type User implements Node { + id: ID! + name: String! + email: String! + password: String! + resetToken: String + resetTokenExpiry: String + permissions: [Permission!]! + cart(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem!] +} + +"""A connection to a list of items.""" +type UserConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [UserEdge]! + aggregate: AggregateUser! +} + +input UserCreateInput { + name: String! + email: String! + password: String! + resetToken: String + resetTokenExpiry: String + permissions: UserCreatepermissionsInput + cart: CartItemCreateManyWithoutUserInput +} + +input UserCreateOneInput { + create: UserCreateInput + connect: UserWhereUniqueInput +} + +input UserCreateOneWithoutCartInput { + create: UserCreateWithoutCartInput + connect: UserWhereUniqueInput +} + +input UserCreatepermissionsInput { + set: [Permission!] +} + +input UserCreateWithoutCartInput { + name: String! + email: String! + password: String! + resetToken: String + resetTokenExpiry: String + permissions: UserCreatepermissionsInput +} + +"""An edge in a connection.""" +type UserEdge { + """The item at the end of the edge.""" + node: User! + + """A cursor for use in pagination.""" + cursor: String! +} + +enum UserOrderByInput { + id_ASC + id_DESC + name_ASC + name_DESC + email_ASC + email_DESC + password_ASC + password_DESC + resetToken_ASC + resetToken_DESC + resetTokenExpiry_ASC + resetTokenExpiry_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type UserPreviousValues { + id: ID! + name: String! + email: String! + password: String! + resetToken: String + resetTokenExpiry: String + permissions: [Permission!]! +} + +type UserSubscriptionPayload { + mutation: MutationType! + node: User + updatedFields: [String!] + previousValues: UserPreviousValues +} + +input UserSubscriptionWhereInput { + """Logical AND on all given filters.""" + AND: [UserSubscriptionWhereInput!] + + """Logical OR on all given filters.""" + OR: [UserSubscriptionWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [UserSubscriptionWhereInput!] + + """ + The subscription event gets dispatched when it's listed in mutation_in + """ + mutation_in: [MutationType!] + + """ + The subscription event gets only dispatched when one of the updated fields names is included in this list + """ + updatedFields_contains: String + + """ + The subscription event gets only dispatched when all of the field names included in this list have been updated + """ + updatedFields_contains_every: [String!] + + """ + The subscription event gets only dispatched when some of the field names included in this list have been updated + """ + updatedFields_contains_some: [String!] + node: UserWhereInput +} + +input UserUpdateDataInput { + name: String + email: String + password: String + resetToken: String + resetTokenExpiry: String + permissions: UserUpdatepermissionsInput + cart: CartItemUpdateManyWithoutUserInput +} + +input UserUpdateInput { + name: String + email: String + password: String + resetToken: String + resetTokenExpiry: String + permissions: UserUpdatepermissionsInput + cart: CartItemUpdateManyWithoutUserInput +} + +input UserUpdateOneInput { + create: UserCreateInput + connect: UserWhereUniqueInput + delete: Boolean + update: UserUpdateDataInput + upsert: UserUpsertNestedInput +} + +input UserUpdateOneWithoutCartInput { + create: UserCreateWithoutCartInput + connect: UserWhereUniqueInput + delete: Boolean + update: UserUpdateWithoutCartDataInput + upsert: UserUpsertWithoutCartInput +} + +input UserUpdatepermissionsInput { + set: [Permission!] +} + +input UserUpdateWithoutCartDataInput { + name: String + email: String + password: String + resetToken: String + resetTokenExpiry: String + permissions: UserUpdatepermissionsInput +} + +input UserUpsertNestedInput { + update: UserUpdateDataInput! + create: UserCreateInput! +} + +input UserUpsertWithoutCartInput { + update: UserUpdateWithoutCartDataInput! + create: UserCreateWithoutCartInput! +} + +input UserWhereInput { + """Logical AND on all given filters.""" + AND: [UserWhereInput!] + + """Logical OR on all given filters.""" + OR: [UserWhereInput!] + + """Logical NOT on all given filters combined by AND.""" + NOT: [UserWhereInput!] + id: ID + + """All values that are not equal to given value.""" + id_not: ID + + """All values that are contained in given list.""" + id_in: [ID!] + + """All values that are not contained in given list.""" + id_not_in: [ID!] + + """All values less than the given value.""" + id_lt: ID + + """All values less than or equal the given value.""" + id_lte: ID + + """All values greater than the given value.""" + id_gt: ID + + """All values greater than or equal the given value.""" + id_gte: ID + + """All values containing the given string.""" + id_contains: ID + + """All values not containing the given string.""" + id_not_contains: ID + + """All values starting with the given string.""" + id_starts_with: ID + + """All values not starting with the given string.""" + id_not_starts_with: ID + + """All values ending with the given string.""" + id_ends_with: ID + + """All values not ending with the given string.""" + id_not_ends_with: ID + name: String + + """All values that are not equal to given value.""" + name_not: String + + """All values that are contained in given list.""" + name_in: [String!] + + """All values that are not contained in given list.""" + name_not_in: [String!] + + """All values less than the given value.""" + name_lt: String + + """All values less than or equal the given value.""" + name_lte: String + + """All values greater than the given value.""" + name_gt: String + + """All values greater than or equal the given value.""" + name_gte: String + + """All values containing the given string.""" + name_contains: String + + """All values not containing the given string.""" + name_not_contains: String + + """All values starting with the given string.""" + name_starts_with: String + + """All values not starting with the given string.""" + name_not_starts_with: String + + """All values ending with the given string.""" + name_ends_with: String + + """All values not ending with the given string.""" + name_not_ends_with: String + email: String + + """All values that are not equal to given value.""" + email_not: String + + """All values that are contained in given list.""" + email_in: [String!] + + """All values that are not contained in given list.""" + email_not_in: [String!] + + """All values less than the given value.""" + email_lt: String + + """All values less than or equal the given value.""" + email_lte: String + + """All values greater than the given value.""" + email_gt: String + + """All values greater than or equal the given value.""" + email_gte: String + + """All values containing the given string.""" + email_contains: String + + """All values not containing the given string.""" + email_not_contains: String + + """All values starting with the given string.""" + email_starts_with: String + + """All values not starting with the given string.""" + email_not_starts_with: String + + """All values ending with the given string.""" + email_ends_with: String + + """All values not ending with the given string.""" + email_not_ends_with: String + password: String + + """All values that are not equal to given value.""" + password_not: String + + """All values that are contained in given list.""" + password_in: [String!] + + """All values that are not contained in given list.""" + password_not_in: [String!] + + """All values less than the given value.""" + password_lt: String + + """All values less than or equal the given value.""" + password_lte: String + + """All values greater than the given value.""" + password_gt: String + + """All values greater than or equal the given value.""" + password_gte: String + + """All values containing the given string.""" + password_contains: String + + """All values not containing the given string.""" + password_not_contains: String + + """All values starting with the given string.""" + password_starts_with: String + + """All values not starting with the given string.""" + password_not_starts_with: String + + """All values ending with the given string.""" + password_ends_with: String + + """All values not ending with the given string.""" + password_not_ends_with: String + resetToken: String + + """All values that are not equal to given value.""" + resetToken_not: String + + """All values that are contained in given list.""" + resetToken_in: [String!] + + """All values that are not contained in given list.""" + resetToken_not_in: [String!] + + """All values less than the given value.""" + resetToken_lt: String + + """All values less than or equal the given value.""" + resetToken_lte: String + + """All values greater than the given value.""" + resetToken_gt: String + + """All values greater than or equal the given value.""" + resetToken_gte: String + + """All values containing the given string.""" + resetToken_contains: String + + """All values not containing the given string.""" + resetToken_not_contains: String + + """All values starting with the given string.""" + resetToken_starts_with: String + + """All values not starting with the given string.""" + resetToken_not_starts_with: String + + """All values ending with the given string.""" + resetToken_ends_with: String + + """All values not ending with the given string.""" + resetToken_not_ends_with: String + resetTokenExpiry: String + + """All values that are not equal to given value.""" + resetTokenExpiry_not: String + + """All values that are contained in given list.""" + resetTokenExpiry_in: [String!] + + """All values that are not contained in given list.""" + resetTokenExpiry_not_in: [String!] + + """All values less than the given value.""" + resetTokenExpiry_lt: String + + """All values less than or equal the given value.""" + resetTokenExpiry_lte: String + + """All values greater than the given value.""" + resetTokenExpiry_gt: String + + """All values greater than or equal the given value.""" + resetTokenExpiry_gte: String + + """All values containing the given string.""" + resetTokenExpiry_contains: String + + """All values not containing the given string.""" + resetTokenExpiry_not_contains: String + + """All values starting with the given string.""" + resetTokenExpiry_starts_with: String + + """All values not starting with the given string.""" + resetTokenExpiry_not_starts_with: String + + """All values ending with the given string.""" + resetTokenExpiry_ends_with: String + + """All values not ending with the given string.""" + resetTokenExpiry_not_ends_with: String + cart_every: CartItemWhereInput + cart_some: CartItemWhereInput + cart_none: CartItemWhereInput +} + +input UserWhereUniqueInput { + id: ID + email: String +} diff --git a/stepped-solutions/51/backend/src/resolvers/Mutation.js b/stepped-solutions/51/backend/src/resolvers/Mutation.js new file mode 100755 index 0000000..5a75caf --- /dev/null +++ b/stepped-solutions/51/backend/src/resolvers/Mutation.js @@ -0,0 +1,312 @@ +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 } + }}` + ); + // 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 + // 5. create the Order + // 6. Clean up - clear the users cart, delete cartItems + // 7. Return the Order to the client + }, +}; + +module.exports = Mutations; diff --git a/stepped-solutions/51/backend/src/schema.graphql b/stepped-solutions/51/backend/src/schema.graphql new file mode 100755 index 0000000..8b14185 --- /dev/null +++ b/stepped-solutions/51/backend/src/schema.graphql @@ -0,0 +1,36 @@ +# import * from './generated/prisma.graphql' + +type SuccessMessage { + message: String +} + +type Mutation { + createItem(title: String, description: String, price: Int, image: String, largeImage: String): Item! + updateItem(id: ID!, title: String, description: String, price: Int): Item! + deleteItem(id: ID!): Item + signup(email: String!, password: String!, name: String!): User! + signin(email: String!, password: String!): User! + signout: SuccessMessage + requestReset(email: String!): SuccessMessage + resetPassword(resetToken: String!, password: String!, confirmPassword: String!): User! + updatePermissions(permissions: [Permission], userId: ID!): User + addToCart(id: ID!): CartItem + removeFromCart(id: ID!): CartItem + createOrder(token: String!): Order! +} + +type Query { + items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, first: Int): [Item]! + item(where: ItemWhereUniqueInput!): Item + itemsConnection(where: ItemWhereInput): ItemConnection! + me: User + users: [User]! +} + +type User{ + id: ID! + name: String! + email: String! + permissions: [Permission!]! + cart: [CartItem!]! +} diff --git a/stepped-solutions/51/frontend/components/TakeMyMoney.js b/stepped-solutions/51/frontend/components/TakeMyMoney.js new file mode 100755 index 0000000..be37df5 --- /dev/null +++ b/stepped-solutions/51/frontend/components/TakeMyMoney.js @@ -0,0 +1,72 @@ +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 = (res, createOrder) => { + console.log('On Token Called!'); + console.log(res.id); + // manually call the mutation once we have the stripe token + createOrder({ + variables: { + token: res.id, + }, + }).catch(err => { + alert(err.message); + }); + }; + 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[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; |
