Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ hs_err_pid*
/.idea/
build/

# composite build
*/build/
*/.gradle/
*/buildSrc/.gradle/

# OS X DS_Store
.DS_Store
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ group = io.github.ermadmi78
version = 5.3.1-SNAPSHOT

# dependencies
kotlinJdkVersion=11
kotlinJdkVersion=17
kotlinVersion = 1.8.10

graphQLJavaVersion = 20.2
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
5 changes: 5 additions & 0 deletions kobby-gradle-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tasks.register("test") {
rootProject.subprojects.forEach {
dependsOn(":${it.name}:test")
}
}
35 changes: 35 additions & 0 deletions kobby-gradle-tests/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import java.nio.file.Paths
import java.util.*

plugins {
`kotlin-dsl`
}

val props = Properties().apply {
Paths.get(projectDir.parentFile.parent, "gradle.properties").toFile()
.inputStream().use { load(it) }
}

val snapshotKobbyVersion = props["version"]
val testLogger = props["testLogger"]

dependencies {
implementation(testLibs.ktor.server.netty)
implementation(testLibs.ktor.server.websockets)
implementation(testLibs.ktor.server.cors)

implementation(testLibs.graphql.kotlin)
implementation(testLibs.extended.scalars) {
exclude(group = "com.graphql-java", module = "graphql-java")
}

implementation(testLibs.kotlin.gradle.plugin)

// firstly run `publishToMavenLocal`
implementation("io.github.ermadmi78:kobby-gradle-plugin:$snapshotKobbyVersion")
implementation("com.adarshr:gradle-test-logger-plugin:$testLogger")
}

kotlin {
jvmToolchain(props.getProperty("kotlinJdkVersion")!!.toInt())
}
10 changes: 10 additions & 0 deletions kobby-gradle-tests/buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("dev.panuszewski.typesafe-conventions") version "0.10.0"
}

dependencyResolutionManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.ermadmi78.kobby.server

import io.ktor.server.engine.*
import io.ktor.server.netty.*
import java.net.ServerSocket

class ApplicationEntrypoint {
fun createContext(port: Int? = null, wait: Boolean = false): NettyApplicationEngine {
// disable development mode
System.setProperty("io.ktor.development", "false")
val localPort = port ?: ServerSocket(0).use { it.localPort }

println("connecting on port $localPort")
return embeddedServer(Netty, localPort) {
graphQLModule()
}.start(wait)
}
}

