summaryrefslogtreecommitdiffstats
path: root/backend
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-01-30 12:41:01 -0500
committerWes Bos <wesbos@gmail.com>2018-01-30 12:41:01 -0500
commitcd0b07b3adb36fb5ec098f9e511ab45d774fee7b (patch)
treea203e5010219ccea1acc6bbd2c0a4bcfa0a78615 /backend
parent0a1648bf47490b312169c531aeb51ef4725e8d2e (diff)
Move to Prisma Backend
Diffstat (limited to 'backend')
-rw-r--r--backend/.gitignore7
-rw-r--r--backend/.graphqlconfig.yml10
-rw-r--r--backend/README.md1
-rw-r--r--backend/database/datamodel.graphql26
-rw-r--r--backend/database/prisma.yml21
-rw-r--r--backend/database/seed.graphql24
-rw-r--r--backend/package.json22
-rw-r--r--backend/src/generated/prisma.graphql615
-rw-r--r--backend/src/index.js33
-rw-r--r--backend/src/resolvers/AuthPayload.js7
-rw-r--r--backend/src/resolvers/Mutation.js104
-rw-r--r--backend/src/resolvers/Query.js31
-rw-r--r--backend/src/schema.graphql36
-rw-r--r--backend/src/utils.js23
14 files changed, 960 insertions, 0 deletions
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,
+};