abstract db connection

This commit is contained in:
Maxime Duchêne-Savard 2026-07-28 16:11:04 -04:00
parent ca62140e6f
commit e2cf9c04b5
3 changed files with 79 additions and 62 deletions

View File

@ -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 <T> queryOne(
sql: String,
bind: PreparedStatement.() -> Unit = {},
map: ResultSet.() -> T,
): T? = query(sql, bind) {
if (next()) map() else null
}
fun <T> queryList(
sql: String,
bind: PreparedStatement.() -> Unit = {},
map: ResultSet.() -> T,
): List<T> = 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 <T> query(
sql: String,
bind: PreparedStatement.() -> Unit,
read: ResultSet.() -> T,
): T = withPreparedStatement(sql) {
bind()
executeQuery().use { result -> result.read() }
}
private fun <T> 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 (

View File

@ -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"),
)
}

View File

@ -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<User> {
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"),
)