Merge pull request 'LMS-18 Library API' (#1) from FEAT-Library-API into main
Reviewed-on: https://gitea.com/NickKalar/LMS-APIs/pulls/1
This commit is contained in:
@@ -7,6 +7,7 @@ data class Library(
|
|||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val address: String,
|
val address: String,
|
||||||
|
val isArchived: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -14,4 +15,5 @@ data class NewLibrary(
|
|||||||
// ID to be inserted by Database
|
// ID to be inserted by Database
|
||||||
val name: String,
|
val name: String,
|
||||||
val address: String,
|
val address: String,
|
||||||
|
val isArchived: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package codes.kalar.routes
|
package codes.kalar.routes
|
||||||
|
|
||||||
|
import codes.kalar.exception.DbElementInsertionException
|
||||||
|
import codes.kalar.exception.DbElementNotFoundException
|
||||||
import codes.kalar.model.Library
|
import codes.kalar.model.Library
|
||||||
|
import codes.kalar.model.NewLibrary
|
||||||
|
import codes.kalar.service.LibraryService
|
||||||
|
import io.ktor.http.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
@@ -8,28 +13,83 @@ import io.ktor.server.routing.*
|
|||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
|
||||||
fun Application.configureLibraryRoutes(dbConnection: Connection) {
|
fun Application.configureLibraryRoutes(dbConnection: Connection) {
|
||||||
|
val libraryService = LibraryService(dbConnection)
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
get("/libraries") {
|
get("/libraries") {
|
||||||
call.respondText("Libraries are neat!")
|
try {
|
||||||
|
val id = call.parameters["id"]?.toLong()
|
||||||
|
if (id != null) {
|
||||||
|
val library = libraryService.readLibraryById(id)
|
||||||
|
call.respond(HttpStatusCode.OK, library)
|
||||||
|
} else {
|
||||||
|
val libraries = libraryService.readAllLibraries()
|
||||||
|
call.respond(HttpStatusCode.OK, libraries)
|
||||||
|
}
|
||||||
|
} catch (cause: DbElementNotFoundException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to find Library.")
|
||||||
|
} catch (cause: IllegalArgumentException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Missing 'id' parameter.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/libraries/{id}") {
|
||||||
|
try {
|
||||||
|
val id = call.pathParameters["id"]!!.toLong()
|
||||||
|
val library = libraryService.readLibraryById(id)
|
||||||
|
call.respond(HttpStatusCode.OK, library)
|
||||||
|
} catch (cause: DbElementNotFoundException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to find Library.")
|
||||||
|
} catch (cause: NumberFormatException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, "Unable to parse number format. \"${call.pathParameters["id"]}\" is not a number.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/libraries/{libraryId}/items/{itemId}") {
|
get("/libraries/{libraryId}/items/{itemId}") {
|
||||||
call.respondText("You asked for ${call.parameters["itemId"]} from ${call.parameters["libraryId"]}")
|
// TODO Add search for collection_it where itemID && libraryID
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/libraries") {
|
post("/libraries") {
|
||||||
val library = call.receive<Library>()
|
val library = call.receive<NewLibrary>()
|
||||||
call.respondText("${library.name} is posted")
|
try {
|
||||||
|
val id = libraryService.create(library)
|
||||||
|
call.respondText("${library.name} is posted with the ID: $id")
|
||||||
|
} catch (cause: DbElementInsertionException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Unable to insert Library.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
patch("/libraries") {
|
patch("/libraries") {
|
||||||
|
try {
|
||||||
val library = call.receive<Library>()
|
val library = call.receive<Library>()
|
||||||
call.respondText("${library.name} is patched")
|
val patchedLibrary = libraryService.update(library)
|
||||||
|
call.respond(HttpStatusCode.OK, patchedLibrary)
|
||||||
|
} 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")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete("/libraries") {
|
delete("/libraries") {
|
||||||
call.respondText("We hate to see you go!")
|
try {
|
||||||
|
val id = call.parameters["id"]!!.toLong()
|
||||||
|
log.info("Deleting item with id=$id")
|
||||||
|
libraryService.delete(id)
|
||||||
|
call.respondText(":(", status = HttpStatusCode.OK)
|
||||||
|
} catch (cause: DbElementNotFoundException) {
|
||||||
|
log.error(cause.message, cause)
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Bad Arguments")
|
||||||
|
} catch (cause: NumberFormatException) {
|
||||||
|
log.error(cause.message, cause)
|
||||||
|
call.respond(HttpStatusCode.BadRequest, cause.message ?: "Invalid ID format")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,101 @@ package codes.kalar.service
|
|||||||
|
|
||||||
import codes.kalar.exception.DbElementInsertionException
|
import codes.kalar.exception.DbElementInsertionException
|
||||||
import codes.kalar.exception.DbElementNotFoundException
|
import codes.kalar.exception.DbElementNotFoundException
|
||||||
import kotlinx.serialization.json.Json
|
import codes.kalar.model.Library
|
||||||
|
import codes.kalar.model.NewLibrary
|
||||||
import java.sql.*
|
import java.sql.*
|
||||||
|
|
||||||
class LibraryService(private val connection: Connection) {
|
class LibraryService(private val connection: Connection) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SELECT_LIBRARY_BY_ID = ""
|
private const val SELECT_LIBRARY_BY_ID = "SELECT * FROM library WHERE id = ?"
|
||||||
private const val INSERT_LIBRARY = ""
|
private const val SELECT_ALL_LIBRARIES = "SELECT * FROM library"
|
||||||
private const val UPDATE_LIBRARY_BY_ID = ""
|
private const val INSERT_LIBRARY = "INSERT INTO library (name, address, is_archived) VALUES (?, ?, ?)"
|
||||||
|
private const val UPDATE_LIBRARY_BY_ID = "UPDATE library SET name = ?, address = ? WHERE id = ?"
|
||||||
// In the event are "deleted" erroneously, having a flag set instead of actually removing the entry allows
|
// In the event are "deleted" erroneously, having a flag set instead of actually removing the entry allows
|
||||||
// for quick reversal.
|
// for quick reversal.
|
||||||
private const val ARCHIVE_LIBRARY_BY_ID = ""
|
private const val ARCHIVE_LIBRARY_BY_ID = "UPDATE library SET is_archived = true WHERE id = ?"
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun create() {}
|
suspend fun create(library: NewLibrary): Long {
|
||||||
|
val statement = connection.prepareStatement(INSERT_LIBRARY, Statement.RETURN_GENERATED_KEYS)
|
||||||
suspend fun read() {}
|
statement.setString(1, library.name)
|
||||||
|
statement.setString(2, library.address)
|
||||||
suspend fun update() {}
|
statement.setBoolean(3, library.isArchived)
|
||||||
|
try {
|
||||||
suspend fun delete() {}
|
statement.execute()
|
||||||
|
val key = statement.generatedKeys
|
||||||
|
if (key.next()) {
|
||||||
|
return key.getLong(1)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
} catch (cause: SQLException) {
|
||||||
|
throw DbElementInsertionException("Could not insert library: ${cause.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun readLibraryById(id: Long): Library {
|
||||||
|
val statement = connection.prepareStatement(SELECT_LIBRARY_BY_ID)
|
||||||
|
statement.setLong(1, id)
|
||||||
|
try {
|
||||||
|
val resultSet = statement.executeQuery()
|
||||||
|
if (resultSet.next() && !resultSet.getBoolean("is_archived")) {
|
||||||
|
return createLibraryFromResult(resultSet)
|
||||||
|
} else {
|
||||||
|
throw DbElementNotFoundException("Could not find collection item. resultSet: $resultSet")
|
||||||
|
}
|
||||||
|
} catch(cause: SQLException) {
|
||||||
|
throw DbElementNotFoundException("Could not find collection item with id $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readAllLibraries(): List<Library> {
|
||||||
|
val libraries = ArrayList<Library>()
|
||||||
|
val statement = connection.prepareStatement(SELECT_ALL_LIBRARIES)
|
||||||
|
val resultSet = statement.executeQuery()
|
||||||
|
while (resultSet.next()) {
|
||||||
|
if (!resultSet.getBoolean("is_archived")) {
|
||||||
|
val library = createLibraryFromResult(resultSet)
|
||||||
|
libraries.add(library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return libraries
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun update(library: Library): Library {
|
||||||
|
val statement = connection.prepareStatement(UPDATE_LIBRARY_BY_ID)
|
||||||
|
try {
|
||||||
|
statement.setString(1, library.name)
|
||||||
|
statement.setString(2, library.address)
|
||||||
|
statement.setLong(3, library.id)
|
||||||
|
statement.execute()
|
||||||
|
return readLibraryById(library.id)
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw DbElementInsertionException("${e.message}\ncollectionItem: $library\n statement: $statement\n ", e)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw DbElementInsertionException("${e.message}\ncollectionItem: $library\n statement: $statement\n ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun delete(id: Long) {
|
||||||
|
val statement = connection.prepareStatement(ARCHIVE_LIBRARY_BY_ID)
|
||||||
|
try {
|
||||||
|
statement.setLong(1, id)
|
||||||
|
statement.execute()
|
||||||
|
} catch (e: SQLException) {
|
||||||
|
throw DbElementNotFoundException("Could not find Library with id $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLibraryFromResult(resultSet: ResultSet): Library {
|
||||||
|
try {
|
||||||
|
val id = resultSet.getLong("id")
|
||||||
|
val name = resultSet.getString("name")
|
||||||
|
val address = resultSet.getString("address")
|
||||||
|
val isArchived = resultSet.getBoolean("is_archived")
|
||||||
|
return Library(id, name, address, isArchived)
|
||||||
|
} catch (cause: NullPointerException) {
|
||||||
|
throw DbElementInsertionException("${cause.message}\nresultSet = ${resultSet.metaData}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,17 +81,31 @@ paths:
|
|||||||
|
|
||||||
/libraries:
|
/libraries:
|
||||||
get:
|
get:
|
||||||
description: ""
|
description: "Search for libraries based on ID or get all libraries"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
in: "path"
|
in: "path"
|
||||||
required: true
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: id=27
|
example: id=1
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: "OK"
|
description: "Either a single library, or a list of all libraries."
|
||||||
|
"400":
|
||||||
|
description: "Bad Request. Likely, the ID you are looking for doesn't exist."
|
||||||
|
post:
|
||||||
|
description: "The method to add a new library."
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NewLibrary"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "The ID of the newly created library will be returned."
|
||||||
|
"400":
|
||||||
|
description: "Bad Request. Unable to insert the new library."
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ components:
|
|||||||
example: ""
|
example: ""
|
||||||
sortTitle:
|
sortTitle:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: "The tite of the book with any articles moved to the end for sorting purposes."
|
description: "The title of the book with any articles moved to the end for sorting purposes."
|
||||||
example: "Fellowship of the Ring, The"
|
example: "Fellowship of the Ring, The"
|
||||||
format:
|
format:
|
||||||
type: "string"
|
type: "string"
|
||||||
@@ -194,7 +194,7 @@ components:
|
|||||||
example: ""
|
example: ""
|
||||||
sortTitle:
|
sortTitle:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: "The tite of the book with any articles moved to the end for sorting purposes."
|
description: "The title of the book with any articles moved to the end for sorting purposes."
|
||||||
example: "Fellowship of the Ring, The"
|
example: "Fellowship of the Ring, The"
|
||||||
format:
|
format:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
|||||||
Reference in New Issue
Block a user