fun main(args: Array<String>) {
ApplicationEntrypoint().createContext(18080, true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.github.ermadmi78.kobby.server


import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
import com.expediagroup.graphql.server.ktor.*
import graphql.scalars.ExtendedScalars
import graphql.schema.GraphQLType
import io.github.ermadmi78.kobby.server.controller.ActorQueryService
import io.github.ermadmi78.kobby.server.controller.CountryQueryService
import io.github.ermadmi78.kobby.server.controller.FilmQueryService
import io.github.ermadmi78.kobby.server.controller.MutationsService
import io.github.ermadmi78.kobby.server.controller.SubscriptionService
import io.github.ermadmi78.kobby.server.controller.TaggableQueryService
import io.github.ermadmi78.kobby.server.controller.graphqlIDType
import io.ktor.serialization.jackson.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import java.time.Duration
import java.time.LocalDate
import kotlin.reflect.KClass
import kotlin.reflect.KType

fun Application.graphQLModule() {
install(WebSockets) {
pingPeriod = Duration.ofSeconds(1)
contentConverter = JacksonWebsocketContentConverter()
}
install(StatusPages) {
defaultGraphQLStatusPages()
}
install(CORS) {
anyHost()
}
install(GraphQL) {
schema {
packages = listOf("io.github.ermadmi78.kobby.server")
queries = listOf(
FilmQueryService(), ActorQueryService(), CountryQueryService(), TaggableQueryService()
)
mutations = listOf(MutationsService())
subscriptions = listOf(SubscriptionService())
hooks = object : FlowSubscriptionSchemaGeneratorHooks() {
override fun willGenerateGraphQLType(type: KType): GraphQLType? =
when (type.classifier as? KClass<*>) {
Long::class -> graphqlIDType
Map::class -> ExtendedScalars.Json
LocalDate::class -> ExtendedScalars.Date
else -> null
}
}
}
engine {
dataLoaderRegistryFactory = KotlinDataLoaderRegistryFactory()
}
server {
contextFactory = DefaultKtorGraphQLContextFactory()
}
}
routing {
graphQLGetRoute()
graphQLPostRoute()
graphQLSubscriptionsRoute("graphql-ws")
graphiQLRoute()
graphQLSDLRoute()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.ermadmi78.kobby.server

import io.ktor.server.netty.NettyApplicationEngine
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters

abstract class WebServer : BuildService<BuildServiceParameters.None>, AutoCloseable {
private lateinit var context: NettyApplicationEngine

fun getServerUrl(): String {
if (!this::context.isInitialized) {
this.context = ApplicationEntrypoint().createContext()
}

val port = context.environment.connectors.map { it.port }.first()
return "localhost:$port"
}

override fun close() {
if (this::context.isInitialized) {
this.context.stop()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Query
import io.github.ermadmi78.kobby.server.models.Actor
import io.github.ermadmi78.kobby.server.models.Actor.Companion.accepted
import io.github.ermadmi78.kobby.server.models.Film.Companion.accepted
import io.github.ermadmi78.kobby.server.models.Gender
import java.time.LocalDate

class ActorQueryService : Query {

suspend fun actor(id: Long): Actor? = Actor.get(id)

suspend fun actors(
firstName: String? = null,
lastName: String? = null,
birthdayFrom: LocalDate? = null,
birthdayTo: LocalDate? = null,
gender: Gender? = null,
limit: Int? = null,
offset: Int? = null
): List<Actor> = Actor.all().accepted(firstName, lastName, birthdayFrom, birthdayTo, gender, limit, offset)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Query
import io.github.ermadmi78.kobby.server.models.Country
import io.github.ermadmi78.kobby.server.models.Country.Companion.accepted

class CountryQueryService : Query {

suspend fun country(id: Long): Country? = Country.get(id)

suspend fun countries(
name: String? = null,
limit: Int? = null,
offset: Int? = null
): List<Country> = Country.all().accepted(name, limit, offset)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Query
import io.github.ermadmi78.kobby.server.models.Film
import io.github.ermadmi78.kobby.server.models.Film.Companion.accepted
import io.github.ermadmi78.kobby.server.models.Genre

class FilmQueryService : Query {

suspend fun film(id: Long): Film? = Film.get(id)

suspend fun films(
title: String? = null,
genre: Genre? = null,
limit: Int? = null,
offset: Int? = null
): List<Film> = Film.all().accepted(title, genre, limit, offset)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.ermadmi78.kobby.server.controller

import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.GraphQLScalarType

val graphqlIDType = GraphQLScalarType.newScalar()
.name("ID")
.description("A type representing a Long")
.coercing(IDCoercing as Coercing<*, *>)
.build()

object IDCoercing : Coercing<Long, String> {
override fun parseValue(input: Any): Long = serialize(input).toLong()
override fun parseLiteral(input: Any): Long? = (input as? StringValue)?.value?.toLong()
override fun serialize(dataFetcherResult: Any): String = dataFetcherResult.toString()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Mutation
import io.github.ermadmi78.kobby.server.models.*
import java.time.LocalDate

class MutationsService : Mutation {
suspend fun createCountry(name: String) = Country.create(name)

suspend fun createActor(countryId: Long, actor: ActorInput, tags: TagInput? = null) =
Actor.create(countryId, actor, tags)

suspend fun createFilm(countryId: Long, film: FilmInput, tags: TagInput? = null) =
Film.create(countryId, film, tags)

suspend fun associate(filmId: Long, actorId: Long): Boolean {
val film = Film.get(filmId) ?: return false
val actor = Actor.get(actorId) ?: return false

return if (film.actors.any { it.id == actor.id }) false else {
film.actors.add(actor)
true
}
}

suspend fun tagFilm(filmId: Long, tagValue: String): Boolean {
val film = Film.get(filmId) ?: return false
return if (film.tags.any { it.value == tagValue }) false else {
film.tags.add(Tag(tagValue))
true
}
}

suspend fun tagActor(actorId: Long, tagValue: String): Boolean {
val actor = Actor.get(actorId) ?: return false
return if (actor.tags.any { it.value == tagValue }) false else {
actor.tags.add(Tag(tagValue))
true
}
}

suspend fun updateBirthday(actorId: Long, birthday: LocalDate): Actor? {
val actor = Actor.get(actorId) ?: return null
return actor.apply {
this.birthday = birthday
}
}

suspend fun truncateMutations(): Boolean {
Actor.truncate()
Film.truncate()
Country.truncate()
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Subscription
import io.github.ermadmi78.kobby.server.models.Actor
import io.github.ermadmi78.kobby.server.models.Country
import io.github.ermadmi78.kobby.server.models.Film
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.filter


class SubscriptionService : Subscription {

companion object {
private fun <T> defaultFlow() = MutableSharedFlow<T>(
extraBufferCapacity = 3,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)

private val countriesFlow = defaultFlow<Country>()
private val filmsFlow = defaultFlow<Film>()
private val actorsFlow = defaultFlow<Actor>()

suspend fun emit(country: Country) = countriesFlow.emit(country)
suspend fun emit(film: Film) = filmsFlow.emit(film)
suspend fun emit(actor: Actor) = actorsFlow.emit(actor)
}

fun countryCreated(): Flow<Country> = countriesFlow.asSharedFlow()

fun filmCreated(countryId: Long? = null): Flow<Film> = filmsFlow.asSharedFlow()
.filter { countryId == null || it.countryId == countryId }

fun actorCreated(countryId: Long? = null): Flow<Actor> = actorsFlow.asSharedFlow()
.filter { countryId == null || it.countryId == countryId }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.ermadmi78.kobby.server.controller

import com.expediagroup.graphql.server.operations.Query
import io.github.ermadmi78.kobby.server.models.Actor
import io.github.ermadmi78.kobby.server.models.Film
import io.github.ermadmi78.kobby.server.models.Taggable

class TaggableQueryService : Query {

suspend fun taggable(tag: String): List<Taggable> = buildList {
addAll(Film.all().filter { it.containsTag(tag) })
addAll(Actor.all().filter { it.containsTag(tag) })
}
}
Loading