diff --git a/src/main/kotlin/dev/mduchene/bolts/persistence/Database.kt b/src/main/kotlin/dev/mduchene/bolts/persistence/Database.kt index c25cc21..604df87 100644 --- a/src/main/kotlin/dev/mduchene/bolts/persistence/Database.kt +++ b/src/main/kotlin/dev/mduchene/bolts/persistence/Database.kt @@ -2,11 +2,39 @@ package dev.mduchene.bolts.persistence import java.sql.Connection import java.sql.DriverManager +import java.sql.PreparedStatement +import java.sql.ResultSet class Database(private val config: DatabaseConfig) { fun getConnection(): Connection = DriverManager.getConnection(config.url, config.username, config.password) + fun executeUpdate( + sql: String, + bind: PreparedStatement.() -> Unit = {}, + ): Int = withPreparedStatement(sql) { + bind() + executeUpdate() + } + + fun queryOne( + sql: String, + bind: PreparedStatement.() -> Unit = {}, + map: ResultSet.() -> T, + ): T? = query(sql, bind) { + if (next()) map() else null + } + + fun queryList( + sql: String, + bind: PreparedStatement.() -> Unit = {}, + map: ResultSet.() -> T, + ): List = query(sql, bind) { + buildList { + while (next()) add(map()) + } + } + fun initialize() { getConnection().use { connection -> connection.createStatement().use { statement -> @@ -17,6 +45,22 @@ class Database(private val config: DatabaseConfig) { } } + private fun query( + sql: String, + bind: PreparedStatement.() -> Unit, + read: ResultSet.() -> T, + ): T = withPreparedStatement(sql) { + bind() + executeQuery().use { result -> result.read() } + } + + private fun withPreparedStatement( + sql: String, + action: PreparedStatement.() -> T, + ): T = getConnection().use { connection -> + connection.prepareStatement(sql).use { statement -> statement.action() } + } + private companion object { const val CREATE_USERS_TABLE = """ CREATE TABLE IF NOT EXISTS users ( diff --git a/src/main/kotlin/dev/mduchene/bolts/user/SessionRepository.kt b/src/main/kotlin/dev/mduchene/bolts/user/SessionRepository.kt index c81fd36..e703d10 100644 --- a/src/main/kotlin/dev/mduchene/bolts/user/SessionRepository.kt +++ b/src/main/kotlin/dev/mduchene/bolts/user/SessionRepository.kt @@ -1,18 +1,14 @@ package dev.mduchene.bolts.user import dev.mduchene.bolts.persistence.Database -import java.sql.ResultSet class SessionRepository(private val database: Database) { fun create(token: String, userId: Long) { val sql = "INSERT INTO sessions (token, user_id) VALUES (?, ?)" - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.setString(1, token) - statement.setLong(2, userId) - statement.executeUpdate() - } + database.executeUpdate(sql) { + setString(1, token) + setLong(2, userId) } } @@ -24,20 +20,10 @@ class SessionRepository(private val database: Database) { WHERE sessions.token = ? """.trimIndent() - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.setString(1, token) - statement.executeQuery().use { result -> - return if (result.next()) result.toUser() else null - } - } - } + return database.queryOne( + sql, + bind = { setString(1, token) }, + map = { toUser() }, + ) } - - private fun ResultSet.toUser() = User( - id = getLong("id"), - username = getString("username"), - passwordHash = getString("password_hash"), - role = getString("role"), - ) } diff --git a/src/main/kotlin/dev/mduchene/bolts/user/UserRepository.kt b/src/main/kotlin/dev/mduchene/bolts/user/UserRepository.kt index fc44570..627e337 100644 --- a/src/main/kotlin/dev/mduchene/bolts/user/UserRepository.kt +++ b/src/main/kotlin/dev/mduchene/bolts/user/UserRepository.kt @@ -11,17 +11,17 @@ class UserRepository(private val database: Database) { RETURNING id, username, password_hash, role """.trimIndent() - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.setString(1, username) - statement.setString(2, passwordHash) - statement.setString(3, role) - statement.executeQuery().use { result -> - check(result.next()) { "User insert returned no row" } - return result.toUser() - } - } - } + return checkNotNull( + database.queryOne( + sql, + bind = { + setString(1, username) + setString(2, passwordHash) + setString(3, role) + }, + map = ResultSet::toUser, + ), + ) { "User insert returned no row" } } fun findByUsername(username: String): User? { @@ -31,45 +31,32 @@ class UserRepository(private val database: Database) { WHERE username = ? """.trimIndent() - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.setString(1, username) - statement.executeQuery().use { result -> - return if (result.next()) result.toUser() else null - } - } - } + return database.queryOne( + sql, + bind = { setString(1, username) }, + map = ResultSet::toUser, + ) } fun findAll(): List { val sql = "SELECT id, username, password_hash, role FROM users ORDER BY username" - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.executeQuery().use { result -> - return buildList { - while (result.next()) add(result.toUser()) - } - } - } - } + return database.queryList(sql, map = ResultSet::toUser) } fun updateRole(id: Long, role: String) { val sql = "UPDATE users SET role = ? WHERE id = ?" - database.getConnection().use { connection -> - connection.prepareStatement(sql).use { statement -> - statement.setString(1, role) - statement.setLong(2, id) - statement.executeUpdate() - } + database.executeUpdate(sql) { + setString(1, role) + setLong(2, id) } } - private fun ResultSet.toUser() = User( - id = getLong("id"), - username = getString("username"), - passwordHash = getString("password_hash"), - role = getString("role"), - ) } + +internal fun ResultSet.toUser() = User( + id = getLong("id"), + username = getString("username"), + passwordHash = getString("password_hash"), + role = getString("role"), +)