First commit
This commit is contained in:
15
.fleet/run.json
Normal file
15
.fleet/run.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "test [run] (1)",
|
||||||
|
"type": "gradle",
|
||||||
|
"workingDir": "$PROJECT_DIR$",
|
||||||
|
"tasks": ["run"],
|
||||||
|
"args": [""],
|
||||||
|
"initScripts": {
|
||||||
|
"flmapper": "ext.mapPath = { path -> path }"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Enable version updates for Gradle
|
||||||
|
- package-ecosystem: "gradle"
|
||||||
|
# Look for `build.gradle` in the `root` directory
|
||||||
|
directory: "/"
|
||||||
|
# Check for updates once daily
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# This workflow will build a Java project with Gradle
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||||
|
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
java: [ '21' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
|
- name: Set up JDK ${{ matrix.java }}
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew build
|
||||||
|
- name: Upload coverage reports
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
files: build/reports/kover/report.xml
|
||||||
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
.DS_Store
|
||||||
|
.idea/shelf
|
||||||
|
/android.tests.dependencies
|
||||||
|
/confluence/target
|
||||||
|
/dependencies
|
||||||
|
/dist
|
||||||
|
/local
|
||||||
|
/gh-pages
|
||||||
|
/ideaSDK
|
||||||
|
/clionSDK
|
||||||
|
/android-studio/sdk
|
||||||
|
out/
|
||||||
|
/tmp
|
||||||
|
workspace.xml
|
||||||
|
*.versionsBackup
|
||||||
|
/idea/testData/debugger/tinyApp/classes*
|
||||||
|
/jps-plugin/testData/kannotator
|
||||||
|
/ultimate/dependencies
|
||||||
|
/ultimate/ideaSDK
|
||||||
|
/ultimate/out
|
||||||
|
/ultimate/tmp
|
||||||
|
/js/js.translator/testData/out/
|
||||||
|
/js/js.translator/testData/out-min/
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!**/src/**/build
|
||||||
|
!**/test/**/build
|
||||||
|
*.iml
|
||||||
|
!**/testData/**/*.iml
|
||||||
|
*.iml
|
||||||
|
/local.properties
|
||||||
|
/.idea
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
4
.kotlin/errors/errors-1732378159970.log
Normal file
4
.kotlin/errors/errors-1732378159970.log
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||||
|
1. Kotlin compile daemon is ready
|
||||||
|
|
||||||
4
.kotlin/errors/errors-1732477966480.log
Normal file
4
.kotlin/errors/errors-1732477966480.log
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||||
|
1. Kotlin compile daemon is ready
|
||||||
|
|
||||||
4
.kotlin/errors/errors-1734368771992.log
Normal file
4
.kotlin/errors/errors-1734368771992.log
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||||
|
1. Kotlin compile daemon is ready
|
||||||
|
|
||||||
4
.kotlin/errors/errors-1735820034213.log
Normal file
4
.kotlin/errors/errors-1735820034213.log
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||||
|
1. Kotlin compile daemon is ready
|
||||||
|
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM gradle:8-jdk21 AS build
|
||||||
|
|
||||||
|
USER gradle
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY build.gradle settings.gradle ./
|
||||||
|
COPY src/ ./src
|
||||||
|
|
||||||
|
RUN gradle installDist --no-daemon
|
||||||
|
|
||||||
|
FROM eclipse-temurin:21-jre-jammy
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /app/build/install/kotlin-ktor-exposed-starter .
|
||||||
|
|
||||||
|
ENTRYPOINT ["./bin/kotlin-ktor-exposed-starter"]
|
||||||
101
README.md
Normal file
101
README.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
[](http://kotlinlang.org)
|
||||||
|
[](https://github.com/ktorio/ktor)
|
||||||
|
[](https://github.com/raharrison/kotlin-ktor-exposed-starter/actions/workflows/build.yml)
|
||||||
|
[](https://codecov.io/gh/raharrison/kotlin-ktor-exposed-starter)
|
||||||
|
|
||||||
|
## Starter project to create a simple RESTful web service in Kotlin
|
||||||
|
|
||||||
|
**Updated for Kotlin 2.0.21 and Ktor 3.0.0**
|
||||||
|
|
||||||
|
Companion article: <https://ryanharrison.co.uk/2018/04/14/kotlin-ktor-exposed-starter.html>
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Clone the repo.
|
||||||
|
2. In the root directory execute `./gradlew run`
|
||||||
|
3. By default, the server will start on port `8080`. See below [Routes](#routes) section for more information.
|
||||||
|
|
||||||
|
### Libraries used:
|
||||||
|
|
||||||
|
- [Ktor](https://github.com/ktorio/ktor) - Kotlin async web framework
|
||||||
|
- [Netty](https://github.com/netty/netty) - Async web server
|
||||||
|
- [Kotlin Serialization](https://github.com/Kotlin/kotlinx.serialization) - JSON serialization/deserialization
|
||||||
|
- [Exposed](https://github.com/JetBrains/Exposed) - Kotlin SQL framework
|
||||||
|
- [H2](https://github.com/h2database/h2database) - Embeddable database
|
||||||
|
- [HikariCP](https://github.com/brettwooldridge/HikariCP) - High performance JDBC connection pooling
|
||||||
|
- [Flyway](https://flywaydb.org/) - Database migrations
|
||||||
|
- [JUnit 5](https://junit.org/junit5/), [AssertJ](http://joel-costigliola.github.io/assertj/)
|
||||||
|
and [Rest Assured](http://rest-assured.io/) for testing
|
||||||
|
- [Kover](https://github.com/Kotlin/kotlinx-kover) for code coverage, publishing
|
||||||
|
to [Codecov](https://about.codecov.io/) through GitHub Actions
|
||||||
|
|
||||||
|
The starter project creates a new in-memory H2 database with one table for `Widget` instances.
|
||||||
|
|
||||||
|
As ktor is async and based on coroutines, standard blocking JDBC may cause performance issues when used
|
||||||
|
directly on the main thread pool (as threads must be reused for other requests). Therefore, another dedicated thread
|
||||||
|
pool is created for all database queries, alongside connection pooling with HikariCP.
|
||||||
|
|
||||||
|
### Routes:
|
||||||
|
|
||||||
|
`GET /widgets` --> get all widgets in the database
|
||||||
|
|
||||||
|
`GET /widgets/{id}` --> get one widget instance by id (integer)
|
||||||
|
|
||||||
|
`POST /widgets` --> add a new widget to the database by providing a JSON object (converted to a NewWidget instance). e.g -
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "new widget",
|
||||||
|
"quantity": 64
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "new widget",
|
||||||
|
"quantity": 64,
|
||||||
|
"dateUpdated": 1519926898
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`PUT /widgets` --> update an existing widgets name or quantity. Pass in the id in the JSON request to determine which record to update
|
||||||
|
|
||||||
|
`DELETE /widgets/{id}` --> delete the widget with the specified id
|
||||||
|
|
||||||
|
### Notifications (WebSocket)
|
||||||
|
|
||||||
|
All updates (creates, updates and deletes) to `Widget` instances are served as notifications through a WebSocket endpoint:
|
||||||
|
|
||||||
|
`WS /updates` --> returns `Notification` instances containing the change type, id and entity (if applicable) e.g:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "CREATE",
|
||||||
|
"id": 12,
|
||||||
|
"entity": {
|
||||||
|
"id": 12,
|
||||||
|
"name": "widget1",
|
||||||
|
"quantity": 5,
|
||||||
|
"dateUpdated": 1533583858169
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The websocket listener will also log out any text messages send by the client. Refer to [this blog post](https://ryanharrison.co.uk/2018/08/19/testing-websockets.html) for some useful tools to test the websocket behaviour.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
The sample Widget service and corresponding endpoints are also tested with 100% coverage. Upon startup of the main JUnit suite (via the `test` source folder), the server is started ready for testing and is torn down after all tests are run.
|
||||||
|
|
||||||
|
- Unit testing of services with AssertJ - DAO and business logic is tested by initialising an in-memory H2 database with
|
||||||
|
Exposed, using the same schema as the main app. With this approach database queries are fully tested without any
|
||||||
|
mocking.
|
||||||
|
- Integration testing of endpoints using a fully running server with Rest Assured - routing tests/status codes/response
|
||||||
|
structure. This utilises the fact that Ktor is a small microframework that can be easily spun up and down as part of
|
||||||
|
the test suite. You could also use the special test engine that [Ktor provides](https://ktor.io/docs/testing.html),
|
||||||
|
however my preference is to always start a full version of the server so that HTTP behaviour can be tested without
|
||||||
|
relying on special internal mechanisms.
|
||||||
|
- Code coverage and reporting performed automatically by Kover as part of the Gradle build
|
||||||
59
build.gradle.kts
Normal file
59
build.gradle.kts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
val ktorVersion = "3.0.0"
|
||||||
|
val exposedVersion = "0.55.0"
|
||||||
|
val h2Version = "2.3.232"
|
||||||
|
val hikariCpVersion = "5.1.0"
|
||||||
|
val flywayVersion = "10.20.1"
|
||||||
|
val logbackVersion = "1.5.12"
|
||||||
|
val assertjVersion = "3.26.3"
|
||||||
|
val restAssuredVersion = "5.5.0"
|
||||||
|
val junitVersion = "5.11.3"
|
||||||
|
val psqlVersion = "42.7.4"
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "2.0.21"
|
||||||
|
kotlin("plugin.serialization") version "2.0.21"
|
||||||
|
id("org.jetbrains.kotlinx.kover") version "0.8.2"
|
||||||
|
application
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-serialization:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-call-logging:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-default-headers:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-websockets:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
|
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.20")
|
||||||
|
implementation("com.github.seratch:kotliquery:1.9.0")
|
||||||
|
implementation("com.h2database:h2:$h2Version")
|
||||||
|
implementation("org.postgresql:postgresql:$psqlVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
|
||||||
|
implementation("org.jetbrains.exposed:exposed-json:$exposedVersion")
|
||||||
|
implementation("com.zaxxer:HikariCP:$hikariCpVersion")
|
||||||
|
implementation("org.flywaydb:flyway-core:$flywayVersion")
|
||||||
|
implementation("ch.qos.logback:logback-classic:$logbackVersion")
|
||||||
|
implementation("de.m3y.kformat:kformat:0.11")
|
||||||
|
|
||||||
|
testImplementation("org.assertj:assertj-core:$assertjVersion")
|
||||||
|
testImplementation("io.rest-assured:rest-assured:$restAssuredVersion")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
|
||||||
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
|
||||||
|
testImplementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("MainKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test> {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
234
gradlew
vendored
Executable file
234
gradlew
vendored
Executable file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright <20> 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions <20>$var<61>, <20>${var}<7D>, <20>${var:-default}<7D>, <20>${var+SET}<7D>,
|
||||||
|
# <20>${var#prefix}<7D>, <20>${var%suffix}<7D>, and <20>$( cmd )<29>;
|
||||||
|
# * compound commands having a testable exit status, especially <20>case<73>;
|
||||||
|
# * various built-in commands including <20>command<6E>, <20>set<65>, and <20>ulimit<69>.
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx1024m" "-Xms1024m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'kotlin-ktor-exposed-starter'
|
||||||
40
src/main/kotlin/Main.kt
Normal file
40
src/main/kotlin/Main.kt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import io.ktor.serialization.kotlinx.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.netty.*
|
||||||
|
import io.ktor.server.plugins.calllogging.*
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.server.plugins.defaultheaders.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import service.DatabaseFactory
|
||||||
|
//import service.WidgetService
|
||||||
|
import util.JsonMapper
|
||||||
|
import web.index
|
||||||
|
//import web.widget
|
||||||
|
|
||||||
|
fun Application.module() {
|
||||||
|
install(DefaultHeaders)
|
||||||
|
install(CallLogging)
|
||||||
|
install(WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(JsonMapper.defaultMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(JsonMapper.defaultMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseFactory.connectAndMigrate()
|
||||||
|
|
||||||
|
//val widgetService = WidgetService()
|
||||||
|
|
||||||
|
routing {
|
||||||
|
index()
|
||||||
|
// widget(widgetService)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
EngineMain.main(args)
|
||||||
|
}
|
||||||
12
src/main/kotlin/dao/DictTypeDao.kt
Normal file
12
src/main/kotlin/dao/DictTypeDao.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.DictTypes
|
||||||
|
|
||||||
|
class DictTypeDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<DictTypeDao>(DictTypes)
|
||||||
|
|
||||||
|
var shortName by DictTypes.shortName
|
||||||
|
var fullName by DictTypes.fullName
|
||||||
|
}
|
||||||
12
src/main/kotlin/dao/DictionaryDao.kt
Normal file
12
src/main/kotlin/dao/DictionaryDao.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.Dictionaries
|
||||||
|
|
||||||
|
class DictionaryDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<DictionaryDao>(Dictionaries)
|
||||||
|
|
||||||
|
var name by Dictionaries.name
|
||||||
|
var fullName by Dictionaries.fullName
|
||||||
|
}
|
||||||
12
src/main/kotlin/dao/LanguageDao.kt
Normal file
12
src/main/kotlin/dao/LanguageDao.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.Languages
|
||||||
|
|
||||||
|
class LanguageDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<LanguageDao>(Languages)
|
||||||
|
|
||||||
|
var name by Languages.name
|
||||||
|
var shortName by Languages.shortName
|
||||||
|
}
|
||||||
13
src/main/kotlin/dao/PronunciationDao.kt
Normal file
13
src/main/kotlin/dao/PronunciationDao.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import models.Translation
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.Pronunciations
|
||||||
|
|
||||||
|
class PronunciationDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<PronunciationDao>(Pronunciations)
|
||||||
|
|
||||||
|
var ipa by Pronunciations.ipa
|
||||||
|
var filename by Pronunciations.filename
|
||||||
|
}
|
||||||
12
src/main/kotlin/dao/PronunciationTypeDao.kt
Normal file
12
src/main/kotlin/dao/PronunciationTypeDao.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import models.Translation
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.PronunciationTypes
|
||||||
|
|
||||||
|
class PronunciationTypeDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<PronunciationTypeDao>(PronunciationTypes)
|
||||||
|
|
||||||
|
var name by PronunciationTypes.name
|
||||||
|
}
|
||||||
11
src/main/kotlin/dao/SuffixDao.kt
Normal file
11
src/main/kotlin/dao/SuffixDao.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.Suffixes
|
||||||
|
|
||||||
|
class SuffixDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<SuffixDao>(Suffixes)
|
||||||
|
|
||||||
|
var text by Suffixes.text
|
||||||
|
}
|
||||||
14
src/main/kotlin/dao/TranslationDao.kt
Normal file
14
src/main/kotlin/dao/TranslationDao.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import models.Translation
|
||||||
|
import org.jetbrains.exposed.dao.*
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import tables.Translations
|
||||||
|
|
||||||
|
class TranslationDao(id: EntityID<Int>) : IntEntity(id) {
|
||||||
|
companion object : IntEntityClass<TranslationDao>(Translations)
|
||||||
|
|
||||||
|
var langName1 by Translations.langName1
|
||||||
|
var langName2 by Translations.langName2
|
||||||
|
var direction by Translations.direction
|
||||||
|
}
|
||||||
193
src/main/kotlin/db/migration/V1__test_connection.kt
Normal file
193
src/main/kotlin/db/migration/V1__test_connection.kt
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package db.migration
|
||||||
|
|
||||||
|
import org.flywaydb.core.api.migration.BaseJavaMigration
|
||||||
|
import org.flywaydb.core.api.migration.Context
|
||||||
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import kotlin.collections.*
|
||||||
|
import kotliquery.*
|
||||||
|
import de.m3y.kformat.*
|
||||||
|
import de.m3y.kformat.Table.Hints
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
val format = Json { encodeDefaults = true }
|
||||||
|
|
||||||
|
data class Dictionary(
|
||||||
|
val id: Int,
|
||||||
|
val lang1Id: Int,
|
||||||
|
val lang2Id: Int,
|
||||||
|
val name: String,
|
||||||
|
val fullName: String)
|
||||||
|
|
||||||
|
data class DictType(
|
||||||
|
val id: Int,
|
||||||
|
val shortName: String,
|
||||||
|
val fullName: String
|
||||||
|
)
|
||||||
|
data class Language(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val shortName: String
|
||||||
|
)
|
||||||
|
data class Translation(
|
||||||
|
val id: Int,
|
||||||
|
val dictionaryId: Int,
|
||||||
|
val lang1Id: Int,
|
||||||
|
val lang2Id: Int,
|
||||||
|
val lang1Name: String,
|
||||||
|
val lang2Name: String,
|
||||||
|
val direction: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Pronunciation(
|
||||||
|
val id: Int,
|
||||||
|
val typeId: Int,
|
||||||
|
val ipa: String?,
|
||||||
|
val filename: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PronunciationType(
|
||||||
|
val id: Int,
|
||||||
|
val name: String?
|
||||||
|
)
|
||||||
|
data class TermsPronunciation(
|
||||||
|
val termId: Int,
|
||||||
|
val pronunciationId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Suffix(
|
||||||
|
val id: Int,
|
||||||
|
val text: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Term(
|
||||||
|
val id: Int,
|
||||||
|
val dictionaryId: Int,
|
||||||
|
val string1: String,
|
||||||
|
val string2: String,
|
||||||
|
val suffix1Id: Int?,
|
||||||
|
val suffix2Id: Int?,
|
||||||
|
val typeId: Int?,
|
||||||
|
val member: String?,
|
||||||
|
val order2: Int?,
|
||||||
|
val flags: String?
|
||||||
|
)
|
||||||
|
val toDictionary: (Row) -> Dictionary = { row ->
|
||||||
|
Dictionary(
|
||||||
|
row.int("id"),
|
||||||
|
row.int("lang1_id"),
|
||||||
|
row.int("lang2_id"),
|
||||||
|
row.string("name"),
|
||||||
|
row.string("full_name")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val toDictType: (Row) -> DictType = { row ->
|
||||||
|
DictType(
|
||||||
|
row.int("id"),
|
||||||
|
row.string("short_name"),
|
||||||
|
row.string("full_name")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val toLanguage: (Row) -> Language = { row ->
|
||||||
|
Language(
|
||||||
|
row.int("id"),
|
||||||
|
row.string("name"),
|
||||||
|
row.string("short_name")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val toTranslation: (Row) -> Translation = { row ->
|
||||||
|
Translation(
|
||||||
|
row.int("id"),
|
||||||
|
row.int("dictionary_id"),
|
||||||
|
row.int("lang1_id"),
|
||||||
|
row.int("lang2_id"),
|
||||||
|
row.string("lang_name1"),
|
||||||
|
row.string("lang_name2"),
|
||||||
|
row.int("direction"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val toPronunciation: (Row) -> Pronunciation = { row ->
|
||||||
|
Pronunciation(
|
||||||
|
row.int("id"),
|
||||||
|
row.int("type_id"),
|
||||||
|
row.stringOrNull("ipa"),
|
||||||
|
row.stringOrNull("filename")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val toTermsPronunciation: (Row) -> TermsPronunciation = { row ->
|
||||||
|
TermsPronunciation(
|
||||||
|
row.int("term_id"),
|
||||||
|
row.int("pronunciation_id"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val toSuffix: (Row) -> Suffix = { row ->
|
||||||
|
Suffix(
|
||||||
|
row.int("id"),
|
||||||
|
row.string("text")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val toTerm: (Row) -> Term = { row ->
|
||||||
|
Term(
|
||||||
|
row.int("id"),
|
||||||
|
row.int("dictionary_id"),
|
||||||
|
row.string("string1"),
|
||||||
|
row.string("string2"),
|
||||||
|
row.intOrNull("suffix1_id"),
|
||||||
|
row.intOrNull("suffix2_id"),
|
||||||
|
row.intOrNull("type_id"),
|
||||||
|
row.stringOrNull("member"),
|
||||||
|
row.intOrNull("order2"),
|
||||||
|
row.stringOrNull("flags")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object SharedData {
|
||||||
|
var hashDict: Map<Int,Dictionary> = mapOf()
|
||||||
|
var hashDictType: Map<Int,DictType> = mapOf()
|
||||||
|
var hashLang: Map<Int,Language> = mapOf()
|
||||||
|
var hashTransMap: Map<Int,Translation> = mapOf()
|
||||||
|
var hashPron: Map<Int,Pronunciation> = mapOf()
|
||||||
|
var hashSuffix: Map<Int,Suffix> = mapOf()
|
||||||
|
var hashTermPron : Map<Int,TermsPronunciation> = mapOf()
|
||||||
|
var listTerm : List<Term> = listOf()
|
||||||
|
var getListofTerms : (Int) -> List<Term> = {
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class V1__test_connection: BaseJavaMigration() {
|
||||||
|
override fun migrate(context: Context?) {
|
||||||
|
val session = sessionOf("jdbc:postgresql://nuc.lan:5432/dict", "dict_user", "PW4dbdict")
|
||||||
|
val allNameQuery = queryOf("select * from dictionary").map(toDictionary).asList
|
||||||
|
val allDict = session.run(allNameQuery)
|
||||||
|
SharedData.hashDict = allDict.associateBy { it.id }
|
||||||
|
val allDictTypeQuery = queryOf("select * from dict_type").map(toDictType).asList
|
||||||
|
val allDictType = session.run(allDictTypeQuery)
|
||||||
|
SharedData.hashDictType = allDictType.associateBy { it.id }
|
||||||
|
val allTranslationQuery = queryOf("select * from translation").map(toTranslation).asList
|
||||||
|
val allTranslation = session.run(allTranslationQuery)
|
||||||
|
SharedData.hashTransMap = allTranslation.associateBy { it.id }
|
||||||
|
val allLangQuery = queryOf("select * from language").map(toLanguage).asList
|
||||||
|
val allLanguages = session.run(allLangQuery)
|
||||||
|
SharedData.hashLang = allLanguages.associateBy { it.id }
|
||||||
|
val allSuffixQuery = queryOf("select * from suffix").map(toSuffix).asList
|
||||||
|
val allSuffix = session.run(allSuffixQuery)
|
||||||
|
SharedData.hashSuffix = allSuffix.associateBy { it.id }
|
||||||
|
val allPronunciationQuery = queryOf("select * from pronunciation").map(toPronunciation).asList
|
||||||
|
val allPronunciation = session.run(allPronunciationQuery)
|
||||||
|
SharedData.hashPron = allPronunciation.associateBy { it.id }
|
||||||
|
val allTermsPronunciationQuery = queryOf("select * from terms_pronunciations").map(toTermsPronunciation).asList
|
||||||
|
val allTermsPronunciation = session.run(allTermsPronunciationQuery)
|
||||||
|
SharedData.hashTermPron = allTermsPronunciation.associateBy { it.termId }
|
||||||
|
SharedData.getListofTerms = {
|
||||||
|
session.run(queryOf("select * from term WHERE dictionary_id=${it}").map(toTerm).asList)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/main/kotlin/db/migration/V2__crete_settingsdb.kt
Normal file
53
src/main/kotlin/db/migration/V2__crete_settingsdb.kt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package db.migration
|
||||||
|
|
||||||
|
import dao.DictionaryDao
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import tables.*
|
||||||
|
import org.flywaydb.core.api.migration.BaseJavaMigration
|
||||||
|
import org.flywaydb.core.api.migration.Context
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.jetbrains.exposed.sql.upsert
|
||||||
|
|
||||||
|
class V2__create_settingsdb: BaseJavaMigration() {
|
||||||
|
override fun migrate(context: Context?) {
|
||||||
|
transaction {
|
||||||
|
SchemaUtils.create(Dictionaries, Languages, Translations, Settings)
|
||||||
|
|
||||||
|
for ((di, lang) in SharedData.hashLang) {
|
||||||
|
Languages.insert {
|
||||||
|
it[id] = lang.id
|
||||||
|
it[name] = lang.name
|
||||||
|
it[shortName] = lang.shortName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, dict) in SharedData.hashDict) {
|
||||||
|
Dictionaries.insert {
|
||||||
|
it[id] = dict.id
|
||||||
|
it[lang1] = dict.lang1Id
|
||||||
|
it[lang2] = dict.lang2Id
|
||||||
|
it[name] = dict.name
|
||||||
|
it[fullName] = dict.fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for ((di, trans) in SharedData.hashTransMap) {
|
||||||
|
Translations.insert {
|
||||||
|
it[id] = trans.id
|
||||||
|
it[lang1Id] = trans.lang1Id
|
||||||
|
it[lang2Id] = trans.lang2Id
|
||||||
|
it[dictionaryId] = trans.dictionaryId
|
||||||
|
it[langName1] = trans.lang1Name
|
||||||
|
it[langName2] = trans.lang2Name
|
||||||
|
it[direction] = trans.direction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/main/kotlin/db/migration/V3__create_dictionaries.kt
Normal file
117
src/main/kotlin/db/migration/V3__create_dictionaries.kt
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package db.migration
|
||||||
|
|
||||||
|
import dao.DictionaryDao
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import tables.*
|
||||||
|
import org.flywaydb.core.api.migration.BaseJavaMigration
|
||||||
|
import org.flywaydb.core.api.migration.Context
|
||||||
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.jetbrains.exposed.sql.upsert
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class V3__create_dictionaries: BaseJavaMigration() {
|
||||||
|
override fun migrate(context: Context?) {
|
||||||
|
println(SharedData.hashDict)
|
||||||
|
//exitProcess(0)
|
||||||
|
for ((db_id,dict) in SharedData.hashDict) {
|
||||||
|
|
||||||
|
val db_name = "jdbc:h2:file:/Users/jaro/data/${SharedData.hashLang[dict.lang1Id]?.shortName}-${SharedData.hashLang[dict.lang2Id]?.shortName}"
|
||||||
|
println(db_name)
|
||||||
|
val db = Database.connect(db_name)
|
||||||
|
transaction (db) {
|
||||||
|
SchemaUtils.create(Dictionaries, Languages, Translations, Pronunciations, PronunciationTypes, DictTypes, Suffixes, Terms)
|
||||||
|
|
||||||
|
for ((di, lang) in SharedData.hashLang) {
|
||||||
|
Languages.insert {
|
||||||
|
it[id] = lang.id
|
||||||
|
it[name] = lang.name
|
||||||
|
it[shortName] = lang.shortName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, d) in SharedData.hashDict) {
|
||||||
|
Dictionaries.insert {
|
||||||
|
it[id] = d.id
|
||||||
|
it[lang1] = d.lang1Id
|
||||||
|
it[lang2] = d.lang2Id
|
||||||
|
it[name] = d.name
|
||||||
|
it[fullName] = d.fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, trans) in SharedData.hashTransMap) {
|
||||||
|
Translations.insert {
|
||||||
|
it[id] = trans.id
|
||||||
|
it[lang1Id] = trans.lang1Id
|
||||||
|
it[lang2Id] = trans.lang2Id
|
||||||
|
it[dictionaryId] = trans.dictionaryId
|
||||||
|
it[langName1] = trans.lang1Name
|
||||||
|
it[langName2] = trans.lang2Name
|
||||||
|
it[direction] = trans.direction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PronunciationTypes.insert {
|
||||||
|
it[id] = 1
|
||||||
|
it[name] = "English"
|
||||||
|
}
|
||||||
|
|
||||||
|
PronunciationTypes.insert {
|
||||||
|
it[id] = 2
|
||||||
|
it[name] = "American english"
|
||||||
|
}
|
||||||
|
|
||||||
|
PronunciationTypes.insert {
|
||||||
|
it[id] = 3
|
||||||
|
it[name] = "Business english"
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, pron) in SharedData.hashPron) {
|
||||||
|
Pronunciations.insert {
|
||||||
|
it[id] = pron.id
|
||||||
|
it[typeId] = pron.typeId
|
||||||
|
it[ipa] = pron.ipa
|
||||||
|
it[filename] = pron.filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, dt) in SharedData.hashDictType) {
|
||||||
|
DictTypes.insert {
|
||||||
|
it[id] = dt.id
|
||||||
|
it[shortName] = dt.shortName
|
||||||
|
it[fullName] = dt.fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((di, ss) in SharedData.hashSuffix) {
|
||||||
|
Suffixes.insert {
|
||||||
|
it[id] = ss.id
|
||||||
|
it[text] = ss.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dictTerms = SharedData.getListofTerms(db_id)
|
||||||
|
for (t in dictTerms) {
|
||||||
|
Terms.insert {
|
||||||
|
it[id] = t.id
|
||||||
|
it[string1] = t.string1
|
||||||
|
it[string2] = t.string2
|
||||||
|
it[suffix1] = t.suffix1Id
|
||||||
|
it[suffix2] = t.suffix2Id
|
||||||
|
it[type] = t.typeId
|
||||||
|
it[member] = t.member
|
||||||
|
it[order2] = t.order2
|
||||||
|
it[flags] = format.decodeFromString<IntArray?>(t.flags?.toString() ?:"[]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dictTerms = listOf()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/kotlin/models/DictType.kt
Normal file
14
src/main/kotlin/models/DictType.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DictType(
|
||||||
|
val id: Int,
|
||||||
|
val shortName: String,
|
||||||
|
val fullName: String
|
||||||
|
)
|
||||||
|
|
||||||
17
src/main/kotlin/models/Dictionary.kt
Normal file
17
src/main/kotlin/models/Dictionary.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Dictionary(
|
||||||
|
val id: Int,
|
||||||
|
val lang1Id: Int,
|
||||||
|
val lang2Id: Int,
|
||||||
|
val name: String?,
|
||||||
|
val fullName: String?,
|
||||||
|
val translations: List<Translation> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
15
src/main/kotlin/models/Language.kt
Normal file
15
src/main/kotlin/models/Language.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Language(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val shortName: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
12
src/main/kotlin/models/Pronunciation.kt
Normal file
12
src/main/kotlin/models/Pronunciation.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Pronunciation(
|
||||||
|
val id: Int,
|
||||||
|
val typeId: Int,
|
||||||
|
val ipa: String?,
|
||||||
|
val filename: String?
|
||||||
|
)
|
||||||
10
src/main/kotlin/models/PronunciationType.kt
Normal file
10
src/main/kotlin/models/PronunciationType.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PronunciationType(
|
||||||
|
val id: Int,
|
||||||
|
val name: String?
|
||||||
|
)
|
||||||
12
src/main/kotlin/models/Setting.kt
Normal file
12
src/main/kotlin/models/Setting.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Settings(
|
||||||
|
val id: Int,
|
||||||
|
val dictionary: Int?,
|
||||||
|
val lastSearch: String?,
|
||||||
|
)
|
||||||
|
|
||||||
11
src/main/kotlin/models/Suffix.kt
Normal file
11
src/main/kotlin/models/Suffix.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Suffix(
|
||||||
|
val id: Int,
|
||||||
|
val text: String
|
||||||
|
)
|
||||||
20
src/main/kotlin/models/Term.kt
Normal file
20
src/main/kotlin/models/Term.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Term(
|
||||||
|
val id: Int,
|
||||||
|
val dictionaryId: Int,
|
||||||
|
val string1: String,
|
||||||
|
val string2: String,
|
||||||
|
val typeId: Int,
|
||||||
|
val suffix1Id: Int,
|
||||||
|
val suffix2Id: Int,
|
||||||
|
val member: String?,
|
||||||
|
val order2: Int,
|
||||||
|
val flags: JsonObject?
|
||||||
|
)
|
||||||
17
src/main/kotlin/models/Translation.kt
Normal file
17
src/main/kotlin/models/Translation.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Translation(
|
||||||
|
val id: Int,
|
||||||
|
val dictionaryId: Int,
|
||||||
|
val lang1Id: Int,
|
||||||
|
val lang2Id: Int,
|
||||||
|
val lang1Name: String?,
|
||||||
|
val lang2Name: String?,
|
||||||
|
val direction: Int
|
||||||
|
)
|
||||||
54
src/main/kotlin/service/DatabaseFactory.kt
Normal file
54
src/main/kotlin/service/DatabaseFactory.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.flywaydb.core.Flyway
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
object DatabaseFactory {
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
|
fun connectAndMigrate() {
|
||||||
|
log.info("Initialising database")
|
||||||
|
val pool = hikari()
|
||||||
|
Database.connect(pool)
|
||||||
|
runFlyway(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hikari(): HikariDataSource {
|
||||||
|
val config = HikariConfig().apply {
|
||||||
|
driverClassName = "org.h2.Driver"
|
||||||
|
jdbcUrl = "jdbc:h2:file:/Users/jaro/data/dict_settings"
|
||||||
|
maximumPoolSize = 3
|
||||||
|
isAutoCommit = false
|
||||||
|
transactionIsolation = "TRANSACTION_REPEATABLE_READ"
|
||||||
|
validate()
|
||||||
|
}
|
||||||
|
return HikariDataSource(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runFlyway(datasource: DataSource) {
|
||||||
|
val flyway = Flyway.configure().dataSource(datasource).load()
|
||||||
|
try {
|
||||||
|
flyway.info()
|
||||||
|
flyway.migrate()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Exception running flyway migration", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
log.info("Flyway migration has finished")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> dbExec(
|
||||||
|
block: () -> T
|
||||||
|
): T = withContext(Dispatchers.IO) {
|
||||||
|
transaction { block() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
8
src/main/kotlin/tables/DictType.kt
Normal file
8
src/main/kotlin/tables/DictType.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object DictTypes : IntIdTable() {
|
||||||
|
val shortName = varchar("short_name", 255)
|
||||||
|
val fullName = varchar("full_name", 255)
|
||||||
|
}
|
||||||
10
src/main/kotlin/tables/Dictionary.kt
Normal file
10
src/main/kotlin/tables/Dictionary.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Dictionaries : IntIdTable() {
|
||||||
|
val lang1 = reference("lang1_id", Languages)
|
||||||
|
val lang2 = reference("lang2_id", Languages)
|
||||||
|
val name = varchar("name", 255)
|
||||||
|
val fullName = varchar("full_name", 255)
|
||||||
|
}
|
||||||
8
src/main/kotlin/tables/Language.kt
Normal file
8
src/main/kotlin/tables/Language.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Languages : IntIdTable() {
|
||||||
|
val name = varchar("name", 255)
|
||||||
|
val shortName = varchar("short_name", 255)
|
||||||
|
}
|
||||||
10
src/main/kotlin/tables/Pronunciation.kt
Normal file
10
src/main/kotlin/tables/Pronunciation.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Pronunciations : IntIdTable() {
|
||||||
|
val typeId = reference("type_id",PronunciationTypes)
|
||||||
|
|
||||||
|
val ipa = varchar("ipa", 255).nullable()
|
||||||
|
val filename = varchar("filename", 255).nullable()
|
||||||
|
}
|
||||||
7
src/main/kotlin/tables/PronunciationType.kt
Normal file
7
src/main/kotlin/tables/PronunciationType.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object PronunciationTypes : IntIdTable() {
|
||||||
|
val name = varchar("name", 255)
|
||||||
|
}
|
||||||
8
src/main/kotlin/tables/Setting.kt
Normal file
8
src/main/kotlin/tables/Setting.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Settings : IntIdTable() {
|
||||||
|
val dictionary = reference("dictionary_id",Dictionaries)
|
||||||
|
val lastSearch = varchar("last_search", 255).nullable()
|
||||||
|
}
|
||||||
7
src/main/kotlin/tables/Suffix.kt
Normal file
7
src/main/kotlin/tables/Suffix.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Suffixes : IntIdTable() {
|
||||||
|
val text = varchar("text", 255)
|
||||||
|
}
|
||||||
17
src/main/kotlin/tables/Term.kt
Normal file
17
src/main/kotlin/tables/Term.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
import org.jetbrains.exposed.sql.json.json
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import tables.Suffixes
|
||||||
|
object Terms : IntIdTable() {
|
||||||
|
val string1 = varchar("string1", 255)
|
||||||
|
val string2 = varchar("string2", 255)
|
||||||
|
val suffix1 = reference("suffix1_id",Suffixes).nullable()
|
||||||
|
val suffix2 = reference("suffix2_id",Suffixes).nullable()
|
||||||
|
val type = reference("type_id",DictTypes).nullable()
|
||||||
|
val member = varchar("member", 255).nullable()
|
||||||
|
val order2 = integer("order2").nullable()
|
||||||
|
val flags = json<IntArray>("flags", Json.Default).nullable()
|
||||||
|
}
|
||||||
13
src/main/kotlin/tables/Translation.kt
Normal file
13
src/main/kotlin/tables/Translation.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||||
|
|
||||||
|
object Translations : IntIdTable() {
|
||||||
|
val dictionaryId = reference("dictionary_id",Dictionaries)
|
||||||
|
val lang1Id = reference("lang1_id",Languages)
|
||||||
|
val lang2Id = reference("lang2_id",Languages)
|
||||||
|
|
||||||
|
val langName1 = varchar("lang_name1", 255)
|
||||||
|
val langName2 = varchar("lang_name2", 255)
|
||||||
|
val direction = integer("direction")
|
||||||
|
}
|
||||||
11
src/main/kotlin/util/JsonMapper.kt
Normal file
11
src/main/kotlin/util/JsonMapper.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
object JsonMapper {
|
||||||
|
|
||||||
|
val defaultMapper = Json {
|
||||||
|
prettyPrint = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
src/main/kotlin/web/IndexResource.kt
Normal file
14
src/main/kotlin/web/IndexResource.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Route.index() {
|
||||||
|
|
||||||
|
val indexPage = javaClass.getResource("/index.html").readText()
|
||||||
|
|
||||||
|
get("/") {
|
||||||
|
call.respondText(indexPage, ContentType.Text.Html)
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/main/kotlin/web/WidgetResource.kt.old
Normal file
64
src/main/kotlin/web/WidgetResource.kt.old
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import model.NewWidget
|
||||||
|
import service.WidgetService
|
||||||
|
|
||||||
|
fun Route.widget(widgetService: WidgetService) {
|
||||||
|
|
||||||
|
route("/widgets") {
|
||||||
|
|
||||||
|
get {
|
||||||
|
call.respond(widgetService.getAllWidgets())
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/{id}") {
|
||||||
|
val id = call.parameters["id"]?.toInt() ?: throw IllegalStateException("Must provide id")
|
||||||
|
val widget = widgetService.getWidget(id)
|
||||||
|
if (widget == null) call.respond(HttpStatusCode.NotFound)
|
||||||
|
else call.respond(widget)
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
val widget = call.receive<NewWidget>()
|
||||||
|
call.respond(HttpStatusCode.Created, widgetService.addWidget(widget))
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
val widget = call.receive<NewWidget>()
|
||||||
|
val updated = widgetService.updateWidget(widget)
|
||||||
|
if (updated == null) call.respond(HttpStatusCode.NotFound)
|
||||||
|
else call.respond(HttpStatusCode.OK, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete("/{id}") {
|
||||||
|
val id = call.parameters["id"]?.toInt() ?: throw IllegalStateException("Must provide id")
|
||||||
|
val removed = widgetService.deleteWidget(id)
|
||||||
|
if (removed) call.respond(HttpStatusCode.OK)
|
||||||
|
else call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocket("/updates") {
|
||||||
|
try {
|
||||||
|
widgetService.addChangeListener(this.hashCode()) {
|
||||||
|
sendSerialized(it)
|
||||||
|
}
|
||||||
|
for (frame in incoming) {
|
||||||
|
if (frame.frameType == FrameType.CLOSE) {
|
||||||
|
break
|
||||||
|
} else if (frame is Frame.Text) {
|
||||||
|
call.application.environment.log.info("Received websocket message: {}", frame.readText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
widgetService.removeChangeListener(this.hashCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/resources/application.conf
Normal file
10
src/main/resources/application.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
ktor {
|
||||||
|
deployment {
|
||||||
|
port = 8080
|
||||||
|
watch = [ build ]
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
modules = [ MainKt.module ]
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/main/resources/index.html
Normal file
53
src/main/resources/index.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Kotlin/Ktor/Exposed Starter - It's Working!</title>
|
||||||
|
<meta name="author" content="Ryan Harrison">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1 id="it-s-working-">It's Working!</h1>
|
||||||
|
<p>This starter project creates a new in-memory H2 database with one table for <code>Widget</code> instances. A simple RESTful interface is provided
|
||||||
|
to perform CRUD operations on <code>Widgets</code> alongside a websocket to be notified in real-time of any changes.</p>
|
||||||
|
<h2 id="routes-">Routes:</h2>
|
||||||
|
<p><code>GET /widgets</code> --> get all widgets in the database</p>
|
||||||
|
<p><code>GET /widgets/{id}</code> --> get one widget instance by id (integer)</p>
|
||||||
|
<p><code>POST /widgets</code> --> add a new widget to the database by providing a JSON object (converted to a NewWidget instance).
|
||||||
|
e.g - </p>
|
||||||
|
<pre><code>{
|
||||||
|
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"new widget"</span>,
|
||||||
|
<span class="hljs-attr">"quantity"</span>: <span class="hljs-number">64</span>
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>returns</p>
|
||||||
|
<pre><code>{
|
||||||
|
<span class="hljs-attr">"id"</span>: <span class="hljs-number">4</span>,
|
||||||
|
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"new widget"</span>,
|
||||||
|
<span class="hljs-attr">"quantity"</span>: <span class="hljs-number">64</span>,
|
||||||
|
<span class="hljs-attr">"dateCreated"</span>: <span class="hljs-number">1519926898</span>
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p><code>PUT /widgets</code> --> update an existing widgets name or quantity. Pass in the id in the JSON request to determine which record to update
|
||||||
|
</p>
|
||||||
|
<p><code>DELETE /widgets/{id}</code> --> delete the widget with the specified id</p>
|
||||||
|
<h2 id="notifications-websocket-">Notifications (WebSocket)</h2>
|
||||||
|
<p>All updates (creates, updates and deletes) to <code>Widget</code> instances are served as notifications through a WebSocket endpoint:</p>
|
||||||
|
<p><code>WS /updates</code> --> returns <code>Notification</code> instances containing the change type, id and entity (if applicable) e.g:</p>
|
||||||
|
<pre><code class="lang-json">{
|
||||||
|
<span class="hljs-attr">"type"</span>: <span class="hljs-string">"CREATE"</span>,
|
||||||
|
<span class="hljs-attr">"id"</span>: <span class="hljs-number">12</span>,
|
||||||
|
<span class="hljs-attr">"entity"</span>: {
|
||||||
|
<span class="hljs-attr">"id"</span>: <span class="hljs-number">12</span>,
|
||||||
|
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"widget1"</span>,
|
||||||
|
<span class="hljs-attr">"quantity"</span>: <span class="hljs-number">5</span>,
|
||||||
|
<span class="hljs-attr">"dateUpdated"</span>: <span class="hljs-number">1533583858169</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
15
src/main/resources/logback.xml
Normal file
15
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<logger name="Exposed" level="DEBUG" />
|
||||||
|
<logger name="ktor.application" level="TRACE" />
|
||||||
|
|
||||||
|
</configuration>
|
||||||
Reference in New Issue
Block a user