diff --git a/src/main/kotlin/model/Patron.kt b/src/main/kotlin/model/Patron.kt index 154f15a..2717ea3 100644 --- a/src/main/kotlin/model/Patron.kt +++ b/src/main/kotlin/model/Patron.kt @@ -1,6 +1,7 @@ package codes.kalar.model import kotlinx.serialization.Serializable +import java.sql.Date @Serializable data class Patron( @@ -9,6 +10,7 @@ data class Patron( val hasGoodStanding: Boolean, val feeTotal: Long, val isArchived: Boolean, + val loginUsername: String, val lastLogin: String?, val password: String?, ) @@ -17,9 +19,10 @@ data class Patron( data class NewPatron( // ID to be inserted by Database val name: String, - val hasGoodStanding: Boolean, - val feeTotal: Long, - val isArchived: Boolean, - val lastLogin: String?, + val hasGoodStanding: Boolean = true, + val feeTotal: Long = 0, + val isArchived: Boolean = false, + val loginUsername: String, + val lastLogin: String = Date(System.currentTimeMillis()).toString(), val password: String?, ) diff --git a/src/main/kotlin/routes/LibraryRoutes.kt b/src/main/kotlin/routes/LibraryRoutes.kt index 520cca4..6329a53 100644 --- a/src/main/kotlin/routes/LibraryRoutes.kt +++ b/src/main/kotlin/routes/LibraryRoutes.kt @@ -67,9 +67,6 @@ fun Application.configureLibraryRoutes(dbConnection: Connection) { } catch (cause: DbElementInsertionException) { log.error(cause.message) call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to update Library.") - } catch (cause: DbElementInsertionException) { - log.error(cause.message) - call.respond(HttpStatusCode.BadRequest, cause.message ?: "Bad Arguments") } catch (cause: ContentTransformationException) { log.error(cause.message) call.respond(HttpStatusCode.BadRequest, cause.message ?: "Bad Arguments") diff --git a/src/main/kotlin/routes/PatronRoutes.kt b/src/main/kotlin/routes/PatronRoutes.kt index c0f95cb..ea533ea 100644 --- a/src/main/kotlin/routes/PatronRoutes.kt +++ b/src/main/kotlin/routes/PatronRoutes.kt @@ -1,6 +1,10 @@ package codes.kalar.routes +import codes.kalar.exception.DbElementInsertionException +import codes.kalar.exception.DbElementNotFoundException +import codes.kalar.model.NewPatron import codes.kalar.model.Patron +import codes.kalar.service.PatronService import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* @@ -9,32 +13,68 @@ import io.ktor.server.routing.* import java.sql.Connection fun Application.configurePatronRoutes(dbConnection: Connection) { + val patronService = PatronService(dbConnection) routing { - get("/patron") { - if (call.request.queryParameters["patron"] == null) { - call.respond(HttpStatusCode.BadRequest, "Invalid parameters") + get("/patron/{id}") { + try { + val id = call.pathParameters["id"]!!.toLong() + val patron = patronService.readPatronById(id) + call.respond(HttpStatusCode.OK, patron) + } catch (cause: DbElementInsertionException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unknown error") + } catch (cause: NumberFormatException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unknown error") } - else { - call.respondText("Hello, ${call.request.queryParameters["patron"]}") + } + + get("/patron") { + try { + val username = call.parameters["username"] + if (username == null) { + call.respond(HttpStatusCode.BadRequest, "username & password required") + } else { + val patron = patronService.readPatronByLoginUsername(username) + call.respond(HttpStatusCode.OK, patron) + } + } catch (cause: DbElementNotFoundException) { + call.respond(HttpStatusCode.BadRequest, "Username not found") } } post("/patron") { - val patron = call.receive() - call.respondText("${patron.name} is posted") + try { + val patron = call.receive() + val id = patronService.create(patron) + call.respondText("Adding ${patron.name} to database with the id of $id", status = HttpStatusCode.OK) + } catch (cause: DbElementInsertionException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "Bad Arguments") + } catch (cause: ContentTransformationException) { + call.respond(HttpStatusCode.BadRequest, "Bad Arguments. Must pass a valid CollectionItem object.") + } } patch("/patron") { - val patron = call.receive() - call.respondText("${patron.name} is patched") + try { + val patron = call.receive() + val patchedPatron = patronService.update(patron) + call.respondText("${patron.name} is patched") + } catch (cause: DbElementInsertionException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to update Patron.") + } catch (cause: ContentTransformationException) { + + } } - delete("/patron") { - if (call.request.queryParameters["id"] == null) { - call.respond(HttpStatusCode.BadRequest, "Invalid parameters") - } else { - call.respondText("Do you have permissions?") + delete("/patron/{id}") { + try { + val id = call.pathParameters["id"]!!.toLong() + patronService.delete(id) + call.respond(HttpStatusCode.OK, "Successfully deleted the patron") + } catch (cause: DbElementInsertionException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to delete Patron.") + } catch (cause: NumberFormatException) { + call.respond(HttpStatusCode.BadRequest, cause.message ?: "ID needs to be a number.") } } } diff --git a/src/main/kotlin/service/CollectionItemService.kt b/src/main/kotlin/service/CollectionItemService.kt index a9b0a95..bd32b71 100644 --- a/src/main/kotlin/service/CollectionItemService.kt +++ b/src/main/kotlin/service/CollectionItemService.kt @@ -10,7 +10,8 @@ import java.sql.* class CollectionItemService(private val connection: Connection) { companion object { - private const val SELECT_ITEM_BY_TITLE = "SELECT * FROM collection_item WHERE levenshtein(title, ?) <= 5 LIMIT 25 OFFSET ?" + private const val SELECT_ITEM_BY_TITLE = + "SELECT * FROM collection_item WHERE levenshtein(title, ?) <= 5 LIMIT 25 OFFSET ?" private const val SELECT_ITEM_BY_ID = "SELECT * FROM collection_item WHERE id = ?" private const val INSERT_ITEM = "INSERT INTO collection_item (title, author, publisher, publishing_date, " + "loc_number, dewey_decimal_number, isbn, sort_title, format, language, page_count, categories, " + @@ -20,14 +21,17 @@ class CollectionItemService(private val connection: Connection) { "publishing_date = ?, loc_number = ?, dewey_decimal_number = ?, isbn = ?, sort_title = ?, format = ?, " + "language = ?, page_count = ?, categories = ?, description = ?, price_in_cents = ?, cover_image_uri = ?, " + "is_checked_in = ?, is_archived = ?, is_lost = ?, lost_date = ? WHERE id = ?" + // In the event books are "deleted" erroneously, having a flag set instead of actually removing the entry allows // for quick reversal. private const val ARCHIVE_ITEM_BY_ID = "UPDATE collection_item SET is_archived = true WHERE id = ?" } fun create(newCollectionItem: NewCollectionItem): Long { - val statement = connection.prepareStatement(INSERT_ITEM, - Statement.RETURN_GENERATED_KEYS) + val statement = connection.prepareStatement( + INSERT_ITEM, + Statement.RETURN_GENERATED_KEYS + ) statement.setString(1, newCollectionItem.title) statement.setString(2, newCollectionItem.author) statement.setString(3, newCollectionItem.publisher) @@ -47,7 +51,7 @@ class CollectionItemService(private val connection: Connection) { statement.setBoolean(17, newCollectionItem.isArchived) statement.setBoolean(18, newCollectionItem.isLost) statement.setDate(19, Date.valueOf(newCollectionItem.lostDate)) - try{ + try { statement.execute() val key = statement.generatedKeys if (key.next()) { @@ -55,8 +59,10 @@ class CollectionItemService(private val connection: Connection) { } return -1 } catch (cause: SQLException) { - throw DbElementInsertionException("Couldn't insert item " + - "${newCollectionItem.title} into database. ${cause.message}") + throw DbElementInsertionException( + "Couldn't insert item " + + "${newCollectionItem.title} into database. ${cause.message}" + ) } } @@ -91,7 +97,7 @@ class CollectionItemService(private val connection: Connection) { } else { throw DbElementNotFoundException("Could not find collection item. resultSet: $resultSet") } - } catch(cause: SQLException) { + } catch (cause: SQLException) { throw DbElementNotFoundException("Could not find collection item with id $id") } } @@ -121,9 +127,15 @@ class CollectionItemService(private val connection: Connection) { statement.setLong(20, collectionItem.id) return statement.execute() } catch (e: SQLException) { - throw DbElementInsertionException("${e.message}\ncollectionItem: $collectionItem\n statement: $statement\n ", e) + throw DbElementInsertionException( + "${e.message}\ncollectionItem: $collectionItem\n statement: $statement\n ", + e + ) } catch (e: IllegalArgumentException) { - throw DbElementInsertionException("${e.message}\ncollectionItem: $collectionItem\n statement: $statement\n ", e) + throw DbElementInsertionException( + "${e.message}\ncollectionItem: $collectionItem\n statement: $statement\n ", + e + ) } } diff --git a/src/main/kotlin/service/PartonService.kt b/src/main/kotlin/service/PartonService.kt deleted file mode 100644 index 6f7eafb..0000000 --- a/src/main/kotlin/service/PartonService.kt +++ /dev/null @@ -1,26 +0,0 @@ -package codes.kalar.service - -import codes.kalar.exception.DbElementInsertionException -import codes.kalar.exception.DbElementNotFoundException -import kotlinx.serialization.json.Json -import java.sql.* - -class PartonService(private val connection: Connection) { - - companion object { - private const val SELECT_PATRON_BY_ = "" - private const val INSERT_PATRON = "" - private const val UPDATE_PATRON_BY_ID = "" - // In the event are "deleted" erroneously, having a flag set instead of actually removing the entry allows - // for quick reversal. - private const val ARCHIVE_PATRON_BY_ID = "" - } - - suspend fun create() {} - - suspend fun read() {} - - suspend fun update() {} - - suspend fun delete() {} -} diff --git a/src/main/kotlin/service/PatronService.kt b/src/main/kotlin/service/PatronService.kt new file mode 100644 index 0000000..3ad3c60 --- /dev/null +++ b/src/main/kotlin/service/PatronService.kt @@ -0,0 +1,131 @@ +package codes.kalar.service + +import codes.kalar.exception.DbElementNotFoundException +import codes.kalar.model.NewPatron +import codes.kalar.model.Patron +import java.sql.* + +class PatronService(private val connection: Connection) { + + companion object { + private const val SELECT_PATRON_BY_ID = "SELECT * FROM patron WHERE id = ?" + private const val SELECT_PATRON_BY_LOGIN_USERNAME = "SELECT * From patron where login_username = ?" + private const val INSERT_PATRON = + "INSERT INTO patron (name, has_good_standing, fee_total, is_archived, login_username, last_login, password) VALUES (?, ?, ?, ?, ?, ?, ?)" + private const val UPDATE_PATRON_BY_ID = + "UPDATE patron SET name = ?, has_good_standing = ?, fee_total = ?, is_archived = ?, login_username = ?, " + + "last_login = ?, password = ? WHERE id = ?" + + // In the event are "deleted" erroneously, having a flag set instead of actually removing the entry allows + // for quick reversal. + private const val ARCHIVE_PATRON_BY_ID = "UPDATE patron set is_archived = true WHERE id = ?" + } + + suspend fun create(newPatron: NewPatron): Long { + val statement = connection.prepareStatement(INSERT_PATRON, Statement.RETURN_GENERATED_KEYS) + statement.setString(1, newPatron.name) + statement.setBoolean(2, true) + statement.setLong(3, 0) + statement.setBoolean(4, false) + statement.setString(5, newPatron.loginUsername) + statement.setDate(6, Date(System.currentTimeMillis())) + statement.setString(7, newPatron.password) + statement.execute() + try { + val key = statement.generatedKeys + if (key.next()) { + return key.getLong(1) + } + return -1 + } catch (cause: SQLException) { + throw DbElementNotFoundException("Couldn't insert patron ${newPatron.name} into database. ${cause.message}") + } + } + + suspend fun readPatronById(id: Long): Patron { + try { + val statement = connection.prepareStatement(SELECT_PATRON_BY_ID, Statement.RETURN_GENERATED_KEYS) + statement.setLong(1, id) + val resultSet = statement.executeQuery() + if (resultSet.next()) { + val patron = createPatronFromResults(resultSet) + return patron + } else { + throw DbElementNotFoundException("Patron with ID $id not found") + } + } catch (cause: SQLException) { + throw DbElementNotFoundException("Patron with ID $id is not found") + } + } + + suspend fun readPatronByLoginUsername(username: String): Patron { + try { + val statement = connection.prepareStatement(SELECT_PATRON_BY_LOGIN_USERNAME) + statement.setString(1, username) + val resultSet = statement.executeQuery() + if (resultSet.next()) { + val patron = createPatronFromResults(resultSet) + return patron + } else { + throw SQLException() + } + } catch (cause: SQLException) { + throw DbElementNotFoundException("Could not find username or password") + } + } + + suspend fun loginPatronByLoginUsername(username: String, password: String): Boolean { + try { + val statement = connection.prepareStatement(SELECT_PATRON_BY_LOGIN_USERNAME) + statement.setString(1, username) + val resultSet = statement.executeQuery() + return if (resultSet.next()) { + resultSet.getString("password") == password + } else { + throw SQLException() + } + } catch (cause: SQLException) { + throw DbElementNotFoundException("Could not find username or password") + } + } + + suspend fun update(patron: Patron): Boolean { + val statement = connection.prepareStatement(UPDATE_PATRON_BY_ID) + statement.setString(1, patron.name) + statement.setBoolean(2, patron.hasGoodStanding) + statement.setLong(3, patron.feeTotal) + statement.setBoolean(4, patron.isArchived) + statement.setString(5, patron.loginUsername) + statement.setString(6, patron.lastLogin) + statement.setString(7, patron.password) + statement.setLong(8, patron.id) + try { + return !statement.execute() + } catch (cause: SQLException) { + throw DbElementNotFoundException("Patron with ID ${patron.id} not found.") + } + } + + suspend fun delete(id: Long) { + val statement = connection.prepareStatement(ARCHIVE_PATRON_BY_ID) + statement.setLong(1, id) + try { + statement.execute() + } catch (cause: SQLException) { + throw DbElementNotFoundException("Patron with ID $id not found.") + } + } + + fun createPatronFromResults(resultSet: ResultSet): Patron { + return Patron( + id = resultSet.getLong("id"), + name = resultSet.getString("name"), + hasGoodStanding = resultSet.getBoolean("has_good_standing"), + feeTotal = resultSet.getLong("fee_total"), + isArchived = resultSet.getBoolean("is_archived"), + lastLogin = resultSet.getString("last_login"), + password = "", // For security reasons... + loginUsername = resultSet.getString("login_username") + ) + } +}