From cd0b07b3adb36fb5ec098f9e511ab45d774fee7b Mon Sep 17 00:00:00 2001 From: Wes Bos Date: Tue, 30 Jan 2018 12:41:01 -0500 Subject: Move to Prisma Backend --- .babelrc | 9 - .env | 1 - README.md | 28 - backend/.gitignore | 7 + backend/.graphqlconfig.yml | 10 + backend/README.md | 1 + backend/database/datamodel.graphql | 26 + backend/database/prisma.yml | 21 + backend/database/seed.graphql | 24 + backend/package.json | 22 + backend/src/generated/prisma.graphql | 615 + backend/src/index.js | 33 + backend/src/resolvers/AuthPayload.js | 7 + backend/src/resolvers/Mutation.js | 104 + backend/src/resolvers/Query.js | 31 + backend/src/schema.graphql | 36 + backend/src/utils.js | 23 + components/AddToCart.js | 100 - components/Cart.js | 46 - components/CartList.js | 114 - components/ChaChing.js | 56 - components/Count.js | 37 - components/CreateItem.js | 128 - components/ErrorMessage.js | 32 - components/Header.js | 42 - components/Item.js | 61 - components/Items.js | 97 - components/LoginAuth0.js | 76 - components/Meta.js | 24 - components/Nav.js | 46 - components/OrderList.js | 52 - components/Page.js | 24 - components/Pagination.js | 43 - components/Search.js | 68 - components/Signup.js | 94 - components/SingleItem.js | 36 - components/TakeMyMoney.js | 43 - components/UpdateItem.js | 75 - config.js | 5 - enhancers.js | 18 - frontend/.babelrc | 9 + frontend/.env | 1 + frontend/README.md | 28 + frontend/components/AddToCart.js | 100 + frontend/components/Cart.js | 46 + frontend/components/CartList.js | 114 + frontend/components/ChaChing.js | 56 + frontend/components/Count.js | 37 + frontend/components/CreateItem.js | 128 + frontend/components/ErrorMessage.js | 32 + frontend/components/Header.js | 42 + frontend/components/Item.js | 61 + frontend/components/Items.js | 97 + frontend/components/LoginAuth0.js | 76 + frontend/components/Meta.js | 24 + frontend/components/Nav.js | 46 + frontend/components/OrderList.js | 52 + frontend/components/Page.js | 24 + frontend/components/Pagination.js | 43 + frontend/components/Search.js | 68 + frontend/components/Signup.js | 94 + frontend/components/SingleItem.js | 36 + frontend/components/TakeMyMoney.js | 43 + frontend/components/UpdateItem.js | 75 + frontend/config.js | 5 + frontend/enhancers.js | 18 + frontend/lib/formatMoney.js | 3 + frontend/lib/image.js | 6 + frontend/lib/initApollo.js | 62 + frontend/lib/withData.js | 76 + frontend/notes.md | 68 + frontend/package-lock.json | 11493 +++++++++++++++++++ frontend/package.json | 50 + frontend/pages/add.js | 16 + frontend/pages/admin/update.js | 16 + frontend/pages/buy.js | 16 + frontend/pages/cart.js | 21 + frontend/pages/index.js | 21 + frontend/pages/item.js | 11 + frontend/pages/items.js | 2 + frontend/pages/orders.js | 21 + frontend/pages/signup.js | 21 + frontend/queries/fragments.js | 16 + frontend/queries/index.js | 196 + frontend/queries/mutations.js | 0 frontend/routes.js | 6 + frontend/server.js | 22 + frontend/src/auth0/auth0Authentication.graphql | 8 + frontend/src/auth0/auth0Authentication.js | 121 + frontend/static/nprogress.css | 72 + graphcool-old/.graphcoolrc | 3 - graphcool-old/graphcool.yml | 56 - graphcool-old/package-lock.json | 790 -- graphcool-old/package.json | 18 - graphcool-old/project.graphcool | 52 - .../src/auth0/auth0Authentication.graphql | 8 - graphcool-old/src/auth0/auth0Authentication.js | 100 - graphcool-old/src/createCharge.js | 52 - graphcool-old/src/createCharge.test.js | 23 - graphcool-old/src/getPrice.js | 26 - graphcool-old/src/hello.graphql | 7 - graphcool-old/src/hello.js | 11 - graphcool-old/src/index.js | 2 - graphcool-old/types.graphql | 56 - graphcool/.graphcoolrc | 4 - graphcool/graphcool.yml | 44 - graphcool/package-lock.json | 39 - graphcool/package.json | 8 - graphcool/src/createCharge.js | 52 - graphcool/src/createCharge.test.js | 23 - graphcool/src/getPrice.js | 26 - graphcool/src/hello.graphql | 7 - graphcool/src/hello.js | 11 - graphcool/src/index.js | 2 - graphcool/types.graphql | 53 - lib/formatMoney.js | 3 - lib/image.js | 6 - lib/initApollo.js | 62 - lib/withData.js | 76 - notes.md | 68 - package-lock.json | 11493 ------------------- package.json | 50 - pages/add.js | 16 - pages/admin/update.js | 16 - pages/buy.js | 16 - pages/cart.js | 21 - pages/index.js | 21 - pages/item.js | 11 - pages/items.js | 2 - pages/orders.js | 21 - pages/signup.js | 21 - queries/fragments.js | 16 - queries/index.js | 196 - queries/mutations.js | 0 routes.js | 6 - server.js | 22 - src/auth0/auth0Authentication.graphql | 8 - src/auth0/auth0Authentication.js | 121 - static/nprogress.css | 72 - 139 files changed, 14659 insertions(+), 15172 deletions(-) delete mode 100644 .babelrc delete mode 100644 .env delete mode 100644 README.md create mode 100644 backend/.gitignore create mode 100644 backend/.graphqlconfig.yml create mode 100644 backend/README.md create mode 100644 backend/database/datamodel.graphql create mode 100644 backend/database/prisma.yml create mode 100644 backend/database/seed.graphql create mode 100644 backend/package.json create mode 100644 backend/src/generated/prisma.graphql create mode 100644 backend/src/index.js create mode 100644 backend/src/resolvers/AuthPayload.js create mode 100644 backend/src/resolvers/Mutation.js create mode 100644 backend/src/resolvers/Query.js create mode 100644 backend/src/schema.graphql create mode 100644 backend/src/utils.js delete mode 100644 components/AddToCart.js delete mode 100644 components/Cart.js delete mode 100644 components/CartList.js delete mode 100644 components/ChaChing.js delete mode 100644 components/Count.js delete mode 100644 components/CreateItem.js delete mode 100644 components/ErrorMessage.js delete mode 100644 components/Header.js delete mode 100644 components/Item.js delete mode 100644 components/Items.js delete mode 100644 components/LoginAuth0.js delete mode 100644 components/Meta.js delete mode 100644 components/Nav.js delete mode 100644 components/OrderList.js delete mode 100644 components/Page.js delete mode 100644 components/Pagination.js delete mode 100644 components/Search.js delete mode 100644 components/Signup.js delete mode 100644 components/SingleItem.js delete mode 100644 components/TakeMyMoney.js delete mode 100644 components/UpdateItem.js delete mode 100644 config.js delete mode 100644 enhancers.js create mode 100644 frontend/.babelrc create mode 100644 frontend/.env create mode 100644 frontend/README.md create mode 100644 frontend/components/AddToCart.js create mode 100644 frontend/components/Cart.js create mode 100644 frontend/components/CartList.js create mode 100644 frontend/components/ChaChing.js create mode 100644 frontend/components/Count.js create mode 100644 frontend/components/CreateItem.js create mode 100644 frontend/components/ErrorMessage.js create mode 100644 frontend/components/Header.js create mode 100644 frontend/components/Item.js create mode 100644 frontend/components/Items.js create mode 100644 frontend/components/LoginAuth0.js create mode 100644 frontend/components/Meta.js create mode 100644 frontend/components/Nav.js create mode 100644 frontend/components/OrderList.js create mode 100644 frontend/components/Page.js create mode 100644 frontend/components/Pagination.js create mode 100644 frontend/components/Search.js create mode 100644 frontend/components/Signup.js create mode 100644 frontend/components/SingleItem.js create mode 100644 frontend/components/TakeMyMoney.js create mode 100644 frontend/components/UpdateItem.js create mode 100644 frontend/config.js create mode 100644 frontend/enhancers.js create mode 100644 frontend/lib/formatMoney.js create mode 100644 frontend/lib/image.js create mode 100644 frontend/lib/initApollo.js create mode 100644 frontend/lib/withData.js create mode 100644 frontend/notes.md create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/pages/add.js create mode 100644 frontend/pages/admin/update.js create mode 100644 frontend/pages/buy.js create mode 100644 frontend/pages/cart.js create mode 100644 frontend/pages/index.js create mode 100644 frontend/pages/item.js create mode 100644 frontend/pages/items.js create mode 100644 frontend/pages/orders.js create mode 100644 frontend/pages/signup.js create mode 100644 frontend/queries/fragments.js create mode 100644 frontend/queries/index.js create mode 100644 frontend/queries/mutations.js create mode 100644 frontend/routes.js create mode 100644 frontend/server.js create mode 100644 frontend/src/auth0/auth0Authentication.graphql create mode 100644 frontend/src/auth0/auth0Authentication.js create mode 100644 frontend/static/nprogress.css delete mode 100644 graphcool-old/.graphcoolrc delete mode 100644 graphcool-old/graphcool.yml delete mode 100644 graphcool-old/package-lock.json delete mode 100644 graphcool-old/package.json delete mode 100644 graphcool-old/project.graphcool delete mode 100644 graphcool-old/src/auth0/auth0Authentication.graphql delete mode 100644 graphcool-old/src/auth0/auth0Authentication.js delete mode 100644 graphcool-old/src/createCharge.js delete mode 100644 graphcool-old/src/createCharge.test.js delete mode 100644 graphcool-old/src/getPrice.js delete mode 100644 graphcool-old/src/hello.graphql delete mode 100644 graphcool-old/src/hello.js delete mode 100644 graphcool-old/src/index.js delete mode 100644 graphcool-old/types.graphql delete mode 100644 graphcool/.graphcoolrc delete mode 100644 graphcool/graphcool.yml delete mode 100644 graphcool/package-lock.json delete mode 100644 graphcool/package.json delete mode 100644 graphcool/src/createCharge.js delete mode 100644 graphcool/src/createCharge.test.js delete mode 100644 graphcool/src/getPrice.js delete mode 100644 graphcool/src/hello.graphql delete mode 100644 graphcool/src/hello.js delete mode 100644 graphcool/src/index.js delete mode 100644 graphcool/types.graphql delete mode 100644 lib/formatMoney.js delete mode 100644 lib/image.js delete mode 100644 lib/initApollo.js delete mode 100644 lib/withData.js delete mode 100644 notes.md delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 pages/add.js delete mode 100644 pages/admin/update.js delete mode 100644 pages/buy.js delete mode 100644 pages/cart.js delete mode 100644 pages/index.js delete mode 100644 pages/item.js delete mode 100644 pages/items.js delete mode 100644 pages/orders.js delete mode 100644 pages/signup.js delete mode 100644 queries/fragments.js delete mode 100644 queries/index.js delete mode 100644 queries/mutations.js delete mode 100644 routes.js delete mode 100644 server.js delete mode 100644 src/auth0/auth0Authentication.graphql delete mode 100644 src/auth0/auth0Authentication.js delete mode 100644 static/nprogress.css diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d9c3f91..0000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": ["next/babel"], - "plugins": [ - [ - "styled-components", - { "ssr": true, "displayName": true, "preprocess": false } - ] - ] -} diff --git a/.env b/.env deleted file mode 100644 index ac968d9..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -COOL=true diff --git a/README.md b/README.md deleted file mode 100644 index c254cc0..0000000 --- a/README.md +++ /dev/null @@ -1,28 +0,0 @@ -## Gotchas - -https://github.com/graphcool/feature-requests/issues/145#issuecomment-318968578 - -Clients -> Advanced Settings -> OAuth -> JsonWebToken Signature Algorithm: HS256 - - - -# Advanced-React - -## Testing - -Test Coverage --coverage -__tests__ -what.test.js -__mock__ -Watching --watch -__snapshots__ - -## Marketing - -I love this look and feel: http://wes.io/lxQe -https://www.youtube.com/watch?v=Y0ePh9OxSWo - - -## Questions -* Apollo Link State will be good for local variables -* Server Side Auth \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..0f79d10 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,7 @@ +dist +package-lock.json +node_modules +.idea +.vscode +*.log +.env* diff --git a/backend/.graphqlconfig.yml b/backend/.graphqlconfig.yml new file mode 100644 index 0000000..b495937 --- /dev/null +++ b/backend/.graphqlconfig.yml @@ -0,0 +1,10 @@ +projects: + app: + schemaPath: "src/schema.graphql" + extensions: + endpoints: + default: "http://localhost:4000" + database: + schemaPath: "src/generated/prisma.graphql" + extensions: + prisma: database/prisma.yml \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..4acc044 --- /dev/null +++ b/backend/README.md @@ -0,0 +1 @@ +What are all these files?! diff --git a/backend/database/datamodel.graphql b/backend/database/datamodel.graphql new file mode 100644 index 0000000..605e242 --- /dev/null +++ b/backend/database/datamodel.graphql @@ -0,0 +1,26 @@ +type Post { + id: ID! @unique + createdAt: DateTime! + updatedAt: DateTime! + isPublished: Boolean! @default(value: "false") + title: String! + text: String! + author: User! +} + +type User { + id: ID! @unique + email: String! @unique + password: String! + name: String! + posts: [Post!]! + website: String + age: Int +} + +type Item { + id: ID! @unique + title: String! + description: String! + price: Int! +} diff --git a/backend/database/prisma.yml b/backend/database/prisma.yml new file mode 100644 index 0000000..6570be2 --- /dev/null +++ b/backend/database/prisma.yml @@ -0,0 +1,21 @@ +# the name for the service (will be part of the service's HTTP endpoint) +service: my-own + +# the cluster and stage the service is deployed to +stage: ${env:PRISMA_STAGE} + +# to disable authentication: +# disableAuth: true +secret: ${env:PRISMA_SECRET} + +# the file path pointing to your data model +datamodel: datamodel.graphql + +# uncomment the following two lines to seed your service with initial data +# seed: +# import: seed.graphql + +cluster: ${env:PRISMA_CLUSTER} + + +disableAuth: true \ No newline at end of file diff --git a/backend/database/seed.graphql b/backend/database/seed.graphql new file mode 100644 index 0000000..5779328 --- /dev/null +++ b/backend/database/seed.graphql @@ -0,0 +1,24 @@ +mutation { + createUser(data: { + email: "developer@example.com" + password: "$2a$10$hACwQ5/HQI6FhbIISOUVeusy3sKyUDhSq36fF5d/54aAdiygJPFzm" # plaintext password: "nooneknows" + name: "Sarah" + posts: { + create: [{ + title: "Hello World" + text: "This is my first blog post ever!" + isPublished: true + }, { + title: "My Second Post" + text: "My first post was good, but this one is better!" + isPublished: true + }, { + title: "Solving World Hunger" + text: "This is a draft..." + isPublished: false + }] + } + }) { + id + } +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..ca26206 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,22 @@ +{ + "name": "my-own", + "scripts": { + "start": "nodemon -e js,graphql -x node -r dotenv/config src/index.js", + "debug": "nodemon -e js,graphql -x node --inspect -r dotenv/config src/index.js", + "playground": "graphql playground", + "dev": "npm-run-all --parallel start playground" + }, + "dependencies": { + "bcryptjs": "2.4.3", + "graphql-yoga": "1.2.1", + "jsonwebtoken": "8.1.1", + "prisma-binding": "1.4.0" + }, + "devDependencies": { + "dotenv": "4.0.0", + "graphql-cli": "2.13.0", + "nodemon": "1.14.11", + "npm-run-all": "4.1.2", + "prisma": "1.0.11" + } +} diff --git a/backend/src/generated/prisma.graphql b/backend/src/generated/prisma.graphql new file mode 100644 index 0000000..50aaa97 --- /dev/null +++ b/backend/src/generated/prisma.graphql @@ -0,0 +1,615 @@ +# THIS FILE HAS BEEN AUTO-GENERATED BY "PRISMA DEPLOY" +# DO NOT EDIT THIS FILE DIRECTLY + +# +# Model Types +# + +type Item implements Node { + id: ID! + title: String! + description: String! + price: Int! +} + +type Post implements Node { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + isPublished: Boolean! + title: String! + text: String! + author(where: UserWhereInput): User! +} + +type User implements Node { + id: ID! + email: String! + password: String! + name: String! + posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post!] + website: String + age: Int +} + + +# +# Other Types +# + +type AggregateItem { + count: Int! +} + +type AggregatePost { + count: Int! +} + +type AggregateUser { + count: Int! +} + +type BatchPayload { + count: Long! +} + +scalar DateTime + +type ItemConnection { + pageInfo: PageInfo! + edges: [ItemEdge]! + aggregate: AggregateItem! +} + +input ItemCreateInput { + title: String! + description: String! + price: Int! +} + +type ItemEdge { + node: Item! + cursor: String! +} + +enum ItemOrderByInput { + id_ASC + id_DESC + title_ASC + title_DESC + description_ASC + description_DESC + price_ASC + price_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type ItemPreviousValues { + id: ID! + title: String! + description: String! + price: Int! +} + +type ItemSubscriptionPayload { + mutation: MutationType! + node: Item + updatedFields: [String!] + previousValues: ItemPreviousValues +} + +input ItemSubscriptionWhereInput { + AND: [ItemSubscriptionWhereInput!] + OR: [ItemSubscriptionWhereInput!] + mutation_in: [MutationType!] + updatedFields_contains: String + updatedFields_contains_every: [String!] + updatedFields_contains_some: [String!] + node: ItemWhereInput +} + +input ItemUpdateInput { + title: String + description: String + price: Int +} + +input ItemWhereInput { + AND: [ItemWhereInput!] + OR: [ItemWhereInput!] + 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 + title: String + title_not: String + title_in: [String!] + title_not_in: [String!] + title_lt: String + title_lte: String + title_gt: String + title_gte: String + title_contains: String + title_not_contains: String + title_starts_with: String + title_not_starts_with: String + title_ends_with: String + title_not_ends_with: String + description: String + description_not: String + description_in: [String!] + description_not_in: [String!] + description_lt: String + description_lte: String + description_gt: String + description_gte: String + description_contains: String + description_not_contains: String + description_starts_with: String + description_not_starts_with: String + description_ends_with: String + description_not_ends_with: String + price: Int + price_not: Int + price_in: [Int!] + price_not_in: [Int!] + price_lt: Int + price_lte: Int + price_gt: Int + price_gte: Int +} + +input ItemWhereUniqueInput { + id: ID +} + +scalar Long + +type Mutation { + createPost(data: PostCreateInput!): Post! + createUser(data: UserCreateInput!): User! + createItem(data: ItemCreateInput!): Item! + updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post + updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User + updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item + deletePost(where: PostWhereUniqueInput!): Post + deleteUser(where: UserWhereUniqueInput!): User + deleteItem(where: ItemWhereUniqueInput!): Item + upsertPost(where: PostWhereUniqueInput!, create: PostCreateInput!, update: PostUpdateInput!): Post! + upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User! + upsertItem(where: ItemWhereUniqueInput!, create: ItemCreateInput!, update: ItemUpdateInput!): Item! + updateManyPosts(data: PostUpdateInput!, where: PostWhereInput!): BatchPayload! + updateManyUsers(data: UserUpdateInput!, where: UserWhereInput!): BatchPayload! + updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput!): BatchPayload! + deleteManyPosts(where: PostWhereInput!): BatchPayload! + deleteManyUsers(where: UserWhereInput!): BatchPayload! + deleteManyItems(where: ItemWhereInput!): BatchPayload! +} + +enum MutationType { + CREATED + UPDATED + DELETED +} + +interface Node { + id: ID! +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + endCursor: String +} + +type PostConnection { + pageInfo: PageInfo! + edges: [PostEdge]! + aggregate: AggregatePost! +} + +input PostCreateInput { + isPublished: Boolean + title: String! + text: String! + author: UserCreateOneWithoutPostsInput! +} + +input PostCreateManyWithoutAuthorInput { + create: [PostCreateWithoutAuthorInput!] + connect: [PostWhereUniqueInput!] +} + +input PostCreateWithoutAuthorInput { + isPublished: Boolean + title: String! + text: String! +} + +type PostEdge { + node: Post! + cursor: String! +} + +enum PostOrderByInput { + id_ASC + id_DESC + createdAt_ASC + createdAt_DESC + updatedAt_ASC + updatedAt_DESC + isPublished_ASC + isPublished_DESC + title_ASC + title_DESC + text_ASC + text_DESC +} + +type PostPreviousValues { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + isPublished: Boolean! + title: String! + text: String! +} + +type PostSubscriptionPayload { + mutation: MutationType! + node: Post + updatedFields: [String!] + previousValues: PostPreviousValues +} + +input PostSubscriptionWhereInput { + AND: [PostSubscriptionWhereInput!] + OR: [PostSubscriptionWhereInput!] + mutation_in: [MutationType!] + updatedFields_contains: String + updatedFields_contains_every: [String!] + updatedFields_contains_some: [String!] + node: PostWhereInput +} + +input PostUpdateInput { + isPublished: Boolean + title: String + text: String + author: UserUpdateOneWithoutPostsInput +} + +input PostUpdateManyWithoutAuthorInput { + create: [PostCreateWithoutAuthorInput!] + connect: [PostWhereUniqueInput!] + disconnect: [PostWhereUniqueInput!] + delete: [PostWhereUniqueInput!] + update: [PostUpdateWithoutAuthorInput!] + upsert: [PostUpsertWithoutAuthorInput!] +} + +input PostUpdateWithoutAuthorDataInput { + isPublished: Boolean + title: String + text: String +} + +input PostUpdateWithoutAuthorInput { + where: PostWhereUniqueInput! + data: PostUpdateWithoutAuthorDataInput! +} + +input PostUpsertWithoutAuthorInput { + where: PostWhereUniqueInput! + update: PostUpdateWithoutAuthorDataInput! + create: PostCreateWithoutAuthorInput! +} + +input PostWhereInput { + AND: [PostWhereInput!] + OR: [PostWhereInput!] + 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 + createdAt: DateTime + createdAt_not: DateTime + createdAt_in: [DateTime!] + createdAt_not_in: [DateTime!] + createdAt_lt: DateTime + createdAt_lte: DateTime + createdAt_gt: DateTime + createdAt_gte: DateTime + updatedAt: DateTime + updatedAt_not: DateTime + updatedAt_in: [DateTime!] + updatedAt_not_in: [DateTime!] + updatedAt_lt: DateTime + updatedAt_lte: DateTime + updatedAt_gt: DateTime + updatedAt_gte: DateTime + isPublished: Boolean + isPublished_not: Boolean + title: String + title_not: String + title_in: [String!] + title_not_in: [String!] + title_lt: String + title_lte: String + title_gt: String + title_gte: String + title_contains: String + title_not_contains: String + title_starts_with: String + title_not_starts_with: String + title_ends_with: String + title_not_ends_with: String + text: String + text_not: String + text_in: [String!] + text_not_in: [String!] + text_lt: String + text_lte: String + text_gt: String + text_gte: String + text_contains: String + text_not_contains: String + text_starts_with: String + text_not_starts_with: String + text_ends_with: String + text_not_ends_with: String + author: UserWhereInput +} + +input PostWhereUniqueInput { + id: ID +} + +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]! + items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Item]! + post(where: PostWhereUniqueInput!): Post + user(where: UserWhereUniqueInput!): User + 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! + itemsConnection(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ItemConnection! + node(id: ID!): Node +} + +type Subscription { + post(where: PostSubscriptionWhereInput): PostSubscriptionPayload + user(where: UserSubscriptionWhereInput): UserSubscriptionPayload + item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload +} + +type UserConnection { + pageInfo: PageInfo! + edges: [UserEdge]! + aggregate: AggregateUser! +} + +input UserCreateInput { + email: String! + password: String! + name: String! + website: String + age: Int + posts: PostCreateManyWithoutAuthorInput +} + +input UserCreateOneWithoutPostsInput { + create: UserCreateWithoutPostsInput + connect: UserWhereUniqueInput +} + +input UserCreateWithoutPostsInput { + email: String! + password: String! + name: String! + website: String + age: Int +} + +type UserEdge { + node: User! + cursor: String! +} + +enum UserOrderByInput { + id_ASC + id_DESC + email_ASC + email_DESC + password_ASC + password_DESC + name_ASC + name_DESC + website_ASC + website_DESC + age_ASC + age_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type UserPreviousValues { + id: ID! + email: String! + password: String! + name: String! + website: String + age: Int +} + +type UserSubscriptionPayload { + mutation: MutationType! + node: User + updatedFields: [String!] + previousValues: UserPreviousValues +} + +input UserSubscriptionWhereInput { + AND: [UserSubscriptionWhereInput!] + OR: [UserSubscriptionWhereInput!] + mutation_in: [MutationType!] + updatedFields_contains: String + updatedFields_contains_every: [String!] + updatedFields_contains_some: [String!] + node: UserWhereInput +} + +input UserUpdateInput { + email: String + password: String + name: String + website: String + age: Int + posts: PostUpdateManyWithoutAuthorInput +} + +input UserUpdateOneWithoutPostsInput { + create: UserCreateWithoutPostsInput + connect: UserWhereUniqueInput + disconnect: UserWhereUniqueInput + delete: UserWhereUniqueInput + update: UserUpdateWithoutPostsInput + upsert: UserUpsertWithoutPostsInput +} + +input UserUpdateWithoutPostsDataInput { + email: String + password: String + name: String + website: String + age: Int +} + +input UserUpdateWithoutPostsInput { + where: UserWhereUniqueInput! + data: UserUpdateWithoutPostsDataInput! +} + +input UserUpsertWithoutPostsInput { + where: UserWhereUniqueInput! + update: UserUpdateWithoutPostsDataInput! + create: UserCreateWithoutPostsInput! +} + +input UserWhereInput { + AND: [UserWhereInput!] + OR: [UserWhereInput!] + 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 + email: String + email_not: String + email_in: [String!] + email_not_in: [String!] + email_lt: String + email_lte: String + email_gt: String + email_gte: String + email_contains: String + email_not_contains: String + email_starts_with: String + email_not_starts_with: String + email_ends_with: String + email_not_ends_with: String + password: String + password_not: String + password_in: [String!] + password_not_in: [String!] + password_lt: String + password_lte: String + password_gt: String + password_gte: String + password_contains: String + password_not_contains: String + password_starts_with: String + password_not_starts_with: String + password_ends_with: String + password_not_ends_with: String + name: String + name_not: String + name_in: [String!] + name_not_in: [String!] + name_lt: String + name_lte: String + name_gt: String + name_gte: String + name_contains: String + name_not_contains: String + name_starts_with: String + name_not_starts_with: String + name_ends_with: String + name_not_ends_with: String + website: String + website_not: String + website_in: [String!] + website_not_in: [String!] + website_lt: String + website_lte: String + website_gt: String + website_gte: String + website_contains: String + website_not_contains: String + website_starts_with: String + website_not_starts_with: String + website_ends_with: String + website_not_ends_with: String + age: Int + age_not: Int + age_in: [Int!] + age_not_in: [Int!] + age_lt: Int + age_lte: Int + age_gt: Int + age_gte: Int + posts_every: PostWhereInput + posts_some: PostWhereInput + posts_none: PostWhereInput +} + +input UserWhereUniqueInput { + id: ID + email: String +} diff --git a/backend/src/index.js b/backend/src/index.js new file mode 100644 index 0000000..c73cb74 --- /dev/null +++ b/backend/src/index.js @@ -0,0 +1,33 @@ +const { GraphQLServer } = require('graphql-yoga'); +const { Prisma } = require('prisma-binding'); + +const Mutation = require('./resolvers/Mutation'); +const Query = require('./resolvers/Query'); +const AuthPayload = require('./resolvers/AuthPayload'); + +const server = new GraphQLServer({ + typeDefs: 'src/schema.graphql', + resolvers: { + // detail resolvers + Mutation, + Query, + AuthPayload, + }, + context: req => ({ + ...req, + db: new Prisma({ + typeDefs: 'src/generated/prisma.graphql', + endpoint: process.env.PRISMA_ENDPOINT, // the endpoint of the Prisma DB service (value is set in .env) + secret: process.env.PRISMA_SECRET, // taken from database/prisma.yml (value is set in .env) + debug: true, // log all GraphQL queries & mutations + }), + }), +}); + +// This is an example of custom express middlware +server.express.use((req, res, next) => { + console.log("Hi I'm middlware"); + next(); +}); + +server.start(() => console.log('Server is running on http://localhost:4000')); diff --git a/backend/src/resolvers/AuthPayload.js b/backend/src/resolvers/AuthPayload.js new file mode 100644 index 0000000..f5945b7 --- /dev/null +++ b/backend/src/resolvers/AuthPayload.js @@ -0,0 +1,7 @@ +const { Context } = require('../utils'); + +const AuthPayload = { + user: async ({ user: { id } }, args, ctx, info) => ctx.db.query.user({ where: { id } }, info), +}; + +module.exports = AuthPayload; diff --git a/backend/src/resolvers/Mutation.js b/backend/src/resolvers/Mutation.js new file mode 100644 index 0000000..2796cf8 --- /dev/null +++ b/backend/src/resolvers/Mutation.js @@ -0,0 +1,104 @@ +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const { forwardTo } = require('prisma-binding'); +const { getUserId, Context } = require('../utils'); + +const mutations = { + // Signup Mutations + async signup(parent, args, ctx, info) { + const password = await bcrypt.hash(args.password, 10); + const user = await ctx.db.mutation.createUser({ + data: { ...args, password }, + }); + + return { + token: jwt.sign({ userId: user.id }, process.env.APP_SECRET), + user, + }; + }, + + async login(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'); + } + + return { + token: jwt.sign({ userId: user.id }, process.env.APP_SECRET), + user, + }; + }, + + // Creation of Post Mutations + createItem: forwardTo('db'), + deleteItem: forwardTo('db'), + async createDraft(parent, { title, text }, ctx, info) { + // const userId = getUserId(ctx); + const userId = getUserId(ctx); + return ctx.db.mutation.createPost( + { + data: { + title, + text, + isPublished: false, + author: { + connect: { id: userId }, + }, + }, + }, + info + ); + }, + + async publish(parent, { id }, ctx, info) { + const userId = getUserId(ctx); + const postExists = await ctx.db.exists.Post({ + id, + author: { id: userId }, + }); + if (!postExists) { + throw new Error(`Post not found or you're not the author`); + } + + return ctx.db.mutation.updatePost( + { + where: { id }, + data: { isPublished: true }, + }, + info + ); + }, + + async deletePost(parent, { id }, ctx, info) { + const userId = getUserId(ctx); + const postExists = await ctx.db.exists.Post({ + id, + author: { id: userId }, + }); + if (!postExists) { + throw new Error(`Post not found or you're not the author`); + } + + return ctx.db.mutation.deletePost({ where: { id } }); + }, + + // Wes Added this brand new one! + async updatePost(parent, args, ctx, info) { + const updatedPost = await ctx.db.mutation.updatePost({ + where: { id: args.id }, + data: { + title: args.title, + text: args.text, + }, + }); + console.log(updatedPost); + return updatedPost; + }, +}; + +module.exports = mutations; diff --git a/backend/src/resolvers/Query.js b/backend/src/resolvers/Query.js new file mode 100644 index 0000000..9d9d142 --- /dev/null +++ b/backend/src/resolvers/Query.js @@ -0,0 +1,31 @@ +const { getUserId, Context } = require('../utils'); + +const Query = { + feed(parent, args, ctx, info) { + return ctx.db.query.posts({ where: { isPublished: true } }, info); + }, + + drafts(parent, args, ctx, info) { + const id = getUserId(ctx); + + const where = { + isPublished: false, + author: { + id, + }, + }; + + return ctx.db.query.posts({ where }, info); + }, + + post(parent, { id }, ctx, info) { + return ctx.db.query.post({ where: { id } }, info); + }, + + me(parent, args, ctx, info) { + const id = getUserId(ctx); + return ctx.db.query.user({ where: { id } }, info); + }, +}; + +module.exports = Query; diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql new file mode 100644 index 0000000..788d490 --- /dev/null +++ b/backend/src/schema.graphql @@ -0,0 +1,36 @@ +# import Post, Item, ItemCreateInput from './generated/prisma.graphql' + +type Query { + feed: [Post!]! + drafts: [Post!]! + post(id: ID!): Post! + me: User +} + +# import Mutation from './generated/prisma.graphql' + +type Mutation { + signup(email: String!, password: String!, name: String!): AuthPayload! + login(email: String!, password: String!): AuthPayload! + createDraft(title: String!, text: String!): Post! + publish(id: ID!): Post! + deletePost(id: ID!): Post! + updatePost(id: ID!, title: String, text: String): Post! + createItem(data: ItemCreateInput): Item! + deleteItem(id: ID!): Item! +} + +type AuthPayload { + token: String! + user: User! +} + +# THe user definition overrides +type User { + id: ID! + email: String! + website: String + name: String! + posts: [Post!]! + age: Int +} diff --git a/backend/src/utils.js b/backend/src/utils.js new file mode 100644 index 0000000..35587db --- /dev/null +++ b/backend/src/utils.js @@ -0,0 +1,23 @@ +const jwt = require('jsonwebtoken'); + +function getUserId(ctx) { + const Authorization = ctx.request.get('Authorization'); + if (Authorization) { + const token = Authorization.replace('Bearer ', ''); + const { userId } = jwt.verify(token, process.env.APP_SECRET); + return userId; + } + + throw new AuthError(); +} + +class AuthError extends Error { + constructor() { + super('Not authorized'); + } +} + +module.exports = { + getUserId, + AuthError, +}; diff --git a/components/AddToCart.js b/components/AddToCart.js deleted file mode 100644 index e46a1f5..0000000 --- a/components/AddToCart.js +++ /dev/null @@ -1,100 +0,0 @@ -import { Component } from 'react'; -import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries'; -import { removeFromCartEnhancer, userEnhancer } from '../enhancers'; -import { graphql, compose } from 'react-apollo'; -import Transition from 'react-transition-group/Transition'; -import makeImage from '../lib/image'; -import styled from 'styled-components'; -import PropTypes from 'prop-types'; - -const JumpImg = styled.img` - border: 0 solid black; - max-width: 100px; - transition: all 0.5s; - position: fixed; - left: ${props => props.x}px; - top: -100%; - &.jump-entered { - border-color: green; - transform-origin: 0 0; - transform: scale(0); - top: ${props => props.y}px; - left: ${props => props.x}px; - } - &.jump-entering { - border-color: yellow; - top: ${props => props.y}px; - left: ${props => props.x}px; - } - &.jump-exited { - border-color: red; - } - &.jump-exiting { - border-color: yellow; - } -`; - -class AddToCart extends Component { - static propTypes = { - currentUserQuery: PropTypes.object, - }; - - componentDidMount() { - this.props.currentUserQuery.refetch(); - } - - addToCart = async () => { - const res = await this.props.addToCart({ - variables: { - userId: this.props.currentUserQuery.user.id, - itemId: this.props.id, - }, - }); - this.props.currentUserQuery.refetch(); - console.log(res); - }; - - removeFromCart = async () => { - const res = await this.props.removeFromCart({ - variables: { - userId: this.props.currentUserQuery.user.id, - itemId: this.props.id, - }, - }); - this.props.currentUserQuery.refetch(); - console.log(res); - }; - - render() { - const user = this.props.currentUserQuery.user; - // TODO WTF - if (!user || this.props.singleItemQuery.loading || !this.props.singleItemQuery.Item) return

