Skip to content

Instantly share code, notes, and snippets.

@marioosh
Created June 26, 2018 08:51
Show Gist options
  • Save marioosh/316468a9ac5e1179e226a3191f03fab8 to your computer and use it in GitHub Desktop.
Save marioosh/316468a9ac5e1179e226a3191f03fab8 to your computer and use it in GitHub Desktop.
HowToGraphql - Sangria tutorial - Mutations
package com.howtographql.scala.sangria
import DBSchema._
import com.howtographql.scala.sangria.models.{AuthProviderSignupData, Link, User, Vote}
import sangria.execution.deferred.{RelationIds, SimpleRelation}
import slick.jdbc.H2Profile.api._
import scala.concurrent.Future
class DAO(db: Database) {
def allLinks = db.run(Links.result)
def getLinks(ids: Seq[Int]): Future[Seq[Link]] = db.run(
Links.filter(_.id inSet ids).result
)
def getLinksByUserIds(ids: Seq[Int]): Future[Seq[Link]] = {
db.run {
Links.filter(_.postedBy inSet ids).result
}
}
def getUsers(ids: Seq[Int]): Future[Seq[User]] = db.run(
Users.filter(_.id inSet ids).result
)
def getVotes(ids: Seq[Int]): Future[Seq[Vote]] = {
db.run(
Votes.filter(_.id inSet ids).result
)
}
def getVotesByRelationIds(rel: RelationIds[Vote]): Future[Seq[Vote]] =
db.run(
Votes.filter { vote =>
rel.rawIds.collect({
case (SimpleRelation("byUser"), ids: Seq[Int]) => vote.userId inSet ids
case (SimpleRelation("byLink"), ids: Seq[Int]) => vote.linkId inSet ids
}).foldLeft(true: Rep[Boolean])(_ || _)
} result
)
def createUser(name: String, authProvider: AuthProviderSignupData): Future[User] = {
val newUser = User(0, name, authProvider.email.email, authProvider.email.password )
val insertAndReturnUserQuery = (Users returning Users.map(_.id)) into {
(user, id) => user.copy(id = id)
}
db.run {
insertAndReturnUserQuery += newUser
}
}
def createLink(url: String, description: String, postedBy: Int): Future[Link] = {
val insertAndReturnLinkQuery = (Links returning Links.map(_.id)) into {
(link, id) => link.copy(id = id)
}
db.run {
insertAndReturnLinkQuery += Link(0, url, description, postedBy)
}
}
def createVote(linkId: Int, userId: Int): Future[Vote] = {
val insertAndReturnVoteQuery = (Votes returning Votes.map(_.id)) into {
(vote, id) => vote.copy(id = id)
}
db.run {
insertAndReturnVoteQuery += Vote(0, userId, linkId)
}
}
}
package com.howtographql.scala.sangria
import akka.http.scaladsl.model.DateTime
import sangria.schema.{ListType, ObjectType}
import models._
import sangria.ast.StringValue
import sangria.execution.deferred.{DeferredResolver, Fetcher, Relation, RelationIds}
import sangria.schema._
import sangria.macros.derive._
object GraphQLSchema {
implicit val GraphQLDateTime = ScalarType[DateTime](//1
"DateTime",//2
coerceOutput = (dt, _) => dt.toString, //3
coerceInput = { //4
case StringValue(dt, _, _ ) => DateTime.fromIsoDateTimeString(dt).toRight(DateTimeCoerceViolation)
case _ => Left(DateTimeCoerceViolation)
},
coerceUserInput = { //5
case s: String => DateTime.fromIsoDateTimeString(s).toRight(DateTimeCoerceViolation)
case _ => Left(DateTimeCoerceViolation)
}
)
val IdentifiableType = InterfaceType(
"Identifiable",
fields[Unit, Identifiable](
Field("id", IntType, resolve = _.value.id)
)
)
lazy val LinkType: ObjectType[Unit, Link] = deriveObjectType[Unit, Link](
Interfaces(IdentifiableType),
ReplaceField("createdAt", Field("createdAt", GraphQLDateTime, resolve = _.value.createdAt)),
ReplaceField("postedBy",
Field("postedBy", UserType, resolve = c => usersFetcher.defer(c.value.postedBy))
),
AddFields(
Field("votes", ListType(VoteType), resolve = c => votesFetcher.deferRelSeq(voteByLinkRel, c.value.id))
)
)
lazy val UserType: ObjectType[Unit, User] = deriveObjectType[Unit, User](
Interfaces(IdentifiableType),
AddFields(
Field("links", ListType(LinkType),
resolve = c => linksFetcher.deferRelSeq(linkByUserRel, c.value.id)),
Field("votes", ListType(VoteType),
resolve = c => votesFetcher.deferRelSeq(voteByUserRel, c.value.id))
)
)
lazy val VoteType: ObjectType[Unit, Vote] = deriveObjectType[Unit, Vote](
Interfaces(IdentifiableType),
ExcludeFields("userId", "linkId"),
AddFields(Field("user", UserType, resolve = c => usersFetcher.defer(c.value.userId))),
AddFields(Field("link", LinkType, resolve = c => linksFetcher.defer(c.value.linkId)))
)
import sangria.marshalling.sprayJson._
import spray.json.DefaultJsonProtocol._
implicit val authProviderEmailFormat = jsonFormat2(AuthProviderEmail)
implicit val authProviderSignupDataFormat = jsonFormat1(AuthProviderSignupData)
implicit val AuthProviderEmailInputType: InputObjectType[AuthProviderEmail] = deriveInputObjectType[AuthProviderEmail](
InputObjectTypeName("AUTH_PROVIDER_EMAIL")
)
lazy val AuthProviderSignupDataInputType: InputObjectType[AuthProviderSignupData] = deriveInputObjectType[AuthProviderSignupData]()
val linkByUserRel = Relation[Link, Int]("byUser", l => Seq(l.postedBy))
val voteByLinkRel = Relation[Vote, Int]("byLink", v => Seq(v.linkId))
val voteByUserRel = Relation[Vote, Int]("byUser", v => Seq(v.userId))
val linksFetcher = Fetcher.rel(
(ctx: MyContext, ids: Seq[Int]) => ctx.dao.getLinks(ids),
(ctx: MyContext, ids: RelationIds[Link]) => ctx.dao.getLinksByUserIds(ids(linkByUserRel))
)
val usersFetcher = Fetcher(
(ctx: MyContext, ids: Seq[Int]) => ctx.dao.getUsers(ids)
)
val votesFetcher = Fetcher.rel(
(ctx: MyContext, ids: Seq[Int]) => ctx.dao.getVotes(ids),
(ctx: MyContext, ids: RelationIds[Vote]) => ctx.dao.getVotesByRelationIds(ids)
)
val Resolver = DeferredResolver.fetchers(linksFetcher, usersFetcher, votesFetcher)
val Id = Argument("id", IntType)
val Ids = Argument("ids", ListInputType(IntType))
val QueryType = ObjectType(
"Query",
fields[MyContext, Unit](
Field("allLinks", ListType(LinkType), resolve = c => c.ctx.dao.allLinks),
Field("link",
OptionType(LinkType),
arguments = Id :: Nil,
resolve = c => linksFetcher.deferOpt(c.arg(Id))
),
Field("links",
ListType(LinkType),
arguments = Ids :: Nil,
resolve = c => linksFetcher.deferSeq(c.arg(Ids))
),
Field("users",
ListType(UserType),
arguments = List(Ids),
resolve = c => usersFetcher.deferSeq(c.arg(Ids))
),
Field("votes",
ListType(VoteType),
arguments = List(Ids),
resolve = c => votesFetcher.deferSeq(c.arg(Ids))
)
)
)
val NameArg = Argument("name", StringType)
val AuthProviderArg = Argument("authProvider", AuthProviderSignupDataInputType)
val UrlArg = Argument("url", StringType)
val DescArg = Argument("description", StringType)
val PostedByArg = Argument("postedById", IntType)
val LinkIdArg = Argument("linkId", IntType)
val UserIdArg = Argument("userId", IntType)
val Mutation = ObjectType(
"Mutation",
fields[MyContext, Unit](
Field("createUser",
UserType,
arguments = NameArg :: AuthProviderArg :: Nil,
resolve = c => c.ctx.dao.createUser(c.arg(NameArg), c.arg(AuthProviderArg))
),
Field("createLink",
LinkType,
arguments = UrlArg :: DescArg :: PostedByArg :: Nil,
resolve = c => c.ctx.dao.createLink(c.arg(UrlArg), c.arg(DescArg), c.arg(PostedByArg))),
Field("createVote",
VoteType,
arguments = LinkIdArg :: UserIdArg :: Nil,
resolve = c => c.ctx.dao.createVote(c.arg(LinkIdArg), c.arg(UserIdArg)))
)
)
val SchemaDefinition = Schema(QueryType, Some(Mutation))
}
package com.howtographql.scala.sangria
import akka.http.scaladsl.model.DateTime
import sangria.execution.deferred.HasId
import sangria.validation.Violation
package object models {
trait Identifiable {
val id: Int
}
object Identifiable {
implicit def hasId[T <: Identifiable]: HasId[T, Int] = HasId(_.id)
}
case class Link(id: Int, url: String, description: String, postedBy: Int, createdAt: DateTime = DateTime.now) extends Identifiable
case object DateTimeCoerceViolation extends Violation {
override def errorMessage: String = "Error during parsing DateTime"
}
case class User(id: Int, name: String, email: String, password: String, createdAt: DateTime = DateTime.now) extends Identifiable
case class Vote(id: Int, userId: Int, linkId: Int, createdAt: DateTime = DateTime.now) extends Identifiable
case class AuthProviderEmail(email: String, password: String)
case class AuthProviderSignupData(email: AuthProviderEmail)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment