Commit ea04b1e5 authored by Andreas Muttscheller's avatar Andreas Muttscheller

Add GraphQL server

parent 32bbd3b8
......@@ -30,9 +30,13 @@ lazy val root = (project in file("."))
"com.amazonaws" % "aws-lambda-java-events" % "2.2.5",
"com.amazonaws" % "aws-lambda-java-core" % "1.2.0",
// ElasicSearch
// ElasticSearch
"com.sksamuel.elastic4s" % "elastic4s-core_2.12" % "6.3.8",
"com.sksamuel.elastic4s" %% "elastic4s-http" % "6.3.8",
"com.sksamuel.elastic4s" %% "elastic4s-aws" % "6.3.8",
// GraphQL
"org.sangria-graphql" %% "sangria" % "1.4.2",
"org.sangria-graphql" %% "sangria-json4s-native" % "1.0.0",
)
)
\ No newline at end of file
......@@ -90,6 +90,13 @@ functions:
- http:
path: stations
method: get
APIGraphQLHandler:
handler: de.codecentric.amuttsch.bahndelayinfo.aws.lambda.APIGraphQLHandler::handleRequest
iamRoleStatementsInherit: true
events:
- http:
path: graphql
method: post
resources:
Resources:
......
......@@ -3,22 +3,13 @@ package de.codecentric.amuttsch.bahndelayinfo.aws
import com.amazonaws.services.dynamodbv2.document.{DynamoDB, Item, Table}
import com.amazonaws.services.dynamodbv2.{AmazonDynamoDB, AmazonDynamoDBClientBuilder}
import de.codecentric.amuttsch.bahndelayinfo.aws
import de.codecentric.amuttsch.bahndelayinfo.models.Station
import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.native.Serialization.write
import scala.io.Source
case class Station(
eva: Int,
ds100: String,
name: String,
latitude: String,
longitude: String,
traffic: String,
ifopt: String
)
object StationImporter extends App {
import com.sksamuel.elastic4s.http.ElasticDsl._
private val customSerializer = new CustomSerializer[String](_ => (
......
package de.codecentric.amuttsch.bahndelayinfo.aws.lambda
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.events.{APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent}
import com.typesafe.scalalogging.Logger
import de.codecentric.amuttsch.bahndelayinfo.graphql.{DBSchema, Repository}
import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.native.Serialization.write
import sangria.execution.{ErrorWithResolver, Executor, QueryAnalysisError}
import sangria.parser.QueryParser
import sangria.marshalling.json4s.native._
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
class APIGraphQLHandler {
implicit val logger: Logger = Logger(classOf[APISlackBotEventHandlerWorker])
implicit val jsonFormats: DefaultFormats.type = DefaultFormats
val repository: Repository = new Repository
def handleRequest(event: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent = {
logger.info(s"Got event ${event.getBody}")
val requestJson = parse(event.getBody)
val fields = requestJson.asInstanceOf[JObject]
val query = (fields \ "query").extract[String]
val opName = (fields \ "operationName").extractOpt[String]
val vars = fields \ "variables"
QueryParser.parse(query) match {
// query parsed successfully, time to execute it!
case Success(queryAst)
var resp = new APIGatewayProxyResponseEvent()
.withStatusCode(200)
val f = Executor.execute(DBSchema.schema, queryAst, repository, operationName = opName, variables = vars)
.map { r =>
resp = new APIGatewayProxyResponseEvent()
.withBody(write(r))
.withStatusCode(200)
}
.recover {
case error: QueryAnalysisError
resp = new APIGatewayProxyResponseEvent()
.withBody(error.getMessage)
.withStatusCode(400)
case error: ErrorWithResolver
resp = new APIGatewayProxyResponseEvent()
.withBody(error.getMessage)
.withStatusCode(400)
}
Await.ready(f, 20 seconds)
resp
// can't parse GraphQL query, return error
case Failure(error)
new APIGatewayProxyResponseEvent()
.withBody(error.getMessage)
.withStatusCode(400)
}
}
}
\ No newline at end of file
......@@ -6,9 +6,9 @@ import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent
import com.amazonaws.services.sns.AmazonSNSClientBuilder
import com.typesafe.scalalogging.Logger
import de.codecentric.amuttsch.bahndelayinfo.aws.PlannedTimetableElement
import de.codecentric.amuttsch.bahndelayinfo.aws.sns.SNSNewDelayInformation
import de.codecentric.amuttsch.bahndelayinfo.fetcher.{DBChangedTimetableFetcher, TimetableInformation}
import de.codecentric.amuttsch.bahndelayinfo.models.PlannedTimetableElement
import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.native.Serialization.write
......
......@@ -10,8 +10,8 @@ import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent
import com.typesafe.scalalogging.Logger
import de.codecentric.amuttsch.bahndelayinfo.aws
import de.codecentric.amuttsch.bahndelayinfo.aws.PlannedTimetableElement
import de.codecentric.amuttsch.bahndelayinfo.fetcher.DBPlannedTimetableFetcher
import de.codecentric.amuttsch.bahndelayinfo.models.PlannedTimetableElement
import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.native.Serialization.write
......
......@@ -16,6 +16,7 @@ import org.json4s.native.Serialization.write
import scala.collection.JavaConverters._
import com.sksamuel.elastic4s.http.ElasticDsl._
import de.codecentric.amuttsch.bahndelayinfo.models.PlannedTimetableElement
class ScheduledPlannedTimetableFetchWorker {
......@@ -66,7 +67,7 @@ class ScheduledPlannedTimetableFetchWorker {
station = tti.station
}
val newElement = aws.PlannedTimetableElement(
val newElement = PlannedTimetableElement(
eva,
LocalDate.now.toString,
station
......
package de.codecentric.amuttsch.bahndelayinfo.graphql
import de.codecentric.amuttsch.bahndelayinfo.models.Station
import sangria.schema._
object DBSchema {
val evaArg = Argument("eva", IntType)
val searchStationArg = Argument("stationName", StringType)
val queryType = ObjectType("Query", fields[Repository, Unit](
Field("station", OptionType(Station.graphqlType),
description = Some(
"Return a station by eva"
),
arguments = evaArg :: Nil,
resolve = c => c.ctx.getStationByEva(c.arg(evaArg))
),
Field("searchStationByName", ListType(Station.graphqlType),
description = Some(
"Search stations by name"
),
arguments = searchStationArg :: Nil,
resolve = c => c.ctx.searchStationByName(c.arg(searchStationArg))
),
))
val schema = Schema(queryType)
}
package de.codecentric.amuttsch.bahndelayinfo.graphql
package de.codecentric.amuttsch.bahndelayinfo.graphql
import com.amazonaws.services.dynamodbv2.{AmazonDynamoDB, AmazonDynamoDBClientBuilder}
import com.amazonaws.services.dynamodbv2.document.{DynamoDB, Item, Table}
import com.softwaremill.sttp._
import de.codecentric.amuttsch.bahndelayinfo.models.Station
import org.json4s._
import org.json4s.native.JsonMethods._
class Repository {
implicit val jsonFormats: DefaultFormats.type = DefaultFormats
implicit val sttpBackend: SttpBackend[Id, Nothing] = HttpURLConnectionBackend()
val ddbClient: AmazonDynamoDB = AmazonDynamoDBClientBuilder.standard.build
val ddb: DynamoDB = new DynamoDB(ddbClient)
val tableStations: Table = ddb.getTable("Stations")
def getStationByEva(eva: Int): Option[Station] = {
tableStations.getItem("eva", eva) match {
case item: Item => parse(item.toJSON).extractOpt[Station]
case null => None
}
}
def searchStationByName(station: String): List[Station] = {
val apiGatewayUrl = scala.util.Properties.envOrElse("APIGATEWAY_URL", "")
val response = sttp
.get(uri"$apiGatewayUrl/stations/?q=$station")
.send()
response.body match {
case Left(_) =>
List.empty[Station]
case Right(body) =>
parse(body).extract[List[Station]]
}
}
}
package de.codecentric.amuttsch.bahndelayinfo.aws
package de.codecentric.amuttsch.bahndelayinfo.models
case class PlannedTimetableElement(
eva: String,
lastFetched: String,
station: String
)
\ No newline at end of file
)
package de.codecentric.amuttsch.bahndelayinfo.models
import de.codecentric.amuttsch.bahndelayinfo.graphql.Repository
import sangria.schema.ObjectType
import sangria.macros.derive._
case class Station(
eva: Int,
ds100: String,
name: String,
latitude: String,
longitude: String,
traffic: String,
ifopt: String
)
object Station {
implicit val graphqlType: ObjectType[Repository, Station] = deriveObjectType(
ObjectTypeDescription("A train station")
)
}
\ No newline at end of file
......@@ -14,8 +14,8 @@ import com.sksamuel.elastic4s.searches.queries.BoolQuery
import com.sksamuel.elastic4s.searches.queries.matches.MatchQuery
import com.softwaremill.sttp._
import de.codecentric.amuttsch.bahndelayinfo.aws
import de.codecentric.amuttsch.bahndelayinfo.aws.Station
import de.codecentric.amuttsch.bahndelayinfo.fetcher.TimetableInformation
import de.codecentric.amuttsch.bahndelayinfo.models.Station
import de.codecentric.amuttsch.bahndelayinfo.utils.JsonSerializers
import org.json4s.native.JsonMethods.parse
import org.json4s.native.Serialization.write
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment