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.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
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 io.ktor.application.call
import io.ktor.application.install
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.launch
import javax.xml.bind.JAXBElement
fun main() {
val repository = PeopleInSpaceRepository()
@ -28,11 +22,23 @@ fun main() {
}
routing {
get("/") {
call.respondText(
this::class.java.classLoader.getResource("index.html")!!.readText(),
ContentType.Text.Html
)
}
static("/") {
resources("")
}
get("/people") {
repository.fetchPeopleAsFlow().collect {
repository.fetchPeopleAsFlow()?.collect {
call.respond(it)
}
}
}
}.start(wait = true)
}
}

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()
mavenCentral()
jcenter()
maven("https://kotlin.bintray.com/kotlin-js-wrappers/")
}
}
tasks.register("clean").configure {
delete("build")
}

View file

@ -10,7 +10,6 @@ android {
compileSdkVersion(29)
buildToolsVersion("29.0.2")
defaultConfig {
minSdkVersion(21)
targetSdkVersion(29)
@ -57,6 +56,11 @@ kotlin {
homepage = "Link to a Kotlin/Native module homepage"
}
js {
browser {
}
}
sourceSets {
val commonMain by getting {
dependencies {
@ -123,9 +127,6 @@ kotlin {
// 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-json-jvm:${Versions.ktor}")
@ -206,6 +207,27 @@ kotlin {
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}")
}
}
}
}
@ -214,4 +236,5 @@ sqldelight {
packageName = "com.surrus.peopleinspace.db"
sourceFolders = listOf("sqldelight")
}
}
}

View file

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

View file

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

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
actual fun createDb(): PeopleInSpaceDatabase {
actual fun createDb(): PeopleInSpaceDatabase? {
val driver = NativeSqliteDriver(PeopleInSpaceDatabase.Schema, "peopleinspace.db")
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
@InternalCoroutinesApi
fun main() = runBlocking {
val api = PeopleInSpaceApi()
println(api.fetchPeople())
fun main() {
runBlocking {
val api = PeopleInSpaceApi()
println(api.fetchPeople())
val repository = PeopleInSpaceRepository()
repository.fetchPeopleAsFlow().collect {
println(it)
val repository = PeopleInSpaceRepository()
repository.fetchPeopleAsFlow()?.collect {
println(it)
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
include(":web")
include(":backend")
rootProject.name = "PeopleInSpace"
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>