initial cut of backend/web modules

This commit is contained in:
John O'Reilly 2020-04-26 19:25:59 +01:00
parent 072d39d101
commit d94534a2b8
20 changed files with 196 additions and 38 deletions

1
backend/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

21
backend/build.gradle.kts Normal file
View file

@ -0,0 +1,21 @@
plugins {
id("kotlin-platform-jvm")
application
kotlin("plugin.serialization")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}")
implementation("io.ktor:ktor-server-core:${Versions.ktor}")
implementation("io.ktor:ktor-server-netty:${Versions.ktor}")
implementation("io.ktor:ktor-serialization:${Versions.ktor}")
implementation("ch.qos.logback:logback-classic:1.2.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.kotlinxSerialization}") // JVM dependency
implementation("io.ktor:ktor-websockets:${Versions.ktor}")
implementation(project(":common"))
}

View file

@ -1,23 +1,17 @@
package com.surrus
import io.ktor.response.* import io.ktor.response.*
import io.ktor.routing.* import io.ktor.routing.*
import io.ktor.serialization.* import io.ktor.serialization.*
import io.ktor.server.engine.* import io.ktor.server.engine.*
import io.ktor.server.netty.* import io.ktor.server.netty.*
import org.litote.kmongo.*
import org.litote.kmongo.async.*
import org.litote.kmongo.coroutine.*
import org.litote.kmongo.async.getCollection
import com.mongodb.ConnectionString
import com.surrus.common.repository.PeopleInSpaceRepository import com.surrus.common.repository.PeopleInSpaceRepository
import io.ktor.application.call import io.ktor.application.call
import io.ktor.application.install import io.ktor.application.install
import io.ktor.features.ContentNegotiation import io.ktor.features.ContentNegotiation
import kotlinx.coroutines.GlobalScope import io.ktor.http.ContentType
import io.ktor.http.content.resources
import io.ktor.http.content.static
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.xml.bind.JAXBElement
fun main() { fun main() {
val repository = PeopleInSpaceRepository() val repository = PeopleInSpaceRepository()
@ -28,8 +22,20 @@ fun main() {
} }
routing { routing {
get("/") {
call.respondText(
this::class.java.classLoader.getResource("index.html")!!.readText(),
ContentType.Text.Html
)
}
static("/") {
resources("")
}
get("/people") { get("/people") {
repository.fetchPeopleAsFlow().collect { repository.fetchPeopleAsFlow()?.collect {
call.respond(it) call.respond(it)
} }
} }

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>People In Space</title>
</head>
<body>
<h1>People In Space</h1>
<div id="root"></div>
</body>
</html>

View file

@ -19,10 +19,8 @@ allprojects {
google() google()
mavenCentral() mavenCentral()
jcenter() jcenter()
maven("https://kotlin.bintray.com/kotlin-js-wrappers/")
} }
} }
tasks.register("clean").configure {
delete("build")
}

View file

@ -10,7 +10,6 @@ android {
compileSdkVersion(29) compileSdkVersion(29)
buildToolsVersion("29.0.2") buildToolsVersion("29.0.2")
defaultConfig { defaultConfig {
minSdkVersion(21) minSdkVersion(21)
targetSdkVersion(29) targetSdkVersion(29)
@ -57,6 +56,11 @@ kotlin {
homepage = "Link to a Kotlin/Native module homepage" homepage = "Link to a Kotlin/Native module homepage"
} }
js {
browser {
}
}
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
@ -123,9 +127,6 @@ kotlin {
// Ktor // Ktor
implementation("io.ktor:ktor-server-core:${Versions.ktor}") implementation("io.ktor:ktor-server-core:${Versions.ktor}")
implementation("io.ktor:ktor-server-netty:${Versions.ktor}")
implementation("io.ktor:ktor-websockets:${Versions.ktor}")
implementation("org.litote.kmongo:kmongo-coroutine-serialization:3.12.2")
implementation("io.ktor:ktor-client-core-jvm:${Versions.ktor}") implementation("io.ktor:ktor-client-core-jvm:${Versions.ktor}")
implementation("io.ktor:ktor-client-json-jvm:${Versions.ktor}") implementation("io.ktor:ktor-client-json-jvm:${Versions.ktor}")
@ -206,6 +207,27 @@ kotlin {
implementation("com.squareup.sqldelight:runtime-macosx64:${Versions.sqlDelight}") implementation("com.squareup.sqldelight:runtime-macosx64:${Versions.sqlDelight}")
} }
} }
val jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.kotlinCoroutines}")
// ktor
implementation("io.ktor:ktor-client-js:${Versions.ktor}") //include http&websockets
implementation("io.ktor:ktor-client-json-js:${Versions.ktor}")
implementation("io.ktor:ktor-client-logging-js:${Versions.ktor}")
implementation("io.ktor:ktor-client-serialization-js:${Versions.ktor}")
// Serialize
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:${Versions.kotlinxSerialization}")
// SQL Delight
//implementation("com.squareup.sqldelight:sqljs-driver:${Versions.sqlDelight}")
implementation("com.squareup.sqldelight:runtime-js:${Versions.sqlDelight}")
}
}
} }
} }
@ -215,3 +237,4 @@ sqldelight {
sourceFolders = listOf("sqldelight") sourceFolders = listOf("sqldelight")
} }
} }