Loading...

; - const cartIds = user.cart.map(item => item.id); - const image = this.props.singleItemQuery.Item.image || {}; - const isInCart = cartIds.includes(this.props.id); - const { x, y } = document.querySelector('.cart').getBoundingClientRect(); - return ( -
- {isInCart ? ( - - ) : ( - - )} - - {status => } - -
- ); - } -} - -const createOrderEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart' }); -const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, { - name: 'singleItemQuery', - options: ({ id }) => ({ - variables: { - id, - }, - }), -}); -export default compose(userEnhancer, createOrderEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart); diff --git a/components/Cart.js b/components/Cart.js deleted file mode 100644 index 88b072c..0000000 --- a/components/Cart.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; -import styled from 'styled-components'; -import { CURRENT_USER_QUERY } from '../queries'; -import formatMoney from '../lib/formatMoney.js'; -import ChaChing from './ChaChing'; - -const CartStyles = styled.div` - background: white; - border-radius: 10px; - padding: 20px; - overflow: hidden; - display: flex; - align-items: center; -`; - -class Cart extends Component { - componentDidMount() { - // This fetches the new data, but doesn't populate the user via props - // this.props.currentUserQuery.refetch(); - // This fetches the new data, and populates the user via props - setTimeout(this.props.currentUserQuery.refetch, 1); - } - - render() { - // Check for loading state.. - const { loading, error } = this.props.currentUserQuery; - const { user } = this.props.currentUserQuery; - if (loading || error || !user) return

