added basic edit save account

- changes to AccountController
  - added postEditAccount
  - change postCreateAccount to use lambda
- added success message to edit.html
- added lambda to deal with password to toAccountRequest in Mapping.kt
- added updateAccount to AccountRegistryService
- added accountRegistryUpdate to WebClientCalls.kt
- added NoChangeException
- added NotFoundException
This commit is contained in:
2025-01-28 21:49:31 +01:00
parent 14e7971f73
commit 794c0c49d6
7 changed files with 137 additions and 22 deletions

View File

@@ -2,6 +2,8 @@ package ltd.hlaeja.controller
import java.util.UUID import java.util.UUID
import ltd.hlaeja.dto.Pagination import ltd.hlaeja.dto.Pagination
import ltd.hlaeja.exception.NoChangeException
import ltd.hlaeja.exception.NotFoundException
import ltd.hlaeja.exception.PasswordException import ltd.hlaeja.exception.PasswordException
import ltd.hlaeja.exception.UsernameDuplicateException import ltd.hlaeja.exception.UsernameDuplicateException
import ltd.hlaeja.form.AccountForm import ltd.hlaeja.form.AccountForm
@@ -30,7 +32,7 @@ class AccountController(
@GetMapping("/edit-{account}") @GetMapping("/edit-{account}")
fun getEditAccount( fun getEditAccount(
@PathVariable account: UUID, @PathVariable account: UUID,
model: Model model: Model,
): Mono<String> = accountRegistryService.getAccount(account) ): Mono<String> = accountRegistryService.getAccount(account)
.doOnNext { .doOnNext {
model.addAttribute("account", account) model.addAttribute("account", account)
@@ -38,6 +40,37 @@ class AccountController(
} }
.then(Mono.just("account/edit")) .then(Mono.just("account/edit"))
@PostMapping("/edit-{account}")
fun postEditAccount(
@PathVariable account: UUID,
@ModelAttribute("accountForm") accountForm: AccountForm,
model: Model,
): Mono<String> = Mono.just(accountForm)
.flatMap {
accountRegistryService.updateAccount(
account,
it.toAccountRequest { password -> if (password.isNullOrEmpty()) null else password },
)
}
.doOnNext {
model.addAttribute("successMessage", "Saved changes!!!")
model.addAttribute("account", account)
model.addAttribute("accountForm", it.toAccountForm())
}
.then(Mono.just("account/edit"))
.onErrorResume { error ->
val errorMessage = when (error) {
is NoChangeException -> Pair("successMessage", "No change to save")
is NotFoundException -> Pair("errorMessage", "User dont exists. how did this happen?")
is UsernameDuplicateException -> Pair("errorMessage", "Username already exists. Please choose another.")
else -> Pair("errorMessage", "An unexpected error occurred. Please try again later.")
}
model.addAttribute(errorMessage.first, errorMessage.second)
model.addAttribute("accountForm", accountForm)
model.addAttribute("account", account)
Mono.just("account/edit")
}
@GetMapping("/create") @GetMapping("/create")
fun getCreateAccount( fun getCreateAccount(
model: Model, model: Model,
@@ -48,22 +81,27 @@ class AccountController(
fun postCreateAccount( fun postCreateAccount(
@ModelAttribute("accountForm") accountForm: AccountForm, @ModelAttribute("accountForm") accountForm: AccountForm,
model: Model, model: Model,
): Mono<String> { ): Mono<String> = Mono.just(accountForm)
return accountRegistryService.addAccount(accountForm.toAccountRequest()) .flatMap {
.map { accountRegistryService.addAccount(
model.addAttribute("success", true) it.toAccountRequest { password ->
"redirect:/account" when {
password.isNullOrEmpty() -> throw PasswordException("Password requirements failed")
else -> password
}
},
)
}
.map { "redirect:/account" }
.onErrorResume { error ->
val errorMessage = when (error) {
is UsernameDuplicateException -> "Username already exists. Please choose another."
is PasswordException -> error.message
else -> "An unexpected error occurred. Please try again later."
} }
.onErrorResume { error -> model.addAttribute("errorMessage", errorMessage)
val errorMessage = when (error) { Mono.just("account/create")
is UsernameDuplicateException -> "Username already exists. Please choose another." }
is PasswordException -> error.message
else -> "An unexpected error occurred. Please try again later."
}
model.addAttribute("errorMessage", errorMessage)
Mono.just("account/create")
}
}
@GetMapping @GetMapping
fun getDefaultAccounts( fun getDefaultAccounts(

View File

@@ -0,0 +1,23 @@
package ltd.hlaeja.exception
@Suppress("unused")
open class NoChangeException : AccountRegistryException {
constructor() : super()
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
constructor(
message: String,
cause: Throwable,
) : super(message, cause)
constructor(
message: String,
cause: Throwable,
enableSuppression: Boolean,
writableStackTrace: Boolean,
) : super(message, cause, enableSuppression, writableStackTrace)
}

View File

@@ -0,0 +1,23 @@
package ltd.hlaeja.exception
@Suppress("unused")
open class NotFoundException : AccountRegistryException {
constructor() : super()
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
constructor(
message: String,
cause: Throwable,
) : super(message, cause)
constructor(
message: String,
cause: Throwable,
enableSuppression: Boolean,
writableStackTrace: Boolean,
) : super(message, cause, enableSuppression, writableStackTrace)
}

View File

@@ -10,6 +10,7 @@ import ltd.hlaeja.util.accountRegistryAccount
import ltd.hlaeja.util.accountRegistryAccounts import ltd.hlaeja.util.accountRegistryAccounts
import ltd.hlaeja.util.accountRegistryAuthenticate import ltd.hlaeja.util.accountRegistryAuthenticate
import ltd.hlaeja.util.accountRegistryCreate import ltd.hlaeja.util.accountRegistryCreate
import ltd.hlaeja.util.accountRegistryUpdate
import org.springframework.http.HttpStatus.BAD_REQUEST import org.springframework.http.HttpStatus.BAD_REQUEST
import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.authentication.AuthenticationServiceException
import org.springframework.security.core.AuthenticationException import org.springframework.security.core.AuthenticationException
@@ -78,4 +79,15 @@ class AccountRegistryService(
else -> Mono.error(ResponseStatusException(BAD_REQUEST, error.message)) else -> Mono.error(ResponseStatusException(BAD_REQUEST, error.message))
} }
} }
fun updateAccount(
account: UUID,
request: Account.Request,
): Mono<Account.Response> = webClient.accountRegistryUpdate(account, request, property)
.onErrorResume { error ->
when (error) {
is AccountRegistryException -> Mono.error(error)
else -> Mono.error(ResponseStatusException(BAD_REQUEST, error.message))
}
}
} }

View File

@@ -1,6 +1,5 @@
package ltd.hlaeja.util package ltd.hlaeja.util
import ltd.hlaeja.exception.PasswordException
import ltd.hlaeja.form.AccountForm import ltd.hlaeja.form.AccountForm
import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.library.accountRegistry.Authentication
@@ -11,9 +10,9 @@ fun SpringAuthentication.toAuthenticationRequest(): Authentication.Request = Aut
credentials as String, credentials as String,
) )
fun AccountForm.toAccountRequest(): Account.Request = Account.Request( fun AccountForm.toAccountRequest(operation: (CharSequence?) -> CharSequence?): Account.Request = Account.Request(
username = username, username = username,
password = password ?: throw PasswordException("Password requirements failed"), password = operation(password),
enabled = enabled, enabled = enabled,
roles = listOf("ROLE_${role.uppercase()}"), roles = listOf("ROLE_${role.uppercase()}"),
) )
@@ -21,5 +20,5 @@ fun AccountForm.toAccountRequest(): Account.Request = Account.Request(
fun Account.Response.toAccountForm(): AccountForm = AccountForm( fun Account.Response.toAccountForm(): AccountForm = AccountForm(
username = username, username = username,
enabled = enabled, enabled = enabled,
role = roles.first().removePrefix("ROLE_").lowercase() role = roles.first().removePrefix("ROLE_").lowercase(),
) )

View File

@@ -2,10 +2,13 @@ package ltd.hlaeja.util
import java.util.UUID import java.util.UUID
import ltd.hlaeja.exception.AccountRegistryException import ltd.hlaeja.exception.AccountRegistryException
import ltd.hlaeja.exception.NoChangeException
import ltd.hlaeja.exception.NotFoundException
import ltd.hlaeja.exception.UsernameDuplicateException import ltd.hlaeja.exception.UsernameDuplicateException
import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.library.accountRegistry.Authentication
import ltd.hlaeja.property.AccountRegistryProperty import ltd.hlaeja.property.AccountRegistryProperty
import org.springframework.http.HttpStatus.ACCEPTED
import org.springframework.http.HttpStatus.BAD_REQUEST import org.springframework.http.HttpStatus.BAD_REQUEST
import org.springframework.http.HttpStatus.CONFLICT import org.springframework.http.HttpStatus.CONFLICT
import org.springframework.http.HttpStatus.LOCKED import org.springframework.http.HttpStatus.LOCKED
@@ -36,7 +39,7 @@ fun WebClient.accountRegistryAccounts(
size: Int, size: Int,
property: AccountRegistryProperty, property: AccountRegistryProperty,
): Flux<Account.Response> = get() ): Flux<Account.Response> = get()
.uri("${property.url}/accounts?page=$page&size=$size".also(::logCall)) .uri("${property.url}/accounts/page-$page/show-$size".also(::logCall))
.retrieve() .retrieve()
.bodyToFlux(Account.Response::class.java) .bodyToFlux(Account.Response::class.java)
@@ -47,7 +50,7 @@ fun WebClient.accountRegistryCreate(
.uri("${property.url}/account".also(::logCall)) .uri("${property.url}/account".also(::logCall))
.bodyValue(request) .bodyValue(request)
.retrieve() .retrieve()
.onStatus(CONFLICT::equals) { throw UsernameDuplicateException() } .onStatus(CONFLICT::equals) { throw UsernameDuplicateException("Remote service returned 409") }
.onStatus(BAD_REQUEST::equals) { throw AccountRegistryException("Remote service returned 400") } .onStatus(BAD_REQUEST::equals) { throw AccountRegistryException("Remote service returned 400") }
.bodyToMono(Account.Response::class.java) .bodyToMono(Account.Response::class.java)
@@ -59,3 +62,17 @@ fun WebClient.accountRegistryAccount(
.retrieve() .retrieve()
.onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NOT_FOUND) } .onStatus(NOT_FOUND::equals) { throw ResponseStatusException(NOT_FOUND) }
.bodyToMono(Account.Response::class.java) .bodyToMono(Account.Response::class.java)
fun WebClient.accountRegistryUpdate(
account: UUID,
request: Account.Request,
property: AccountRegistryProperty,
): Mono<Account.Response> = put()
.uri("${property.url}/account-$account".also(::logCall))
.bodyValue(request)
.retrieve()
.onStatus(ACCEPTED::equals) { throw NoChangeException("Remote service returned 202") }
.onStatus(BAD_REQUEST::equals) { throw AccountRegistryException("Remote service returned 400") }
.onStatus(NOT_FOUND::equals) { throw NotFoundException("Remote service returned 404") }
.onStatus(CONFLICT::equals) { throw UsernameDuplicateException("Remote service returned 409") }
.bodyToMono(Account.Response::class.java)

View File

@@ -13,6 +13,9 @@
<div th:if="${errorMessage != null}" style="color: red; margin-bottom: 10px;"> <div th:if="${errorMessage != null}" style="color: red; margin-bottom: 10px;">
<span th:text="${errorMessage}">Error Message</span> <span th:text="${errorMessage}">Error Message</span>
</div> </div>
<div th:if="${successMessage != null}" style="color: blue; margin-bottom: 10px;">
<span th:text="${successMessage}">success Message</span>
</div>
<!-- Username --> <!-- Username -->
<div> <div>