Daniel Pitts
10/13/2024, 4:26 PMDaniel Pitts
10/13/2024, 4:28 PMclass Login(id: EntityID<UUID>) : UUIDEntity(id) {
var username: String by Logins.username
private var password by Logins.hashedPassword
companion object : UUIDEntityClass<Login>(Logins)
// Only allowed on new entities
fun setPassword(password: String) {
this.password = BCrypt.hashpw(password, BCrypt.gensalt())
}
// Only allowed on existing entities.
fun updatePassword(oldPassword: String, newPassword: String) {
check(checkPassword(oldPassword)) { "Old password does not match" }
this.password = BCrypt.hashpw(newPassword, BCrypt.gensalt())
}
fun checkPassword(password: String): Boolean = BCrypt.checkpw(password, this.password)
}
Oleg Babichev
10/14/2024, 2:29 PMBaseLogin
, and after that 2 children NewLogin
and ExistingLogin
But it looks like it's not a variant due to cache mechanism, that is caches entities per Table, not per Entity class (if I'm right)... it means that if you create NewLogin
entity, take id, and try to get ExistingLogin
, it would try to return existing NewLogin
and fail with class cast exception... it could be "fixed" with removeFromCache
So the variant I made at the first attempt looks like this:
abstract class BaseLogin(id: EntityID<UUID>) : UUIDEntity(id) {
protected var password by Logins.hashedPassword
companion object : UUIDEntityClass<Login>(Logins)
}
class NewLogin(id: EntityID<UUID>) : BaseLogin(id) {
companion object : UUIDEntityClass<NewLogin>(Logins) {
override fun findById(id: EntityID<UUID>) = error("Use Login entity instead")
override fun forEntityIds(ids: List<EntityID<UUID>>) = error("Use Login entity instead")
override fun all() = error("Use Login entity instead")
override fun searchQuery(op: Op<Boolean>) = error("Use Login entity instead")
fun create(init: NewLogin.() -> Unit): NewLogin {
return new(init)
}
}
fun asLogin(): Login {
val id = this.id.value
flush()
removeFromCache(this)
return Login[id]
}
fun setNewPassword(password: String) {
this.password = password
}
}
open class Login(id: EntityID<UUID>) : BaseLogin(id) {
companion object : UUIDEntityClass<Login>(Logins) {
override fun new(init: Login.() -> Unit) = error("Use NewLogin entity instead")
override fun new(id: UUID?, init: Login.() -> Unit) = error("Use NewLogin entity instead")
}
fun updatePassword(oldPassword: String, newPassword: String) {
check(checkPassword(oldPassword)) { "Old password does not match" }
this.password = newPassword
}
private fun checkPassword(password: String): Boolean = password == this.password
}
@Test
fun test() {
withTables(Logins) {
val entity = NewLogin
.new {
setNewPassword("p1")
}
.asLogin()
entity.updatePassword("p1", "p2")
assertEquals("p2", Logins.selectAll().first()[Logins.hashedPassword])
}
}
I'd prefer to make one function create()
instead of combination new{}.asLogin()
, but that function should use new
internally, so it will not be possible to prevent call of new
anyway, and it's necessary to control what happens in the cache, because without removeFromCache
sequential calls of Login.get()
and NewLogin.get()
(and vice versa of course) would cause class cast exception.