import akka.actor.ActorRef import akka.util.Timeout import generic.{Event, Versioned, View} import generic.View.Get import sangria.execution.UserFacingError import sangria.schema._ import sangria.macros.derive._ import akka.pattern.ask import akka.stream.Materializer import sangria.execution.deferred.{Fetcher, HasId} import scala.concurrent.ExecutionContext import sangria.streaming.akkaStreams._ object schema { case class MutationError(message: String) extends Exception(message) with UserFacingError val authors = Fetcher.caching((c: Ctx, ids: Seq[String]) ⇒ c.loadAuthors(ids))(HasId(_.id)) def createSchema(implicit timeout: Timeout, ec: ExecutionContext, mat: Materializer) = { val VersionedType = InterfaceType("Versioned", fields[Ctx, Versioned]( Field("id", StringType, resolve = _.value.id), Field("version", LongType, resolve = _.value.version))) implicit val AuthorType = deriveObjectType[Unit, Author](Interfaces(VersionedType)) val EventType = InterfaceType("Event", fields[Ctx, Event]( Field("id", StringType, resolve = _.value.id), Field("version", LongType, resolve = _.value.version))) val AuthorCreatedType = deriveObjectType[Unit, AuthorCreated](Interfaces(EventType)) val AuthorNameChangedType = deriveObjectType[Unit, AuthorNameChanged](Interfaces(EventType)) val AuthorDeletedType = deriveObjectType[Unit, AuthorDeleted](Interfaces(EventType)) val ArticleCreatedType = deriveObjectType[Unit, ArticleCreated]( Interfaces(EventType), ReplaceField("authorId", Field("author", OptionType(AuthorType), resolve = c ⇒ authors.deferOpt(c.value.authorId)))) val ArticleTextChangedType = deriveObjectType[Unit, ArticleTextChanged](Interfaces(EventType)) val ArticleDeletedType = deriveObjectType[Unit, ArticleDeleted](Interfaces(EventType)) implicit val ArticleType = deriveObjectType[Ctx, Article]( Interfaces(VersionedType), ReplaceField("authorId", Field("author", OptionType(AuthorType), resolve = c ⇒ authors.deferOpt(c.value.authorId)))) val IdArg = Argument("id", StringType) val OffsetArg = Argument("offset", OptionInputType(IntType), 0) val LimitArg = Argument("limit", OptionInputType(IntType), 100) def entityFields[T](name: String, tpe: ObjectType[Ctx, T], actor: Ctx ⇒ ActorRef) = fields[Ctx, Unit]( Field(name, OptionType(tpe), arguments = IdArg :: Nil, resolve = c ⇒ (actor(c.ctx) ? Get(c.arg(IdArg))).mapTo[Option[T]]), Field(name + "s", ListType(tpe), arguments = OffsetArg :: LimitArg :: Nil, resolve = c ⇒ (actor(c.ctx) ? View.List(c.arg(OffsetArg), c.arg(LimitArg))).mapTo[Seq[T]])) val QueryType = ObjectType("Query", entityFields[Author]("author", AuthorType, _.authors) ++ entityFields[Article]("article", ArticleType, _.articles)) val MutationType = deriveContextObjectType[Ctx, Mutation, Unit](identity) /** creates a subscription field for a specific event type */ def subscriptionField[T <: Event](tpe: ObjectType[Ctx, T]) = { val fieldName = tpe.name.head.toLower + tpe.name.tail Field.subs(fieldName, tpe, resolve = (c: Context[Ctx, Unit]) ⇒ c.ctx.eventStream .filter(event ⇒ tpe.valClass.isAssignableFrom(event.getClass)) .map(event ⇒ Action(event.asInstanceOf[T]))) } val SubscriptionType = ObjectType("Subscription", fields[Ctx, Unit]( subscriptionField(AuthorCreatedType), subscriptionField(AuthorNameChangedType), subscriptionField(AuthorDeletedType), subscriptionField(ArticleCreatedType), subscriptionField(ArticleTextChangedType), subscriptionField(ArticleDeletedType), Field.subs("allEvents", EventType, resolve = _.ctx.eventStream.map(Action(_))) )) Schema(QueryType, Some(MutationType), Some(SubscriptionType)) } }