Skip to content

Commit

Permalink
aPureBase#37 The context needs to be set per request
Browse files Browse the repository at this point in the history
  • Loading branch information
Allali84 committed Mar 1, 2020
1 parent f96c9c5 commit e11ffa9
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 67 deletions.
1 change: 1 addition & 0 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mainClassName = "io.ktor.server.netty.EngineMain"
dependencies {
compile project(':kgraphql-ktor')
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-auth:$ktor_version"
compile "ch.qos.logback:logback-classic:$logback_version"
compile "org.jetbrains.exposed:exposed-core:$exposed_version"
compile "org.jetbrains.exposed:exposed-jdbc:$exposed_version"
Expand Down
137 changes: 81 additions & 56 deletions example/src/main/kotlin/com/apurebase/kgraphql/Application.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.apurebase.kgraphql

import com.apurebase.kgraphql.dao.DatabaseFactory
import com.apurebase.kgraphql.exception.NotAuthenticatedException
import com.apurebase.kgraphql.exception.NotFoundException
import com.apurebase.kgraphql.model.*
import com.apurebase.kgraphql.service.UFOSightingService
import io.ktor.application.Application
import io.ktor.application.ApplicationCall
import io.ktor.application.install
import io.ktor.auth.Authentication
import io.ktor.auth.authenticate
import io.ktor.auth.authentication
import io.ktor.auth.basic
import io.ktor.features.CORS
import io.ktor.features.DefaultHeaders
import io.ktor.http.HttpHeaders
Expand All @@ -17,6 +23,11 @@ import java.time.LocalDate

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

private val contextSetup: ContextBuilder.(ApplicationCall) -> Unit = { call ->
call.authentication.principal<User>()?.let {
+it
}
}

@UnstableDefault
@KtorExperimentalAPI
Expand All @@ -30,93 +41,107 @@ fun Application.module() {
anyHost()
maxAgeInSeconds = CORS.CORS_DEFAULT_MAX_AGE
}
install(Authentication) {
basic {
realm = "ktor"
validate {
User(4, it.name)
}
}
}
DatabaseFactory.init()
val service = UFOSightingService()
routing {
graphql {
authenticate(optional = true) {
graphql(contextSetup) {

configure {
useDefaultPrettyPrinter = true
}
configure {
useDefaultPrettyPrinter = true
}

stringScalar<LocalDate> {
serialize = { date -> date.toString() }
deserialize = { dateString -> LocalDate.parse(dateString) }
}
stringScalar<LocalDate> {
serialize = { date -> date.toString() }
deserialize = { dateString -> LocalDate.parse(dateString) }
}

query("sightings") {
description = "Returns a subset of the UFO Sighting records"
query("sightings") {
description = "Returns a subset of the UFO Sighting records"

resolver { size: Int? -> service.findAll(size ?: 10).toMutableList() }.withArgs {
arg<Int> { name = "size"; defaultValue = 10; description = "The number of records to return" }
resolver { size: Int? -> service.findAll(size ?: 10).toMutableList() }.withArgs {
arg<Int> { name = "size"; defaultValue = 10; description = "The number of records to return" }
}
}
}

query("sighting") {
description = "Returns a single UFO Sighting record based on the id"
query("sighting") {
description = "Returns a single UFO Sighting record based on the id"

resolver { id: Int ->
service.findById(id) ?: throw NotFoundException("Sighting with id: $id does not exist")
resolver { id: Int ->
service.findById(id) ?: throw NotFoundException("Sighting with id: $id does not exist")
}
}
}

query("user") {
description = "Returns a single User based on the id"
query("user") {
description = "Returns a single User based on the id or the authenticated user."

resolver { id: Int ->
users.getOrNull(id - 1) ?: throw NotFoundException("User with id: $id does not exist")
resolver { ctx: Context, id: Int? ->
if (id == null) {
User(4, ctx.get<User>()?.name ?: throw NotAuthenticatedException())
} else {
users.getOrNull(id - 1) ?: throw NotFoundException("User with id: $id does not exist")
}
}
}
}

type<User> {
description = "A User who has reported a UFO sighting"
type<User> {
description = "A User who has reported a UFO sighting"

property<UFOSighting?>("sighting") {
resolver { user -> service.findById(user.id) }
property<UFOSighting?>("sighting") {
resolver { user -> service.findById(user.id) }
}
}
}

mutation("createUFOSighting") {
description = "Adds a new UFO Sighting to the database"
mutation("createUFOSighting") {
description = "Adds a new UFO Sighting to the database"

resolver {
input: CreateUFOSightingInput -> service.create(input.toUFOSighting())
}
resolver {
input: CreateUFOSightingInput -> service.create(input.toUFOSighting())
}

}
}

query("topSightings") {
description = "Returns a list of the top state,country based on the number of sightings"
query("topSightings") {
description = "Returns a list of the top state,country based on the number of sightings"

resolver { -> service.getTopSightings() }
}
resolver { -> service.getTopSightings() }
}

query("topCountrySightings") {
description = "Returns a list of the top countries based on the number of sightings"
query("topCountrySightings") {
description = "Returns a list of the top countries based on the number of sightings"

resolver { -> service.getTopCountrySightings() }
}
resolver { -> service.getTopCountrySightings() }
}

type<CountrySightings> {
description = "A country sighting; contains total number of occurrences"
type<CountrySightings> {
description = "A country sighting; contains total number of occurrences"

property(CountrySightings::numOccurrences) {
description = "The number of occurrences of the sighting"
property(CountrySightings::numOccurrences) {
description = "The number of occurrences of the sighting"
}
}
}

inputType<CreateUFOSightingInput>()
inputType<CreateUFOSightingInput>()

type<UFOSighting> {
description = "A UFO sighting"
type<UFOSighting> {
description = "A UFO sighting"

property(UFOSighting::dateSighting) {
description = "The date of the sighting"
}
property(UFOSighting::dateSighting) {
description = "The date of the sighting"
}

property<User>("user") {
resolver {
users[(0..2).shuffled().last()]
property<User>("user") {
resolver {
users[(0..2).shuffled().last()]
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.apurebase.kgraphql.exception

class NotAuthenticatedException : RuntimeException("There is no user authenticated")
4 changes: 3 additions & 1 deletion example/src/main/kotlin/com/apurebase/kgraphql/model/User.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.apurebase.kgraphql.model

import io.ktor.auth.Principal

val users = listOf(
User(id = 1, name = "Amber"),
User(id = 2, name = "Greg"),
User(id = 3, name = "Frank"))

data class User(val id: Int = -1, val name: String = "")
data class User(val id: Int = -1, val name: String = ""): Principal
1 change: 1 addition & 0 deletions kgraphql-ktor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dependencies {

testImplementation "com.github.makarenkoanton:kraph:0.6.1"
testImplementation "io.ktor:ktor-server-test-host:$ktor_version"
testImplementation "io.ktor:ktor-auth:$ktor_version"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,16 @@ const val GRAPHQL_ENDPOINT = "graphql"

@KtorExperimentalAPI
@UnstableDefault
fun Route.graphql(ctx: Context? = null, block: SchemaBuilder.() -> Unit) {
fun Route.graphql(ctxBuilder: ContextBuilder.(ApplicationCall) -> Unit = {}, block: SchemaBuilder.() -> Unit) {
val schema = KGraphQL.schema(block)
install(ContentNegotiation) {
register(ContentType.Application.Json, GraphqlSerializationConverter())
serialization(json = Json.nonstrict)
}
post(GRAPHQL_ENDPOINT) {
val request = call.receive<GraphqlRequest>()
val result = if (ctx != null)
schema.execute(request.query, request.variables.toString(), ctx)
else
schema.execute(request.query, request.variables.toString())
val ctx = context { ctxBuilder(this, call) }
val result = schema.execute(request.query, request.variables.toString(), ctx)
call.respond(result)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.apurebase.kgraphql

import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
import io.ktor.application.ApplicationCall
import io.ktor.application.install
import io.ktor.auth.*
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.routing.routing
Expand All @@ -14,13 +17,25 @@ import org.junit.jupiter.api.Test

class KtorFeatureTest {

data class User(val id: Int = -1, val name: String = ""): Principal

@UnstableDefault
@KtorExperimentalAPI
private fun withServer(ctx: Context? = null, block: SchemaBuilder.() -> Unit): (Kraph.() -> Unit) -> String {
private fun withServer(ctxBuilder: ContextBuilder.(ApplicationCall) -> Unit = {}, block: SchemaBuilder.() -> Unit): (Kraph.() -> Unit) -> String {
return {
withTestApplication({
install(Authentication) {
basic {
realm = "ktor"
validate {
User(4, it.name)
}
}
}
routing {
graphql(ctx) { block() }
authenticate(optional = true) {
graphql(ctxBuilder) { block() }
}
}
}) {
handleRequest {
Expand Down Expand Up @@ -77,11 +92,12 @@ class KtorFeatureTest {
@Test
fun `Simple context test`() {
val georgeName = "George"
val context = context {
val contextSetup: ContextBuilder.(ApplicationCall) -> Unit = { _ ->
+ UserData(georgeName, "STUFF")
inject("ADA")
}
val server = withServer(context) {

val server = withServer(contextSetup) {
query("actor") {
resolver { -> Actor("George", 23) }
}
Expand Down Expand Up @@ -141,5 +157,4 @@ class KtorFeatureTest {
}
} shouldBeEqualTo "{\"data\":{\"test\":\"success: InputTwo(one=InputOne(enum=M1, id=M1), quantity=3434, tokens=[23, 34, 21, 434])\"}}"
}

}

0 comments on commit e11ffa9

Please sign in to comment.