From 5b0b4843d4a6bb99043c43b979fb95550cf172ee Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 13:19:09 -0300 Subject: [PATCH 01/12] chore: fix typo --- .../kotlin/br/all/user/controller/UserAccountController.kt | 4 ++-- ...untPresenter.kt => RestfulRegisterUserAccountPresenter.kt} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename web/src/main/kotlin/br/all/user/presenter/{RestfullRegisterUserAccountPresenter.kt => RestfulRegisterUserAccountPresenter.kt} (93%) diff --git a/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt b/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt index e5973fa04..7e1524cbb 100644 --- a/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt +++ b/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt @@ -3,7 +3,7 @@ package br.all.user.controller import br.all.application.user.CredentialsService import br.all.application.user.create.RegisterUserAccountService import br.all.application.user.create.RegisterUserAccountService.RequestModel -import br.all.user.presenter.RestfullRegisterUserAccountPresenter +import br.all.user.presenter.RestfulRegisterUserAccountPresenter import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -49,7 +49,7 @@ class UserAccountController( ] ) fun registerUser(@RequestBody request: RequestModel): ResponseEntity<*> { - val presenter = RestfullRegisterUserAccountPresenter() + val presenter = RestfulRegisterUserAccountPresenter() val encodedPasswordRequest = request.copy(password = encoder.encode(request.password)) registerUserAccountService.register(presenter, encodedPasswordRequest) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) diff --git a/web/src/main/kotlin/br/all/user/presenter/RestfullRegisterUserAccountPresenter.kt b/web/src/main/kotlin/br/all/user/presenter/RestfulRegisterUserAccountPresenter.kt similarity index 93% rename from web/src/main/kotlin/br/all/user/presenter/RestfullRegisterUserAccountPresenter.kt rename to web/src/main/kotlin/br/all/user/presenter/RestfulRegisterUserAccountPresenter.kt index b42879b01..03f83317a 100644 --- a/web/src/main/kotlin/br/all/user/presenter/RestfullRegisterUserAccountPresenter.kt +++ b/web/src/main/kotlin/br/all/user/presenter/RestfulRegisterUserAccountPresenter.kt @@ -9,7 +9,7 @@ import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity.status import java.util.* -class RestfullRegisterUserAccountPresenter : RegisterUserAccountPresenter { +class RestfulRegisterUserAccountPresenter : RegisterUserAccountPresenter { var responseEntity: ResponseEntity<*>? = null From 413f3756096dec5a6ac4835bb6d13010828d7a53 Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 13:26:45 -0300 Subject: [PATCH 02/12] feat: add retrieve user profile presenter and service interfaces --- .../user/find/RetrieveUserProfilePresenter.kt | 6 ++++++ .../user/find/RetrieveUserProfileService.kt | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfilePresenter.kt create mode 100644 account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfilePresenter.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfilePresenter.kt new file mode 100644 index 000000000..e70cf8c96 --- /dev/null +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfilePresenter.kt @@ -0,0 +1,6 @@ +package br.all.application.user.find + +import br.all.application.user.find.RetrieveUserProfileService.ResponseModel +import br.all.domain.shared.presenter.GenericPresenter + +interface RetrieveUserProfilePresenter : GenericPresenter \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt new file mode 100644 index 000000000..f581ee8b6 --- /dev/null +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt @@ -0,0 +1,18 @@ +package br.all.application.user.find +import java.util.UUID + +interface RetrieveUserProfileService { + fun retrieveData(presenter: RetrieveUserProfilePresenter, request: RequestModel) + + data class RequestModel( + val userId: UUID + ) + + data class ResponseModel( + val userId: UUID, + val username: String, + val email: String, + val affiliation: String, + val country: String + ) +} \ No newline at end of file From a8cd1f6b38a9ab9126473cc1535a33ebbfc3c84b Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:07:15 -0300 Subject: [PATCH 03/12] feat: add new repository method to return user account dto --- .../user/repository/UserAccountRepository.kt | 1 + .../infrastructure/user/UserAccountMapper.kt | 18 +++++++++++++++++- .../user/UserAccountRepositoryImpl.kt | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt b/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt index 52531acd7..4510788b5 100644 --- a/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt +++ b/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt @@ -11,4 +11,5 @@ interface UserAccountRepository { fun existsByEmail(email: String): Boolean fun existsByUsername(username: String): Boolean fun deleteById(id: UUID) + fun loadUserProfileById(id: UUID): UserAccountDto? } \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt index f48e3b375..4094522bc 100644 --- a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt +++ b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt @@ -18,4 +18,20 @@ fun UserAccountDto.toUserAccountEntity(): UserAccountEntity { return UserAccountEntity(id, credentials, email, country, affiliation, createdAt) } -fun AccountCredentialsEntity.toAccountCredentialsDto() = AccountCredentialsDto(id, username, password, authorities, refreshToken) \ No newline at end of file +fun AccountCredentialsEntity.toAccountCredentialsDto() = AccountCredentialsDto(id, username, password, authorities, refreshToken) + +fun UserAccountEntity.toUserAccountDto() = UserAccountDto( + id = this.id, + username = this.accountCredentialsEntity.username, + password = this.accountCredentialsEntity.password, + email = this.email, + country = this.country, + affiliation = this.affiliation, + createdAt = this.createdAt, + authorities = this.accountCredentialsEntity.authorities, + refreshToken = this.accountCredentialsEntity.refreshToken, + isAccountNonExpired = this.accountCredentialsEntity.isAccountNonExpired, + isAccountNonLocked = this.accountCredentialsEntity.isAccountNonLocked, + isCredentialsNonExpired = this.accountCredentialsEntity.isCredentialsNonExpired, + isEnabled = this.accountCredentialsEntity.isEnabled +) \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt index a3b5fd2b9..b7868774f 100644 --- a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt +++ b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt @@ -22,6 +22,9 @@ class UserAccountRepositoryImpl( credentialsRepository.deleteById(id) } + override fun loadUserProfileById(id: UUID): UserAccountDto? = + userAccountRepository.findById(id).orElse(null)?.toUserAccountDto() + override fun loadCredentialsByUsername(username: String) = credentialsRepository.findByUsername(username)?.toAccountCredentialsDto() From de5e230518a8b5074175869ec7311d45933d99bb Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:07:31 -0300 Subject: [PATCH 04/12] feat: add retrieve user profile service implementation --- .../find/RetrieveUserProfileServiceImpl.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt new file mode 100644 index 000000000..fef37eaec --- /dev/null +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt @@ -0,0 +1,30 @@ +package br.all.application.user.find + +import br.all.application.user.find.RetrieveUserProfileService.RequestModel +import br.all.application.user.find.RetrieveUserProfileService.ResponseModel +import br.all.application.user.repository.UserAccountRepository + +class RetrieveUserProfileServiceImpl( + private val userAccountRepository: UserAccountRepository +) : RetrieveUserProfileService { + override fun retrieveData( + presenter: RetrieveUserProfilePresenter, + request: RequestModel + ) { + val user = userAccountRepository.loadUserProfileById(request.userId) + if (user == null) { + presenter.prepareFailView(NoSuchElementException("User with id ${request.userId} doesn't exist!")) + return + } + + val profile = ResponseModel( + userId = user.id, + username = user.username, + email = user.email, + affiliation = user.affiliation, + country = user.country + ) + + presenter.prepareSuccessView(profile) + } +} \ No newline at end of file From 013aa3fed59314eb267566e4b1b7f70e74cfb44a Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:07:40 -0300 Subject: [PATCH 05/12] feat: add restful user profile presenter --- .../RestfulRetrieveUserProfilePresenter.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt diff --git a/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt b/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt new file mode 100644 index 000000000..9af358e7e --- /dev/null +++ b/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt @@ -0,0 +1,37 @@ +package br.all.user.presenter + +import br.all.application.user.find.RetrieveUserProfileService.ResponseModel +import br.all.application.user.find.RetrieveUserProfilePresenter +import br.all.shared.error.createErrorResponseFrom +import org.springframework.hateoas.RepresentationModel +import org.springframework.http.ResponseEntity +import org.springframework.http.ResponseEntity.ok +import java.util.* + +class RestfulRetrieveUserProfilePresenter : RetrieveUserProfilePresenter { + + var responseEntity: ResponseEntity<*>? = null + + override fun prepareSuccessView(response: ResponseModel) { + val restfulResponse = ViewModel( + response.userId, + response.username, + response.email, + response.affiliation, + response.country + ) + responseEntity = ok(restfulResponse) + } + + override fun prepareFailView(throwable: Throwable) = run { responseEntity = createErrorResponseFrom(throwable) } + + override fun isDone() = responseEntity != null + + private data class ViewModel( + val userId: UUID, + val username: String, + val email: String, + val affiliation: String, + val country: String + ) : RepresentationModel() +} \ No newline at end of file From 0933384d0c838cd3545d2bf7a2950350b7e9f9c2 Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:08:37 -0300 Subject: [PATCH 06/12] feat: add new service bean configuration --- .../kotlin/br/all/user/controller/UserAccountConfiguration.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/main/kotlin/br/all/user/controller/UserAccountConfiguration.kt b/web/src/main/kotlin/br/all/user/controller/UserAccountConfiguration.kt index b5113266f..44d088719 100644 --- a/web/src/main/kotlin/br/all/user/controller/UserAccountConfiguration.kt +++ b/web/src/main/kotlin/br/all/user/controller/UserAccountConfiguration.kt @@ -1,6 +1,7 @@ package br.all.user.controller import br.all.application.user.create.RegisterUserAccountServiceImpl +import br.all.application.user.find.RetrieveUserProfileServiceImpl import br.all.application.user.repository.UserAccountRepository import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -10,4 +11,7 @@ class UserAccountConfiguration { @Bean fun registerUser(repository: UserAccountRepository) = RegisterUserAccountServiceImpl(repository) + + @Bean + fun retrieveUserProfile(repository: UserAccountRepository) = RetrieveUserProfileServiceImpl(repository) } \ No newline at end of file From c4df4f45afce2023aef410d30f6d6bcaeb4b8be8 Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:15:46 -0300 Subject: [PATCH 07/12] feat: add endpoint to new get profile service --- .../user/controller/UserAccountController.kt | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt b/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt index 7e1524cbb..3919eaef8 100644 --- a/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt +++ b/web/src/main/kotlin/br/all/user/controller/UserAccountController.kt @@ -3,7 +3,10 @@ package br.all.user.controller import br.all.application.user.CredentialsService import br.all.application.user.create.RegisterUserAccountService import br.all.application.user.create.RegisterUserAccountService.RequestModel +import br.all.application.user.find.RetrieveUserProfileService +import br.all.security.service.AuthenticationInfoService import br.all.user.presenter.RestfulRegisterUserAccountPresenter +import br.all.user.presenter.RestfulRetrieveUserProfilePresenter import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.media.Content import io.swagger.v3.oas.annotations.media.Schema @@ -12,6 +15,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping @@ -21,7 +25,9 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("api/v1/user") class UserAccountController( private val registerUserAccountService: RegisterUserAccountService, - private val encoder: PasswordEncoder + private val encoder: PasswordEncoder, + private val retrieveUserProfileService: RetrieveUserProfileService, + private val authenticationInfoService: AuthenticationInfoService ) { @PostMapping @@ -54,4 +60,41 @@ class UserAccountController( registerUserAccountService.register(presenter, encodedPasswordRequest) return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) } + + @GetMapping("/profile") + @Operation(summary = "Retrieve public information of a user") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "Success retrieving user profile", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = RetrieveUserProfileService.ResponseModel::class) + )] + ), + ApiResponse( + responseCode = "401", + description = "Fail retrieving user profile - unauthenticated collaborator", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "403", + description = "Fail retrieving user profile - unauthorized collaborator", + content = [Content(schema = Schema(hidden = true))] + ), + ApiResponse( + responseCode = "404", + description = "Fail retrieving user profile - nonexistent user", + content = [Content(schema = Schema(hidden = true))] + ), + ]) + fun retrieveUserPublicData(): ResponseEntity<*> { + val presenter = RestfulRetrieveUserProfilePresenter() + val userId = authenticationInfoService.getAuthenticatedUserId() + val request = RetrieveUserProfileService.RequestModel(userId) + + retrieveUserProfileService.retrieveData(presenter, request) + return presenter.responseEntity ?: ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } } \ No newline at end of file From 221fc5190d2309e8750288e222959ea44da0077d Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 15:16:10 -0300 Subject: [PATCH 08/12] refactor: tweak spring security to block unauthenticated requests to new profile endpoint --- .../kotlin/br/all/security/config/JwtAuthenticationFilter.kt | 2 +- .../main/kotlin/br/all/security/config/SecurityConfiguration.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/main/kotlin/br/all/security/config/JwtAuthenticationFilter.kt b/web/src/main/kotlin/br/all/security/config/JwtAuthenticationFilter.kt index e8988bf86..a1b181363 100644 --- a/web/src/main/kotlin/br/all/security/config/JwtAuthenticationFilter.kt +++ b/web/src/main/kotlin/br/all/security/config/JwtAuthenticationFilter.kt @@ -21,7 +21,7 @@ class JwtAuthenticationFilter( ) : OncePerRequestFilter() { private val matchersToSkip: List = listOf( - AntPathRequestMatcher("/api/v1/user"), + AntPathRequestMatcher("/api/v1/user", "POST"), AntPathRequestMatcher("/api/v1/auth/"), AntPathRequestMatcher("/api/v1/auth/logout/"), AntPathRequestMatcher("/webjars/**"), diff --git a/web/src/main/kotlin/br/all/security/config/SecurityConfiguration.kt b/web/src/main/kotlin/br/all/security/config/SecurityConfiguration.kt index 9f373a709..06d34bc97 100644 --- a/web/src/main/kotlin/br/all/security/config/SecurityConfiguration.kt +++ b/web/src/main/kotlin/br/all/security/config/SecurityConfiguration.kt @@ -44,6 +44,7 @@ class SecurityConfiguration( "/webjars/**" ).permitAll() .requestMatchers(HttpMethod.POST, "/api/v1/user").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/user").authenticated() .anyRequest().fullyAuthenticated() } From 593b959bd0ffffda5bf60bb9087c678ad8ffc9ac Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 16:53:37 -0300 Subject: [PATCH 09/12] refactor: add new specific dto for user profile and implement it --- .../user/find/RetrieveUserProfileService.kt | 4 +++- .../find/RetrieveUserProfileServiceImpl.kt | 22 +++++++++++++------ .../user/repository/UserAccountRepository.kt | 2 +- .../user/repository/UserProfileDto.kt | 10 +++++++++ .../infrastructure/user/UserAccountMapper.kt | 14 +++--------- .../user/UserAccountRepositoryImpl.kt | 5 +++-- .../RestfulRetrieveUserProfilePresenter.kt | 6 +++-- 7 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 account/src/main/kotlin/br/all/application/user/repository/UserProfileDto.kt diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt index f581ee8b6..87746012e 100644 --- a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt @@ -1,4 +1,5 @@ package br.all.application.user.find +import java.time.LocalDateTime import java.util.UUID interface RetrieveUserProfileService { @@ -13,6 +14,7 @@ interface RetrieveUserProfileService { val username: String, val email: String, val affiliation: String, - val country: String + val country: String, + val authorities: Set, ) } \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt index fef37eaec..f382bc86a 100644 --- a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImpl.kt @@ -11,18 +11,26 @@ class RetrieveUserProfileServiceImpl( presenter: RetrieveUserProfilePresenter, request: RequestModel ) { - val user = userAccountRepository.loadUserProfileById(request.userId) - if (user == null) { + val userProfile = userAccountRepository.loadUserProfileById(request.userId) + if (userProfile == null) { presenter.prepareFailView(NoSuchElementException("User with id ${request.userId} doesn't exist!")) return } + val userCredentials = userAccountRepository.loadCredentialsById(request.userId) + if (userCredentials == null) { + presenter.prepareFailView(NoSuchElementException("Account credentials with id ${request.userId} doesn't exist!")) + return + } + + val profile = ResponseModel( - userId = user.id, - username = user.username, - email = user.email, - affiliation = user.affiliation, - country = user.country + userId = userProfile.id, + username = userCredentials.username, + email = userProfile.email, + affiliation = userProfile.affiliation, + country = userProfile.country, + authorities = userCredentials.authorities ) presenter.prepareSuccessView(profile) diff --git a/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt b/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt index 4510788b5..d14aa597a 100644 --- a/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt +++ b/account/src/main/kotlin/br/all/application/user/repository/UserAccountRepository.kt @@ -11,5 +11,5 @@ interface UserAccountRepository { fun existsByEmail(email: String): Boolean fun existsByUsername(username: String): Boolean fun deleteById(id: UUID) - fun loadUserProfileById(id: UUID): UserAccountDto? + fun loadUserProfileById(id: UUID): UserProfileDto? } \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/application/user/repository/UserProfileDto.kt b/account/src/main/kotlin/br/all/application/user/repository/UserProfileDto.kt new file mode 100644 index 000000000..1060041c4 --- /dev/null +++ b/account/src/main/kotlin/br/all/application/user/repository/UserProfileDto.kt @@ -0,0 +1,10 @@ +package br.all.application.user.repository + +import java.util.UUID + +data class UserProfileDto( + val id: UUID, + val email: String, + val country: String, + val affiliation: String, +) diff --git a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt index 4094522bc..29982ff06 100644 --- a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt +++ b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountMapper.kt @@ -2,6 +2,7 @@ package br.all.infrastructure.user import br.all.application.user.repository.AccountCredentialsDto import br.all.application.user.repository.UserAccountDto +import br.all.application.user.repository.UserProfileDto fun UserAccountDto.toUserAccountEntity(): UserAccountEntity { val credentials = AccountCredentialsEntity( @@ -20,18 +21,9 @@ fun UserAccountDto.toUserAccountEntity(): UserAccountEntity { fun AccountCredentialsEntity.toAccountCredentialsDto() = AccountCredentialsDto(id, username, password, authorities, refreshToken) -fun UserAccountEntity.toUserAccountDto() = UserAccountDto( +fun UserAccountEntity.toUserProfileDto() = UserProfileDto( id = this.id, - username = this.accountCredentialsEntity.username, - password = this.accountCredentialsEntity.password, email = this.email, country = this.country, - affiliation = this.affiliation, - createdAt = this.createdAt, - authorities = this.accountCredentialsEntity.authorities, - refreshToken = this.accountCredentialsEntity.refreshToken, - isAccountNonExpired = this.accountCredentialsEntity.isAccountNonExpired, - isAccountNonLocked = this.accountCredentialsEntity.isAccountNonLocked, - isCredentialsNonExpired = this.accountCredentialsEntity.isCredentialsNonExpired, - isEnabled = this.accountCredentialsEntity.isEnabled + affiliation = this.affiliation ) \ No newline at end of file diff --git a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt index b7868774f..8c3d02b06 100644 --- a/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt +++ b/account/src/main/kotlin/br/all/infrastructure/user/UserAccountRepositoryImpl.kt @@ -3,6 +3,7 @@ package br.all.infrastructure.user import br.all.application.user.repository.AccountCredentialsDto import br.all.application.user.repository.UserAccountDto import br.all.application.user.repository.UserAccountRepository +import br.all.application.user.repository.UserProfileDto import org.springframework.stereotype.Repository import java.util.* @@ -22,8 +23,8 @@ class UserAccountRepositoryImpl( credentialsRepository.deleteById(id) } - override fun loadUserProfileById(id: UUID): UserAccountDto? = - userAccountRepository.findById(id).orElse(null)?.toUserAccountDto() + override fun loadUserProfileById(id: UUID): UserProfileDto? = + userAccountRepository.findById(id).orElse(null)?.toUserProfileDto() override fun loadCredentialsByUsername(username: String) = credentialsRepository.findByUsername(username)?.toAccountCredentialsDto() diff --git a/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt b/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt index 9af358e7e..9e3fc3156 100644 --- a/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt +++ b/web/src/main/kotlin/br/all/user/presenter/RestfulRetrieveUserProfilePresenter.kt @@ -18,7 +18,8 @@ class RestfulRetrieveUserProfilePresenter : RetrieveUserProfilePresenter { response.username, response.email, response.affiliation, - response.country + response.country, + response.authorities ) responseEntity = ok(restfulResponse) } @@ -32,6 +33,7 @@ class RestfulRetrieveUserProfilePresenter : RetrieveUserProfilePresenter { val username: String, val email: String, val affiliation: String, - val country: String + val country: String, + val authorities: Set ) : RepresentationModel() } \ No newline at end of file From 3cabe3a286044ef679c45892f28a368a5855cfcd Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 16:53:52 -0300 Subject: [PATCH 10/12] chore: remove unused import --- .../br/all/application/user/find/RetrieveUserProfileService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt index 87746012e..2016e2c86 100644 --- a/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt +++ b/account/src/main/kotlin/br/all/application/user/find/RetrieveUserProfileService.kt @@ -1,5 +1,4 @@ package br.all.application.user.find -import java.time.LocalDateTime import java.util.UUID interface RetrieveUserProfileService { From 8a2f277f4f35867898ebc9a39f499c8aff9d63e9 Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 17:01:07 -0300 Subject: [PATCH 11/12] feat: add user profile dto factory --- .../br/all/application/user/utils/TestDataFactory.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/account/src/test/kotlin/br/all/application/user/utils/TestDataFactory.kt b/account/src/test/kotlin/br/all/application/user/utils/TestDataFactory.kt index 0b2e69e64..4bf2c37c3 100644 --- a/account/src/test/kotlin/br/all/application/user/utils/TestDataFactory.kt +++ b/account/src/test/kotlin/br/all/application/user/utils/TestDataFactory.kt @@ -2,6 +2,7 @@ package br.all.application.user.utils import br.all.application.user.create.RegisterUserAccountService import br.all.application.user.repository.AccountCredentialsDto +import br.all.application.user.repository.UserProfileDto import io.github.serpro69.kfaker.Faker import java.util.* @@ -25,4 +26,10 @@ class TestDataFactory { refreshToken = faker.lorem.words() ) + fun userProfile() = UserProfileDto( + id = UUID.randomUUID(), + email = faker.internet.email(), + country = faker.address.countryCode(), + affiliation = faker.leagueOfLegends.rank(), + ) } \ No newline at end of file From 8d374c0bd2fa5400d59b3fecd476c43538cc1f79 Mon Sep 17 00:00:00 2001 From: matheusspacifico Date: Wed, 6 Aug 2025 17:01:18 -0300 Subject: [PATCH 12/12] feat: add retrieve user profile service tests --- .../RetrieveUserProfileServiceImplTest.kt | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 account/src/test/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImplTest.kt diff --git a/account/src/test/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImplTest.kt b/account/src/test/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImplTest.kt new file mode 100644 index 000000000..3b3ebc4ba --- /dev/null +++ b/account/src/test/kotlin/br/all/application/user/find/RetrieveUserProfileServiceImplTest.kt @@ -0,0 +1,105 @@ +package br.all.application.user.find + +import br.all.application.user.repository.UserAccountRepository +import br.all.application.user.utils.TestDataFactory +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.slot +import io.mockk.verify +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import java.util.* +import kotlin.NoSuchElementException + +@Tag("UnitTest") +@Tag("ServiceTest") +@ExtendWith(MockKExtension::class) +class RetrieveUserProfileServiceImplTest { + + @MockK(relaxUnitFun = true) + private lateinit var userAccountRepository: UserAccountRepository + + @MockK(relaxUnitFun = true) + private lateinit var presenter: RetrieveUserProfilePresenter + + private lateinit var sut: RetrieveUserProfileServiceImpl + private lateinit var factory: TestDataFactory + + @BeforeEach + fun setUp() { + sut = RetrieveUserProfileServiceImpl(userAccountRepository) + factory = TestDataFactory() + } + + @Nested + @DisplayName("When retrieving a user profile") + inner class WhenRetrievingUserProfile { + + @Test + fun `should retrieve user profile and prepare success view`() { + val userProfile = factory.userProfile() + val userCredentials = factory.accountCredentials().copy(id = userProfile.id) + val request = RetrieveUserProfileService.RequestModel(userId = userProfile.id) + + every { userAccountRepository.loadUserProfileById(request.userId) } returns userProfile + every { userAccountRepository.loadCredentialsById(request.userId) } returns userCredentials + + val responseSlot = slot() + every { presenter.prepareSuccessView(capture(responseSlot)) } returns Unit + + sut.retrieveData(presenter, request) + + verify(exactly = 1) { presenter.prepareSuccessView(any()) } + verify(exactly = 0) { presenter.prepareFailView(any()) } + + val capturedResponse = responseSlot.captured + assertEquals(userProfile.id, capturedResponse.userId) + assertEquals(userCredentials.username, capturedResponse.username) + assertEquals(userProfile.email, capturedResponse.email) + assertEquals(userProfile.affiliation, capturedResponse.affiliation) + assertEquals(userProfile.country, capturedResponse.country) + assertEquals(userCredentials.authorities, capturedResponse.authorities) + } + + @Test + fun `should prepare fail view when user profile is not found`() { + val userId = UUID.randomUUID() + val request = RetrieveUserProfileService.RequestModel(userId = userId) + + every { userAccountRepository.loadUserProfileById(userId) } returns null + + val exceptionSlot = slot() + every { presenter.prepareFailView(capture(exceptionSlot)) } returns Unit + + sut.retrieveData(presenter, request) + + verify(exactly = 0) { presenter.prepareSuccessView(any()) } + verify(exactly = 1) { presenter.prepareFailView(any()) } + + val capturedException = exceptionSlot.captured + assertEquals("User with id $userId doesn't exist!", capturedException.message) + } + + @Test + fun `should prepare fail view when user credentials are not found`() { + val userProfile = factory.userProfile() + val request = RetrieveUserProfileService.RequestModel(userId = userProfile.id) + + every { userAccountRepository.loadUserProfileById(request.userId) } returns userProfile + every { userAccountRepository.loadCredentialsById(request.userId) } returns null + + val exceptionSlot = slot() + every { presenter.prepareFailView(capture(exceptionSlot)) } returns Unit + + sut.retrieveData(presenter, request) + + verify(exactly = 0) { presenter.prepareSuccessView(any()) } + verify(exactly = 1) { presenter.prepareFailView(any()) } + + val capturedException = exceptionSlot.captured + assertEquals("Account credentials with id ${request.userId} doesn't exist!", capturedException.message) + } + } +}