diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-01-30 12:41:01 -0500 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-01-30 12:41:01 -0500 |
| commit | cd0b07b3adb36fb5ec098f9e511ab45d774fee7b (patch) | |
| tree | a203e5010219ccea1acc6bbd2c0a4bcfa0a78615 /backend/src | |
| parent | 0a1648bf47490b312169c531aeb51ef4725e8d2e (diff) | |
Move to Prisma Backend
Diffstat (limited to 'backend/src')
| -rw-r--r-- | backend/src/generated/prisma.graphql | 615 | ||||
| -rw-r--r-- | backend/src/index.js | 33 | ||||
| -rw-r--r-- | backend/src/resolvers/AuthPayload.js | 7 | ||||
| -rw-r--r-- | backend/src/resolvers/Mutation.js | 104 | ||||
| -rw-r--r-- | backend/src/resolvers/Query.js | 31 | ||||
| -rw-r--r-- | backend/src/schema.graphql | 36 | ||||
| -rw-r--r-- | backend/src/utils.js | 23 |
7 files changed, 849 insertions, 0 deletions
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, +}; |