View file

@ -10,7 +10,7 @@ import kotlinx.coroutines.launch
lateinit var appContext: Context lateinit var appContext: Context
actual fun createDb(): PeopleInSpaceDatabase { actual fun createDb(): PeopleInSpaceDatabase? {
val driver = AndroidSqliteDriver(PeopleInSpaceDatabase.Schema, appContext, "peopleinspace.db") val driver = AndroidSqliteDriver(PeopleInSpaceDatabase.Schema, appContext, "peopleinspace.db")
return PeopleInSpaceDatabase(driver) return PeopleInSpaceDatabase(driver)
} }

View file

@ -9,7 +9,7 @@ import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
expect fun createDb() : PeopleInSpaceDatabase expect fun createDb() : PeopleInSpaceDatabase?
// TEMP until following is resolved https://github.com/ktorio/ktor/issues/1622 // TEMP until following is resolved https://github.com/ktorio/ktor/issues/1622
expect fun ktorScope(block: suspend () -> Unit) expect fun ktorScope(block: suspend () -> Unit)
@ -18,7 +18,7 @@ expect fun ktorScope(block: suspend () -> Unit)
class PeopleInSpaceRepository { class PeopleInSpaceRepository {
private val peopleInSpaceApi = PeopleInSpaceApi() private val peopleInSpaceApi = PeopleInSpaceApi()
private val peopleInSpaceDatabase = createDb() private val peopleInSpaceDatabase = createDb()
private val peopleInSpaceQueries = peopleInSpaceDatabase.peopleInSpaceQueries private val peopleInSpaceQueries = peopleInSpaceDatabase?.peopleInSpaceQueries
init { init {
ktorScope { ktorScope {
@ -26,25 +26,25 @@ class PeopleInSpaceRepository {
} }
} }
fun fetchPeopleAsFlow() = peopleInSpaceQueries.selectAll(mapper = { name, craft -> fun fetchPeopleAsFlow() = peopleInSpaceQueries?.selectAll(mapper = { name, craft ->
Assignment(name = name, craft = craft) Assignment(name = name, craft = craft)
}).asFlow().mapToList() })?.asFlow()?.mapToList()
private suspend fun fetchAndStorePeople() { private suspend fun fetchAndStorePeople() {
val result = peopleInSpaceApi.fetchPeople() val result = peopleInSpaceApi.fetchPeople()
// this is very basic implementation for now that removes all existing rows // this is very basic implementation for now that removes all existing rows
// in db and then inserts reults from api request // in db and then inserts reults from api request
peopleInSpaceQueries.deleteAll() peopleInSpaceQueries?.deleteAll()
result.people.forEach { result.people.forEach {
peopleInSpaceQueries.insertItem(it.name, it.craft) peopleInSpaceQueries?.insertItem(it.name, it.craft)
} }
} }
// called from iOS/watchOS/macOS client // called from iOS/watchOS/macOS client
fun fetchPeople(success: (List<Assignment>) -> Unit) { fun fetchPeople(success: (List<Assignment>) -> Unit) {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
fetchPeopleAsFlow().collect { fetchPeopleAsFlow()?.collect {
success(it) success(it)
} }
} }

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
actual fun createDb(): PeopleInSpaceDatabase { actual fun createDb(): PeopleInSpaceDatabase? {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db") val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
return PeopleInSpaceDatabase(driver) return PeopleInSpaceDatabase(driver)
} }

View file

@ -0,0 +1,15 @@
package com.surrus.common.repository
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
actual fun createDb(): PeopleInSpaceDatabase? {
return null
}
actual fun ktorScope(block: suspend () -> Unit) {
GlobalScope.launch(Dispatchers.Main) { block() }
}

View file

@ -7,12 +7,14 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@InternalCoroutinesApi @InternalCoroutinesApi
fun main() = runBlocking { fun main() {
runBlocking {
val api = PeopleInSpaceApi() val api = PeopleInSpaceApi()
println(api.fetchPeople()) println(api.fetchPeople())
val repository = PeopleInSpaceRepository() val repository = PeopleInSpaceRepository()
repository.fetchPeopleAsFlow().collect { repository.fetchPeopleAsFlow()?.collect {
println(it) println(it)
} }
} }
}

View file

@ -4,7 +4,7 @@ import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
actual fun createDb(): PeopleInSpaceDatabase { actual fun createDb(): PeopleInSpaceDatabase? {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
.also { PeopleInSpaceDatabase.Schema.create(it) } .also { PeopleInSpaceDatabase.Schema.create(it) }
return PeopleInSpaceDatabase(driver) return PeopleInSpaceDatabase(driver)

View file

@ -3,7 +3,7 @@ package com.surrus.common.repository
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
import com.surrus.peopleinspace.db.PeopleInSpaceDatabase import com.surrus.peopleinspace.db.PeopleInSpaceDatabase
actual fun createDb(): PeopleInSpaceDatabase { actual fun createDb(): PeopleInSpaceDatabase? {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db") val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
return PeopleInSpaceDatabase(driver) return PeopleInSpaceDatabase(driver)
} }

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
actual fun createDb(): PeopleInSpaceDatabase { actual fun createDb(): PeopleInSpaceDatabase? {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db") val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
return PeopleInSpaceDatabase(driver) return PeopleInSpaceDatabase(driver)
} }

View file

@ -1,3 +1,5 @@
include(":web")
include(":backend")
rootProject.name = "PeopleInSpace" rootProject.name = "PeopleInSpace"
enableFeaturePreview("GRADLE_METADATA") enableFeaturePreview("GRADLE_METADATA")

1
web/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

29
web/build.gradle.kts Normal file
View file

@ -0,0 +1,29 @@
plugins {
kotlin("js")
kotlin("plugin.serialization")
}
dependencies {
implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.5")
implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1")
implementation(npm("text-encoding"))
implementation(npm("abort-controller"))
implementation(npm("bufferutil"))
implementation(npm("utf-8-validate"))
implementation(npm("fs"))
//React, React DOM + Wrappers (chapter 3)
implementation("org.jetbrains:kotlin-react:16.13.0-pre.93-kotlin-1.3.70")
implementation("org.jetbrains:kotlin-react-dom:16.13.0-pre.93-kotlin-1.3.70")
implementation(npm("react", "16.13.0"))
implementation(npm("react-dom", "16.13.0"))
implementation(project(":common"))
}
kotlin.target.browser { }

View file

@ -0,0 +1,29 @@
import com.surrus.common.remote.Assignment
import com.surrus.common.remote.PeopleInSpaceApi
import react.*
import react.dom.*
import kotlinx.coroutines.*
val scope = MainScope()
val api = PeopleInSpaceApi()
val App = functionalComponent<RProps> { _ ->
val (people, setPeople) = useState(emptyList<Assignment>())
useEffect(dependencies = listOf()) {
scope.launch {
setPeople(api.fetchPeople().people)
}
}
h1 {
+"People In Space"
}
ul {
people.forEach { item ->
li {
+"${item.name} (${item.craft})"
}
}
}
}

View file

@ -0,0 +1,9 @@
import react.child
import react.dom.render
import kotlin.browser.document
fun main() {
render(document.getElementById("root")) {
child(functionalComponent = App)
}
}

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>People In Space</title>
</head>
<body>
<div id="root"></div>
<script src="web.js"></script>
</body>
</html>