Skip to content

Commit

Permalink
Merge pull request #8 from jducoeur/add_graphql
Browse files Browse the repository at this point in the history
Add GraphQL Support
  • Loading branch information
jducoeur authored Sep 3, 2019
2 parents 6c70e1c + 366943e commit 92fd015
Show file tree
Hide file tree
Showing 23 changed files with 1,268 additions and 19 deletions.
10 changes: 6 additions & 4 deletions querki/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ lazy val clients = Seq(querkiClient)
lazy val scalaV = "2.11.12"
lazy val akkaV = "2.4.18"
lazy val enumeratumV = "1.5.3"
lazy val appV = "2.8.6"
lazy val appV = "2.9.0"

lazy val sharedSrcDir = "scala"

Expand Down Expand Up @@ -61,8 +61,8 @@ lazy val querkiServer = (project in file("scalajvm")).settings(
"ai.x" %% "diff" % "1.2.0" % "test",
// Only used for debugging at this point:
"com.github.pathikrit" %% "better-files" % "2.17.1",
"org.typelevel" %% "cats-core" % "1.3.1",
"org.typelevel" %% "cats-effect" % "1.0.0",
"org.typelevel" %% "cats-core" % "1.6.1",
"org.typelevel" %% "cats-effect" % "1.3.1",
"com.github.julien-truffaut" %% "monocle-core" % "1.5.0",
"com.github.julien-truffaut" %% "monocle-macro" % "1.5.0",
// Updated version of the XML library:
Expand All @@ -76,7 +76,9 @@ lazy val querkiServer = (project in file("scalajvm")).settings(
// In-memory Akka Persistence driver, used for tests. Note that this is for Akka 2.4!
"com.github.dnvriend" %% "akka-persistence-inmemory" % "1.3.9" % "test",
// In-memory H2 database, used for tests:
"com.h2database" % "h2" % "1.4.192" % "test"
"com.h2database" % "h2" % "1.4.192" % "test",
// For graphql processing:
"org.sangria-graphql" %% "sangria" % "1.4.2"
),

// ConductR params
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package querki.graphql

import scala.concurrent.Future

trait GraphQLFunctions {
/**
* Run the given GraphQL expression, and return the result.
*
* Note that the result is a JSON String, which will contain errors if there are any. This should never
* return a failed Future.
*
* @param graphQL a GraphQL expression that is legal for Querki
* @param pretty iff true, the resulting String will be pretty-printed
* @return the resulting JSON structure, rendered as a String
*/
def runGraphQL(graphQL: String, pretty: Boolean = false): Future[String]
}
14 changes: 7 additions & 7 deletions querki/scalajvm/app/controllers/ApplicationBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ trait ApplicationBase extends Controller with EcologyMember {
// This reflects the fact that there are many more or less public pages. It is the responsibility
// of the caller to use this flag sensibly. Note that RequestContext.requester is guaranteed to
// be set iff requireLogin is true.
def withUser[B](requireLogin:Boolean, parser:BodyParser[B] = BodyParsers.parse.anyContent)(f: PlayRequestContext => Future[Result]) = withAuth(parser) { user => implicit request =>
def withUser[B](requireLogin:Boolean, parser:BodyParser[B] = BodyParsers.parse.anyContent)(f: PlayRequestContextFull[B] => Future[Result]) = withAuth(parser) { user => implicit request =>
if (requireLogin && user == User.Anonymous) {
Future.successful(onUnauthorized(request))
} else {
Expand Down Expand Up @@ -168,10 +168,10 @@ trait ApplicationBase extends Controller with EcologyMember {
* Note that this is the usual replacement for withSpace -- it fetches just enough info to
* send messages off to the UserSession level, and nothing more.
*/
def withRouting
(ownerIdStr:String, spaceId:String)
(f: (PlayRequestContext => Future[Result])):EssentialAction =
withUser(false) { originalRC =>
def withRouting[B]
(ownerIdStr:String, spaceId:String, parser:BodyParser[B] = BodyParsers.parse.anyContent)
(f: (PlayRequestContextFull[B] => Future[Result])):EssentialAction =
withUser(false, parser) { originalRC =>
try {
// Give the listeners a chance to chime in. Note that this is where things like invitation
// management come into play.
Expand Down Expand Up @@ -212,8 +212,8 @@ trait ApplicationBase extends Controller with EcologyMember {
def write[Result: Writer](r: Result) = upickle.default.write(r)
}

def withLocalClient(ownerId:String, spaceIdStr:String)(cb:(PlayRequestContext, LocalClient) => Future[Result]) =
withRouting(ownerId, spaceIdStr)
def withLocalClient[B](ownerId:String, spaceIdStr:String, parser:BodyParser[B] = BodyParsers.parse.anyContent)(cb:(PlayRequestContextFull[B], LocalClient) => Future[Result]) =
withRouting(ownerId, spaceIdStr, parser)
{ implicit rawRc =>
// Unlike the API calls, we have to assume we have a name-style ThingId here:
SpaceOps.getSpaceId(rawRc.ownerId, spaceIdStr).flatMap { spaceId =>
Expand Down
26 changes: 26 additions & 0 deletions querki/scalajvm/app/controllers/GraphQLController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controllers

import java.nio.charset.StandardCharsets

import autowire._
import javax.inject.{Inject, Provider}
import querki.globals._
import querki.graphql.GraphQLFunctions

import scala.concurrent.Future

class GraphQLController @Inject() (val appProv:Provider[play.api.Application]) extends ApplicationBase {
def graphQL(ownerId: String, spaceIdStr: String) = withLocalClient(ownerId, spaceIdStr) { (rc, client) =>
val resultOpt = for {
rawBuffer <- rc.request.body.asRaw
bytes <- rawBuffer.asBytes()
query = bytes.decodeString(StandardCharsets.UTF_8)
}
yield client[GraphQLFunctions].runGraphQL(query).call()

resultOpt.map { resultFut =>
resultFut.map(Ok(_))
}.getOrElse(
Future.successful(BadRequest("The content-type should be 'application/graphql', and there must be UTF-8 encoded GraphQL in the body")))
}
}
2 changes: 2 additions & 0 deletions querki/scalajvm/app/models/Property.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ case class Property[VT, RT](
mt:DateTime)(implicit val ecology:Ecology)
extends Thing(i, s, m, Kind.Property, pf, mt)
{
type valType = VT

def Core = ecology.api[querki.core.Core]

def default(implicit state:SpaceState) = {
Expand Down
2 changes: 1 addition & 1 deletion querki/scalajvm/app/querki/console/ConsoleEcot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ConsoleEcot(e:Ecology) extends QuerkiEcot(e) with querki.core.MethodDefs w
}
val qlContext = QLContext(ExactlyOne(LinkType(state)), Some(context.rc), TimeProvider.qlEndTime)
val cmdText = QLText(cmdStr)

// First, we process the command as QL. Note that we do this completely ignoring permissions
// (aside from the built-in read permission), but that's okay -- processMethod() is pure.
QL.processMethod(cmdText, qlContext).flatMap { qv =>
Expand Down
Loading

0 comments on commit 92fd015

Please sign in to comment.