Cart Loading...

; - const total = user.cart.reduce((a, b) => a + b.price, 0); - - return ( - - There - {user.cart.length === 1 ? ' is ' : 'are '} - - {user.cart.length === 1 ? ' item ' : ' items '} - in your cart totaling - - - ); - } -} - -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -export default compose(userEnhancer)(Cart); diff --git a/components/CartList.js b/components/CartList.js deleted file mode 100644 index b54aa39..0000000 --- a/components/CartList.js +++ /dev/null @@ -1,114 +0,0 @@ -import { Component } from 'react'; -import withData from '../lib/withData'; -import Items from '../components/Items'; -import Signup from '../components/Signup'; -import LoginAuth0 from '../components/LoginAuth0'; -import Page from '../components/Page'; -import { USER_ORDERS_QUERY, GET_CART_STATE } from '../queries'; -import { graphql, compose } from 'react-apollo'; -import has from 'lodash.has'; -import get from 'lodash.get'; -import styled from 'styled-components'; -import formatMoney from '../lib/formatMoney'; -import makeImage from '../lib/image'; -import TakeMyMoney from './TakeMyMoney'; -import { removeFromCartEnhancer } from '../enhancers'; -import { CURRENT_USER_QUERY } from '../queries'; -import PropTypes from 'prop-types'; - -const CartStyles = styled.div` - padding: 20px; - position: relative; - background: white; - position: fixed; - height: 100%; - top: 0; - right: 0; - bottom: 0; - transform: translateX(100%); - transition: all 0.3s; - box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.2); - z-index: 5; - ${props => props.open && `transform: translateX(0);`}; - .toggleOpen { - position: absolute; - top: 0; - left: 0; - transform: translateX(-100%); - } -`; - -class CartList extends Component { - static contextTypes = { - client: PropTypes.object, - }; - - componentDidMount() { - setTimeout(this.props.currentUserQuery.refetch, 1); - // Set the cart state to be closed - this.context.client.writeQuery({ - query: GET_CART_STATE, - data: { ui: { isCartOpen: true, __typename: 'Network' } }, - }); - } - - toggleOpen = () => { - this.props.uiQuery.updateQuery(() => ({ ui: { isCartOpen: false, __typename: 'Network' } })); - }; - - render() { - if (this.props.loading) { - return

Loading....

; - } - - if (this.props.error) { - return

Error....

; - } - - // if (!has(this.props, 'currentUserQuery.user.cart')) { - // return

Don't have it yet!

; - // } - // OMG Clean this up - let { user = {} } = this.props.currentUserQuery || {}; - if (!user) user = {}; - const cart = user.cart || []; - const userId = user.id; - // const userId = this.props.currentUserQuery.user.id; - - const total = cart.reduce((a, b) => a + b.price, 0); - return ( - - -

{cart.length} Items

- - - - - -
- ); - } -} - -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -const uiEnhancer = graphql(GET_CART_STATE, { name: 'uiQuery' }); -export default compose(userEnhancer, removeFromCartEnhancer, uiEnhancer)(CartList); diff --git a/components/ChaChing.js b/components/ChaChing.js deleted file mode 100644 index 5874c27..0000000 --- a/components/ChaChing.js +++ /dev/null @@ -1,56 +0,0 @@ -import TransitionGroup from 'react-transition-group/TransitionGroup'; -import Transition from 'react-transition-group/Transition'; -import styled from 'styled-components'; - -const CartCount = styled.div` - padding: 10px; - transition: all 0.5s; - display: inline-block; - background: white; - background: linear-gradient(to bottom, #eeeeee 0%, #eeeeee 100%); - transform: translateY(100%); - z-index: 1; - position: relative; - &.cart-entering { - background: green; - transform: translateY(100%); - } - &.cart-entered { - transform: translateY(0); - } - &.cart-exiting { - position: absolute; - z-index: 0; - left: 0; - transform: translateY(-100%); - } -`; - -const ChaChingStyles = styled.div` - position: relative; - display: inline-block; - overflow: hidden; -`; - -const ChaChing = ({ amount }) => ( - - - - {status => ( - - {amount} - - )} - - - -); - -export default ChaChing; diff --git a/components/Count.js b/components/Count.js deleted file mode 100644 index 800a902..0000000 --- a/components/Count.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { Component } from 'react' -import { graphql, gql } from 'react-apollo' - -import { ALL_ITEMS_QUERY } from '../queries'; - -class Count extends Component { - - render() { - - // 1 - if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { - return
Loading
- } - - // 2 - if (this.props.allLinksQuery && this.props.allLinksQuery.error) { - return
Error
- } - - // 3 - const itemsToRender = this.props.allLinksQuery.allItems - - return ( -
-

