Created
October 13, 2018 19:52
-
-
Save matisiekpl/57e1934850ab2827a42e1250803a1529 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// My schema for E-learning system for my school | |
directive @isAuthenticated on FIELD | FIELD_DEFINITION | |
directive @hasRole(role: String) on FIELD | FIELD_DEFINITION | |
directive @permit(code: String) on QUERY | FIELD | FIELD_DEFINITION | |
directive @private on FIELD | FIELD_DEFINITION | |
enum ContestType { | |
PRIVATE, | |
PUBLIC | |
} | |
enum AnswerType { | |
PROGRAM | |
} | |
enum AnswerStatus { | |
CREATED, PENDING, SUCCESS, FAILURE | |
} | |
enum Role { | |
MEMBER, ADMIN | |
} | |
type ProgramAnswerResult { | |
time: Int! | |
} | |
type ProgramTestCase { | |
maxTime: Int! | |
maxCpuUsage: Int! | |
input: String! | |
output: String! | |
} | |
type File { | |
url: String! | |
} | |
type Answer { | |
id: ID! @unique | |
sender: User! | |
message: String! | |
files: [File!]! | |
status: AnswerStatus! | |
programTestCase: [ProgramTestCase!]! | |
} | |
type Task { | |
id: ID! @unique | |
name: String! | |
description: String! | |
answerType: AnswerType! | |
end: Int! | |
created: Int! | |
updated: Int! | |
answers: [Answer!]! | |
} | |
type Contest { | |
id: ID! @unique | |
start: Int! | |
end: Int! | |
name: String! | |
description: String | |
type: ContestType! | |
tasks: [Task!]! | |
} | |
type Message { | |
id: ID! @unique | |
author: User! | |
content: String! | |
created: Int! | |
} | |
type Chat { | |
id: ID! @unique | |
name: String! | |
messages: [Message!]! | |
} | |
type Comment { | |
id: ID! @unique | |
content: String! | |
created: Int! | |
updated: Int! | |
author: User! | |
} | |
type Post { | |
id: ID! @unique | |
title: String! | |
content: String! | |
created: Int! | |
updated: Int! | |
author: User! | |
comments: [Comment!]! | |
} | |
type Blog { | |
id: ID! @unique | |
name: String! | |
description: String | |
posts: [Post!]! | |
} | |
type Class { | |
id: ID! @unique | |
name: String! | |
start: Int! | |
end: Int! | |
members: [User!]! | |
contests: [Contest!]! | |
blogs: [Blog!]! | |
chats: [Chat!]! | |
} | |
type User { | |
id: ID! @unique | |
email: String! @unique | |
password: String! @private | |
name: String! | |
roles: [Role!]! | |
} | |
type AuthPayload { | |
token: String | |
user: User | |
status: Boolean! | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GraphQLServer, PubSub } from 'graphql-yoga' | |
import { importSchema } from 'graphql-import' | |
import { Prisma, Query, Mutation, Subscription } from './generated/prisma' | |
import { Context } from './utils' | |
import { readFileSync } from 'fs'; | |
import { join } from 'path'; | |
import { makeExecutableSchema } from 'graphql-tools'; | |
import { mergeTypes } from 'merge-graphql-schemas'; | |
import { signInResolver, signUpResolver } from './resolvers/auth'; | |
import { runInNewContext } from 'vm'; | |
import { extractFragmentReplacements } from 'prisma-binding'; | |
import { parse } from 'graphql'; | |
// Some code, that is required to make resolvers callable on fields | |
export function addFragmentToFieldResolvers(schemaAST, fragmentSelection) { | |
return schemaAST.definitions.reduce((result, schemaDefinition) => { | |
if (schemaDefinition.kind === 'ObjectTypeDefinition') { | |
return { | |
...result, | |
[schemaDefinition.name.value]: schemaDefinition.fields.reduce((result, fieldDefinition) => { | |
//TODO this includes check is naive and will break for some strings | |
if (fragmentSelection.includes(fieldDefinition.name.value)) { | |
return result; | |
} | |
return { | |
...result, | |
[fieldDefinition.name.value]: { | |
fragment: `fragment Fragment on ${schemaDefinition.name.value} ${fragmentSelection}`, | |
resolve: (parent, args, context, info) => { | |
return parent[fieldDefinition.name.value]; | |
} | |
} | |
}; | |
}, {}) | |
}; | |
} | |
else { | |
return result; | |
} | |
}, {}); | |
} | |
// This function is copying resolvers from Prisma auto-generated SDK into our app | |
export function prepareTopLevelResolvers(resolverObject: Query | Mutation | Subscription, isSubscription: boolean = false) { | |
return Object.entries(resolverObject).reduce((result, entry) => { | |
const resolverName = entry[0]; | |
const resolverFunction = entry[1]; | |
if (isSubscription) { | |
return { | |
...result, | |
[resolverName]: { | |
subscribe: async (parent, args, context, info) => { | |
return await resolverFunction(args, info); | |
} | |
} | |
}; | |
} else { | |
return { | |
...result, | |
[resolverName]: async (parent, args, context, info) => { | |
return await resolverFunction(args, info); | |
} | |
}; | |
} | |
}, {}); | |
} | |
const preparedFieldResolvers = addFragmentToFieldResolvers(parse(readFileSync('./database/datamodel.graphql').toString()), `{ id }`) | |
const generatedFragmentReplacements = extractFragmentReplacements(preparedFieldResolvers); | |
// Creating new instance of Prisma - here on test server | |
const db = new Prisma({ | |
endpoint: 'https://eu1.prisma.sh/public-bevelreaper-37/funtest/dev', // the endpoint of the Prisma API | |
debug: false, // log all GraphQL queries & mutations sent to the Prisma API | |
fragmentReplacements: generatedFragmentReplacements | |
}); | |
// Merging Prisma auto-generated resolvers with our custom resolvers | |
const resolvers = { | |
Query: { | |
...prepareTopLevelResolvers(db.query) | |
}, | |
Mutation: { | |
...prepareTopLevelResolvers(db.mutation), | |
signIn: signInResolver, | |
signUp: signUpResolver | |
}, | |
Subscription: { | |
...prepareTopLevelResolvers(db.subscription, true) | |
}, | |
...preparedFieldResolvers | |
} | |
// Annotations in GraphQL are named as "Directives" - here you can observe the @private directive, that will throw exception, when you will go to get private field - for example - password field in User type | |
// Permit directive is a special directive, that requires "code" as a param and it executes code in nodejs vm (solution inspired from Firebase security rules) | |
const directiveResolvers = { | |
async hasRole(next, source, { role }, ctx) { | |
}, | |
async permit(next, source, { code }, ctx) { | |
const sandbox = { ctx, resurce: source }; | |
const result = runInNewContext('async () => ' + code, sandbox); | |
console.log(await result()); | |
if (await result()) return await next(); | |
throw new Error('Operation not permitted'); | |
}, | |
private(next, source, args, ctx) { | |
throw new Error('Given field is private'); | |
} | |
} | |
// Merging fields | |
const ultimateSchemaString = mergeTypes([ | |
readFileSync(join(__dirname, './generated/prisma.graphql')).toString(), | |
readFileSync(join(__dirname, './../database/datamodel.graphql')).toString(), | |
readFileSync(join(__dirname, './schema.graphql')).toString() | |
], { | |
all: true | |
}); | |
// Constructing final schema | |
const ultimateSchema = makeExecutableSchema({ | |
typeDefs: ultimateSchemaString, | |
resolvers, | |
directiveResolvers | |
}); | |
// Running the server | |
const server = new GraphQLServer({ | |
schema: ultimateSchema, | |
context: req => ({ | |
...req, | |
db | |
}), | |
}) | |
server.start(() => console.log('Server is running on http://localhost:4000')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Custom schema, that should not be migrated on Prisma server | |
type Mutation { | |
signUp(email: String!, password: String!, name: String!): AuthPayload! | |
signIn(email: String!, password: String!): AuthPayload! | |
createUser(data: UserCreateInput!): User! @isAuthenticated | |
} | |
type Query { | |
users: [User!]! | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment