2018-08-04 03:05:51 +00:00
|
|
|
import 'dart:async';
|
2018-08-04 15:01:49 +00:00
|
|
|
import 'dart:math';
|
2018-08-04 03:05:51 +00:00
|
|
|
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
|
|
|
import 'package:angel_graphql/angel_graphql.dart';
|
|
|
|
import 'package:graphql_schema/graphql_schema.dart';
|
|
|
|
import 'package:graphql_server/graphql_server.dart';
|
|
|
|
import 'package:graphql_server/mirrors.dart';
|
|
|
|
|
|
|
|
import 'src/models/models.dart';
|
|
|
|
|
|
|
|
Future configureServer(Angel app) async {
|
|
|
|
// Create standard Angel services. Note that these will also *automatically* be
|
|
|
|
// exposed via a REST API as well.
|
|
|
|
var droidService = mountService<Droid>(app, '/api/droids');
|
|
|
|
var humansService = mountService<Human>(app, '/api/humans');
|
|
|
|
var starshipService = mountService<Starship>(app, '/api/starships');
|
2018-08-04 15:01:49 +00:00
|
|
|
var rnd = new Random();
|
2018-08-04 03:05:51 +00:00
|
|
|
|
|
|
|
// Create the GraphQL schema.
|
|
|
|
// This code uses dart:mirrors to easily create GraphQL types from Dart PODO's.
|
2018-08-04 15:01:49 +00:00
|
|
|
var droidType = convertDartClass(Droid);
|
2018-08-04 03:13:33 +00:00
|
|
|
var episodeType = convertDartType(Episode);
|
2018-08-04 15:01:49 +00:00
|
|
|
var humanType = convertDartClass(Human);
|
2018-08-04 03:13:33 +00:00
|
|
|
var starshipType = convertDartType(Starship);
|
2018-08-04 19:18:53 +00:00
|
|
|
|
|
|
|
// A Hero can be either a Droid or Human; create a union type that represents this.
|
2018-08-04 15:01:49 +00:00
|
|
|
var heroType = new GraphQLUnionType('Hero', [droidType, humanType]);
|
2018-08-04 03:05:51 +00:00
|
|
|
|
|
|
|
// Create the query type.
|
|
|
|
//
|
|
|
|
// Use the `resolveViaServiceIndex` helper to load data directly from an
|
|
|
|
// Angel service.
|
2018-08-04 15:01:49 +00:00
|
|
|
var queryType = objectType(
|
|
|
|
'StarWarsQuery',
|
|
|
|
description: 'A long time ago, in a galaxy far, far away...',
|
|
|
|
fields: [
|
|
|
|
field(
|
|
|
|
'droids',
|
2018-08-04 19:18:53 +00:00
|
|
|
listType(droidType.nonNullable()),
|
|
|
|
description: 'All droids in the known galaxy.',
|
2018-08-04 15:01:49 +00:00
|
|
|
resolve: resolveViaServiceIndex(droidService),
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'humans',
|
2018-08-04 19:18:53 +00:00
|
|
|
listType(humanType.nonNullable()),
|
|
|
|
description: 'All humans in the known galaxy.',
|
2018-08-04 15:01:49 +00:00
|
|
|
resolve: resolveViaServiceIndex(humansService),
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'starships',
|
2018-08-04 19:18:53 +00:00
|
|
|
listType(starshipType.nonNullable()),
|
|
|
|
description: 'All starships in the known galaxy.',
|
2018-08-04 15:01:49 +00:00
|
|
|
resolve: resolveViaServiceIndex(starshipService),
|
|
|
|
),
|
|
|
|
field(
|
|
|
|
'hero',
|
2018-08-04 19:18:53 +00:00
|
|
|
heroType,
|
|
|
|
description:
|
|
|
|
'Finds a random hero within the known galaxy, whether a Droid or Human.',
|
|
|
|
inputs: [
|
|
|
|
new GraphQLFieldInput('ep', episodeType),
|
|
|
|
],
|
|
|
|
resolve: randomHeroResolver(droidService, humansService, rnd),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
// Convert our object types to input objects, so that they can be passed to
|
|
|
|
// mutations.
|
2018-08-04 20:24:59 +00:00
|
|
|
var humanChangesType = humanType.toInputObject('HumanChanges');
|
2018-08-04 19:18:53 +00:00
|
|
|
|
|
|
|
// Create the mutation type.
|
|
|
|
var mutationType = objectType(
|
|
|
|
'StarWarsMutation',
|
|
|
|
fields: [
|
|
|
|
// We'll use the `modify_human` mutation to modify a human in the database.
|
|
|
|
field(
|
|
|
|
'modify_human',
|
|
|
|
humanType.nonNullable(),
|
|
|
|
description: 'Modifies a human in the database.',
|
|
|
|
inputs: [
|
|
|
|
new GraphQLFieldInput('id', graphQLId.nonNullable()),
|
|
|
|
new GraphQLFieldInput('data', humanChangesType.nonNullable()),
|
2018-08-04 15:12:26 +00:00
|
|
|
],
|
2018-08-04 19:18:53 +00:00
|
|
|
resolve: resolveViaServiceModify(humansService),
|
2018-08-04 15:01:49 +00:00
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
2018-08-04 03:05:51 +00:00
|
|
|
|
|
|
|
// Finally, create the schema.
|
2018-08-04 19:18:53 +00:00
|
|
|
var schema = graphQLSchema(
|
|
|
|
queryType: queryType,
|
|
|
|
mutationType: mutationType,
|
|
|
|
);
|
2018-08-04 03:05:51 +00:00
|
|
|
|
|
|
|
// Next, create a GraphQL object, which will be passed to `graphQLHttp`, and
|
|
|
|
// used to mount a spec-compliant GraphQL endpoint on the server.
|
|
|
|
var graphQL = new GraphQL(schema);
|
|
|
|
|
|
|
|
// Mount the GraphQL endpoint.
|
|
|
|
app.all('/graphql', graphQLHttp(graphQL));
|
|
|
|
|
|
|
|
// In development, we'll want to mount GraphiQL, for easy management of the database.
|
|
|
|
if (!app.isProduction) {
|
|
|
|
app.get('/graphiql', graphiQL());
|
|
|
|
}
|
2018-08-04 15:01:49 +00:00
|
|
|
|
|
|
|
// Seed the database.
|
|
|
|
var leia = await humansService.create({
|
|
|
|
'name': 'Leia Organa',
|
|
|
|
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
|
|
|
'total_credits': 520,
|
|
|
|
});
|
|
|
|
|
2018-08-04 15:12:26 +00:00
|
|
|
var lando = await humansService.create({
|
|
|
|
'name': 'Lando Calrissian',
|
|
|
|
'appears_in': ['EMPIRE', 'JEDI'],
|
|
|
|
'total_credits': 525430,
|
|
|
|
});
|
|
|
|
|
2018-08-04 15:01:49 +00:00
|
|
|
var hanSolo = await humansService.create({
|
|
|
|
'name': 'Han Solo',
|
|
|
|
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
|
|
|
'total_credits': 23,
|
2018-08-04 15:12:26 +00:00
|
|
|
'friends': [leia, lando],
|
2018-08-04 15:01:49 +00:00
|
|
|
});
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
// Luke, of course.
|
|
|
|
await humansService.create({
|
2018-08-04 15:01:49 +00:00
|
|
|
'name': 'Luke Skywalker',
|
|
|
|
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
|
|
|
'total_credits': 682,
|
2018-08-04 15:12:26 +00:00
|
|
|
'friends': [leia, hanSolo, lando],
|
2018-08-04 15:01:49 +00:00
|
|
|
});
|
2018-08-04 03:05:51 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 19:18:53 +00:00
|
|
|
GraphQLFieldResolver randomHeroResolver(
|
|
|
|
Service droidService, Service humansService, Random rnd) {
|
|
|
|
return (_, args) async {
|
|
|
|
var allHeroes = [];
|
|
|
|
var allDroids = await droidService.index() as Iterable;
|
|
|
|
var allHumans = await humansService.index() as Iterable;
|
|
|
|
allHeroes..addAll(allDroids)..addAll(allHumans);
|
|
|
|
|
|
|
|
// Ignore the annoying cast here, hopefully Dart 2 fixes cases like this
|
|
|
|
allHeroes = allHeroes
|
|
|
|
.where((m) =>
|
|
|
|
!args.containsKey('ep') ||
|
|
|
|
(m['appears_in'].contains(args['ep']) as bool))
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
return allHeroes.isEmpty ? null : allHeroes[rnd.nextInt(allHeroes.length)];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-08-04 15:01:49 +00:00
|
|
|
Service mountService<T extends Model>(Angel app, String path) =>
|
|
|
|
app.use(path, new TypedService(new MapService())) as Service;
|