First commit

This commit is contained in:
Jaroslav Držík
2025-01-05 07:50:23 +01:00
commit 12e0eb37a8
52 changed files with 1544 additions and 0 deletions

40
src/main/kotlin/Main.kt Normal file
View 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)
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}
}
}

View 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
}
}
}
}
}

View 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()
}
}
}
}

View 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
)

View 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()
)

View 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
)

View 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?
)

View 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?
)

View 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?,
)

View 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
)

View 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?
)

View 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
)

View 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() }
}
}

View 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)
}

View 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)
}

View 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)
}

View 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()
}

View File

@@ -0,0 +1,7 @@
package tables
import org.jetbrains.exposed.dao.id.IntIdTable
object PronunciationTypes : IntIdTable() {
val name = varchar("name", 255)
}

View 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()
}

View File

@@ -0,0 +1,7 @@
package tables
import org.jetbrains.exposed.dao.id.IntIdTable
object Suffixes : IntIdTable() {
val text = varchar("text", 255)
}

View 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()
}

View 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")
}

View File

@@ -0,0 +1,11 @@
package util
import kotlinx.serialization.json.Json
object JsonMapper {
val defaultMapper = Json {
prettyPrint = true
}
}

View 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)
}
}

View 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())
}
}
}

View File

@@ -0,0 +1,10 @@
ktor {
deployment {
port = 8080
watch = [ build ]
}
application {
modules = [ MainKt.module ]
}
}

View 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&#39;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> --&gt; get all widgets in the database</p>
<p><code>GET /widgets/{id}</code> --&gt; get one widget instance by id (integer)</p>
<p><code>POST /widgets</code> --&gt; 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> --&gt; 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> --&gt; 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> --&gt; 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>

View 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>