From a88017a576bbf3709b4868f75c730330d5a42506 Mon Sep 17 00:00:00 2001 From: Marcin Bihun Date: Tue, 3 Dec 2024 22:36:11 +0100 Subject: [PATCH 1/4] feat: #206 add endpoint to evaluate statuses --- .../module/material/MaterialController.java | 7 +++++++ .../module/material/MaterialStatusService.java | 9 +++++++++ .../material/model/StatusesToChangeResponse.java | 11 +++++++++++ .../rest/MaterialControllerDefinition.java | 6 ++++++ 4 files changed, 33 insertions(+) create mode 100644 kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/model/StatusesToChangeResponse.java diff --git a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialController.java b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialController.java index fad32bf2..f24042b4 100644 --- a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialController.java +++ b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialController.java @@ -10,6 +10,7 @@ import pl.sknikod.kodemybackend.infrastructure.database.Material; import pl.sknikod.kodemybackend.infrastructure.module.material.model.MaterialPageable; import pl.sknikod.kodemybackend.infrastructure.module.material.model.SingleMaterialResponse; +import pl.sknikod.kodemybackend.infrastructure.module.material.model.StatusesToChangeResponse; import pl.sknikod.kodemybackend.infrastructure.rest.MaterialControllerDefinition; import java.net.URI; @@ -60,6 +61,12 @@ public ResponseEntity showDetails(Long materialId) { .body(materialGetByIdService.showDetails(materialId)); } + @Override + public ResponseEntity showStatusesToChange(Long materialId) { + return ResponseEntity.status(HttpStatus.OK) + .body(materialStatusService.showStatusesToChange(materialId)); + } + @Override public ResponseEntity> manage(int size, int page, MaterialSortField sortField, Sort.Direction sortDirection, MaterialFilterSearchParams filterSearchParams) { return ResponseEntity.status(HttpStatus.OK) diff --git a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialStatusService.java b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialStatusService.java index 627b2543..d3ad37e1 100644 --- a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialStatusService.java +++ b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/MaterialStatusService.java @@ -4,6 +4,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import pl.sknikod.kodemybackend.infrastructure.database.Material; +import pl.sknikod.kodemybackend.infrastructure.module.material.model.StatusesToChangeResponse; import pl.sknikod.kodemybackend.infrastructure.store.MaterialStore; import pl.sknikod.kodemycommons.exception.Validation400Exception; import pl.sknikod.kodemycommons.exception.content.ExceptionUtil; @@ -32,6 +33,14 @@ public Material.MaterialStatus update(Long materialId, Material.MaterialStatus n .getOrElseThrow(ExceptionUtil::throwIfFailure); } + StatusesToChangeResponse showStatusesToChange(Long materialId) { + return materialStore.findById(materialId, true) + .map(MaterialStore.FindByIdObject::getMaterial) + .map(material -> getPossibleStatuses(material.getStatus())) + .map(StatusesToChangeResponse::new) + .getOrElseThrow(ExceptionUtil::throwIfFailure); + } + private boolean canUserUpdateStatus(SimpleGrantedAuthority neededAuthority, Material material) { UserPrincipal userPrincipal = AuthFacade.getCurrentUserPrincipal().get(); return userPrincipal.getAuthorities().contains(neededAuthority) diff --git a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/model/StatusesToChangeResponse.java b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/model/StatusesToChangeResponse.java new file mode 100644 index 00000000..47ddd9fc --- /dev/null +++ b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/module/material/model/StatusesToChangeResponse.java @@ -0,0 +1,11 @@ +package pl.sknikod.kodemybackend.infrastructure.module.material.model; + +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import pl.sknikod.kodemybackend.infrastructure.database.Material; + +import java.util.List; + +public record StatusesToChangeResponse( + @Enumerated(EnumType.STRING) List statuses) { +} diff --git a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/rest/MaterialControllerDefinition.java b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/rest/MaterialControllerDefinition.java index dbc721fc..31213640 100644 --- a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/rest/MaterialControllerDefinition.java +++ b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/infrastructure/rest/MaterialControllerDefinition.java @@ -22,6 +22,7 @@ import pl.sknikod.kodemybackend.infrastructure.module.material.MaterialUpdateService; import pl.sknikod.kodemybackend.infrastructure.module.material.model.MaterialPageable; import pl.sknikod.kodemybackend.infrastructure.module.material.model.SingleMaterialResponse; +import pl.sknikod.kodemybackend.infrastructure.module.material.model.StatusesToChangeResponse; import pl.sknikod.kodemycommons.doc.SwaggerResponse; import java.time.Instant; @@ -85,6 +86,11 @@ ResponseEntity reindex( @GetMapping("/{materialId}") ResponseEntity showDetails(@PathVariable Long materialId); + @Operation(summary = "Show all possible statuses to change") + @SwaggerResponse.SuccessCode200 + @GetMapping("/{materialId}/status/evaluate") + ResponseEntity showStatusesToChange(@PathVariable Long materialId); + @Operation(summary = "Show all materials") @SwaggerResponse.SuccessCode200 @SwaggerResponse.UnauthorizedCode401 From 93b8cf76166ff8727c718f2e3af6ab602704e53c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Kie=C5=82basa?= Date: Wed, 4 Dec 2024 17:40:45 +0100 Subject: [PATCH 2/4] feat: #185 add base test for auth --- .github/workflows/dev.kodemy.deploy.yml | 2 +- .../security/JwtAuthorizationFilter.java | 4 -- .../kodemycommons/security/JwtProvider.java | 21 +++++-- .../configuration/JwtConfiguration.java | 41 ------------- kodemy-auth/build.gradle | 6 ++ .../configuration/SecurityConfiguration.java | 15 +++-- .../engine/github/GithubExchangeFlow.java | 3 +- .../src/main/resources/application-local.yml | 6 +- .../src/main/resources/application.yml | 5 +- .../sknikod/kodemyauth/SuperclassSpec.groovy | 48 ++++++++++++++++ .../factory/RefreshTokenFactory.java | 19 +++++++ .../kodemyauth/factory/RoleFactory.java | 15 +++++ .../kodemyauth/factory/UserFactory.java | 20 +++++++ .../module/auth/AccessTokenServiceSpec.groovy | 17 ++++++ .../auth/RefreshTokensServiceSpec.groovy | 43 ++++++++++++++ .../github/GithubExchangeFlowSpec.groovy | 57 +++++++++++++++++++ .../store/RefreshTokenStoreSpec.groovy | 38 +++++++++++++ .../pl/sknikod/kodemyauth/SuperclassTest.java | 36 ------------ .../configuration/SecurityConfiguration.java | 14 +++-- .../src/main/resources/application-local.yml | 6 +- .../src/main/resources/application.yml | 6 +- .../kodemybackend/MvcSuperclassTest.java | 4 -- .../sknikod/kodemybackend/SuperclassTest.java | 7 --- .../configuration/SecurityConfiguration.java | 13 +++-- .../src/main/resources/application.yml | 6 +- .../sknikod/kodemysearch/SuperclassTest.java | 3 - 26 files changed, 324 insertions(+), 131 deletions(-) delete mode 100644 commons/src/main/java/pl/sknikod/kodemycommons/security/configuration/JwtConfiguration.java create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/SuperclassSpec.groovy create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RefreshTokenFactory.java create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RoleFactory.java create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/UserFactory.java create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/AccessTokenServiceSpec.groovy create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/RefreshTokensServiceSpec.groovy create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/oauth2/engine/github/GithubExchangeFlowSpec.groovy create mode 100644 kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/store/RefreshTokenStoreSpec.groovy delete mode 100644 kodemy-auth/src/test/java/pl/sknikod/kodemyauth/SuperclassTest.java diff --git a/.github/workflows/dev.kodemy.deploy.yml b/.github/workflows/dev.kodemy.deploy.yml index 799c2043..46072908 100644 --- a/.github/workflows/dev.kodemy.deploy.yml +++ b/.github/workflows/dev.kodemy.deploy.yml @@ -144,7 +144,7 @@ jobs: cd $WORKING_DIRECTORY/kodemy-api-gateway ./gradlew clean assemble -x test cd $WORKING_DIRECTORY/kodemy-auth - ./gradlew clean assemble -x test + ./gradlew clean assemble cd $WORKING_DIRECTORY/kodemy-backend ./gradlew clean assemble -x test cd $WORKING_DIRECTORY/kodemy-notification diff --git a/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtAuthorizationFilter.java b/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtAuthorizationFilter.java index b82300ed..f1beddf6 100644 --- a/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtAuthorizationFilter.java +++ b/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtAuthorizationFilter.java @@ -7,16 +7,12 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; import java.io.IOException; import java.util.Collections; diff --git a/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtProvider.java b/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtProvider.java index 61dc13ec..bacf6e81 100644 --- a/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtProvider.java +++ b/commons/src/main/java/pl/sknikod/kodemycommons/security/JwtProvider.java @@ -8,13 +8,13 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; import org.springframework.lang.NonNull; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.util.Assert; import pl.sknikod.kodemycommons.exception.Authentication403Exception; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; import javax.crypto.SecretKey; import java.util.*; @@ -22,12 +22,12 @@ @Slf4j public class JwtProvider { - private final JwtConfiguration.JwtProperties jwtProperties; + private final Properties jwtProperties; private final SecretKey key; private static final String ISSUER = "pl.sknikod.kodemy"; private final JwtParser parser; - public JwtProvider(JwtConfiguration.JwtProperties jwtProperties) { + public JwtProvider(Properties jwtProperties) { this.jwtProperties = jwtProperties; this.key = generateKey(jwtProperties.getSecretKey()); this.parser = buildParser(this.key); @@ -52,7 +52,7 @@ public Token generateDelegationToken(String subject, String authority) { ClaimKey.AUTHORITIES, List.of(authority))); } - public Token generateUserToken(@NonNull Input input) { + public Token generateUserToken(Input input) { Assert.notNull(input, "input cannot be null"); var authorities = input.authorities != null && !input.authorities.isEmpty() ? input.authorities.stream().map(SimpleGrantedAuthority::getAuthority).toList() @@ -71,8 +71,8 @@ public Token generateToken(TokenType tokenType, String subject, Map c final UUID jti = UUID.randomUUID(); final Date issuedAt = new Date(System.currentTimeMillis()); final Date expiration = new Date(issuedAt.getTime() + 1000L * 60 * switch (tokenType) { - case USER_TOKEN -> jwtProperties.getBearer().getExpirationMin(); - case DELEGATION_TOKEN -> jwtProperties.getDelegation().getExpirationMin(); + case USER_TOKEN -> jwtProperties.getBearerExpirationMin(); + case DELEGATION_TOKEN -> jwtProperties.getDelegationExpirationMin(); }); return new Token(jti, generate(jti, subject, issuedAt, expiration, claims), expiration); @@ -199,4 +199,13 @@ public Deserialize(UUID bearerId, Long id, String username, Integer state, Set attributes, ClientRegistration clientRegistration, AccessToken accessToken) { + private String fixEmailNull(Map attributes, ClientRegistration clientRegistration, AccessToken accessToken) { log.info("Fetching {}'s user emails", clientRegistration.getRegistrationId()); var headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); @@ -67,6 +67,7 @@ private Email fixEmailNull(Map attributes, ClientRegistration cl .toTry(() -> new InternalError500Exception("Failed to fetch user emails")) .map(HttpEntity::getBody) .map(emails -> emails.stream().filter(e -> e.primary).findFirst().orElse(null)) + .map(Email::getEmail) .getOrElseThrow(ExceptionUtil::throwIfFailure); } diff --git a/kodemy-auth/src/main/resources/application-local.yml b/kodemy-auth/src/main/resources/application-local.yml index f965961d..afb85a32 100644 --- a/kodemy-auth/src/main/resources/application-local.yml +++ b/kodemy-auth/src/main/resources/application-local.yml @@ -29,12 +29,10 @@ service: baseUrl: gateway: http://localhost:8080 -jwt: - bearer: - expiration-min: 720 - app: security: + jwt: + bearer-expiration-min: 720 oauth2: baseUrl: front: http://localhost:3000 diff --git a/kodemy-auth/src/main/resources/application.yml b/kodemy-auth/src/main/resources/application.yml index 7ee36c97..fe74f049 100644 --- a/kodemy-auth/src/main/resources/application.yml +++ b/kodemy-auth/src/main/resources/application.yml @@ -71,14 +71,13 @@ service: baseUrl: gateway: http://kodemy-api-gateway:8080 -jwt: - secret-key: YWJjZGVmZ2hjvbwjrW5vcHFyc3R1dnd4eXoxMjM0NTY3OnDMTIzNDU2Nzg5MDEyMzQ1Njc4OTAf= - app: security: cors: credentials: true mapping: "/**" + jwt: + secret-key: YWJjZGVmZ2hjvbwjrW5vcHFyc3R1dnd4eXoxMjM0NTY3OnDMTIzNDU2Nzg5MDEyMzQ1Njc4OTAf= oauth2: baseUrl: front: ${FRONTEND_PUBLIC_HOST} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/SuperclassSpec.groovy b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/SuperclassSpec.groovy new file mode 100644 index 00000000..fdb42406 --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/SuperclassSpec.groovy @@ -0,0 +1,48 @@ +package pl.sknikod.kodemyauth + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import org.springframework.boot.autoconfigure.ImportAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers +import spock.lang.Specification + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@ContextConfiguration(classes = KodemyAuthApplication.class) +@ImportAutoConfiguration(value = TestChannelBinderConfiguration.class) +@Testcontainers +abstract class SuperclassSpec extends Specification { + @Container + private static final PostgreSQLContainer DATASOURCE + private static final WireMockServer WIREMOCK + private static int WIREMOCK_PORT = 9999 + + static { + DATASOURCE = new PostgreSQLContainer<>("postgres:14.1-alpine") + WIREMOCK = new WireMockServer(WireMockConfiguration.options().port(WIREMOCK_PORT)) + } + + @DynamicPropertySource + private static void containerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", DATASOURCE::getJdbcUrl) + registry.add("spring.datasource.username", DATASOURCE::getUsername) + registry.add("spring.datasource.password", DATASOURCE::getPassword) + + final String wiremockBaseUrl = "http://localhost:${WIREMOCK_PORT}" + Map.of( + "service.baseUrl.gateway", "${wiremockBaseUrl}/gateway", + "security.oauth2.client.registration.github.tokenUri", "${wiremockBaseUrl}/login/oauth/access_token", + "security.oauth2.client.registration.github.authorizationUri", "${wiremockBaseUrl}/login/oauth/authorize", + "security.oauth2.client.registration.github.userInfoEndpoint.uri", "${wiremockBaseUrl}/user", + "eureka.client.enabled", false, + ).forEach { + key, value -> registry.add(key, { value }) + } + } +} \ No newline at end of file diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RefreshTokenFactory.java b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RefreshTokenFactory.java new file mode 100644 index 00000000..87c87ce7 --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RefreshTokenFactory.java @@ -0,0 +1,19 @@ +package pl.sknikod.kodemyauth.factory; + +import pl.sknikod.kodemyauth.infrastructure.database.RefreshToken; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class RefreshTokenFactory { + public static RefreshToken create(UUID token, UUID bearerJti) { + RefreshToken refreshToken = new RefreshToken( + token, + bearerJti, + LocalDateTime.now(), + UserFactory.create() + ); + refreshToken.setId(1L); + return refreshToken; + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RoleFactory.java b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RoleFactory.java new file mode 100644 index 00000000..5f72e5ab --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/RoleFactory.java @@ -0,0 +1,15 @@ +package pl.sknikod.kodemyauth.factory; + +import pl.sknikod.kodemyauth.infrastructure.database.Permission; +import pl.sknikod.kodemyauth.infrastructure.database.Role; + +import java.util.Set; + +public class RoleFactory { + public static Role create() { + Role role = new Role(); + role.setId(1L); + role.setPermissions(Set.of(new Permission("PERMISSION"))); + return role; + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/UserFactory.java b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/UserFactory.java new file mode 100644 index 00000000..01305ceb --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/factory/UserFactory.java @@ -0,0 +1,20 @@ +package pl.sknikod.kodemyauth.factory; + +import pl.sknikod.kodemyauth.infrastructure.database.User; + +public class UserFactory { + public static User create(Long userId) { + User user = new User( + "username", + "email@email.com", + null, + RoleFactory.create() + ); + user.setId(userId); + return user; + } + + public static User create() { + return create(1L); + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/AccessTokenServiceSpec.groovy b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/AccessTokenServiceSpec.groovy new file mode 100644 index 00000000..20a4438d --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/AccessTokenServiceSpec.groovy @@ -0,0 +1,17 @@ +package pl.sknikod.kodemyauth.infrastructure.module.auth + +import spock.lang.Specification + +class AccessTokenServiceSpec extends Specification { + def TOKEN = "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJwbC5za25pa29kLmtvZGVteSIsImp0aSI6ImQxMWI0MDhkLWYwNTMtNDIwMC04YjFkLTE2ODRkYTg4NzEyNCIsInN1YiI6IkthcnRWZW4iLCJpYXQiOjE3MzMyNjU0MDUsImV4cCI6MTczMzMwODYwNSwic3RhdGUiOjgsImF1dGhvcml0aWVzIjpbXSwiaWQiOjF9.qt751MNLNOlLqUkattm3bH0BJtVsmBQBk3g5WTcwBHJTrWNVwhNALv0TQRpRfQQJ" + + def accessTokenService = new AccessTokenService() + + def "should get access token"() { + when: + def result = accessTokenService.getAccessToken("Bearer " + TOKEN) + + then: + result == TOKEN + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/RefreshTokensServiceSpec.groovy b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/RefreshTokensServiceSpec.groovy new file mode 100644 index 00000000..45f1b70c --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/auth/RefreshTokensServiceSpec.groovy @@ -0,0 +1,43 @@ +package pl.sknikod.kodemyauth.infrastructure.module.auth + +import io.vavr.control.Try +import pl.sknikod.kodemyauth.factory.RefreshTokenFactory +import pl.sknikod.kodemyauth.factory.RoleFactory +import pl.sknikod.kodemyauth.infrastructure.database.RoleRepository +import pl.sknikod.kodemyauth.infrastructure.store.RefreshTokenStore +import pl.sknikod.kodemycommons.security.JwtProvider +import spock.lang.Specification + +import java.time.Instant + +class RefreshTokensServiceSpec extends Specification { + def roleRepository = Mock(RoleRepository) + def refreshTokenStore = Mock(RefreshTokenStore) + def jwtProvider = Mock(JwtProvider) + + def refreshTokensService = new RefreshTokensService( + roleRepository, refreshTokenStore, jwtProvider + ) + + static final UUID REFRESH = UUID.randomUUID() + static final UUID BEARER_JTI = UUID.randomUUID() + + def "should refresh token"() { + given: + def oldRefreshToken = RefreshTokenFactory.create(REFRESH, BEARER_JTI) + refreshTokenStore.findByTokenAndBearerJti(REFRESH, BEARER_JTI) >> Try.success(oldRefreshToken) + roleRepository.findById(_ as Long) >> Optional.of(RoleFactory.create()) + var newTokenId = UUID.randomUUID() + jwtProvider.generateUserToken(_) + >> new JwtProvider.Token(newTokenId, "TOKEN", Date.from(Instant.now())) + refreshTokenStore.createAndGet(_, _) + >> Try.success(RefreshTokenFactory.create(newTokenId, UUID.randomUUID())) + refreshTokenStore.invalidate(oldRefreshToken) >> {} + + when: + def result = refreshTokensService.refresh(REFRESH, BEARER_JTI) + + then: + result.refresh() == newTokenId.toString() + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/oauth2/engine/github/GithubExchangeFlowSpec.groovy b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/oauth2/engine/github/GithubExchangeFlowSpec.groovy new file mode 100644 index 00000000..e18f6b95 --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/module/oauth2/engine/github/GithubExchangeFlowSpec.groovy @@ -0,0 +1,57 @@ +package pl.sknikod.kodemyauth.infrastructure.module.oauth2.engine.github + +import org.springframework.core.ParameterizedTypeReference +import org.springframework.http.HttpEntity +import org.springframework.http.HttpMethod +import org.springframework.http.ResponseEntity +import org.springframework.security.oauth2.client.registration.ClientRegistration +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository +import org.springframework.web.client.RestTemplate +import spock.lang.Specification + +class GithubExchangeFlowSpec extends Specification { + def restTemplate = Mock(RestTemplate) + + def githubExchangeFlow = new GithubExchangeFlow(restTemplate) + + def "should get successfully provider user"() { + given: + def code = "code" + def registrationId = githubExchangeFlow.registration.toString() + def repository = Mock(ClientRegistrationRepository) + def client = Mock(ClientRegistration) { + getRegistrationId() >> registrationId + getClientId() >> "clientId" + getClientSecret() >> "clientSecret" + getProviderDetails() >> Mock(ClientRegistration.ProviderDetails) { + getTokenUri() >> "tokenUri" + getUserInfoEndpoint() >> Mock(ClientRegistration.ProviderDetails.UserInfoEndpoint) { + getUri() >> "userInfoEndpoint" + } + } + } + repository.findByRegistrationId(registrationId) >> client + + restTemplate.exchange("tokenUri", HttpMethod.POST, _ as HttpEntity, _ as ParameterizedTypeReference, _ as Object[]) + >> ResponseEntity.ok(Map.of("access_token", "access_token")) + + restTemplate.exchange("userInfoEndpoint", HttpMethod.GET, _ as HttpEntity, _ as ParameterizedTypeReference, _ as Object[]) + >> ResponseEntity.ok(Map.of("login", "login", "email", "")) + + def email = new GithubExchangeFlow.Email() + email.setEmail("email@email.com") + email.setPrimary(true) + + restTemplate.exchange("userInfoEndpoint/emails", HttpMethod.GET, _ as HttpEntity, _ as ParameterizedTypeReference, _ as Object[]) + >> ResponseEntity.ok([email]) + + when: + def result = githubExchangeFlow.exchange(repository, code) + + then: + verifyAll { + result.username == "login" + result.email == "email@email.com" + } + } +} diff --git a/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/store/RefreshTokenStoreSpec.groovy b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/store/RefreshTokenStoreSpec.groovy new file mode 100644 index 00000000..9dfd50a6 --- /dev/null +++ b/kodemy-auth/src/test/groovy/pl/sknikod/kodemyauth/infrastructure/store/RefreshTokenStoreSpec.groovy @@ -0,0 +1,38 @@ +package pl.sknikod.kodemyauth.infrastructure.store + +import pl.sknikod.kodemyauth.factory.UserFactory +import pl.sknikod.kodemyauth.infrastructure.database.RefreshToken +import pl.sknikod.kodemyauth.infrastructure.database.RefreshTokenRepository +import pl.sknikod.kodemyauth.infrastructure.database.UserRepository +import spock.lang.Specification + +class RefreshTokenStoreSpec extends Specification { + def refreshTokenRepository = Mock(RefreshTokenRepository) + def userRepository = Mock(UserRepository) + + def refreshTokenStore = new RefreshTokenStore(refreshTokenRepository, 1440, userRepository) + + private static final long USER_ID = 1L + private static final UUID BEARER_ID = UUID.randomUUID() + + def "should create new refresh token"() { + given: + userRepository.findById(USER_ID) >> Optional.of(UserFactory.create(USER_ID)) + refreshTokenRepository.save(_ as RefreshToken) >> { RefreshToken refreshToken -> + { + refreshToken.setId(1L) + return refreshToken + } + } + + when: + def result = refreshTokenStore.createAndGet(USER_ID, BEARER_ID) + + then: + verifyAll(result.get()) { + id == 1L + bearerId == BEARER_ID + user.id == USER_ID + } + } +} diff --git a/kodemy-auth/src/test/java/pl/sknikod/kodemyauth/SuperclassTest.java b/kodemy-auth/src/test/java/pl/sknikod/kodemyauth/SuperclassTest.java deleted file mode 100644 index 2311ff41..00000000 --- a/kodemy-auth/src/test/java/pl/sknikod/kodemyauth/SuperclassTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package pl.sknikod.kodemyauth; - -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; - -@SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.MOCK -) -@ContextConfiguration(classes = KodemyAuthApplication.class) -@ImportAutoConfiguration(value = TestChannelBinderConfiguration.class) -@Import({JwtConfiguration.class}) -@Testcontainers -public class SuperclassTest { - @Container - private static final PostgreSQLContainer dataSource; - - static { - dataSource = new PostgreSQLContainer<>("postgres:14.1-alpine"); - } - - @DynamicPropertySource - private static void containerProperties(DynamicPropertyRegistry registry) { - registry.add("spring.datasource.url", dataSource::getJdbcUrl); - registry.add("spring.datasource.username", dataSource::getUsername); - registry.add("spring.datasource.password", dataSource::getPassword); - } -} \ No newline at end of file diff --git a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/configuration/SecurityConfiguration.java b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/configuration/SecurityConfiguration.java index d900a981..06caa38c 100644 --- a/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/configuration/SecurityConfiguration.java +++ b/kodemy-backend/src/main/java/pl/sknikod/kodemybackend/configuration/SecurityConfiguration.java @@ -4,9 +4,9 @@ import lombok.AllArgsConstructor; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -14,10 +14,10 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; import pl.sknikod.kodemycommons.exception.handler.ServletExceptionHandler; import pl.sknikod.kodemycommons.security.JwtAuthorizationFilter; import pl.sknikod.kodemycommons.security.JwtProvider; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; @Configuration @EnableWebSecurity @@ -25,7 +25,6 @@ securedEnabled = true, jsr250Enabled = true) @AllArgsConstructor -@Import({JwtConfiguration.class}) //@EnableJpaAuditing(auditorAwareRef = "auditorAware") @EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class) public class SecurityConfiguration { @@ -56,12 +55,17 @@ public ServletExceptionHandler servletExceptionHandler(ObjectMapper objectMapper } @Bean - public JwtProvider jwtProvider(JwtConfiguration jwtConfiguration) { - return new JwtProvider(jwtConfiguration.getJwtProperties()); + public JwtProvider jwtProvider(JwtProperties jwtProperties) { + return new JwtProvider(jwtProperties); } @Bean public JwtAuthorizationFilter jwtAuthorizationFilter(JwtProvider jwtProvider) { return new JwtAuthorizationFilter(jwtProvider); } + + @Component + @ConfigurationProperties(prefix = "app.security.jwt") + public static class JwtProperties extends JwtProvider.Properties { + } } \ No newline at end of file diff --git a/kodemy-backend/src/main/resources/application-local.yml b/kodemy-backend/src/main/resources/application-local.yml index babfef0a..10b02900 100644 --- a/kodemy-backend/src/main/resources/application-local.yml +++ b/kodemy-backend/src/main/resources/application-local.yml @@ -21,8 +21,10 @@ service: baseUrl: auth: http://localhost:8080 -jwt: - expiration-mins: 60 +app: + security: + jwt: + bearer-expiration-min: 60 eureka: client: diff --git a/kodemy-backend/src/main/resources/application.yml b/kodemy-backend/src/main/resources/application.yml index 19ed89a1..88e65ca0 100644 --- a/kodemy-backend/src/main/resources/application.yml +++ b/kodemy-backend/src/main/resources/application.yml @@ -68,8 +68,10 @@ service: connect-timeout-ms: 3000 read-timeout-ms: 5000 -jwt: - secret-key: YWJjZGVmZ2hjvbwjrW5vcHFyc3R1dnd4eXoxMjM0NTY3OnDMTIzNDU2Nzg5MDEyMzQ1Njc4OTAf= +app: + security: + jwt: + secret-key: YWJjZGVmZ2hjvbwjrW5vcHFyc3R1dnd4eXoxMjM0NTY3OnDMTIzNDU2Nzg5MDEyMzQ1Njc4OTAf= eureka: client: diff --git a/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/MvcSuperclassTest.java b/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/MvcSuperclassTest.java index ad796e1a..782114c2 100644 --- a/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/MvcSuperclassTest.java +++ b/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/MvcSuperclassTest.java @@ -3,14 +3,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; import org.springframework.test.web.servlet.MockMvc; -import pl.sknikod.kodemybackend.configuration.SecurityConfiguration; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; @SpringBootTest @AutoConfigureMockMvc -@Import({SecurityConfiguration.class, JwtConfiguration.class}) public class MvcSuperclassTest extends SuperclassTest { @Autowired protected MockMvc mockMvc; diff --git a/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/SuperclassTest.java b/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/SuperclassTest.java index ebd4df84..c3c45224 100644 --- a/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/SuperclassTest.java +++ b/kodemy-backend/src/test/java/pl/sknikod/kodemybackend/SuperclassTest.java @@ -2,30 +2,23 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.platform.commons.logging.LoggerFactory; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.context.annotation.Import; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; - -import java.util.logging.Logger; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK ) @ContextConfiguration(classes = KodemyBackendApplication.class) @ImportAutoConfiguration(value = TestChannelBinderConfiguration.class) -@Import({JwtConfiguration.class}) @Testcontainers public class SuperclassTest { @Container diff --git a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/configuration/SecurityConfiguration.java b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/configuration/SecurityConfiguration.java index 42004072..78616b43 100644 --- a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/configuration/SecurityConfiguration.java +++ b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/configuration/SecurityConfiguration.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -14,10 +15,10 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.stereotype.Component; import pl.sknikod.kodemycommons.exception.handler.ServletExceptionHandler; import pl.sknikod.kodemycommons.security.JwtAuthorizationFilter; import pl.sknikod.kodemycommons.security.JwtProvider; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; @Configuration @EnableWebSecurity @@ -25,7 +26,6 @@ securedEnabled = true, jsr250Enabled = true) @AllArgsConstructor -@Import({JwtConfiguration.class}) @EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class) public class SecurityConfiguration { @Bean @@ -55,12 +55,17 @@ public ServletExceptionHandler servletExceptionHandler(ObjectMapper objectMapper } @Bean - public JwtProvider jwtProvider(JwtConfiguration jwtConfiguration) { - return new JwtProvider(jwtConfiguration.getJwtProperties()); + public JwtProvider jwtProvider(JwtProperties jwtProperties) { + return new JwtProvider(jwtProperties); } @Bean public JwtAuthorizationFilter jwtAuthorizationFilter(JwtProvider jwtProvider) { return new JwtAuthorizationFilter(jwtProvider); } + + @Component + @ConfigurationProperties(prefix = "app.security.jwt") + public static class JwtProperties extends JwtProvider.Properties { + } } \ No newline at end of file diff --git a/kodemy-search/src/main/resources/application.yml b/kodemy-search/src/main/resources/application.yml index 948221d6..29d89a06 100755 --- a/kodemy-search/src/main/resources/application.yml +++ b/kodemy-search/src/main/resources/application.yml @@ -90,8 +90,10 @@ logbook: - path: /actuator/health methods: "*" -jwt: - secret-key: P8K53KMRFBWDRH3EBWYV3B9MQG6QU8V59PYRWGDG96MNDCYBQCJ5EEGXT5ZF76E6 +app: + security: + jwt: + secret-key: P8K53KMRFBWDRH3EBWYV3B9MQG6QU8V59PYRWGDG96MNDCYBQCJ5EEGXT5ZF76E6 opensearch: host: http://opensearch:9200 diff --git a/kodemy-search/src/test/java/pl/sknikod/kodemysearch/SuperclassTest.java b/kodemy-search/src/test/java/pl/sknikod/kodemysearch/SuperclassTest.java index 7f84d718..ccc90120 100644 --- a/kodemy-search/src/test/java/pl/sknikod/kodemysearch/SuperclassTest.java +++ b/kodemy-search/src/test/java/pl/sknikod/kodemysearch/SuperclassTest.java @@ -3,15 +3,12 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.context.annotation.Import; import org.springframework.test.context.ContextConfiguration; -import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOCK ) @ContextConfiguration(classes = KodemySearchApplication.class) @ImportAutoConfiguration(value = TestChannelBinderConfiguration.class) -@Import({JwtConfiguration.class}) public class SuperclassTest { } \ No newline at end of file From 7e25aedc5d6450fc1a0f82d34cc129821b92f759 Mon Sep 17 00:00:00 2001 From: Marcin Bihun Date: Thu, 5 Dec 2024 21:16:02 +0100 Subject: [PATCH 3/4] fix: #210 change filtering in Material Search --- .../module/material/MaterialSearchService.java | 7 ++----- .../module/material/SearchCriteria.java | 15 +++++++++++++++ .../module/material/SearchRequestBuilder.java | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java index 50ccc403..5c448d69 100644 --- a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java +++ b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java @@ -45,11 +45,8 @@ private SearchCriteria createSearchCriteria(@NonNull MaterialControllerDefinitio )); var categoryIds = filterSearchParams.getCategoryIds(); if (Objects.nonNull(categoryIds) && !categoryIds.isEmpty()) { - categoryIds.forEach(categoryId -> { - criteria.addPhraseField(new SearchCriteria.PhraseField( - "categoryId", String.valueOf(categoryId), false, false - )); - }); + criteria.addArrayField(new SearchCriteria.ArrayField( + "categoryId", categoryIds.stream().map(Object::toString).toList())); } if (Objects.nonNull(filterSearchParams.getMinAvgGrade()) || Objects.nonNull(filterSearchParams.getMaxAvgGrade())) criteria.addRangeField(new SearchCriteria.RangeField<>( diff --git a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchCriteria.java b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchCriteria.java index 5f43019c..cd848002 100644 --- a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchCriteria.java +++ b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchCriteria.java @@ -15,6 +15,7 @@ public class SearchCriteria { String anyPhrase; List phraseFields = new ArrayList<>(); List> rangeFields = new ArrayList<>(); + List arrayFields = new ArrayList<>(); Pageable pageable; public SearchCriteria(@NonNull String anyPhrase, @NonNull Pageable pageable) { @@ -30,6 +31,10 @@ public void addRangeField(RangeField field) { rangeFields.add(field); } + public void addArrayField(ArrayField field) { + arrayFields.add(field); + } + @Getter private abstract static class Field { private final String name; @@ -65,6 +70,16 @@ public PhraseField(String name, String value, boolean wildcard, boolean mustNot) } } + @Getter + public static class ArrayField extends Field { + private final List values; + + public ArrayField(String name, List values) { + super(name); + this.values = values; + } + } + @Getter public static class ContentField { private final String value; diff --git a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchRequestBuilder.java b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchRequestBuilder.java index 5a274bf7..d6c1098b 100644 --- a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchRequestBuilder.java +++ b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/SearchRequestBuilder.java @@ -3,11 +3,13 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; import org.opensearch.client.json.JsonData; +import org.opensearch.client.opensearch._types.FieldValue; import org.opensearch.client.opensearch._types.SortOptions; import org.opensearch.client.opensearch._types.SortOrder; import org.opensearch.client.opensearch._types.query_dsl.MatchPhraseQuery; import org.opensearch.client.opensearch._types.query_dsl.Query; import org.opensearch.client.opensearch._types.query_dsl.RangeQuery; +import org.opensearch.client.opensearch._types.query_dsl.TermsQuery; import org.opensearch.client.opensearch._types.query_dsl.WildcardQuery; import org.opensearch.client.opensearch.core.SearchRequest; import org.springframework.data.domain.Pageable; @@ -42,6 +44,7 @@ public SearchRequestBuilder(String indexName, SearchCriteria criteria) { any(criteria.getAnyPhrase()); criteria.getPhraseFields().forEach(this::append); criteria.getRangeFields().forEach(this::append); + criteria.getArrayFields().forEach(this::append); } private void with(Pageable pageable) { @@ -74,7 +77,7 @@ private void append(SearchCriteria.PhraseField field) { var query = field.isWildcard() ? WildcardQuery.of(w -> w.field(field.getName()).value(field.getValue())).toQuery() : MatchPhraseQuery.of(m -> m.field(field.getName()).query(field.getValue())).toQuery(); - (field.isMustNot() ? mustNotQueries : shouldQueries).add(query); + (field.isMustNot() ? mustNotQueries : mustQueries).add(query); } private void append(SearchCriteria.RangeField field) { @@ -87,6 +90,17 @@ private void append(SearchCriteria.RangeField field) { mustQueries.add(rangeQueryBuilder.build().toQuery()); } + private void append(SearchCriteria.ArrayField field) { + if (field == null || field.getValues().isEmpty()) { + return; + } + var query = TermsQuery.of(t -> t.field(field.getName()) + .terms(terms -> terms.value(field.getValues().stream().map(FieldValue::of).toList())) + ).toQuery(); + mustQueries.add(query); + } + + public SearchRequest build() { return new SearchRequest.Builder() .index(indexName) From 34c47effcba13e05ae6f995df6de6ccb4fb93f94 Mon Sep 17 00:00:00 2001 From: Marcin Bihun Date: Fri, 6 Dec 2024 09:19:23 +0100 Subject: [PATCH 4/4] fix: #210 change Object::valueOf to String::valueOf --- .../infrastructure/module/material/MaterialSearchService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java index 5c448d69..d95d9947 100644 --- a/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java +++ b/kodemy-search/src/main/java/pl/sknikod/kodemysearch/infrastructure/module/material/MaterialSearchService.java @@ -46,7 +46,7 @@ private SearchCriteria createSearchCriteria(@NonNull MaterialControllerDefinitio var categoryIds = filterSearchParams.getCategoryIds(); if (Objects.nonNull(categoryIds) && !categoryIds.isEmpty()) { criteria.addArrayField(new SearchCriteria.ArrayField( - "categoryId", categoryIds.stream().map(Object::toString).toList())); + "categoryId", categoryIds.stream().map(String::valueOf).toList())); } if (Objects.nonNull(filterSearchParams.getMinAvgGrade()) || Objects.nonNull(filterSearchParams.getMaxAvgGrade())) criteria.addRangeField(new SearchCriteria.RangeField<>(