Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
54 changes: 42 additions & 12 deletions .github/workflows/deploy-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,57 @@ on:
- "master"

jobs:
deploy:
build-server:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Authenticate package registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin
- name: Get tag
run: echo "BUILD_TAG=$(git describe --tags)" >> $GITHUB_ENV

- name: Build and tag image
run: docker build -f server/Dockerfile -t ghcr.io/dellisd/reroute-server:${BUILD_TAG} -t ghcr.io/dellisd/reroute-server:dev .
- name: Push image
if: success()
run: docker push --all-tags ghcr.io/dellisd/reroute-server

build-client:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up Secrets
env:
MAPBOX_KEY: ${{ secrets.MAPBOX_KEY }}
run: echo "mapbox.key=$MAPBOX_KEY" >> local.properties

- name: Build dist
run: ./gradlew :web:jsBrowserWebpack
- name: Authenticate package registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin
- name: Get tag
run: echo "BUILD_TAG=$(git describe --tags)" >> $GITHUB_ENV

- name: Deploy 🚉
- name: Build and tag image
run: docker build -f web/Dockerfile -t ghcr.io/dellisd/reroute-client:${BUILD_TAG} -t ghcr.io/dellisd/reroute-client:dev .
- name: Push image
if: success()
uses: JamesIves/[email protected]
run: docker push --all-tags ghcr.io/dellisd/reroute-client

deploy:
runs-on: ubuntu-latest
needs: [build-server, build-client]
steps:
- name: Deploy 🚉
uses: appleboy/ssh-action@master
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: web/build/distributions
SINGLE_COMMIT: true
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
script: |
docker pull ghcr.io/dellisd/reroute-server:dev ghcr.io/dellisd/reroute-client:dev
docker stack deploy -c ${{ secrets.DOCKER_DEPLOY_PATH }}/docker-compose.yml api --with-registry-auth
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
plugins {
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
}

group = "ca.derekellis.reroute"
Expand Down
19 changes: 17 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[versions]
kotlin = "1.7.0"
kotlinx-serialization = "1.3.3"
sqldelight = "2.0.0-SNAPSHOT"
ktor = "2.0.2"
inject = "0.4.1"
Expand All @@ -11,28 +12,42 @@ absurdSql = "0.0.53"


[libraries]
kotlinx-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"

# Ktor Server
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-server-autoHead = { module = "io.ktor:ktor-server-auto-head-response", version.ref = "ktor" }
# Ktor Client
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# Ktor Other
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }

sqldelight-driver-sqljs = { module = "app.cash.sqldelight:sqljs-driver", version.ref = "sqldelight" }
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
sqldelight-primitiveAdapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqldelight" }

inject-compiler = { module = "me.tatarka.inject:kotlin-inject-compiler-ksp", version.ref = "inject" }
inject-runtime = { module = "me.tatarka.inject:kotlin-inject-runtime", version.ref = "inject" }

compose-routing = "app.softwork:routing-compose:0.2.3"
spatialk-geojson = "io.github.dellisd.spatialk:geojson:0.2.0"
kgtfs-gtfs = "ca.derekellis.kgtfs:gtfs:0.1.0-SNAPSHOT"
logback = "ch.qos.logback:logback-classic:1.2.11"
klock = "com.soywiz.korlibs.klock:klock:2.2.0"

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose = { id = "org.jetbrains.compose", version = "0.0.0-on_kotlin_1.7.0-rc-dev705" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
ksp = { id = "com.google.devtools.ksp", version = "1.7.0-1.0.6" }
buildkonfig = { id = "com.codingfeline.buildkonfig", version = "0.12.0" }
shadow = { id = "com.github.johnrengelman.shadow", version = "7.1.2" }
5 changes: 0 additions & 5 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
resolved "https://registry.yarnpkg.com/@jlongster/sql.js/-/sql.js-1.6.7.tgz#f5391db62c25a6ba8a162bdcfed6e3392ef2b40f"
integrity sha512-4hf0kZr5WPoirdR5hUSfQ9O0JpH/qlW1CaR2wZ6zGrDz1xjSdTPuR8AW/oXzIHnJvZSEvlcIE+dfXJZwh/Lxfw==

"@js-joda/[email protected]":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==

"@mapbox/geojson-rewind@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz#adbe16dc683eb40e90934c51a5e28c7bbf44f4e1"
Expand Down
15 changes: 15 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM gradle:jdk17 AS builder

COPY . /home/gradle/src
WORKDIR /home/gradle/src

RUN gradle :server:shadowJar

FROM openjdk:17-slim-buster

COPY --from=builder /home/gradle/src/server/build/libs /usr/src/app
WORKDIR /usr/src/app
EXPOSE 8888
CMD ["java", "-jar", "server-all.jar"]

LABEL org.opencontainers.image.source=https://github.com/dellisd/reroute
49 changes: 49 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ksp)
alias(libs.plugins.shadow)
application
}

group = "ca.derekellis.reroute"
version = "1.0-SNAPSHOT"

repositories {
google()
mavenCentral()
maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots")
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots")
}

dependencies {
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.contentNegotiation)
implementation(libs.ktor.server.autoHead)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.serialization.json)
implementation(libs.logback)
implementation(libs.inject.runtime)
implementation(libs.kgtfs.gtfs)
implementation(project(":shared"))

ksp(libs.inject.compiler)
}