There are {itemsToRender.length} items for sale

-
- ) - } - -} - -// 1 - -// We export the graphQL HOC - this will fetch the data and inject it into the Count compeont via props - -export { ALL_ITEMS_QUERY } -export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' }) (Count) diff --git a/components/CreateItem.js b/components/CreateItem.js deleted file mode 100644 index 27b62e5..0000000 --- a/components/CreateItem.js +++ /dev/null @@ -1,128 +0,0 @@ -import React, { Component } from 'react'; -import { graphql, gql } from 'react-apollo'; -import { ALL_ITEMS_QUERY, CREATE_ITEM_MUTATION } from '../queries'; -import ErrorMessage from './ErrorMessage'; -import makeImage from '../lib/image'; -import { fileEndpoint } from '../config'; - -class CreateLink extends Component { - state = { - description: '', - title: '', - image: '', - price: 0, - fullPrice: 0, - loading: false, - error: { - message: '', - }, - }; - - componentWillReceiveProps(nextProps) { - console.log(nextProps); - } - - uploadFile = async e => { - this.setState({ loading: true }); - const files = e.currentTarget.files; - - const data = new FormData(); - data.append('data', files[0]); - - // use the file endpoint - const res = await fetch(fileEndpoint, { - method: 'POST', - body: data, - }); - const file = await res.json(); - this.setState({ image: file.id, loading: false }); - }; - - _createLink = async e => { - e.preventDefault(); - // pull the values from state - const { description, title, image, price, fullPrice } = this.state; - // create a mutation - // TODO: handle any errors - // turn loading on - this.setState({ loading: true }); - try { - const res = await this.props.createItemMutation({ - // pass in those variables from state - variables: { - description, - title, - price: parseInt(price), - fullPrice, - imageId: image, - }, - }); - } catch (error) { - this.setState({ error }); - console.log(error); - } - this.setState({ loading: false }); - }; - - render() { - return ( -
- {this.state.loading ? 'LOADING...' : 'Ready!'} - - this.setState({ error: {} })} /> -
-

- Image - -

-

- Title - this.setState({ title: e.target.value })} - type="text" - placeholder="Title" - /> -

- -