d11d2cd9-9fd7-4364-8864-c7961b139b18 — Commit 4579073b
Changed files
.../kotlin-compiler-8638745083392202918.salive | 0 build.gradle.kts | 4 ++ gradle/libs.versions.toml | 6 +++ src/main/kotlin/com/gs/Application.kt | 2 + src/main/kotlin/com/gs/db/Tables.kt | 24 +++++++++ src/main/kotlin/com/gs/models/Review.kt | 10 ++++ src/main/kotlin/com/gs/plugins/Database.kt | 28 ++++++++++ src/main/kotlin/com/gs/plugins/Routing.kt | 61 ++-------------------- src/main/kotlin/com/gs/routes/ItemRoutes.kt | 60 +++++++++++++++++++++ src/main/kotlin/com/gs/routes/ReviewRoutes.kt | 54 +++++++++++++++++++ src/main/resources/application.conf | 9 ++++ 11 files changed, 201 insertions(+), 57 deletions(-)
Diff
diff --git a/.kotlin/sessions/kotlin-compiler-8638745083392202918.salive b/.kotlin/sessions/kotlin-compiler-8638745083392202918.salive
deleted file mode 100644
index e69de29..0000000
diff --git a/build.gradle.kts b/build.gradle.kts
index 9679cfd..a94bc9b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,4 +18,8 @@ dependencies {
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.server.status.pages)
implementation(libs.logback)
+ implementation(libs.exposed.core)
+ implementation(libs.exposed.dao)
+ implementation(libs.exposed.jdbc)
+ implementation(libs.postgresql)
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ef1408e..4164bbb 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,6 +2,8 @@
kotlin = "2.1.10"
ktor = "3.1.1"
logback = "1.4.14"
+exposed = "0.57.0"
+postgresql = "42.7.4"
[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
@@ -10,6 +12,10 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
+exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
+exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
+exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
+postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
diff --git a/src/main/kotlin/com/gs/Application.kt b/src/main/kotlin/com/gs/Application.kt
index 7eaaff9..0c1a7eb 100644
--- a/src/main/kotlin/com/gs/Application.kt
+++ b/src/main/kotlin/com/gs/Application.kt
@@ -2,6 +2,7 @@ package com.gs
import io.ktor.server.application.*
import io.ktor.server.netty.*
+import com.gs.plugins.configureDatabase
import com.gs.plugins.configureRouting
import com.gs.plugins.configureSerialization
@@ -11,5 +12,6 @@ fun main(args: Array<String>) {
fun Application.module() {
configureSerialization()
+ configureDatabase()
configureRouting()
}
diff --git a/src/main/kotlin/com/gs/db/Tables.kt b/src/main/kotlin/com/gs/db/Tables.kt
new file mode 100644
index 0000000..576dfc2
--- /dev/null
+++ b/src/main/kotlin/com/gs/db/Tables.kt
@@ -0,0 +1,24 @@
+package com.gs.db
+
+import org.jetbrains.exposed.sql.Table
+
+object Items : Table("items") {
+ val id = varchar("id", 128)
+ val productName = varchar("product_name", 256)
+ val brands = varchar("brands", 256)
+ val quantity = varchar("quantity", 64)
+ val categories = varchar("categories", 256)
+ val description = text("description")
+ val ingredientsText = text("ingredients_text")
+
+ override val primaryKey = PrimaryKey(id)
+}
+
+object Reviews : Table("reviews") {
+ val id = varchar("id", 128)
+ val itemId = varchar("item_id", 128).references(Items.id)
+ val rating = integer("rating")
+ val comment = text("comment")
+
+ override val primaryKey = PrimaryKey(id)
+}
diff --git a/src/main/kotlin/com/gs/models/Review.kt b/src/main/kotlin/com/gs/models/Review.kt
new file mode 100644
index 0000000..5b77966
--- /dev/null
+++ b/src/main/kotlin/com/gs/models/Review.kt
@@ -0,0 +1,10 @@
+package com.gs.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Review(
+ val id: String,
+ val rating: Int,
+ val comment: String,
+)
diff --git a/src/main/kotlin/com/gs/plugins/Database.kt b/src/main/kotlin/com/gs/plugins/Database.kt
new file mode 100644
index 0000000..09908bc
--- /dev/null
+++ b/src/main/kotlin/com/gs/plugins/Database.kt
@@ -0,0 +1,28 @@
+package com.gs.plugins
+
+import com.gs.db.Items
+import com.gs.db.Reviews
+import io.ktor.server.application.*
+import org.jetbrains.exposed.sql.Database
+import org.jetbrains.exposed.sql.SchemaUtils
+import org.jetbrains.exposed.sql.transactions.transaction
+
+fun Application.configureDatabase() {
+ val url = environment.config.propertyOrNull("database.url")?.getString()
+ ?: "jdbc:postgresql://localhost:5432/grocery_scanner"
+ val user = environment.config.propertyOrNull("database.user")?.getString()
+ ?: "postgres"
+ val password = environment.config.propertyOrNull("database.password")?.getString()
+ ?: ""
+
+ Database.connect(
+ url = url,
+ driver = "org.postgresql.Driver",
+ user = user,
+ password = password,
+ )
+
+ transaction {
+ SchemaUtils.create(Items, Reviews)
+ }
+}
diff --git a/src/main/kotlin/com/gs/plugins/Routing.kt b/src/main/kotlin/com/gs/plugins/Routing.kt
index c9a0fda..9daed78 100644
--- a/src/main/kotlin/com/gs/plugins/Routing.kt
+++ b/src/main/kotlin/com/gs/plugins/Routing.kt
@@ -1,66 +1,13 @@
package com.gs.plugins
-import com.gs.models.Item
-import io.ktor.http.*
+import com.gs.routes.itemRoutes
+import com.gs.routes.reviewRoutes
import io.ktor.server.application.*
-import io.ktor.server.request.*
-import io.ktor.server.response.*
import io.ktor.server.routing.*
-private val itemStore = mutableMapOf(
- "1" to Item(
- id = "1",
- productName = "Whole Milk",
- brands = "Arla",
- quantity = "1L",
- categories = "Dairy",
- description = "Fresh whole milk",
- ingredientsText = "Whole milk",
- ),
- "2" to Item(
- id = "2",
- productName = "Sourdough Bread",
- brands = "Schulstad",
- quantity = "500g",
- categories = "Bakery",
- description = "Traditional sourdough bread",
- ingredientsText = "Wheat flour, water, salt, sourdough culture",
- ),
- "8723400941804" to Item(
- id = "723400941804",
- productName = "Strong Mint",
- brands = "V6",
- quantity = "49g",
- categories = "Candy",
- description = "Sugar free for teeth protection",
- ingredientsText = "Sødestoffer",
- ),
-)
-
fun Application.configureRouting() {
routing {
- route("/items/{id}") {
- get {
- val id = call.parameters["id"]!!
- val item = itemStore[id]
- if (item == null) {
- call.respond(HttpStatusCode.NotFound)
- return@get
- }
- call.respond(item)
- }
-
- put {
- val id = call.parameters["id"]!!
- if (!itemStore.keys.contains(id)) {
- call.respond(HttpStatusCode.NotFound)
- return@put
- }
- val updated = call.receive<Item>()
- val newItem = updated.copy(id = id)
- itemStore[id] = newItem
- call.respond(newItem)
- }
- }
+ itemRoutes()
+ reviewRoutes()
}
}
diff --git a/src/main/kotlin/com/gs/routes/ItemRoutes.kt b/src/main/kotlin/com/gs/routes/ItemRoutes.kt
new file mode 100644
index 0000000..44f7ef1
--- /dev/null
+++ b/src/main/kotlin/com/gs/routes/ItemRoutes.kt
@@ -0,0 +1,60 @@
+package com.gs.routes
+
+import com.gs.db.Items
+import com.gs.models.Item
+import io.ktor.http.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.transactions.transaction
+
+fun ResultRow.toItem() = Item(
+ id = this[Items.id],
+ productName = this[Items.productName],
+ brands = this[Items.brands],
+ quantity = this[Items.quantity],
+ categories = this[Items.categories],
+ description = this[Items.description],
+ ingredientsText = this[Items.ingredientsText],
+)
+
+fun Route.itemRoutes() {
+ route("/items/{id}") {
+ get {
+ val id = call.parameters["id"]!!
+ val item = transaction {
+ Items.selectAll().where { Items.id eq id }.singleOrNull()?.toItem()
+ }
+ if (item == null) {
+ call.respond(HttpStatusCode.NotFound)
+ return@get
+ }
+ call.respond(item)
+ }
+
+ put {
+ val id = call.parameters["id"]!!
+ val exists = transaction { Items.selectAll().where { Items.id eq id }.count() > 0 }
+ if (!exists) {
+ call.respond(HttpStatusCode.NotFound)
+ return@put
+ }
+ val updated = call.receive<Item>()
+ transaction {
+ Items.update({ Items.id eq id }) {
+ it[productName] = updated.productName
+ it[brands] = updated.brands
+ it[quantity] = updated.quantity
+ it[categories] = updated.categories
+ it[description] = updated.description
+ it[ingredientsText] = updated.ingredientsText
+ }
+ }
+ val item = transaction {
+ Items.selectAll().where { Items.id eq id }.single().toItem()
+ }
+ call.respond(item)
+ }
+ }
+}
diff --git a/src/main/kotlin/com/gs/routes/ReviewRoutes.kt b/src/main/kotlin/com/gs/routes/ReviewRoutes.kt
new file mode 100644
index 0000000..893bb58
--- /dev/null
+++ b/src/main/kotlin/com/gs/routes/ReviewRoutes.kt
@@ -0,0 +1,54 @@
+package com.gs.routes
+
+import com.gs.db.Items
+import com.gs.db.Reviews
+import com.gs.models.Review
+import io.ktor.http.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.transactions.transaction
+import java.util.UUID
+
+private fun ResultRow.toReview() = Review(
+ id = this[Reviews.id],
+ rating = this[Reviews.rating],
+ comment = this[Reviews.comment],
+)
+
+fun Route.reviewRoutes() {
+ get("/items/{id}/reviews") {
+ val id = call.parameters["id"]!!
+ val exists = transaction { Items.selectAll().where { Items.id eq id }.count() > 0 }
+ if (!exists) {
+ call.respond(HttpStatusCode.NotFound)
+ return@get
+ }
+ val reviews = transaction {
+ Reviews.selectAll().where { Reviews.itemId eq id }.map { it.toReview() }
+ }
+ call.respond(reviews)
+ }
+
+ post("/items/{id}/reviews") {
+ val id = call.parameters["id"]!!
+ val exists = transaction { Items.selectAll().where { Items.id eq id }.count() > 0 }
+ if (!exists) {
+ call.respond(HttpStatusCode.NotFound)
+ return@post
+ }
+ val input = call.receive<Review>()
+ val reviewId = UUID.randomUUID().toString()
+ val review = transaction {
+ Reviews.insert {
+ it[Reviews.id] = reviewId
+ it[itemId] = id
+ it[rating] = input.rating
+ it[comment] = input.comment
+ }
+ Reviews.selectAll().where { Reviews.id eq reviewId }.single().toReview()
+ }
+ call.respond(HttpStatusCode.Created, review)
+ }
+}
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index 77465ba..0bf201f 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -6,3 +6,12 @@ ktor {
port = 8080
}
}
+
+database {
+ url = "jdbc:postgresql://localhost:5432/grocery_scanner"
+ url = ${?DB_URL}
+ user = "mikkelthygesen"
+ user = ${?DB_USER}
+ password = ""
+ password = ${?DB_PASSWORD}
+}