kotlin {
sourceSets {
getByName("main") {
kotlin.srcDir("$buildDir/generated/ksp/main/kotlin")
}
}
}

tasks.withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"
}

application {
mainClass.set("ca.derekellis.reroute.server.MainKt")
}
26 changes: 26 additions & 0 deletions server/src/main/kotlin/ca/derekellis/reroute/server/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ca.derekellis.reroute.server

import ca.derekellis.reroute.server.di.ServerComponent
import ca.derekellis.reroute.server.di.create
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.autohead.AutoHeadResponse
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.routing.routing

fun main() {
val component = ServerComponent::class.create()

embeddedServer(Netty, port = 8888) {
install(ContentNegotiation) {
json()
}
install(AutoHeadResponse)

routing {
component.dataRoute.routes()
}
}.start(wait = true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package ca.derekellis.reroute.server.data

import app.cash.sqldelight.db.SqlCursor
import ca.derekellis.kgtfs.dsl.Gtfs
import ca.derekellis.reroute.models.Route
import ca.derekellis.reroute.models.RouteAtStop
import ca.derekellis.reroute.models.Stop
import ca.derekellis.reroute.models.TransitDataBundle
import ca.derekellis.reroute.server.di.ServerScope
import io.github.dellisd.spatialk.geojson.Position
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import me.tatarka.inject.annotations.Inject
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.div
import kotlin.io.path.notExists
import kotlin.io.path.outputStream

/**
* TODO: Parameterize the GTFS data source
*/
@Inject
@ServerScope
class DataHandler {
private val logger = LoggerFactory.getLogger(javaClass)
private val cacheDir: Path = Files.createTempDirectory("reroute")
private val gtfs = Gtfs(
source = "https://www.octranspo.com/files/google_transit.zip",
dbPath = (cacheDir / "gtfs.db").toString()
)
private val cachePrepLock = Mutex()

private val dataFile = cacheDir / "data.json"

init {
logger.debug("Writing data files to $cacheDir")
}

/**
* TODO: Handle cache invalidation, somehow
*/
@OptIn(ExperimentalSerializationApi::class)
suspend fun getDataFile(): Path {
cachePrepLock.withLock {
if (dataFile.notExists()) {
logger.info("Data file does not exist, generating one")
val data = prepData()

Json.encodeToStream(data, dataFile.outputStream())
}
}

return dataFile
}

private suspend fun prepData(): TransitDataBundle = gtfs {
val calendars = calendar.allOnDate()
val args = createArguments(calendars.size)

val stops = stops.getAll()
.filter { it.latitude != null && it.name != null }
.map { stop ->
val position = stop.longitude?.let { lng -> stop.latitude?.let { lat -> Position(lng, lat) } }
Stop(stop.id.value, stop.code, stop.name!!, position!!)
}

//language=SQLite
val routeQueryResults = rawQuery("""
SELECT R.route_id as gtfs_id, route_short_name, trip_headsign, direction_id, COUNT(*) as weight
FROM Trip
JOIN Route R on Trip.route_id = R.route_id
WHERE Trip.service_id IN $args
GROUP BY Trip.route_id, trip_headsign, direction_id;
""".trimIndent(), mapper = { cursor ->
cursor.map {
Route(
"",
it.getString(0)!!,
it.getString(1)!!,
it.getString(2)!!,
it.getLong(3)!!.toInt(),
it.getLong(4)!!.toInt()
)
}
}, parameters = calendars.size, binders = {
calendars.forEachIndexed { i, calendar -> bindString(i + 1, calendar.serviceId.value) }
})

val routes = routeQueryResults
.groupBy { "${it.name}-${it.directionId}" }
.mapValues { (key, values) -> values.mapIndexed { i, route -> route.copy(id = "$key#$i") } }
.flatMap { (_, values) -> values }
val routesIndex = routes.associateBy { "${it.gtfsId}-${it.directionId}-${it.headsign}" }

//language=SQLite
val mappingQueryResults = rawQuery("""
SELECT DISTINCT stop_id, route_id, direction_id, trip_headsign
FROM StopTime
JOIN Trip T on StopTime.trip_id = T.trip_id
WHERE T.service_id IN $args;
""".trimIndent(), mapper = { cursor ->
cursor.map {
val routeKey = "${it.getString(1)}-${it.getLong(2)}-${it.getString(3)}"
RouteAtStop(it.getString(0)!!, routesIndex.getValue(routeKey).id)
}
}, parameters = calendars.size, binders = {
calendars.forEachIndexed { i, calendar -> bindString(i + 1, calendar.serviceId.value) }
})

TransitDataBundle(stops, routes, mappingQueryResults)
}

private fun <T> SqlCursor.map(mapper: (SqlCursor) -> T): List<T> = buildList {
while (next()) {
add(mapper(this@map))
}
}

private fun createArguments(count: Int): String {
if (count == 0) return "()"

return buildString(count + 2) {
append("(?")
repeat(count - 1) {
append(",?")
}
append(')')
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ca.derekellis.reroute.server.di

import ca.derekellis.reroute.server.routes.DataRoute
import me.tatarka.inject.annotations.Component

@Component
@ServerScope
abstract class ServerComponent {
abstract val dataRoute: DataRoute
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ca.derekellis.reroute.server.di

import me.tatarka.inject.annotations.Scope
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER

@Scope
@Target(CLASS, FUNCTION, PROPERTY_GETTER)
annotation class ServerScope
Loading