package be.simplenotes.domain import arrow.core.Either import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.raise.ensureNotNull import be.simplenotes.domain.security.PasswordHash import be.simplenotes.domain.security.SimpleJwt import be.simplenotes.domain.validation.UserValidations import be.simplenotes.persistence.repositories.UserRepository import be.simplenotes.search.NoteSearcher import be.simplenotes.types.LoggedInUser import be.simplenotes.types.PersistedUser import io.konform.validation.ValidationErrors import jakarta.inject.Singleton import kotlinx.serialization.Serializable interface UserService { fun register(form: RegisterForm): Either fun login(form: LoginForm): Either fun delete(form: DeleteForm): Either } @Singleton internal class UserServiceImpl( private val userRepository: UserRepository, private val passwordHash: PasswordHash, private val jwt: SimpleJwt, private val searcher: NoteSearcher, ) : UserService { override fun register(form: RegisterForm) = either { val user = UserValidations.validateRegister(form).bind() ensure(!userRepository.exists(user.username)) { RegisterError.UserExists } ensureNotNull(userRepository.create(user.copy(password = passwordHash.crypt(user.password)))) { RegisterError.UserExists } } override fun login(form: LoginForm) = either { val user = UserValidations.validateLogin(form).bind() val persistedUser = ensureNotNull(userRepository.find(user.username)) { LoginError.Unregistered } ensure(passwordHash.verify(form.password!!, persistedUser.password)) { LoginError.WrongPassword } jwt.sign(LoggedInUser(persistedUser)) } override fun delete(form: DeleteForm) = either { val user = UserValidations.validateDelete(form).bind() val persistedUser = ensureNotNull(userRepository.find(user.username)) { DeleteError.Unregistered } ensure(passwordHash.verify(user.password, persistedUser.password)) { DeleteError.WrongPassword } ensure(userRepository.delete(persistedUser.id)) { DeleteError.Unregistered } searcher.dropIndex(persistedUser.id) } } sealed class DeleteError { object Unregistered : DeleteError() object WrongPassword : DeleteError() class InvalidForm(val validationErrors: ValidationErrors) : DeleteError() } data class DeleteForm(val username: String?, val password: String?, val checked: Boolean) sealed class LoginError { object Unregistered : LoginError() object WrongPassword : LoginError() class InvalidLoginForm(val validationErrors: ValidationErrors) : LoginError() } typealias Token = String sealed class RegisterError { object UserExists : RegisterError() class InvalidRegisterForm(val validationErrors: ValidationErrors) : RegisterError() } typealias RegisterForm = LoginForm @Serializable data class LoginForm(val username: String?, val password: String?)