Skip to content

Instantly share code, notes, and snippets.

@marioosh
Created June 26, 2018 07:46
Show Gist options
  • Save marioosh/7c3ee5fed1238c5daf89a4459727f575 to your computer and use it in GitHub Desktop.
Save marioosh/7c3ee5fed1238c5daf89a4459727f575 to your computer and use it in GitHub Desktop.
HowToGraphql - Sangria tutorial - Relations - finish
package com.howtographql.scala.sangria
import DBSchema._
import com.howtographql.scala.sangria.models.{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
)
}
package com.howtographql.scala.sangria
import java.sql.Timestamp
import akka.http.scaladsl.model.DateTime
import com.howtographql.scala.sangria.models._
import slick.jdbc.H2Profile.api._
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.language.postfixOps
object DBSchema {
implicit val dateTimeColumnType = MappedColumnType.base[DateTime, Timestamp](
dt => new Timestamp(dt.clicks),
ts => DateTime(ts.getTime)
)
class LinksTable(tag: Tag) extends Table[Link](tag, "LINKS"){
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def url = column[String]("URL")
def description = column[String]("DESCRIPTION")
def postedBy = column[Int]("USER_ID")
def createdAt = column[DateTime]("CREATED_AT")
def * = (id, url, description, postedBy, createdAt).mapTo[Link]
def postedByFK = foreignKey("postedBy_FK", postedBy, Users)(_.id)
}
val Links = TableQuery[LinksTable]
class UsersTable(tag: Tag) extends Table[User](tag, "USERS"){
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def email = column[String]("EMAIL")
def password = column[String]("PASSWORD")
def createdAt = column[DateTime]("CREATED_AT")
def * = (id, name, email, password, createdAt).mapTo[User]
}
val Users = TableQuery[UsersTable]
class VotesTable(tag: Tag) extends Table[Vote](tag, "VOTES"){
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def userId = column[Int]("USER_ID")
def linkId = column[Int]("LINK_ID")
def createdAt = column[DateTime]("CREATED_AT")
def * = (id, userId, linkId, createdAt).mapTo[Vote]
def userFK = foreignKey("user_FK", userId, Users)(_.id)
def linkFK = foreignKey("link_FK", linkId, Links)(_.id)
}
val Votes = TableQuery[VotesTable]
/**
* Load schema and populate sample data within this Sequence od DBActions
*/
val databaseSetup = DBIO.seq(
Users.schema.create,
Links.schema.create,
Votes.schema.create,
Users forceInsertAll Seq(
User(1, "mario", "[email protected]", "s3cr3t"),
User(2, "Fred", "[email protected]", "wilmalove")
),
Links forceInsertAll Seq(
Link(1, "http://howtographql.com", "Awesome community driven GraphQL tutorial",1, DateTime(2017,9,12)),
Link(2, "http://graphql.org", "Official GraphQL web page",1, DateTime(2017,10,1)),
Link(3, "https://facebook.github.io/graphql/", "GraphQL specification",2, DateTime(2017,10,2))
),
Votes forceInsertAll Seq(
Vote(1, 1, 1),
Vote(2, 1, 2),
Vote(3, 1, 3),
Vote(4, 2, 2),
)
)
def createDatabase: DAO = {
val db = Database.forConfig("h2mem")
Await.result(db.run(databaseSetup), 10 seconds)
new DAO(db)
}
}
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)))
)
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 SchemaDefinition = Schema(QueryType)
}
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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment