From 92279cb0beb7fa195554b4da79f0244917673f89 Mon Sep 17 00:00:00 2001 From: Soroush Date: Sun, 6 Aug 2023 20:30:32 +0200 Subject: [PATCH 1/6] REINIT[simple spring project] --- .devcontainer/Dockerfile | 25 - .devcontainer/devcontainer.json | 39 - .editorconfig | 23 - .gitattributes | 150 - .gitignore | 177 +- .husky/pre-commit | 5 - .lintstagedrc.js | 3 - .mvn/wrapper/maven-wrapper.jar | Bin 59925 -> 62547 bytes .mvn/wrapper/maven-wrapper.properties | 20 +- .prettierignore | 8 - .prettierrc | 18 - .yo-rc-global.json | 8 - .yo-rc.json | 47 - LICENSE | 21 - README.md | 152 - checkstyle.xml | 20 - mvnw | 220 +- mvnw.cmd | 33 +- package.json | 64 - pom.xml | 1125 +---- sonar-project.properties | 32 - src/main/docker/app.yml | 28 - .../docker/central-server-config/README.md | 1 - .../grafana/provisioning/dashboards/JVM.json | 3778 ----------------- .../provisioning/dashboards/dashboard.yml | 11 - .../provisioning/datasources/datasource.yml | 50 - src/main/docker/jhipster-control-center.yml | 52 - src/main/docker/jib/entrypoint.sh | 39 - src/main/docker/monitoring.yml | 31 - src/main/docker/postgresql.yml | 15 - src/main/docker/prometheus/prometheus.yml | 31 - src/main/docker/sonar.yml | 13 - src/main/docker/zipkin.yml | 7 - .../shuoros/peoplify/ApplicationWebXml.java | 19 - .../shuoros/peoplify/GeneratedByJHipster.java | 13 - .../github/shuoros/peoplify/PeoplifyApp.java | 105 - .../shuoros/peoplify/PeoplifyApplication.java | 13 + .../peoplify/aop/logging/LoggingAspect.java | 115 - .../config/ApplicationProperties.java | 16 - .../peoplify/config/AsyncConfiguration.java | 48 - .../peoplify/config/CRLFLogConverter.java | 56 - .../peoplify/config/CacheConfiguration.java | 78 - .../shuoros/peoplify/config/Constants.java | 15 - .../config/DatabaseConfiguration.java | 16 - .../config/DateTimeFormatConfiguration.java | 20 - .../peoplify/config/JacksonConfiguration.java | 51 - .../config/LiquibaseConfiguration.java | 69 - .../peoplify/config/LocaleConfiguration.java | 26 - .../config/LoggingAspectConfiguration.java | 17 - .../peoplify/config/LoggingConfiguration.java | 47 - .../config/SecurityConfiguration.java | 90 - .../peoplify/config/WebConfigurer.java | 58 - .../shuoros/peoplify/config/package-info.java | 4 - .../domain/AbstractAuditingEntity.java | 75 - .../shuoros/peoplify/domain/Authority.java | 61 - .../github/shuoros/peoplify/domain/User.java | 232 - .../shuoros/peoplify/domain/package-info.java | 4 - .../management/SecurityMetersService.java | 51 - .../repository/AuthorityRepository.java | 9 - .../peoplify/repository/UserRepository.java | 36 - .../peoplify/repository/package-info.java | 4 - .../security/AuthoritiesConstants.java | 15 - .../security/DomainUserDetailsService.java | 64 - .../peoplify/security/SecurityUtils.java | 100 - .../security/SpringSecurityAuditorAware.java | 18 - .../security/UserNotActivatedException.java | 19 - .../peoplify/security/jwt/JWTConfigurer.java | 21 - .../peoplify/security/jwt/JWTFilter.java | 47 - .../peoplify/security/jwt/TokenProvider.java | 126 - .../peoplify/security/package-info.java | 4 - .../service/EmailAlreadyUsedException.java | 10 - .../service/InvalidPasswordException.java | 10 - .../shuoros/peoplify/service/MailService.java | 112 - .../shuoros/peoplify/service/UserService.java | 325 -- .../service/UsernameAlreadyUsedException.java | 10 - .../peoplify/service/dto/AdminUserDTO.java | 196 - .../service/dto/PasswordChangeDTO.java | 39 - .../shuoros/peoplify/service/dto/UserDTO.java | 51 - .../peoplify/service/dto/package-info.java | 4 - .../peoplify/service/mapper/UserMapper.java | 147 - .../peoplify/service/mapper/package-info.java | 4 - .../peoplify/service/package-info.java | 4 - .../peoplify/web/rest/AccountResource.java | 194 - .../peoplify/web/rest/PublicUserResource.java | 65 - .../peoplify/web/rest/UserJWTController.java | 68 - .../peoplify/web/rest/UserResource.java | 212 - .../rest/errors/BadRequestAlertException.java | 42 - .../errors/EmailAlreadyUsedException.java | 11 - .../web/rest/errors/ErrorConstants.java | 17 - .../web/rest/errors/ExceptionTranslator.java | 222 - .../web/rest/errors/FieldErrorVM.java | 32 - .../rest/errors/InvalidPasswordException.java | 14 - .../errors/LoginAlreadyUsedException.java | 11 - .../web/rest/errors/package-info.java | 6 - .../peoplify/web/rest/package-info.java | 4 - .../web/rest/vm/KeyAndPasswordVM.java | 27 - .../shuoros/peoplify/web/rest/vm/LoginVM.java | 53 - .../peoplify/web/rest/vm/ManagedUserVM.java | 35 - .../peoplify/web/rest/vm/package-info.java | 4 - .../main/resources/application.properties | 0 src/main/resources/banner.txt | 10 - src/main/resources/config/application-dev.yml | 107 - .../resources/config/application-prod.yml | 126 - src/main/resources/config/application-tls.yml | 19 - src/main/resources/config/application.yml | 212 - src/main/resources/config/bootstrap-prod.yml | 6 - src/main/resources/config/bootstrap.yml | 14 - .../00000000000000_initial_schema.xml | 121 - .../config/liquibase/data/authority.csv | 3 - .../resources/config/liquibase/data/user.csv | 3 - .../config/liquibase/data/user_authority.csv | 4 - .../resources/config/liquibase/master.xml | 17 - src/main/resources/i18n/messages.properties | 21 - src/main/resources/logback-spring.xml | 77 - src/main/resources/templates/error.html | 92 - .../templates/mail/activationEmail.html | 20 - .../templates/mail/creationEmail.html | 20 - .../templates/mail/passwordResetEmail.html | 22 - .../shuoros/peoplify/IntegrationTest.java | 22 - .../peoplify/PeoplifyApplicationTests.java | 13 + .../peoplify/TechnicalStructureTest.java | 37 - .../config/AsyncSyncConfiguration.java | 16 - .../shuoros/peoplify/config/EmbeddedSQL.java | 11 - .../config/PostgreSqlTestContainer.java | 42 - .../config/SpringBootTestClassOrderer.java | 22 - .../peoplify/config/SqlTestContainer.java | 9 - ...tainersSpringContextCustomizerFactory.java | 79 - .../peoplify/config/WebConfigurerTest.java | 117 - .../config/WebConfigurerTestController.java | 14 - .../config/timezone/HibernateTimeZoneIT.java | 162 - .../SecurityMetersServiceTests.java | 70 - .../repository/timezone/DateTimeWrapper.java | 133 - .../timezone/DateTimeWrapperRepository.java | 10 - .../security/DomainUserDetailsServiceIT.java | 113 - .../security/SecurityUtilsUnitTest.java | 101 - .../peoplify/security/jwt/JWTFilterTest.java | 117 - .../jwt/TokenProviderSecurityMetersTests.java | 158 - .../security/jwt/TokenProviderTest.java | 141 - .../peoplify/service/MailServiceIT.java | 235 - .../peoplify/service/UserServiceIT.java | 185 - .../service/mapper/UserMapperTest.java | 132 - .../peoplify/web/rest/AccountResourceIT.java | 762 ---- .../web/rest/PublicUserResourceIT.java | 99 - .../shuoros/peoplify/web/rest/TestUtil.java | 206 - .../web/rest/UserJWTControllerIT.java | 98 - .../peoplify/web/rest/UserResourceIT.java | 582 --- .../web/rest/WithUnauthenticatedMockUser.java | 23 - .../rest/errors/ExceptionTranslatorIT.java | 118 - .../ExceptionTranslatorTestController.java | 66 - src/test/resources/META-INF/spring.factories | 1 - .../resources/config/application-testdev.yml | 34 - .../resources/config/application-testprod.yml | 34 - src/test/resources/config/application.yml | 92 - src/test/resources/config/bootstrap.yml | 0 .../resources/i18n/messages_en.properties | 1 - src/test/resources/junit-platform.properties | 4 - src/test/resources/logback.xml | 51 - .../templates/mail/activationEmail.html | 19 - .../templates/mail/creationEmail.html | 19 - .../templates/mail/passwordResetEmail.html | 21 - .../resources/templates/mail/testEmail.html | 1 - src/test/resources/testcontainers.properties | 1 - 162 files changed, 236 insertions(+), 14766 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .editorconfig delete mode 100644 .gitattributes delete mode 100644 .husky/pre-commit delete mode 100644 .lintstagedrc.js delete mode 100644 .prettierignore delete mode 100644 .prettierrc delete mode 100644 .yo-rc-global.json delete mode 100644 .yo-rc.json delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 checkstyle.xml mode change 100644 => 100755 mvnw delete mode 100644 package.json delete mode 100644 sonar-project.properties delete mode 100644 src/main/docker/app.yml delete mode 100644 src/main/docker/central-server-config/README.md delete mode 100644 src/main/docker/grafana/provisioning/dashboards/JVM.json delete mode 100644 src/main/docker/grafana/provisioning/dashboards/dashboard.yml delete mode 100644 src/main/docker/grafana/provisioning/datasources/datasource.yml delete mode 100644 src/main/docker/jhipster-control-center.yml delete mode 100644 src/main/docker/jib/entrypoint.sh delete mode 100644 src/main/docker/monitoring.yml delete mode 100644 src/main/docker/postgresql.yml delete mode 100644 src/main/docker/prometheus/prometheus.yml delete mode 100644 src/main/docker/sonar.yml delete mode 100644 src/main/docker/zipkin.yml delete mode 100644 src/main/java/io/github/shuoros/peoplify/ApplicationWebXml.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/GeneratedByJHipster.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/PeoplifyApp.java create mode 100644 src/main/java/io/github/shuoros/peoplify/PeoplifyApplication.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/aop/logging/LoggingAspect.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/ApplicationProperties.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/AsyncConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/CRLFLogConverter.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/CacheConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/Constants.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/DatabaseConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/DateTimeFormatConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/JacksonConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/LiquibaseConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/LocaleConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/LoggingAspectConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/LoggingConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/SecurityConfiguration.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/WebConfigurer.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/config/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/domain/AbstractAuditingEntity.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/domain/Authority.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/domain/User.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/domain/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/management/SecurityMetersService.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/repository/AuthorityRepository.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/repository/UserRepository.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/repository/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/AuthoritiesConstants.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/DomainUserDetailsService.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/SecurityUtils.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/SpringSecurityAuditorAware.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/UserNotActivatedException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/jwt/JWTConfigurer.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/jwt/JWTFilter.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/jwt/TokenProvider.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/security/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/EmailAlreadyUsedException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/InvalidPasswordException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/MailService.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/UserService.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/UsernameAlreadyUsedException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/dto/AdminUserDTO.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/dto/PasswordChangeDTO.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/dto/UserDTO.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/dto/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/mapper/UserMapper.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/mapper/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/service/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/AccountResource.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/PublicUserResource.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/UserJWTController.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/UserResource.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/BadRequestAlertException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/EmailAlreadyUsedException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/ErrorConstants.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslator.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/FieldErrorVM.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/InvalidPasswordException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/LoginAlreadyUsedException.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/errors/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/package-info.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/vm/KeyAndPasswordVM.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/vm/LoginVM.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/vm/ManagedUserVM.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/web/rest/vm/package-info.java rename .mvn/jvm.config => src/main/resources/application.properties (100%) delete mode 100644 src/main/resources/banner.txt delete mode 100644 src/main/resources/config/application-dev.yml delete mode 100644 src/main/resources/config/application-prod.yml delete mode 100644 src/main/resources/config/application-tls.yml delete mode 100644 src/main/resources/config/application.yml delete mode 100644 src/main/resources/config/bootstrap-prod.yml delete mode 100644 src/main/resources/config/bootstrap.yml delete mode 100644 src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml delete mode 100644 src/main/resources/config/liquibase/data/authority.csv delete mode 100644 src/main/resources/config/liquibase/data/user.csv delete mode 100644 src/main/resources/config/liquibase/data/user_authority.csv delete mode 100644 src/main/resources/config/liquibase/master.xml delete mode 100644 src/main/resources/i18n/messages.properties delete mode 100644 src/main/resources/logback-spring.xml delete mode 100644 src/main/resources/templates/error.html delete mode 100644 src/main/resources/templates/mail/activationEmail.html delete mode 100644 src/main/resources/templates/mail/creationEmail.html delete mode 100644 src/main/resources/templates/mail/passwordResetEmail.html delete mode 100644 src/test/java/io/github/shuoros/peoplify/IntegrationTest.java create mode 100644 src/test/java/io/github/shuoros/peoplify/PeoplifyApplicationTests.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/TechnicalStructureTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/AsyncSyncConfiguration.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/EmbeddedSQL.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/PostgreSqlTestContainer.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/SpringBootTestClassOrderer.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/SqlTestContainer.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/TestContainersSpringContextCustomizerFactory.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTestController.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/config/timezone/HibernateTimeZoneIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/management/SecurityMetersServiceTests.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapper.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapperRepository.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/security/DomainUserDetailsServiceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/security/SecurityUtilsUnitTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/security/jwt/JWTFilterTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderSecurityMetersTests.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/service/MailServiceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/service/UserServiceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/service/mapper/UserMapperTest.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/AccountResourceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/PublicUserResourceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/TestUtil.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/UserJWTControllerIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/UserResourceIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/WithUnauthenticatedMockUser.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorIT.java delete mode 100644 src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorTestController.java delete mode 100644 src/test/resources/META-INF/spring.factories delete mode 100644 src/test/resources/config/application-testdev.yml delete mode 100644 src/test/resources/config/application-testprod.yml delete mode 100644 src/test/resources/config/application.yml delete mode 100644 src/test/resources/config/bootstrap.yml delete mode 100644 src/test/resources/i18n/messages_en.properties delete mode 100644 src/test/resources/junit-platform.properties delete mode 100644 src/test/resources/logback.xml delete mode 100644 src/test/resources/templates/mail/activationEmail.html delete mode 100644 src/test/resources/templates/mail/creationEmail.html delete mode 100644 src/test/resources/templates/mail/passwordResetEmail.html delete mode 100644 src/test/resources/templates/mail/testEmail.html delete mode 100644 src/test/resources/testcontainers.properties diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 98a7ee7..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java/.devcontainer/base.Dockerfile - -# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 11, 17, 11-bullseye, 17-bullseye, 11-buster, 17-buster -ARG VARIANT="11" -FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} - -# [Option] Install Maven -ARG INSTALL_MAVEN="false" -ARG MAVEN_VERSION="" -# [Option] Install Gradle -ARG INSTALL_GRADLE="false" -ARG GRADLE_VERSION="" -RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ - && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi - -# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 -ARG NODE_VERSION="none" -RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index ab653f0..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,39 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java -{ - "name": "Peoplify", - "build": { - "dockerfile": "Dockerfile", - "args": { - // Update the VARIANT arg to pick a Java version: 11, 17 - // Append -bullseye or -buster to pin to an OS version. - // Use the -bullseye variants on local arm64/Apple Silicon. - "VARIANT": "11", - // Options - "INSTALL_MAVEN": "true", - "INSTALL_GRADLE": "false", - "NODE_VERSION": "lts/*" - } - }, - - // Set *default* container specific settings.json values on container create. - "settings": { - "java.home": "/docker-java-home" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": ["vscjava.vscode-java-pack", "pivotal.vscode-boot-dev-pack", "esbenp.prettier-vscode"], - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8080], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "java -version", - - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode", - "features": { - "docker-in-docker": "latest", - "docker-from-docker": "latest" - } -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c2fa6a2..0000000 --- a/.editorconfig +++ /dev/null @@ -1,23 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - -[*] - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -# Change these settings to your own preference -indent_style = space -indent_size = 4 - -[*.{ts,tsx,js,jsx,json,css,scss,yml,html,vue}] -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index ca61722..0000000 --- a/.gitattributes +++ /dev/null @@ -1,150 +0,0 @@ -# This file is inspired by https://github.com/alexkaratarakis/gitattributes -# -# Auto detect text files and perform LF normalization -# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ -* text=auto - -# The above will handle all files NOT found below -# These files are text and should be normalized (Convert crlf => lf) - -*.bat text eol=crlf -*.cmd text eol=crlf -*.ps1 text eol=crlf -*.coffee text -*.css text -*.cql text -*.df text -*.ejs text -*.html text -*.java text -*.js text -*.json text -*.less text -*.properties text -*.sass text -*.scss text -*.sh text eol=lf -*.sql text -*.txt text -*.ts text -*.xml text -*.yaml text -*.yml text - -# Documents -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain -*.markdown text -*.md text -*.adoc text -*.textile text -*.mustache text -*.csv text -*.tab text -*.tsv text -*.txt text -AUTHORS text -CHANGELOG text -CHANGES text -CONTRIBUTING text -COPYING text -copyright text -*COPYRIGHT* text -INSTALL text -license text -LICENSE text -NEWS text -readme text -*README* text -TODO text - -# Graphics -*.png binary -*.jpg binary -*.jpeg binary -*.gif binary -*.tif binary -*.tiff binary -*.ico binary -# SVG treated as an asset (binary) by default. If you want to treat it as text, -# comment-out the following line and uncomment the line after. -*.svg binary -#*.svg text -*.eps binary - -# These files are binary and should be left untouched -# (binary is a macro for -text -diff) -*.class binary -*.jar binary -*.war binary - -## LINTERS -.csslintrc text -.eslintrc text -.jscsrc text -.jshintrc text -.jshintignore text -.stylelintrc text - -## CONFIGS -*.conf text -*.config text -.editorconfig text -.gitattributes text -.gitconfig text -.gitignore text -.htaccess text -*.npmignore text - -## HEROKU -Procfile text -.slugignore text - -## AUDIO -*.kar binary -*.m4a binary -*.mid binary -*.midi binary -*.mp3 binary -*.ogg binary -*.ra binary - -## VIDEO -*.3gpp binary -*.3gp binary -*.as binary -*.asf binary -*.asx binary -*.fla binary -*.flv binary -*.m4v binary -*.mng binary -*.mov binary -*.mp4 binary -*.mpeg binary -*.mpg binary -*.swc binary -*.swf binary -*.webm binary - -## ARCHIVES -*.7z binary -*.gz binary -*.rar binary -*.tar binary -*.zip binary - -## FONTS -*.ttf binary -*.eot binary -*.otf binary -*.woff binary -*.woff2 binary diff --git a/.gitignore b/.gitignore index 4dd6fcb..549e00a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,158 +1,33 @@ -###################### -# Project Specific -###################### -/target/classes/static/** -/src/test/javascript/coverage/ - -###################### -# Node -###################### -/node/ -node_tmp/ -node_modules/ -npm-debug.log.* -/.awcache/* -/.cache-loader/* - -###################### -# SASS -###################### -.sass-cache/ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -###################### -# Eclipse -###################### -*.pydevproject -.project -.metadata -tmp/ -tmp/**/* -*.tmp -*.bak -*.swp -*~.nib -local.properties +### STS ### +.apt_generated .classpath -.settings/ -.loadpath .factorypath -/src/main/resources/rebel.xml - -# External tool builders -.externalToolBuilders/** - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - -# STS-specific -/.sts4-cache/* +.project +.settings +.springBeans +.sts4-cache -###################### -# IntelliJ -###################### -.idea/ -*.iml +### IntelliJ IDEA ### +.idea *.iws +*.iml *.ipr -*.ids -*.orig -classes/ -out/ - -###################### -# Visual Studio Code -###################### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -###################### -# Maven -###################### -/log/ -/target/ - -###################### -# Gradle -###################### -.gradle/ -/build/ - -###################### -# Package Files -###################### -*.jar -*.war -*.ear -*.db - -###################### -# Windows -###################### -# Windows image file caches -Thumbs.db - -# Folder config file -Desktop.ini - -###################### -# Mac OSX -###################### -.DS_Store -.svn - -# Thumbnails -._* - -# Files that might appear on external disk -.Spotlight-V100 -.Trashes - -###################### -# Directories -###################### -/bin/ -/deploy/ - -###################### -# Logs -###################### -*.log* - -###################### -# Others -###################### -*.class -*.*~ -*~ -.merge_file* - -###################### -# Gradle Wrapper -###################### -!gradle/wrapper/gradle-wrapper.jar - -###################### -# Maven Wrapper -###################### -!.mvn/wrapper/maven-wrapper.jar - -###################### -# ESLint -###################### -.eslintcache -###################### -# Code coverage -###################### -/coverage/ -/.nyc_output/ +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 3efbda8..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - - -npx --no-install lint-staged diff --git a/.lintstagedrc.js b/.lintstagedrc.js deleted file mode 100644 index dad2893..0000000 --- a/.lintstagedrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - '{,src/**/}*.{md,json,yml,html,java}': ['prettier --write'], -}; diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index bf82ff01c6cdae4a1bb754a6e062954d77ac5c11..cb28b0e37c7d206feb564310fdeec0927af4123a 100644 GIT binary patch delta 52729 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%0DIoqqGCqH%97HM??=#psgoIz5zcKJ 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 delta 50109 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 - - - - - - - - - - - - - - diff --git a/mvnw b/mvnw old mode 100644 new mode 100755 index 5643201..66df285 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -54,7 +53,7 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true @@ -62,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -72,68 +71,38 @@ esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`\\unset -f command; \\command -v java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -184,96 +150,99 @@ find_maven_basedir() { fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$(cd "$wdir/.." || exit 1; pwd) fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi + log "Couldn't find $wrapperJarPath, downloading it ..." + if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" else - jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi @@ -282,35 +251,58 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 8a15b7f..95ba6f5 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,13 +18,12 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% + echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ @@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% ( "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% @@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* diff --git a/package.json b/package.json deleted file mode 100644 index ec78f9f..0000000 --- a/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "peoplify", - "version": "0.0.0", - "private": true, - "description": "Description for peoplify", - "license": "UNLICENSED", - "scripts": { - "app:start": "./mvnw", - "backend:build-cache": "./mvnw dependency:go-offline", - "backend:debug": "./mvnw -Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000\"", - "backend:doc:test": "./mvnw -ntp javadoc:javadoc --batch-mode", - "backend:info": "./mvnw -ntp enforcer:display-info --batch-mode", - "backend:nohttp:test": "./mvnw -ntp checkstyle:check --batch-mode", - "backend:start": "./mvnw", - "backend:unit:test": "./mvnw -ntp verify --batch-mode -Dlogging.level.ROOT=OFF -Dlogging.level.org.zalando=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.io.github.shuoros.peoplify=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF", - "ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment", - "ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true", - "ci:e2e:prepare": "npm run ci:e2e:prepare:docker", - "ci:e2e:prepare:docker": "npm run docker:db:up && npm run docker:others:up && docker ps -a", - "preci:e2e:server:start": "npm run docker:db:await --if-present && npm run docker:others:await --if-present", - "ci:e2e:server:start": "java -jar target/e2e.$npm_package_config_packaging --spring.profiles.active=e2e,$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.org.zalando=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.io.github.shuoros.peoplify=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR", - "ci:e2e:teardown": "npm run ci:e2e:teardown:docker", - "ci:e2e:teardown:docker": "npm run docker:db:down --if-present && npm run docker:others:down && docker ps -a", - "docker:app:up": "docker-compose -f src/main/docker/app.yml up -d", - "docker:db:down": "docker-compose -f src/main/docker/postgresql.yml down -v", - "docker:db:up": "docker-compose -f src/main/docker/postgresql.yml up -d", - "docker:others:await": "", - "docker:others:down": "", - "predocker:others:up": "", - "docker:others:up": "", - "java:docker": "./mvnw -ntp verify -DskipTests -Pprod jib:dockerBuild", - "java:docker:arm64": "npm run java:docker -- -Djib-maven-plugin.architecture=arm64", - "java:docker:dev": "npm run java:docker -- -Pdev,webapp", - "java:docker:prod": "npm run java:docker -- -Pprod", - "java:jar": "./mvnw -ntp verify -DskipTests --batch-mode", - "java:jar:dev": "npm run java:jar -- -Pdev,webapp", - "java:jar:prod": "npm run java:jar -- -Pprod", - "java:war": "./mvnw -ntp verify -DskipTests --batch-mode -Pwar", - "java:war:dev": "npm run java:war -- -Pdev,webapp", - "java:war:prod": "npm run java:war -- -Pprod", - "prepare": "husky install", - "prettier:check": "prettier --check \"{,src/**/,.blueprint/**/}*.{md,json,yml,html,java}\"", - "prettier:format": "prettier --write \"{,src/**/,.blueprint/**/}*.{md,json,yml,html,java}\"" - }, - "config": { - "backend_port": "8080", - "default_environment": "prod", - "packaging": "jar" - }, - "devDependencies": { - "generator-jhipster": "7.9.3", - "husky": "7.0.4", - "lint-staged": "13.0.3", - "prettier": "2.7.1", - "prettier-plugin-java": "1.6.2", - "prettier-plugin-packagejson": "2.2.18" - }, - "engines": { - "node": ">=16.17.0" - }, - "cacheDirectories": [ - "node_modules" - ] -} diff --git a/pom.xml b/pom.xml index ae0c3c4..395698c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,1077 +1,54 @@ - - 4.0.0 + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.2 + + + io.github.shuoros + peoplify + 0.0.1-SNAPSHOT + peoplify + Demo project for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + - io.github.shuoros.peoplify - peoplify - 0.0.1-SNAPSHOT - jar - Peoplify - Description for peoplify - - - - - - - - - - - - - - 3.2.5 - 11 - UTF-8 - UTF-8 - yyyyMMddHHmmss - ${java.version} - ${java.version} - io.github.shuoros.peoplify.PeoplifyApp - -Djava.security.egd=file:/dev/./urandom -Xmx1G - jdt_apt - false - - - - - - - - 7.9.3 - - 2.7.3 - - 5.6.10.Final - - 4.15.0 - 4.15.0 - 2.0.1.Final - 4.0.0 - 0.22.0 - 1.5.2.Final - - 3.2.0 - 3.12.1 - 3.10.1 - 3.4.1 - 2.10 - 3.1.0 - 3.0.0-M7 - 3.2.2 - 2.2.1 - 3.3.0 - 3.0.0-M7 - 3.3.2 - 3.1.2 - 10.3.2 - 0.0.10 - 5.0.0 - 2.4.0 - 0.8.8 - 3.2.1 - eclipse-temurin:11-jre-focal - amd64 - 1.0.0 - 1.1.0 - 3.9.1.2184 - - - - - - - - tech.jhipster - jhipster-dependencies - ${jhipster-dependencies.version} - pom - import - - - - - - - - tech.jhipster - jhipster-framework - - - javax.annotation - javax.annotation-api - - - org.springframework.boot - spring-boot-starter-cache - - - com.fasterxml.jackson.module - jackson-module-jaxb-annotations - - - com.fasterxml.jackson.datatype - jackson-datatype-hibernate5 - - - com.fasterxml.jackson.datatype - jackson-datatype-hppc - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.springdoc - springdoc-openapi-webmvc-core - - - com.zaxxer - HikariCP - - - org.apache.commons - commons-lang3 - - - javax.cache - cache-api - - - org.ehcache - ehcache - - - org.hibernate - hibernate-jcache - - - org.hibernate - hibernate-jpamodelgen - provided - - - org.hibernate - hibernate-core - - - org.hibernate.validator - hibernate-validator - - - org.liquibase - liquibase-core - - ${liquibase.version} - - - org.mapstruct - mapstruct - ${mapstruct.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - provided - - - org.springframework.boot - spring-boot-configuration-processor - provided - - - org.springframework.boot - spring-boot-loader-tools - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.testcontainers - jdbc - test - - - org.springframework.boot - spring-boot-starter-logging - - - org.springframework.boot - spring-boot-starter-mail - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-test - test - - - org.springframework.security - spring-security-test - test - - - com.tngtech.archunit - archunit-junit5-api - ${archunit-junit5.version} - test - - - - - com.tngtech.archunit - archunit-junit5-engine - ${archunit-junit5.version} - test - - - org.zalando - problem-spring-web - - - org.springframework.boot - spring-boot-starter-undertow - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - runtime - - - io.jsonwebtoken - jjwt-jackson - runtime - - - - org.springframework.security - spring-security-data - - - io.micrometer - micrometer-registry-prometheus - - - io.dropwizard.metrics - metrics-core - - - - - - spring-boot:run - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - org.apache.maven.plugins - maven-eclipse-plugin - - - org.apache.maven.plugins - maven-enforcer-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - - - org.apache.maven.plugins - maven-idea-plugin - - - org.apache.maven.plugins - maven-resources-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.jacoco - jacoco-maven-plugin - - - org.sonarsource.scanner.maven - sonar-maven-plugin - - - org.springframework.boot - spring-boot-maven-plugin - - - com.google.cloud.tools - jib-maven-plugin - - - org.codehaus.mojo - properties-maven-plugin - - - org.gaul - modernizer-maven-plugin - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - com.puppycrawl.tools - checkstyle - ${checkstyle.version} - - - io.spring.nohttp - nohttp-checkstyle - ${nohttp-checkstyle.version} - - - - checkstyle.xml - pom.xml,README.md - .git/**/*,target/**/*,node_modules/**/*,node/**/* - ./ - - - - - check - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${java.version} - ${java.version} - - - org.springframework.boot - spring-boot-configuration-processor - ${spring-boot.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - - org.hibernate - hibernate-jpamodelgen - ${hibernate.version} - - - org.glassfish.jaxb - jaxb-runtime - ${jaxb-runtime.version} - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - ${maven.compiler.source} - - - - org.apache.maven.plugins - maven-war-plugin - ${maven-war-plugin.version} - - - default-war - - war - - package - - - - WEB-INF/**,META-INF/** - false - - - - org.codehaus.mojo - properties-maven-plugin - ${properties-maven-plugin.version} - - - initialize - - read-project-properties - - - - sonar-project.properties - - - - - - - io.github.git-commit-id - git-commit-id-maven-plugin - ${git-commit-id-plugin.version} - - - - revision - - - - - false - false - true - - ^git.commit.id.abbrev$ - ^git.commit.id.describe$ - ^git.branch$ - - - - - org.gaul - modernizer-maven-plugin - ${modernizer-maven-plugin.version} - - - modernizer - package - - modernizer - - - - - ${java.version} - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - pre-unit-tests - - prepare-agent - - - - - post-unit-test - test - - report - - - - pre-integration-tests - - prepare-agent-integration - - - - - post-integration-tests - post-integration-test - - report-integration - - - - - - com.google.cloud.tools - jib-maven-plugin - ${jib-maven-plugin.version} - - - ${jib-maven-plugin.image} - - - ${jib-maven-plugin.architecture} - linux - - - - - peoplify:latest - - - - bash - - /entrypoint.sh - - - 8080 - - - ALWAYS - 0 - - USE_CURRENT_TIMESTAMP - 1000 - - - src/main/docker/jib - - - /entrypoint.sh - 755 - - - - - - - maven-clean-plugin - ${maven-clean-plugin.version} - - - maven-site-plugin - ${maven-site-plugin.version} - - - org.apache.maven.plugins - maven-eclipse-plugin - ${maven-eclipse-plugin.version} - - true - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - ${maven-enforcer-plugin.version} - - - enforce-versions - - enforce - - - - enforce-dependencyConvergence - - - - - false - - - enforce - - - - - - - You are running an older version of Maven. JHipster requires at least Maven ${maven.version} - [${maven.version},) - - - You are running an incompatible version of Java. JHipster supports JDK 11 to 18. - [11,12),[12,13),[13,14),[14,15),[15,16),[16,17),[17,18),[18,19) - - - - - - org.apache.maven.plugins - maven-idea-plugin - ${maven-idea-plugin.version} - - node_modules - - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - - default-resources - validate - - copy-resources - - - ${project.build.directory}/classes - false - - # - - - - src/main/resources/ - true - - config/*.yml - - - - src/main/resources/ - false - - config/*.yml - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - alphabetical - - **/*IT* - **/*IntTest* - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin.version} - - - org.sonarsource.scanner.maven - sonar-maven-plugin - ${sonar-maven-plugin.version} - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - repackage - - - - - ${start-class} - true - - - - - - - - - - - no-liquibase - - ,no-liquibase - - - - api-docs - - ,api-docs - - - - tls - - ,tls - - - - dev - - true - - - - org.springframework.boot - spring-boot-devtools - true - - - org.postgresql - postgresql - - - org.testcontainers - postgresql - test - - - - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - ${maven-failsafe-plugin.version} - - - ${project.build.outputDirectory} - - alphabetical - - **/*IT* - **/*IntTest* - - @{argLine} -Dspring.profiles.active=testdev - - - - integration-test - - integration-test - - - - verify - - verify - - - - - - org.liquibase - liquibase-maven-plugin - ${liquibase.version} - - ${project.basedir}/src/main/resources/config/liquibase/master.xml - ${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml - org.postgresql.Driver - jdbc:postgresql://localhost:5432/peoplify - - peoplify - - hibernate:spring:io.github.shuoros.peoplify.domain?dialect=tech.jhipster.domain.util.FixedPostgreSQL10Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - !test - - - - org.liquibase - liquibase-core - ${liquibase.version} - - - org.liquibase.ext - liquibase-hibernate5 - ${liquibase-hibernate5.version} - - - org.springframework.boot - spring-boot-starter-data-jpa - ${spring-boot.version} - - - javax.validation - validation-api - ${validation-api.version} - - - tech.jhipster - jhipster-framework - ${jhipster-dependencies.version} - - - - - - - - - dev${profile.tls}${profile.no-liquibase} - - - - prod - - - org.testcontainers - postgresql - test - - - org.postgresql - postgresql - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - ${maven-failsafe-plugin.version} - - - ${project.build.outputDirectory} - - alphabetical - - **/*IT* - **/*IntTest* - - @{argLine} -Dspring.profiles.active=testprod - - - - integration-test - - integration-test - - - - verify - - verify - - - - - - org.liquibase - liquibase-maven-plugin - ${liquibase.version} - - ${project.basedir}/src/main/resources/config/liquibase/master.xml - ${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml - org.postgresql.Driver - jdbc:postgresql://localhost:5432/peoplify - - peoplify - - hibernate:spring:io.github.shuoros.peoplify.domain?dialect=tech.jhipster.domain.util.FixedPostgreSQL10Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - true - debug - !test - - - - org.liquibase - liquibase-core - ${liquibase.version} - - - org.liquibase.ext - liquibase-hibernate5 - ${liquibase-hibernate5.version} - - - org.springframework.boot - spring-boot-starter-data-jpa - ${spring-boot.version} - - - javax.validation - validation-api - ${validation-api.version} - - - tech.jhipster - jhipster-framework - ${jhipster-dependencies.version} - - - - - - - - maven-clean-plugin - - - - target/classes/static/ - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - build-info - - - - - - io.github.git-commit-id - git-commit-id-maven-plugin - - - - - - prod${profile.api-docs}${profile.tls}${profile.e2e}${profile.no-liquibase} - - - - war - - - - org.apache.maven.plugins - maven-war-plugin - - - - - - - IDE - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - org.hibernate - hibernate-jpamodelgen - - - - - - eclipse - - - m2e.version - - - - - - org.springframework.boot - spring-boot-starter-undertow - - - - - - - - org.eclipse.m2e - lifecycle-mapping - ${lifecycle-mapping.version} - - - - - - org.jacoco - - jacoco-maven-plugin - - - ${jacoco-maven-plugin.version} - - - prepare-agent - - - - - - - - - - - - - - - - diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 64dbc5f..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,32 +0,0 @@ -sonar.projectKey=peoplify -sonar.projectName=peoplify generated by jhipster - -# Typescript tests files must be inside sources and tests, othewise `INFO: Test execution data ignored for 80 unknown files, including:` is shown. -sonar.sources=src -sonar.tests=src -sonar.host.url=http://localhost:9001 - -sonar.test.inclusions=src/test/**/*.* -sonar.coverage.jacoco.xmlReportPaths=target/site/**/jacoco*.xml -sonar.java.codeCoveragePlugin=jacoco -sonar.junit.reportPaths=target/surefire-reports,target/failsafe-reports - -sonar.sourceEncoding=UTF-8 -sonar.exclusions=src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.js, target/classes/static/**/*.* - -sonar.issue.ignore.multicriteria=S3437,S4502,S4684,S5145,UndocumentedApi -# Rule https://rules.sonarsource.com/java/RSPEC-3437 is ignored, as a JPA-managed field cannot be transient -sonar.issue.ignore.multicriteria.S3437.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.S3437.ruleKey=squid:S3437 -# Rule https://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack -sonar.issue.ignore.multicriteria.S4502.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.S4502.ruleKey=java:S4502 -# Rule https://rules.sonarsource.com/java/RSPEC-4684 -sonar.issue.ignore.multicriteria.S4684.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.S4684.ruleKey=java:S4684 -# Rule https://rules.sonarsource.com/java/RSPEC-5145 log filter is applied -sonar.issue.ignore.multicriteria.S5145.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.S5145.ruleKey=javasecurity:S5145 -# Rule https://rules.sonarsource.com/java/RSPEC-1176 is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory -sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey=squid:UndocumentedApi diff --git a/src/main/docker/app.yml b/src/main/docker/app.yml deleted file mode 100644 index 7b756c2..0000000 --- a/src/main/docker/app.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -version: '3.8' -services: - peoplify-app: - image: peoplify - environment: - - _JAVA_OPTIONS=-Xmx512m -Xms256m - - SPRING_PROFILES_ACTIVE=prod,api-docs - - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true - - SPRING_DATASOURCE_URL=jdbc:postgresql://peoplify-postgresql:5432/peoplify - - SPRING_LIQUIBASE_URL=jdbc:postgresql://peoplify-postgresql:5432/peoplify - - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:8080:8080 - peoplify-postgresql: - image: postgres:14.5 - # volumes: - # - ~/volumes/jhipster/peoplify/postgresql/:/var/lib/postgresql/data/ - environment: - - POSTGRES_USER=peoplify - - POSTGRES_PASSWORD= - - POSTGRES_HOST_AUTH_METHOD=trust - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:5432:5432 diff --git a/src/main/docker/central-server-config/README.md b/src/main/docker/central-server-config/README.md deleted file mode 100644 index c3e9cfd..0000000 --- a/src/main/docker/central-server-config/README.md +++ /dev/null @@ -1 +0,0 @@ -# Central configuration sources details diff --git a/src/main/docker/grafana/provisioning/dashboards/JVM.json b/src/main/docker/grafana/provisioning/dashboards/JVM.json deleted file mode 100644 index 5104abc..0000000 --- a/src/main/docker/grafana/provisioning/dashboards/JVM.json +++ /dev/null @@ -1,3778 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "limit": 100, - "name": "Annotations & Alerts", - "showIn": 0, - "type": "dashboard" - }, - { - "datasource": "Prometheus", - "enable": true, - "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0", - "iconColor": "rgba(255, 96, 96, 1)", - "name": "Restart Detection", - "showIn": 0, - "step": "1m", - "tagKeys": "restart-tag", - "textFormat": "uptime reset", - "titleFormat": "Restart" - } - ] - }, - "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)", - "editable": true, - "gnetId": 4701, - "graphTooltip": 1, - "iteration": 1553765841423, - "links": [], - "panels": [ - { - "content": "\n# Acknowledgments\n\nThank you to [Michael Weirauch](https://twitter.com/emwexx) for creating this dashboard: see original JVM (Micrometer) dashboard at [https://grafana.com/dashboards/4701](https://grafana.com/dashboards/4701)\n\n\n\n", - "gridPos": { - "h": 3, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 141, - "links": [], - "mode": "markdown", - "timeFrom": null, - "timeShift": null, - "title": "Acknowledgments", - "type": "text" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 3 - }, - "id": 125, - "panels": [], - "repeat": null, - "title": "Quick Facts", - "type": "row" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - "datasource": "Prometheus", - "decimals": 1, - "editable": true, - "error": false, - "format": "s", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 0, - "y": 4 - }, - "height": "", - "id": 63, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "70%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "", - "metric": "", - "refId": "A", - "step": 14400 - } - ], - "thresholds": "", - "title": "Uptime", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - "datasource": "Prometheus", - "decimals": null, - "editable": true, - "error": false, - "format": "dateTimeAsIso", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 6, - "y": 4 - }, - "height": "", - "id": 92, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "70%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "", - "metric": "", - "refId": "A", - "step": 14400 - } - ], - "thresholds": "", - "title": "Start time", - "type": "singlestat", - "valueFontSize": "70%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], - "datasource": "Prometheus", - "decimals": 2, - "editable": true, - "error": false, - "format": "percent", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 12, - "y": 4 - }, - "id": 65, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "70%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "", - "refId": "A", - "step": 14400 - } - ], - "thresholds": "70,90", - "title": "Heap used", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], - "datasource": "Prometheus", - "decimals": 2, - "editable": true, - "error": false, - "format": "percent", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 18, - "y": 4 - }, - "id": 75, - "interval": null, - "links": [], - "mappingType": 2, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "70%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - }, - { - "from": "-99999999999999999999999999999999", - "text": "N/A", - "to": "0" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "", - "refId": "A", - "step": 14400 - } - ], - "thresholds": "70,90", - "title": "Non-Heap used", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - }, - { - "op": "=", - "text": "x", - "value": "" - } - ], - "valueName": "current" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 7 - }, - "id": 126, - "panels": [], - "repeat": null, - "title": "I/O Overview", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 8 - }, - "id": 111, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "HTTP", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Rate", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": null, - "format": "ops", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": { - "HTTP": "#890f02", - "HTTP - 5xx": "#bf1b00" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 8 - }, - "id": 112, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "HTTP - 5xx", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Errors", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": null, - "format": "ops", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 8 - }, - "id": 113, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "HTTP - AVG", - "refId": "A" - }, - { - "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "HTTP - MAX", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 15 - }, - "id": 127, - "panels": [], - "repeat": null, - "title": "JVM Memory", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 16 - }, - "id": 24, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "committed", - "refId": "B", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "max", - "refId": "C", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "JVM Heap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 16 - }, - "id": 25, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "committed", - "refId": "B", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "max", - "refId": "C", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "JVM Non-Heap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 16 - }, - "id": 26, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "committed", - "refId": "B", - "step": 2400 - }, - { - "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "max", - "refId": "C", - "step": 2400 - }, - { - "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": true, - "intervalFactor": 2, - "legendFormat": "vss", - "metric": "", - "refId": "D", - "step": 2400 - }, - { - "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "rss", - "refId": "E", - "step": 2400 - }, - { - "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "pss", - "refId": "F", - "step": 2400 - }, - { - "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "swap", - "refId": "G", - "step": 2400 - }, - { - "expr": "process_memory_swappss_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "swappss", - "refId": "H", - "step": 2400 - }, - { - "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "phys (pss+swap)", - "refId": "I", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "JVM Total", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": "", - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 128, - "panels": [], - "repeat": null, - "title": "JVM Misc", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 24 - }, - "id": 106, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "system", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "process", - "refId": "B" - }, - { - "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[1h])", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "process-1h", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "CPU", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 1, - "format": "percentunit", - "label": "", - "logBase": 1, - "max": "1", - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 24 - }, - "id": 93, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "system-1m", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "", - "format": "time_series", - "intervalFactor": 2, - "refId": "B" - }, - { - "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "cpu", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Load", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 1, - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 24 - }, - "id": 32, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_threads_live{application=\"$application\", instance=\"$instance\"} or jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "live", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "jvm_threads_daemon{application=\"$application\", instance=\"$instance\"} or jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "daemon", - "metric": "", - "refId": "B", - "step": 2400 - }, - { - "expr": "jvm_threads_peak{application=\"$application\", instance=\"$instance\"} or jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "peak", - "refId": "C", - "step": 2400 - }, - { - "expr": "process_threads{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "process", - "refId": "D", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Threads", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 0, - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": { - "blocked": "#bf1b00", - "new": "#fce2de", - "runnable": "#7eb26d", - "terminated": "#511749", - "timed-waiting": "#c15c17", - "waiting": "#eab839" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 24 - }, - "id": 124, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{state}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Thread States", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": { - "debug": "#1F78C1", - "error": "#BF1B00", - "info": "#508642", - "trace": "#6ED0E0", - "warn": "#EAB839" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 18, - "x": 0, - "y": 31 - }, - "height": "", - "id": 91, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "hideEmpty": false, - "hideZero": false, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": true, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "error", - "yaxis": 1 - }, - { - "alias": "warn", - "yaxis": 1 - } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{level}}", - "metric": "", - "refId": "A", - "step": 1200 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Log Events (1m)", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 0, - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 31 - }, - "id": 61, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "process_open_fds{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": false, - "intervalFactor": 2, - "legendFormat": "open", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "process_max_fds{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": false, - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "B", - "step": 2400 - }, - { - "expr": "process_files_open{application=\"$application\", instance=\"$instance\"} or process_files_open_files{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "open", - "refId": "C" - }, - { - "expr": "process_files_max{application=\"$application\", instance=\"$instance\"} or process_files_max_files{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "max", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "File Descriptors", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 0, - "format": "short", - "label": null, - "logBase": 10, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 38 - }, - "id": 129, - "panels": [], - "repeat": "persistence_counts", - "title": "JVM Memory Pools (Heap)", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 39 - }, - "id": 3, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "jvm_memory_pool_heap", - "scopedVars": { - "jvm_memory_pool_heap": { - "selected": false, - "text": "PS Eden Space", - "value": "PS Eden Space" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_heap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 39 - }, - "id": 134, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1553765841423, - "repeatPanelId": 3, - "scopedVars": { - "jvm_memory_pool_heap": { - "selected": false, - "text": "PS Old Gen", - "value": "PS Old Gen" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_heap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 39 - }, - "id": 135, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1553765841423, - "repeatPanelId": 3, - "scopedVars": { - "jvm_memory_pool_heap": { - "selected": false, - "text": "PS Survivor Space", - "value": "PS Survivor Space" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_heap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 46 - }, - "id": 130, - "panels": [], - "repeat": null, - "title": "JVM Memory Pools (Non-Heap)", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 47 - }, - "id": 78, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "jvm_memory_pool_nonheap", - "scopedVars": { - "jvm_memory_pool_nonheap": { - "selected": false, - "text": "Metaspace", - "value": "Metaspace" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_nonheap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 47 - }, - "id": 136, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1553765841423, - "repeatPanelId": 78, - "scopedVars": { - "jvm_memory_pool_nonheap": { - "selected": false, - "text": "Compressed Class Space", - "value": "Compressed Class Space" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_nonheap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 47 - }, - "id": 137, - "legend": { - "alignAsTable": false, - "avg": false, - "current": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "maxPerRow": 3, - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1553765841423, - "repeatPanelId": 78, - "scopedVars": { - "jvm_memory_pool_nonheap": { - "selected": false, - "text": "Code Cache", - "value": "Code Cache" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 1800 - }, - { - "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "commited", - "metric": "", - "refId": "B", - "step": 1800 - }, - { - "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "max", - "metric": "", - "refId": "C", - "step": 1800 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "$jvm_memory_pool_nonheap", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["mbytes", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 54 - }, - "id": 131, - "panels": [], - "repeat": null, - "title": "Garbage Collection", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 55 - }, - "id": 98, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", - "format": "time_series", - "hide": false, - "intervalFactor": 2, - "legendFormat": "{{action}} ({{cause}})", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Collections", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ops", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 8, - "y": 55 - }, - "id": 101, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", - "format": "time_series", - "hide": false, - "instant": false, - "intervalFactor": 1, - "legendFormat": "avg {{action}} ({{cause}})", - "refId": "A" - }, - { - "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "hide": false, - "instant": false, - "intervalFactor": 1, - "legendFormat": "max {{action}} ({{cause}})", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Pause Durations", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 16, - "y": 55 - }, - "id": 99, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "allocated", - "refId": "A" - }, - { - "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "promoted", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Allocated/Promoted", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 62 - }, - "id": 132, - "panels": [], - "repeat": null, - "title": "Classloading", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 63 - }, - "id": 37, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_classes_loaded{application=\"$application\", instance=\"$instance\"} or jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "loaded", - "metric": "", - "refId": "A", - "step": 1200 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Classes loaded", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 63 - }, - "id": 38, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "delta(jvm_classes_loaded{application=\"$application\",instance=\"$instance\"}[5m]) or delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[5m])", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "delta", - "metric": "", - "refId": "A", - "step": 1200 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Class delta (5m)", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["ops", "short"], - "yaxes": [ - { - "decimals": null, - "format": "short", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 70 - }, - "id": 133, - "panels": [], - "repeat": null, - "title": "Buffer Pools", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 71 - }, - "id": 33, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "capacity", - "metric": "", - "refId": "B", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Direct Buffers", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 71 - }, - "id": 83, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"direct\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"direct\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "count", - "metric": "", - "refId": "A", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Direct Buffers", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 0, - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 71 - }, - "id": 85, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "used", - "metric": "", - "refId": "A", - "step": 2400 - }, - { - "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "capacity", - "metric": "", - "refId": "B", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Mapped Buffers", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "format": "bytes", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "editable": true, - "error": false, - "fill": 1, - "grid": { - "leftLogBase": 1, - "leftMax": null, - "leftMin": null, - "rightLogBase": 1, - "rightMax": null, - "rightMin": null - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 71 - }, - "id": 84, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "paceLength": 10, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"mapped\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "count", - "metric": "", - "refId": "A", - "step": 2400 - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Mapped Buffers", - "tooltip": { - "msResolution": false, - "shared": true, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "x-axis": true, - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "y-axis": true, - "y_formats": ["short", "short"], - "yaxes": [ - { - "decimals": 0, - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": 0, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "10s", - "schemaVersion": 18, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": { - "text": "test", - "value": "test" - }, - "datasource": "Prometheus", - "definition": "", - "hide": 0, - "includeAll": false, - "label": "Application", - "multi": false, - "name": "application", - "options": [], - "query": "label_values(application)", - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allFormat": "glob", - "allValue": null, - "current": { - "text": "localhost:8080", - "value": "localhost:8080" - }, - "datasource": "Prometheus", - "definition": "", - "hide": 0, - "includeAll": false, - "label": "Instance", - "multi": false, - "multiFormat": "glob", - "name": "instance", - "options": [], - "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)", - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allFormat": "glob", - "allValue": null, - "current": { - "text": "All", - "value": "$__all" - }, - "datasource": "Prometheus", - "definition": "", - "hide": 0, - "includeAll": true, - "label": "JVM Memory Pools Heap", - "multi": false, - "multiFormat": "glob", - "name": "jvm_memory_pool_heap", - "options": [], - "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allFormat": "glob", - "allValue": null, - "current": { - "text": "All", - "value": "$__all" - }, - "datasource": "Prometheus", - "definition": "", - "hide": 0, - "includeAll": true, - "label": "JVM Memory Pools Non-Heap", - "multi": false, - "multiFormat": "glob", - "name": "jvm_memory_pool_nonheap", - "options": [], - "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 2, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now-30m", - "to": "now" - }, - "timepicker": { - "now": true, - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "browser", - "title": "JVM (Micrometer)", - "uid": "Ud1CFe3iz", - "version": 1 -} diff --git a/src/main/docker/grafana/provisioning/dashboards/dashboard.yml b/src/main/docker/grafana/provisioning/dashboards/dashboard.yml deleted file mode 100644 index 4817a83..0000000 --- a/src/main/docker/grafana/provisioning/dashboards/dashboard.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: 1 - -providers: - - name: 'Prometheus' - orgId: 1 - folder: '' - type: file - disableDeletion: false - editable: true - options: - path: /etc/grafana/provisioning/dashboards diff --git a/src/main/docker/grafana/provisioning/datasources/datasource.yml b/src/main/docker/grafana/provisioning/datasources/datasource.yml deleted file mode 100644 index 57b2bb3..0000000 --- a/src/main/docker/grafana/provisioning/datasources/datasource.yml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: 1 - -# list of datasources that should be deleted from the database -deleteDatasources: - - name: Prometheus - orgId: 1 - -# list of datasources to insert/update depending -# whats available in the database -datasources: - # name of the datasource. Required - - name: Prometheus - # datasource type. Required - type: prometheus - # access mode. direct or proxy. Required - access: proxy - # org id. will default to orgId 1 if not specified - orgId: 1 - # url - # On MacOS, replace localhost by host.docker.internal - url: http://localhost:9090 - # database password, if used - password: - # database user, if used - user: - # database name, if used - database: - # enable/disable basic auth - basicAuth: false - # basic auth username - basicAuthUser: admin - # basic auth password - basicAuthPassword: admin - # enable/disable with credentials headers - withCredentials: - # mark as default datasource. Max one per org - isDefault: true - # fields that will be converted to json and stored in json_data - jsonData: - graphiteVersion: '1.1' - tlsAuth: false - tlsAuthWithCACert: false - # json object of data that will be encrypted. - secureJsonData: - tlsCACert: '...' - tlsClientCert: '...' - tlsClientKey: '...' - version: 1 - # allow users to edit datasources from the UI. - editable: true diff --git a/src/main/docker/jhipster-control-center.yml b/src/main/docker/jhipster-control-center.yml deleted file mode 100644 index ad187ac..0000000 --- a/src/main/docker/jhipster-control-center.yml +++ /dev/null @@ -1,52 +0,0 @@ -## How to use JHCC docker compose -# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal -# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file. -### Discovery mode -# JHCC support 3 kinds of discovery mode: Consul, Eureka and static -# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of this values: consul,eureka,static -### Discovery properties -# According to the discovery mode choose as Spring profile, you have to set the right properties -# please note that current properties are set to run JHCC with default values, personalize them if needed -# and remove those from other modes. You can only have one mode active. -#### Eureka -# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/ -#### Consul -# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal -# - SPRING_CLOUD_CONSUL_PORT=8500 -#### Static -# Add instances to "MyApp" -# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081 -# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082 -# Or add a new application named MyNewApp -# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080 -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production - -#### IMPORTANT -# If you choose Consul or Eureka mode: -# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them. -# This is required because JHCC need to communicate with Consul or Eureka. -# - In Consul mode, the ports are in the consul.yml file. -# - In Eureka mode, the ports are in the jhipster-registry.yml file. - -version: '3.8' -services: - jhipster-control-center: - image: 'jhipster/jhipster-control-center:v0.5.0' - command: - - /bin/sh - - -c - # Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes - - echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar - environment: - - _JAVA_OPTIONS=-Xmx512m -Xms256m - - SPRING_PROFILES_ACTIVE=prod,api-docs,static - - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application - - SPRING_SECURITY_USER_PASSWORD=admin - # The token should have the same value than the one declared in you Spring configuration under the jhipster.security.authentication.jwt.base64-secret configuration's entry - - JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET=ZDUxMTM5YWRmNjBiZDhmOTJlMTkxNWVmM2VlOTJhYTZiMjEyYjZiZDJlOGRhMmE5NmJjODFkYTI1MjQ2MzdjODdhNjk3OTg4YTA2MDk0Mjk1NDVkZWU2MGFlYzFmOTEwNzRlODVkZTI4NjVjNjM4YTE1ODY5NzA4ZmZhNzE5NDk= - - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_PEOPLIFY_0_URI=http://host.docker.internal:8080 - - LOGGING_FILE_NAME=/tmp/jhipster-control-center.log - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:7419:7419 diff --git a/src/main/docker/jib/entrypoint.sh b/src/main/docker/jib/entrypoint.sh deleted file mode 100644 index c28d374..0000000 --- a/src/main/docker/jib/entrypoint.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP} - -# usage: file_env VAR [DEFAULT] -# ie: file_env 'XYZ_DB_PASSWORD' 'example' -# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of -# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) -file_env() { - local var="$1" - local fileVar="${var}_FILE" - local def="${2:-}" - if [[ ${!var:-} && ${!fileVar:-} ]]; then - echo >&2 "error: both $var and $fileVar are set (but are exclusive)" - exit 1 - fi - local val="$def" - if [[ ${!var:-} ]]; then - val="${!var}" - elif [[ ${!fileVar:-} ]]; then - val="$(< "${!fileVar}")" - fi - - if [[ -n $val ]]; then - export "$var"="$val" - fi - - unset "$fileVar" -} - -file_env 'SPRING_DATASOURCE_URL' -file_env 'SPRING_DATASOURCE_USERNAME' -file_env 'SPRING_DATASOURCE_PASSWORD' -file_env 'SPRING_LIQUIBASE_URL' -file_env 'SPRING_LIQUIBASE_USER' -file_env 'SPRING_LIQUIBASE_PASSWORD' -file_env 'JHIPSTER_REGISTRY_PASSWORD' - -exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "io.github.shuoros.peoplify.PeoplifyApp" "$@" diff --git a/src/main/docker/monitoring.yml b/src/main/docker/monitoring.yml deleted file mode 100644 index 76ca9bc..0000000 --- a/src/main/docker/monitoring.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -version: '3.8' -services: - peoplify-prometheus: - image: prom/prometheus:v2.38.0 - volumes: - - ./prometheus/:/etc/prometheus/ - command: - - '--config.file=/etc/prometheus/prometheus.yml' - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:9090:9090 - # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and - # grafana/provisioning/datasources/datasource.yml - network_mode: 'host' # to test locally running service - peoplify-grafana: - image: grafana/grafana:9.1.0 - volumes: - - ./grafana/provisioning/:/etc/grafana/provisioning/ - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_USERS_ALLOW_SIGN_UP=false - - GF_INSTALL_PLUGINS=grafana-piechart-panel - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:3000:3000 - # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and - # grafana/provisioning/datasources/datasource.yml - network_mode: 'host' # to test locally running service diff --git a/src/main/docker/postgresql.yml b/src/main/docker/postgresql.yml deleted file mode 100644 index ed5616c..0000000 --- a/src/main/docker/postgresql.yml +++ /dev/null @@ -1,15 +0,0 @@ -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -version: '3.8' -services: - peoplify-postgresql: - image: postgres:14.5 - # volumes: - # - ~/volumes/jhipster/peoplify/postgresql/:/var/lib/postgresql/data/ - environment: - - POSTGRES_USER=peoplify - - POSTGRES_PASSWORD= - - POSTGRES_HOST_AUTH_METHOD=trust - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:5432:5432 diff --git a/src/main/docker/prometheus/prometheus.yml b/src/main/docker/prometheus/prometheus.yml deleted file mode 100644 index b370a2f..0000000 --- a/src/main/docker/prometheus/prometheus.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Sample global config for monitoring JHipster applications -global: - scrape_interval: 15s # By default, scrape targets every 15 seconds. - evaluation_interval: 15s # By default, scrape targets every 15 seconds. - # scrape_timeout is set to the global default (10s). - - # Attach these labels to any time series or alerts when communicating with - # external systems (federation, remote storage, Alertmanager). - external_labels: - monitor: 'jhipster' - -# A scrape configuration containing exactly one endpoint to scrape: -# Here it's Prometheus itself. -scrape_configs: - # The job name is added as a label `job=` to any timeseries scraped from this config. - - job_name: 'prometheus' - - # Override the global default and scrape targets from this job every 5 seconds. - scrape_interval: 5s - - # scheme defaults to 'http' enable https in case your application is server via https - #scheme: https - # basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details - #basic_auth: - # username: admin - # password: admin - metrics_path: /management/prometheus - static_configs: - - targets: - # On MacOS, replace localhost by host.docker.internal - - localhost:8080 diff --git a/src/main/docker/sonar.yml b/src/main/docker/sonar.yml deleted file mode 100644 index 8524ca2..0000000 --- a/src/main/docker/sonar.yml +++ /dev/null @@ -1,13 +0,0 @@ -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -version: '3.8' -services: - peoplify-sonar: - image: sonarqube:9.6.0-community - # Authentication is turned off for out of the box experience while trying out SonarQube - # For real use cases delete sonar.forceAuthentication variable or set sonar.forceAuthentication=true - environment: - - sonar.forceAuthentication=false - # If you want to expose these ports outside your dev PC, - # remove the "127.0.0.1:" prefix - ports: - - 127.0.0.1:9001:9000 diff --git a/src/main/docker/zipkin.yml b/src/main/docker/zipkin.yml deleted file mode 100644 index 07938b9..0000000 --- a/src/main/docker/zipkin.yml +++ /dev/null @@ -1,7 +0,0 @@ -# This configuration is intended for development purpose, it's **your** responsibility to harden it for production -version: '3.8' -services: - zipkin: - image: openzipkin/zipkin:2.23 - ports: - - 127.0.0.1:9411:9411 diff --git a/src/main/java/io/github/shuoros/peoplify/ApplicationWebXml.java b/src/main/java/io/github/shuoros/peoplify/ApplicationWebXml.java deleted file mode 100644 index d549c2e..0000000 --- a/src/main/java/io/github/shuoros/peoplify/ApplicationWebXml.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.shuoros.peoplify; - -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import tech.jhipster.config.DefaultProfileUtil; - -/** - * This is a helper Java class that provides an alternative to creating a {@code web.xml}. - * This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc. - */ -public class ApplicationWebXml extends SpringBootServletInitializer { - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - // set a default to use when no profile is configured. - DefaultProfileUtil.addDefaultProfile(application.application()); - return application.sources(PeoplifyApp.class); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/GeneratedByJHipster.java b/src/main/java/io/github/shuoros/peoplify/GeneratedByJHipster.java deleted file mode 100644 index f3de145..0000000 --- a/src/main/java/io/github/shuoros/peoplify/GeneratedByJHipster.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.shuoros.peoplify; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javax.annotation.Generated; - -@Generated(value = "JHipster", comments = "Generated by JHipster 7.9.3") -@Retention(RetentionPolicy.SOURCE) -@Target({ ElementType.TYPE }) -public @interface GeneratedByJHipster { -} diff --git a/src/main/java/io/github/shuoros/peoplify/PeoplifyApp.java b/src/main/java/io/github/shuoros/peoplify/PeoplifyApp.java deleted file mode 100644 index 73b726e..0000000 --- a/src/main/java/io/github/shuoros/peoplify/PeoplifyApp.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.github.shuoros.peoplify; - -import io.github.shuoros.peoplify.config.ApplicationProperties; -import io.github.shuoros.peoplify.config.CRLFLogConverter; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import javax.annotation.PostConstruct; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.core.env.Environment; -import tech.jhipster.config.DefaultProfileUtil; -import tech.jhipster.config.JHipsterConstants; - -@SpringBootApplication -@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class }) -public class PeoplifyApp { - - private static final Logger log = LoggerFactory.getLogger(PeoplifyApp.class); - - private final Environment env; - - public PeoplifyApp(Environment env) { - this.env = env; - } - - /** - * Initializes peoplify. - *

- * Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile - *

- * Properties are configured in the {@code application.yml} file. - * See {@link tech.jhipster.config.JHipsterProperties} for a good example. - */ -@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) -public class ApplicationProperties { - // jhipster-needle-application-properties-property - // jhipster-needle-application-properties-property-getter - // jhipster-needle-application-properties-property-class -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/AsyncConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/AsyncConfiguration.java deleted file mode 100644 index 63b403e..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/AsyncConfiguration.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.util.concurrent.Executor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; -import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor; - -@Configuration -@EnableAsync -@EnableScheduling -@Profile("!testdev & !testprod") -public class AsyncConfiguration implements AsyncConfigurer { - - private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class); - - private final TaskExecutionProperties taskExecutionProperties; - - public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) { - this.taskExecutionProperties = taskExecutionProperties; - } - - @Override - @Bean(name = "taskExecutor") - public Executor getAsyncExecutor() { - log.debug("Creating Async Task Executor"); - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); - executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); - executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); - executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); - return new ExceptionHandlingAsyncTaskExecutor(executor); - } - - @Override - public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { - return new SimpleAsyncUncaughtExceptionHandler(); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/CRLFLogConverter.java b/src/main/java/io/github/shuoros/peoplify/config/CRLFLogConverter.java deleted file mode 100644 index 2920d94..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/CRLFLogConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.pattern.CompositeConverter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; -import org.springframework.boot.ansi.AnsiColor; -import org.springframework.boot.ansi.AnsiElement; -import org.springframework.boot.ansi.AnsiOutput; -import org.springframework.boot.ansi.AnsiStyle; - -public class CRLFLogConverter extends CompositeConverter { - - public static final Marker CRLF_SAFE_MARKER = MarkerFactory.getMarker("CRLF_SAFE"); - - private static final String[] SAFE_LOGGERS = { "org.hibernate" }; - private static final Map ELEMENTS; - - static { - Map ansiElements = new HashMap<>(); - ansiElements.put("faint", AnsiStyle.FAINT); - ansiElements.put("red", AnsiColor.RED); - ansiElements.put("green", AnsiColor.GREEN); - ansiElements.put("yellow", AnsiColor.YELLOW); - ansiElements.put("blue", AnsiColor.BLUE); - ansiElements.put("magenta", AnsiColor.MAGENTA); - ansiElements.put("cyan", AnsiColor.CYAN); - ELEMENTS = Collections.unmodifiableMap(ansiElements); - } - - @Override - protected String transform(ILoggingEvent event, String in) { - AnsiElement element = ELEMENTS.get(getFirstOption()); - if ((event.getMarker() != null && event.getMarker().contains(CRLF_SAFE_MARKER)) || isLoggerSafe(event)) { - return in; - } - String replacement = element == null ? "_" : toAnsiString("_", element); - return in.replaceAll("[\n\r\t]", replacement); - } - - protected boolean isLoggerSafe(ILoggingEvent event) { - for (String safeLogger : SAFE_LOGGERS) { - if (event.getLoggerName().startsWith(safeLogger)) { - return true; - } - } - return false; - } - - protected String toAnsiString(String in, AnsiElement element) { - return AnsiOutput.toString(element, in); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/CacheConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/CacheConfiguration.java deleted file mode 100644 index a6e4d13..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/CacheConfiguration.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.time.Duration; -import org.ehcache.config.builders.*; -import org.ehcache.jsr107.Eh107Configuration; -import org.hibernate.cache.jcache.ConfigSettings; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; -import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; -import org.springframework.boot.info.BuildProperties; -import org.springframework.boot.info.GitProperties; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.interceptor.KeyGenerator; -import org.springframework.context.annotation.*; -import tech.jhipster.config.JHipsterProperties; -import tech.jhipster.config.cache.PrefixedKeyGenerator; - -@Configuration -@EnableCaching -public class CacheConfiguration { - - private GitProperties gitProperties; - private BuildProperties buildProperties; - private final javax.cache.configuration.Configuration jcacheConfiguration; - - public CacheConfiguration(JHipsterProperties jHipsterProperties) { - JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache(); - - jcacheConfiguration = - Eh107Configuration.fromEhcacheCacheConfiguration( - CacheConfigurationBuilder - .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(ehcache.getMaxEntries())) - .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds()))) - .build() - ); - } - - @Bean - public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) { - return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager); - } - - @Bean - public JCacheManagerCustomizer cacheManagerCustomizer() { - return cm -> { - createCache(cm, io.github.shuoros.peoplify.repository.UserRepository.USERS_BY_LOGIN_CACHE); - createCache(cm, io.github.shuoros.peoplify.repository.UserRepository.USERS_BY_EMAIL_CACHE); - createCache(cm, io.github.shuoros.peoplify.domain.User.class.getName()); - createCache(cm, io.github.shuoros.peoplify.domain.Authority.class.getName()); - createCache(cm, io.github.shuoros.peoplify.domain.User.class.getName() + ".authorities"); - // jhipster-needle-ehcache-add-entry - }; - } - - private void createCache(javax.cache.CacheManager cm, String cacheName) { - javax.cache.Cache cache = cm.getCache(cacheName); - if (cache != null) { - cache.clear(); - } else { - cm.createCache(cacheName, jcacheConfiguration); - } - } - - @Autowired(required = false) - public void setGitProperties(GitProperties gitProperties) { - this.gitProperties = gitProperties; - } - - @Autowired(required = false) - public void setBuildProperties(BuildProperties buildProperties) { - this.buildProperties = buildProperties; - } - - @Bean - public KeyGenerator keyGenerator() { - return new PrefixedKeyGenerator(this.gitProperties, this.buildProperties); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/Constants.java b/src/main/java/io/github/shuoros/peoplify/config/Constants.java deleted file mode 100644 index 1dfc2ad..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/Constants.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.shuoros.peoplify.config; - -/** - * Application constants. - */ -public final class Constants { - - // Regex for acceptable logins - public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; - - public static final String SYSTEM = "system"; - public static final String DEFAULT_LANGUAGE = "en"; - - private Constants() {} -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/DatabaseConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/DatabaseConfiguration.java deleted file mode 100644 index b31f36c..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/DatabaseConfiguration.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.transaction.annotation.EnableTransactionManagement; -import tech.jhipster.config.JHipsterConstants; - -@Configuration -@EnableJpaRepositories({ "io.github.shuoros.peoplify.repository" }) -@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") -@EnableTransactionManagement -public class DatabaseConfiguration {} diff --git a/src/main/java/io/github/shuoros/peoplify/config/DateTimeFormatConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/DateTimeFormatConfiguration.java deleted file mode 100644 index b5d181e..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/DateTimeFormatConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.format.FormatterRegistry; -import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * Configure the converters to use the ISO format for dates by default. - */ -@Configuration -public class DateTimeFormatConfiguration implements WebMvcConfigurer { - - @Override - public void addFormatters(FormatterRegistry registry) { - DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); - registrar.setUseIsoFormat(true); - registrar.registerFormatters(registry); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/JacksonConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/JacksonConfiguration.java deleted file mode 100644 index b7b19b6..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/JacksonConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.zalando.problem.jackson.ProblemModule; -import org.zalando.problem.violations.ConstraintViolationProblemModule; - -@Configuration -public class JacksonConfiguration { - - /** - * Support for Java date and time API. - * @return the corresponding Jackson module. - */ - @Bean - public JavaTimeModule javaTimeModule() { - return new JavaTimeModule(); - } - - @Bean - public Jdk8Module jdk8TimeModule() { - return new Jdk8Module(); - } - - /* - * Support for Hibernate types in Jackson. - */ - @Bean - public Hibernate5Module hibernate5Module() { - return new Hibernate5Module(); - } - - /* - * Module for serialization/deserialization of RFC7807 Problem. - */ - @Bean - public ProblemModule problemModule() { - return new ProblemModule(); - } - - /* - * Module for serialization/deserialization of ConstraintViolationProblem. - */ - @Bean - public ConstraintViolationProblemModule constraintViolationProblemModule() { - return new ConstraintViolationProblemModule(); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/LiquibaseConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/LiquibaseConfiguration.java deleted file mode 100644 index 9e3b2b5..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/LiquibaseConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.util.concurrent.Executor; -import javax.sql.DataSource; -import liquibase.integration.spring.SpringLiquibase; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.core.env.Profiles; -import tech.jhipster.config.JHipsterConstants; -import tech.jhipster.config.liquibase.SpringLiquibaseUtil; - -@Configuration -public class LiquibaseConfiguration { - - private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class); - - private final Environment env; - - public LiquibaseConfiguration(Environment env) { - this.env = env; - } - - @Bean - public SpringLiquibase liquibase( - @Qualifier("taskExecutor") Executor executor, - @LiquibaseDataSource ObjectProvider liquibaseDataSource, - LiquibaseProperties liquibaseProperties, - ObjectProvider dataSource, - DataSourceProperties dataSourceProperties - ) { - // If you don't want Liquibase to start asynchronously, substitute by this: - // SpringLiquibase liquibase = SpringLiquibaseUtil.createSpringLiquibase(liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); - SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase( - this.env, - executor, - liquibaseDataSource.getIfAvailable(), - liquibaseProperties, - dataSource.getIfUnique(), - dataSourceProperties - ); - liquibase.setChangeLog("classpath:config/liquibase/master.xml"); - liquibase.setContexts(liquibaseProperties.getContexts()); - liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); - liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema()); - liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace()); - liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable()); - liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable()); - liquibase.setDropFirst(liquibaseProperties.isDropFirst()); - liquibase.setLabels(liquibaseProperties.getLabels()); - liquibase.setChangeLogParameters(liquibaseProperties.getParameters()); - liquibase.setRollbackFile(liquibaseProperties.getRollbackFile()); - liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate()); - if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE))) { - liquibase.setShouldRun(false); - } else { - liquibase.setShouldRun(liquibaseProperties.isEnabled()); - log.debug("Configuring Liquibase"); - } - return liquibase; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/LocaleConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/LocaleConfiguration.java deleted file mode 100644 index 41e56c0..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/LocaleConfiguration.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.config.annotation.*; -import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; -import tech.jhipster.config.locale.AngularCookieLocaleResolver; - -@Configuration -public class LocaleConfiguration implements WebMvcConfigurer { - - @Bean - public LocaleResolver localeResolver() { - AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver(); - cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY"); - return cookieLocaleResolver; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); - localeChangeInterceptor.setParamName("language"); - registry.addInterceptor(localeChangeInterceptor); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/LoggingAspectConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/LoggingAspectConfiguration.java deleted file mode 100644 index 6d37931..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/LoggingAspectConfiguration.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import io.github.shuoros.peoplify.aop.logging.LoggingAspect; -import org.springframework.context.annotation.*; -import org.springframework.core.env.Environment; -import tech.jhipster.config.JHipsterConstants; - -@Configuration -@EnableAspectJAutoProxy -public class LoggingAspectConfiguration { - - @Bean - @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) - public LoggingAspect loggingAspect(Environment env) { - return new LoggingAspect(env); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/LoggingConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/LoggingConfiguration.java deleted file mode 100644 index 5be1e62..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/LoggingConfiguration.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import static tech.jhipster.config.logging.LoggingUtils.*; - -import ch.qos.logback.classic.LoggerContext; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.Map; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import tech.jhipster.config.JHipsterProperties; - -/* - * Configures the console and Logstash log appenders from the app properties - */ -@Configuration -public class LoggingConfiguration { - - public LoggingConfiguration( - @Value("${spring.application.name}") String appName, - @Value("${server.port}") String serverPort, - JHipsterProperties jHipsterProperties, - ObjectMapper mapper - ) throws JsonProcessingException { - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - - Map map = new HashMap<>(); - map.put("app_name", appName); - map.put("app_port", serverPort); - String customFields = mapper.writeValueAsString(map); - - JHipsterProperties.Logging loggingProperties = jHipsterProperties.getLogging(); - JHipsterProperties.Logging.Logstash logstashProperties = loggingProperties.getLogstash(); - - if (loggingProperties.isUseJsonFormat()) { - addJsonConsoleAppender(context, customFields); - } - if (logstashProperties.isEnabled()) { - addLogstashTcpSocketAppender(context, customFields, logstashProperties); - } - if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) { - addContextListener(context, customFields, loggingProperties); - } - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/SecurityConfiguration.java b/src/main/java/io/github/shuoros/peoplify/config/SecurityConfiguration.java deleted file mode 100644 index eff30cd..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/SecurityConfiguration.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import io.github.shuoros.peoplify.security.*; -import io.github.shuoros.peoplify.security.jwt.*; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.filter.CorsFilter; -import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; -import tech.jhipster.config.JHipsterProperties; - -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -@Import(SecurityProblemSupport.class) -public class SecurityConfiguration { - - private final JHipsterProperties jHipsterProperties; - - private final TokenProvider tokenProvider; - - private final CorsFilter corsFilter; - private final SecurityProblemSupport problemSupport; - - public SecurityConfiguration( - TokenProvider tokenProvider, - CorsFilter corsFilter, - JHipsterProperties jHipsterProperties, - SecurityProblemSupport problemSupport - ) { - this.tokenProvider = tokenProvider; - this.corsFilter = corsFilter; - this.problemSupport = problemSupport; - this.jHipsterProperties = jHipsterProperties; - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf() - .disable() - .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) - .exceptionHandling() - .authenticationEntryPoint(problemSupport) - .accessDeniedHandler(problemSupport) - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests() - .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() - .antMatchers("/swagger-ui/**").permitAll() - .antMatchers("/test/**").permitAll() - .antMatchers("/api/authenticate").permitAll() - .antMatchers("/api/register").permitAll() - .antMatchers("/api/activate").permitAll() - .antMatchers("/api/account/reset-password/init").permitAll() - .antMatchers("/api/account/reset-password/finish").permitAll() - .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) - .antMatchers("/api/**").authenticated() - .antMatchers("/management/health").permitAll() - .antMatchers("/management/health/**").permitAll() - .antMatchers("/management/info").permitAll() - .antMatchers("/management/prometheus").permitAll() - .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) - .and() - .httpBasic() - .and() - .apply(securityConfigurerAdapter()); - return http.build(); - // @formatter:on - } - - private JWTConfigurer securityConfigurerAdapter() { - return new JWTConfigurer(tokenProvider); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/WebConfigurer.java b/src/main/java/io/github/shuoros/peoplify/config/WebConfigurer.java deleted file mode 100644 index a3f8d68..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/WebConfigurer.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import javax.servlet.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.web.server.*; -import org.springframework.boot.web.servlet.ServletContextInitializer; -import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.core.env.Profiles; -import org.springframework.util.CollectionUtils; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; -import tech.jhipster.config.JHipsterProperties; - -/** - * Configuration of web application with Servlet 3.0 APIs. - */ -@Configuration -public class WebConfigurer implements ServletContextInitializer { - - private final Logger log = LoggerFactory.getLogger(WebConfigurer.class); - - private final Environment env; - - private final JHipsterProperties jHipsterProperties; - - public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) { - this.env = env; - this.jHipsterProperties = jHipsterProperties; - } - - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - if (env.getActiveProfiles().length != 0) { - log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); - } - - log.info("Web application fully configured"); - } - - @Bean - public CorsFilter corsFilter() { - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - CorsConfiguration config = jHipsterProperties.getCors(); - if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) { - log.debug("Registering CORS filter"); - source.registerCorsConfiguration("/api/**", config); - source.registerCorsConfiguration("/management/**", config); - source.registerCorsConfiguration("/v3/api-docs", config); - source.registerCorsConfiguration("/swagger-ui/**", config); - } - return new CorsFilter(source); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/package-info.java b/src/main/java/io/github/shuoros/peoplify/config/package-info.java deleted file mode 100644 index 0ae1f19..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Spring Framework configuration files. - */ -package io.github.shuoros.peoplify.config; diff --git a/src/main/java/io/github/shuoros/peoplify/domain/AbstractAuditingEntity.java b/src/main/java/io/github/shuoros/peoplify/domain/AbstractAuditingEntity.java deleted file mode 100644 index 5a6c6be..0000000 --- a/src/main/java/io/github/shuoros/peoplify/domain/AbstractAuditingEntity.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.shuoros.peoplify.domain; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.io.Serializable; -import java.time.Instant; -import javax.persistence.Column; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - -/** - * Base abstract class for entities which will hold definitions for created, last modified, created by, - * last modified by attributes. - */ -@MappedSuperclass -@EntityListeners(AuditingEntityListener.class) -@JsonIgnoreProperties(value = { "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate" }, allowGetters = true) -public abstract class AbstractAuditingEntity implements Serializable { - - private static final long serialVersionUID = 1L; - - public abstract T getId(); - - @CreatedBy - @Column(name = "created_by", nullable = false, length = 50, updatable = false) - private String createdBy; - - @CreatedDate - @Column(name = "created_date", updatable = false) - private Instant createdDate = Instant.now(); - - @LastModifiedBy - @Column(name = "last_modified_by", length = 50) - private String lastModifiedBy; - - @LastModifiedDate - @Column(name = "last_modified_date") - private Instant lastModifiedDate = Instant.now(); - - public String getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(String createdBy) { - this.createdBy = createdBy; - } - - public Instant getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(Instant createdDate) { - this.createdDate = createdDate; - } - - public String getLastModifiedBy() { - return lastModifiedBy; - } - - public void setLastModifiedBy(String lastModifiedBy) { - this.lastModifiedBy = lastModifiedBy; - } - - public Instant getLastModifiedDate() { - return lastModifiedDate; - } - - public void setLastModifiedDate(Instant lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/domain/Authority.java b/src/main/java/io/github/shuoros/peoplify/domain/Authority.java deleted file mode 100644 index 3e84725..0000000 --- a/src/main/java/io/github/shuoros/peoplify/domain/Authority.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.shuoros.peoplify.domain; - -import java.io.Serializable; -import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -/** - * An authority (a security role) used by Spring Security. - */ -@Entity -@Table(name = "jhi_authority") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class Authority implements Serializable { - - private static final long serialVersionUID = 1L; - - @NotNull - @Size(max = 50) - @Id - @Column(length = 50) - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Authority)) { - return false; - } - return Objects.equals(name, ((Authority) o).name); - } - - @Override - public int hashCode() { - return Objects.hashCode(name); - } - - // prettier-ignore - @Override - public String toString() { - return "Authority{" + - "name='" + name + '\'' + - "}"; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/domain/User.java b/src/main/java/io/github/shuoros/peoplify/domain/User.java deleted file mode 100644 index 28819a5..0000000 --- a/src/main/java/io/github/shuoros/peoplify/domain/User.java +++ /dev/null @@ -1,232 +0,0 @@ -package io.github.shuoros.peoplify.domain; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import io.github.shuoros.peoplify.config.Constants; -import java.io.Serializable; -import java.time.Instant; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; -import javax.persistence.*; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; -import org.apache.commons.lang3.StringUtils; -import org.hibernate.annotations.BatchSize; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -/** - * A user. - */ -@Entity -@Table(name = "jhi_user") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -public class User extends AbstractAuditingEntity implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") - @SequenceGenerator(name = "sequenceGenerator") - private Long id; - - @NotNull - @Pattern(regexp = Constants.LOGIN_REGEX) - @Size(min = 1, max = 50) - @Column(length = 50, unique = true, nullable = false) - private String login; - - @JsonIgnore - @NotNull - @Size(min = 60, max = 60) - @Column(name = "password_hash", length = 60, nullable = false) - private String password; - - @Size(max = 50) - @Column(name = "first_name", length = 50) - private String firstName; - - @Size(max = 50) - @Column(name = "last_name", length = 50) - private String lastName; - - @Email - @Size(min = 5, max = 254) - @Column(length = 254, unique = true) - private String email; - - @NotNull - @Column(nullable = false) - private boolean activated = false; - - @Size(min = 2, max = 10) - @Column(name = "lang_key", length = 10) - private String langKey; - - @Size(max = 256) - @Column(name = "image_url", length = 256) - private String imageUrl; - - @Size(max = 20) - @Column(name = "activation_key", length = 20) - @JsonIgnore - private String activationKey; - - @Size(max = 20) - @Column(name = "reset_key", length = 20) - @JsonIgnore - private String resetKey; - - @Column(name = "reset_date") - private Instant resetDate = null; - - @JsonIgnore - @ManyToMany - @JoinTable( - name = "jhi_user_authority", - joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, - inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") } - ) - @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) - @BatchSize(size = 20) - private Set authorities = new HashSet<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getLogin() { - return login; - } - - // Lowercase the login before saving it in database - public void setLogin(String login) { - this.login = StringUtils.lowerCase(login, Locale.ENGLISH); - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } - - public String getActivationKey() { - return activationKey; - } - - public void setActivationKey(String activationKey) { - this.activationKey = activationKey; - } - - public String getResetKey() { - return resetKey; - } - - public void setResetKey(String resetKey) { - this.resetKey = resetKey; - } - - public Instant getResetDate() { - return resetDate; - } - - public void setResetDate(Instant resetDate) { - this.resetDate = resetDate; - } - - public String getLangKey() { - return langKey; - } - - public void setLangKey(String langKey) { - this.langKey = langKey; - } - - public Set getAuthorities() { - return authorities; - } - - public void setAuthorities(Set authorities) { - this.authorities = authorities; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof User)) { - return false; - } - return id != null && id.equals(((User) o).id); - } - - @Override - public int hashCode() { - // see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/ - return getClass().hashCode(); - } - - // prettier-ignore - @Override - public String toString() { - return "User{" + - "login='" + login + '\'' + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", imageUrl='" + imageUrl + '\'' + - ", activated='" + activated + '\'' + - ", langKey='" + langKey + '\'' + - ", activationKey='" + activationKey + '\'' + - "}"; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/domain/package-info.java b/src/main/java/io/github/shuoros/peoplify/domain/package-info.java deleted file mode 100644 index 2e514af..0000000 --- a/src/main/java/io/github/shuoros/peoplify/domain/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * JPA domain objects. - */ -package io.github.shuoros.peoplify.domain; diff --git a/src/main/java/io/github/shuoros/peoplify/management/SecurityMetersService.java b/src/main/java/io/github/shuoros/peoplify/management/SecurityMetersService.java deleted file mode 100644 index f2949cb..0000000 --- a/src/main/java/io/github/shuoros/peoplify/management/SecurityMetersService.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.shuoros.peoplify.management; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import org.springframework.stereotype.Service; - -@Service -public class SecurityMetersService { - - public static final String INVALID_TOKENS_METER_NAME = "security.authentication.invalid-tokens"; - public static final String INVALID_TOKENS_METER_DESCRIPTION = - "Indicates validation error count of the tokens presented by the clients."; - public static final String INVALID_TOKENS_METER_BASE_UNIT = "errors"; - public static final String INVALID_TOKENS_METER_CAUSE_DIMENSION = "cause"; - - private final Counter tokenInvalidSignatureCounter; - private final Counter tokenExpiredCounter; - private final Counter tokenUnsupportedCounter; - private final Counter tokenMalformedCounter; - - public SecurityMetersService(MeterRegistry registry) { - this.tokenInvalidSignatureCounter = invalidTokensCounterForCauseBuilder("invalid-signature").register(registry); - this.tokenExpiredCounter = invalidTokensCounterForCauseBuilder("expired").register(registry); - this.tokenUnsupportedCounter = invalidTokensCounterForCauseBuilder("unsupported").register(registry); - this.tokenMalformedCounter = invalidTokensCounterForCauseBuilder("malformed").register(registry); - } - - private Counter.Builder invalidTokensCounterForCauseBuilder(String cause) { - return Counter - .builder(INVALID_TOKENS_METER_NAME) - .baseUnit(INVALID_TOKENS_METER_BASE_UNIT) - .description(INVALID_TOKENS_METER_DESCRIPTION) - .tag(INVALID_TOKENS_METER_CAUSE_DIMENSION, cause); - } - - public void trackTokenInvalidSignature() { - this.tokenInvalidSignatureCounter.increment(); - } - - public void trackTokenExpired() { - this.tokenExpiredCounter.increment(); - } - - public void trackTokenUnsupported() { - this.tokenUnsupportedCounter.increment(); - } - - public void trackTokenMalformed() { - this.tokenMalformedCounter.increment(); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/repository/AuthorityRepository.java b/src/main/java/io/github/shuoros/peoplify/repository/AuthorityRepository.java deleted file mode 100644 index 715fb58..0000000 --- a/src/main/java/io/github/shuoros/peoplify/repository/AuthorityRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.shuoros.peoplify.repository; - -import io.github.shuoros.peoplify.domain.Authority; -import org.springframework.data.jpa.repository.JpaRepository; - -/** - * Spring Data JPA repository for the {@link Authority} entity. - */ -public interface AuthorityRepository extends JpaRepository {} diff --git a/src/main/java/io/github/shuoros/peoplify/repository/UserRepository.java b/src/main/java/io/github/shuoros/peoplify/repository/UserRepository.java deleted file mode 100644 index 1fb0456..0000000 --- a/src/main/java/io/github/shuoros/peoplify/repository/UserRepository.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.shuoros.peoplify.repository; - -import io.github.shuoros.peoplify.domain.User; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.data.domain.*; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - * Spring Data JPA repository for the {@link User} entity. - */ -@Repository -public interface UserRepository extends JpaRepository { - String USERS_BY_LOGIN_CACHE = "usersByLogin"; - - String USERS_BY_EMAIL_CACHE = "usersByEmail"; - Optional findOneByActivationKey(String activationKey); - List findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime); - Optional findOneByResetKey(String resetKey); - Optional findOneByEmailIgnoreCase(String email); - Optional findOneByLogin(String login); - - @EntityGraph(attributePaths = "authorities") - @Cacheable(cacheNames = USERS_BY_LOGIN_CACHE) - Optional findOneWithAuthoritiesByLogin(String login); - - @EntityGraph(attributePaths = "authorities") - @Cacheable(cacheNames = USERS_BY_EMAIL_CACHE) - Optional findOneWithAuthoritiesByEmailIgnoreCase(String email); - - Page findAllByIdNotNullAndActivatedIsTrue(Pageable pageable); -} diff --git a/src/main/java/io/github/shuoros/peoplify/repository/package-info.java b/src/main/java/io/github/shuoros/peoplify/repository/package-info.java deleted file mode 100644 index 5ef5899..0000000 --- a/src/main/java/io/github/shuoros/peoplify/repository/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Spring Data JPA repositories. - */ -package io.github.shuoros.peoplify.repository; diff --git a/src/main/java/io/github/shuoros/peoplify/security/AuthoritiesConstants.java b/src/main/java/io/github/shuoros/peoplify/security/AuthoritiesConstants.java deleted file mode 100644 index afe0a74..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/AuthoritiesConstants.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.shuoros.peoplify.security; - -/** - * Constants for Spring Security authorities. - */ -public final class AuthoritiesConstants { - - public static final String ADMIN = "ROLE_ADMIN"; - - public static final String USER = "ROLE_USER"; - - public static final String ANONYMOUS = "ROLE_ANONYMOUS"; - - private AuthoritiesConstants() {} -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/DomainUserDetailsService.java b/src/main/java/io/github/shuoros/peoplify/security/DomainUserDetailsService.java deleted file mode 100644 index 5383417..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/DomainUserDetailsService.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import io.github.shuoros.peoplify.domain.Authority; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import java.util.*; -import java.util.stream.Collectors; -import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -/** - * Authenticate a user from the database. - */ -@Component("userDetailsService") -public class DomainUserDetailsService implements UserDetailsService { - - private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class); - - private final UserRepository userRepository; - - public DomainUserDetailsService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Override - @Transactional(readOnly = true) - public UserDetails loadUserByUsername(final String login) { - log.debug("Authenticating {}", login); - - if (new EmailValidator().isValid(login, null)) { - return userRepository - .findOneWithAuthoritiesByEmailIgnoreCase(login) - .map(user -> createSpringSecurityUser(login, user)) - .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); - } - - String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); - return userRepository - .findOneWithAuthoritiesByLogin(lowercaseLogin) - .map(user -> createSpringSecurityUser(lowercaseLogin, user)) - .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")); - } - - private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) { - if (!user.isActivated()) { - throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); - } - List grantedAuthorities = user - .getAuthorities() - .stream() - .map(Authority::getName) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/SecurityUtils.java b/src/main/java/io/github/shuoros/peoplify/security/SecurityUtils.java deleted file mode 100644 index 61f1046..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/SecurityUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import java.util.Arrays; -import java.util.Optional; -import java.util.stream.Stream; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * Utility class for Spring Security. - */ -public final class SecurityUtils { - - private SecurityUtils() {} - - /** - * Get the login of the current user. - * - * @return the login of the current user. - */ - public static Optional getCurrentUserLogin() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); - } - - private static String extractPrincipal(Authentication authentication) { - if (authentication == null) { - return null; - } else if (authentication.getPrincipal() instanceof UserDetails) { - UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); - return springSecurityUser.getUsername(); - } else if (authentication.getPrincipal() instanceof String) { - return (String) authentication.getPrincipal(); - } - return null; - } - - /** - * Get the JWT of the current user. - * - * @return the JWT of the current user. - */ - public static Optional getCurrentUserJWT() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - return Optional - .ofNullable(securityContext.getAuthentication()) - .filter(authentication -> authentication.getCredentials() instanceof String) - .map(authentication -> (String) authentication.getCredentials()); - } - - /** - * Check if a user is authenticated. - * - * @return true if the user is authenticated, false otherwise. - */ - public static boolean isAuthenticated() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); - } - - /** - * Checks if the current user has any of the authorities. - * - * @param authorities the authorities to check. - * @return true if the current user has any of the authorities, false otherwise. - */ - public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return ( - authentication != null && getAuthorities(authentication).anyMatch(authority -> Arrays.asList(authorities).contains(authority)) - ); - } - - /** - * Checks if the current user has none of the authorities. - * - * @param authorities the authorities to check. - * @return true if the current user has none of the authorities, false otherwise. - */ - public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) { - return !hasCurrentUserAnyOfAuthorities(authorities); - } - - /** - * Checks if the current user has a specific authority. - * - * @param authority the authority to check. - * @return true if the current user has the authority, false otherwise. - */ - public static boolean hasCurrentUserThisAuthority(String authority) { - return hasCurrentUserAnyOfAuthorities(authority); - } - - private static Stream getAuthorities(Authentication authentication) { - return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/SpringSecurityAuditorAware.java b/src/main/java/io/github/shuoros/peoplify/security/SpringSecurityAuditorAware.java deleted file mode 100644 index 6f0132e..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/SpringSecurityAuditorAware.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import io.github.shuoros.peoplify.config.Constants; -import java.util.Optional; -import org.springframework.data.domain.AuditorAware; -import org.springframework.stereotype.Component; - -/** - * Implementation of {@link AuditorAware} based on Spring Security. - */ -@Component -public class SpringSecurityAuditorAware implements AuditorAware { - - @Override - public Optional getCurrentAuditor() { - return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM)); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/UserNotActivatedException.java b/src/main/java/io/github/shuoros/peoplify/security/UserNotActivatedException.java deleted file mode 100644 index d5f63dc..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/UserNotActivatedException.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import org.springframework.security.core.AuthenticationException; - -/** - * This exception is thrown in case of a not activated user trying to authenticate. - */ -public class UserNotActivatedException extends AuthenticationException { - - private static final long serialVersionUID = 1L; - - public UserNotActivatedException(String message) { - super(message); - } - - public UserNotActivatedException(String message, Throwable t) { - super(message, t); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTConfigurer.java b/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTConfigurer.java deleted file mode 100644 index 9bc89ee..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTConfigurer.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import org.springframework.security.config.annotation.SecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -public class JWTConfigurer extends SecurityConfigurerAdapter { - - private final TokenProvider tokenProvider; - - public JWTConfigurer(TokenProvider tokenProvider) { - this.tokenProvider = tokenProvider; - } - - @Override - public void configure(HttpSecurity http) { - JWTFilter customFilter = new JWTFilter(tokenProvider); - http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTFilter.java b/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTFilter.java deleted file mode 100644 index afcf764..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/jwt/JWTFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.GenericFilterBean; - -/** - * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is - * found. - */ -public class JWTFilter extends GenericFilterBean { - - public static final String AUTHORIZATION_HEADER = "Authorization"; - - private final TokenProvider tokenProvider; - - public JWTFilter(TokenProvider tokenProvider) { - this.tokenProvider = tokenProvider; - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { - HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; - String jwt = resolveToken(httpServletRequest); - if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { - Authentication authentication = this.tokenProvider.getAuthentication(jwt); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - filterChain.doFilter(servletRequest, servletResponse); - } - - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/jwt/TokenProvider.java b/src/main/java/io/github/shuoros/peoplify/security/jwt/TokenProvider.java deleted file mode 100644 index f16c90f..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/jwt/TokenProvider.java +++ /dev/null @@ -1,126 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import io.github.shuoros.peoplify.management.SecurityMetersService; -import io.jsonwebtoken.*; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import io.jsonwebtoken.security.SignatureException; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.*; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; -import tech.jhipster.config.JHipsterProperties; - -@Component -public class TokenProvider { - - private final Logger log = LoggerFactory.getLogger(TokenProvider.class); - - private static final String AUTHORITIES_KEY = "auth"; - - private static final String INVALID_JWT_TOKEN = "Invalid JWT token."; - - private final Key key; - - private final JwtParser jwtParser; - - private final long tokenValidityInMilliseconds; - - private final long tokenValidityInMillisecondsForRememberMe; - - private final SecurityMetersService securityMetersService; - - public TokenProvider(JHipsterProperties jHipsterProperties, SecurityMetersService securityMetersService) { - byte[] keyBytes; - String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret(); - if (!ObjectUtils.isEmpty(secret)) { - log.debug("Using a Base64-encoded JWT secret key"); - keyBytes = Decoders.BASE64.decode(secret); - } else { - log.warn( - "Warning: the JWT key used is not Base64-encoded. " + - "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security." - ); - secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret(); - keyBytes = secret.getBytes(StandardCharsets.UTF_8); - } - key = Keys.hmacShaKeyFor(keyBytes); - jwtParser = Jwts.parserBuilder().setSigningKey(key).build(); - this.tokenValidityInMilliseconds = 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds(); - this.tokenValidityInMillisecondsForRememberMe = - 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe(); - - this.securityMetersService = securityMetersService; - } - - public String createToken(Authentication authentication, boolean rememberMe) { - String authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")); - - long now = (new Date()).getTime(); - Date validity; - if (rememberMe) { - validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); - } else { - validity = new Date(now + this.tokenValidityInMilliseconds); - } - - return Jwts - .builder() - .setSubject(authentication.getName()) - .claim(AUTHORITIES_KEY, authorities) - .signWith(key, SignatureAlgorithm.HS512) - .setExpiration(validity) - .compact(); - } - - public Authentication getAuthentication(String token) { - Claims claims = jwtParser.parseClaimsJws(token).getBody(); - - Collection authorities = Arrays - .stream(claims.get(AUTHORITIES_KEY).toString().split(",")) - .filter(auth -> !auth.trim().isEmpty()) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - - User principal = new User(claims.getSubject(), "", authorities); - - return new UsernamePasswordAuthenticationToken(principal, token, authorities); - } - - public boolean validateToken(String authToken) { - try { - jwtParser.parseClaimsJws(authToken); - - return true; - } catch (ExpiredJwtException e) { - this.securityMetersService.trackTokenExpired(); - - log.trace(INVALID_JWT_TOKEN, e); - } catch (UnsupportedJwtException e) { - this.securityMetersService.trackTokenUnsupported(); - - log.trace(INVALID_JWT_TOKEN, e); - } catch (MalformedJwtException e) { - this.securityMetersService.trackTokenMalformed(); - - log.trace(INVALID_JWT_TOKEN, e); - } catch (SignatureException e) { - this.securityMetersService.trackTokenInvalidSignature(); - - log.trace(INVALID_JWT_TOKEN, e); - } catch (IllegalArgumentException e) { // TODO: should we let it bubble (no catch), to avoid defensive programming and follow the fail-fast principle? - log.error("Token validation error {}", e.getMessage()); - } - - return false; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/security/package-info.java b/src/main/java/io/github/shuoros/peoplify/security/package-info.java deleted file mode 100644 index c1d4fb6..0000000 --- a/src/main/java/io/github/shuoros/peoplify/security/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Spring Security configuration. - */ -package io.github.shuoros.peoplify.security; diff --git a/src/main/java/io/github/shuoros/peoplify/service/EmailAlreadyUsedException.java b/src/main/java/io/github/shuoros/peoplify/service/EmailAlreadyUsedException.java deleted file mode 100644 index 5f4282f..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/EmailAlreadyUsedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.shuoros.peoplify.service; - -public class EmailAlreadyUsedException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public EmailAlreadyUsedException() { - super("Email is already in use!"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/InvalidPasswordException.java b/src/main/java/io/github/shuoros/peoplify/service/InvalidPasswordException.java deleted file mode 100644 index ed95060..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/InvalidPasswordException.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.shuoros.peoplify.service; - -public class InvalidPasswordException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public InvalidPasswordException() { - super("Incorrect password"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/MailService.java b/src/main/java/io/github/shuoros/peoplify/service/MailService.java deleted file mode 100644 index d8b3d91..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/MailService.java +++ /dev/null @@ -1,112 +0,0 @@ -package io.github.shuoros.peoplify.service; - -import io.github.shuoros.peoplify.domain.User; -import java.nio.charset.StandardCharsets; -import java.util.Locale; -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.MessageSource; -import org.springframework.mail.MailException; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.thymeleaf.context.Context; -import org.thymeleaf.spring5.SpringTemplateEngine; -import tech.jhipster.config.JHipsterProperties; - -/** - * Service for sending emails. - *

- * We use the {@link Async} annotation to send emails asynchronously. - */ -@Service -public class MailService { - - private final Logger log = LoggerFactory.getLogger(MailService.class); - - private static final String USER = "user"; - - private static final String BASE_URL = "baseUrl"; - - private final JHipsterProperties jHipsterProperties; - - private final JavaMailSender javaMailSender; - - private final MessageSource messageSource; - - private final SpringTemplateEngine templateEngine; - - public MailService( - JHipsterProperties jHipsterProperties, - JavaMailSender javaMailSender, - MessageSource messageSource, - SpringTemplateEngine templateEngine - ) { - this.jHipsterProperties = jHipsterProperties; - this.javaMailSender = javaMailSender; - this.messageSource = messageSource; - this.templateEngine = templateEngine; - } - - @Async - public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) { - log.debug( - "Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}", - isMultipart, - isHtml, - to, - subject, - content - ); - - // Prepare message using a Spring helper - MimeMessage mimeMessage = javaMailSender.createMimeMessage(); - try { - MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name()); - message.setTo(to); - message.setFrom(jHipsterProperties.getMail().getFrom()); - message.setSubject(subject); - message.setText(content, isHtml); - javaMailSender.send(mimeMessage); - log.debug("Sent email to User '{}'", to); - } catch (MailException | MessagingException e) { - log.warn("Email could not be sent to user '{}'", to, e); - } - } - - @Async - public void sendEmailFromTemplate(User user, String templateName, String titleKey) { - if (user.getEmail() == null) { - log.debug("Email doesn't exist for user '{}'", user.getLogin()); - return; - } - Locale locale = Locale.forLanguageTag(user.getLangKey()); - Context context = new Context(locale); - context.setVariable(USER, user); - context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); - String content = templateEngine.process(templateName, context); - String subject = messageSource.getMessage(titleKey, null, locale); - sendEmail(user.getEmail(), subject, content, false, true); - } - - @Async - public void sendActivationEmail(User user) { - log.debug("Sending activation email to '{}'", user.getEmail()); - sendEmailFromTemplate(user, "mail/activationEmail", "email.activation.title"); - } - - @Async - public void sendCreationEmail(User user) { - log.debug("Sending creation email to '{}'", user.getEmail()); - sendEmailFromTemplate(user, "mail/creationEmail", "email.activation.title"); - } - - @Async - public void sendPasswordResetMail(User user) { - log.debug("Sending password reset email to '{}'", user.getEmail()); - sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/UserService.java b/src/main/java/io/github/shuoros/peoplify/service/UserService.java deleted file mode 100644 index f9d2748..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/UserService.java +++ /dev/null @@ -1,325 +0,0 @@ -package io.github.shuoros.peoplify.service; - -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.Authority; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.AuthorityRepository; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.github.shuoros.peoplify.security.SecurityUtils; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.dto.UserDTO; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cache.CacheManager; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import tech.jhipster.security.RandomUtil; - -/** - * Service class for managing users. - */ -@Service -@Transactional -public class UserService { - - private final Logger log = LoggerFactory.getLogger(UserService.class); - - private final UserRepository userRepository; - - private final PasswordEncoder passwordEncoder; - - private final AuthorityRepository authorityRepository; - - private final CacheManager cacheManager; - - public UserService( - UserRepository userRepository, - PasswordEncoder passwordEncoder, - AuthorityRepository authorityRepository, - CacheManager cacheManager - ) { - this.userRepository = userRepository; - this.passwordEncoder = passwordEncoder; - this.authorityRepository = authorityRepository; - this.cacheManager = cacheManager; - } - - public Optional activateRegistration(String key) { - log.debug("Activating user for activation key {}", key); - return userRepository - .findOneByActivationKey(key) - .map(user -> { - // activate given user for the registration key. - user.setActivated(true); - user.setActivationKey(null); - this.clearUserCaches(user); - log.debug("Activated user: {}", user); - return user; - }); - } - - public Optional completePasswordReset(String newPassword, String key) { - log.debug("Reset user password for reset key {}", key); - return userRepository - .findOneByResetKey(key) - .filter(user -> user.getResetDate().isAfter(Instant.now().minus(1, ChronoUnit.DAYS))) - .map(user -> { - user.setPassword(passwordEncoder.encode(newPassword)); - user.setResetKey(null); - user.setResetDate(null); - this.clearUserCaches(user); - return user; - }); - } - - public Optional requestPasswordReset(String mail) { - return userRepository - .findOneByEmailIgnoreCase(mail) - .filter(User::isActivated) - .map(user -> { - user.setResetKey(RandomUtil.generateResetKey()); - user.setResetDate(Instant.now()); - this.clearUserCaches(user); - return user; - }); - } - - public User registerUser(AdminUserDTO userDTO, String password) { - userRepository - .findOneByLogin(userDTO.getLogin().toLowerCase()) - .ifPresent(existingUser -> { - boolean removed = removeNonActivatedUser(existingUser); - if (!removed) { - throw new UsernameAlreadyUsedException(); - } - }); - userRepository - .findOneByEmailIgnoreCase(userDTO.getEmail()) - .ifPresent(existingUser -> { - boolean removed = removeNonActivatedUser(existingUser); - if (!removed) { - throw new EmailAlreadyUsedException(); - } - }); - User newUser = new User(); - String encryptedPassword = passwordEncoder.encode(password); - newUser.setLogin(userDTO.getLogin().toLowerCase()); - // new user gets initially a generated password - newUser.setPassword(encryptedPassword); - newUser.setFirstName(userDTO.getFirstName()); - newUser.setLastName(userDTO.getLastName()); - if (userDTO.getEmail() != null) { - newUser.setEmail(userDTO.getEmail().toLowerCase()); - } - newUser.setImageUrl(userDTO.getImageUrl()); - newUser.setLangKey(userDTO.getLangKey()); - // new user is not active - newUser.setActivated(false); - // new user gets registration key - newUser.setActivationKey(RandomUtil.generateActivationKey()); - Set authorities = new HashSet<>(); - authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); - newUser.setAuthorities(authorities); - userRepository.save(newUser); - this.clearUserCaches(newUser); - log.debug("Created Information for User: {}", newUser); - return newUser; - } - - private boolean removeNonActivatedUser(User existingUser) { - if (existingUser.isActivated()) { - return false; - } - userRepository.delete(existingUser); - userRepository.flush(); - this.clearUserCaches(existingUser); - return true; - } - - public User createUser(AdminUserDTO userDTO) { - User user = new User(); - user.setLogin(userDTO.getLogin().toLowerCase()); - user.setFirstName(userDTO.getFirstName()); - user.setLastName(userDTO.getLastName()); - if (userDTO.getEmail() != null) { - user.setEmail(userDTO.getEmail().toLowerCase()); - } - user.setImageUrl(userDTO.getImageUrl()); - if (userDTO.getLangKey() == null) { - user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language - } else { - user.setLangKey(userDTO.getLangKey()); - } - String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()); - user.setPassword(encryptedPassword); - user.setResetKey(RandomUtil.generateResetKey()); - user.setResetDate(Instant.now()); - user.setActivated(true); - if (userDTO.getAuthorities() != null) { - Set authorities = userDTO - .getAuthorities() - .stream() - .map(authorityRepository::findById) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toSet()); - user.setAuthorities(authorities); - } - userRepository.save(user); - this.clearUserCaches(user); - log.debug("Created Information for User: {}", user); - return user; - } - - /** - * Update all information for a specific user, and return the modified user. - * - * @param userDTO user to update. - * @return updated user. - */ - public Optional updateUser(AdminUserDTO userDTO) { - return Optional - .of(userRepository.findById(userDTO.getId())) - .filter(Optional::isPresent) - .map(Optional::get) - .map(user -> { - this.clearUserCaches(user); - user.setLogin(userDTO.getLogin().toLowerCase()); - user.setFirstName(userDTO.getFirstName()); - user.setLastName(userDTO.getLastName()); - if (userDTO.getEmail() != null) { - user.setEmail(userDTO.getEmail().toLowerCase()); - } - user.setImageUrl(userDTO.getImageUrl()); - user.setActivated(userDTO.isActivated()); - user.setLangKey(userDTO.getLangKey()); - Set managedAuthorities = user.getAuthorities(); - managedAuthorities.clear(); - userDTO - .getAuthorities() - .stream() - .map(authorityRepository::findById) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(managedAuthorities::add); - this.clearUserCaches(user); - log.debug("Changed Information for User: {}", user); - return user; - }) - .map(AdminUserDTO::new); - } - - public void deleteUser(String login) { - userRepository - .findOneByLogin(login) - .ifPresent(user -> { - userRepository.delete(user); - this.clearUserCaches(user); - log.debug("Deleted User: {}", user); - }); - } - - /** - * Update basic information (first name, last name, email, language) for the current user. - * - * @param firstName first name of user. - * @param lastName last name of user. - * @param email email id of user. - * @param langKey language key. - * @param imageUrl image URL of user. - */ - public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) { - SecurityUtils - .getCurrentUserLogin() - .flatMap(userRepository::findOneByLogin) - .ifPresent(user -> { - user.setFirstName(firstName); - user.setLastName(lastName); - if (email != null) { - user.setEmail(email.toLowerCase()); - } - user.setLangKey(langKey); - user.setImageUrl(imageUrl); - this.clearUserCaches(user); - log.debug("Changed Information for User: {}", user); - }); - } - - @Transactional - public void changePassword(String currentClearTextPassword, String newPassword) { - SecurityUtils - .getCurrentUserLogin() - .flatMap(userRepository::findOneByLogin) - .ifPresent(user -> { - String currentEncryptedPassword = user.getPassword(); - if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) { - throw new InvalidPasswordException(); - } - String encryptedPassword = passwordEncoder.encode(newPassword); - user.setPassword(encryptedPassword); - this.clearUserCaches(user); - log.debug("Changed password for User: {}", user); - }); - } - - @Transactional(readOnly = true) - public Page getAllManagedUsers(Pageable pageable) { - return userRepository.findAll(pageable).map(AdminUserDTO::new); - } - - @Transactional(readOnly = true) - public Page getAllPublicUsers(Pageable pageable) { - return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new); - } - - @Transactional(readOnly = true) - public Optional getUserWithAuthoritiesByLogin(String login) { - return userRepository.findOneWithAuthoritiesByLogin(login); - } - - @Transactional(readOnly = true) - public Optional getUserWithAuthorities() { - return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin); - } - - /** - * Not activated users should be automatically deleted after 3 days. - *

- * This is scheduled to get fired everyday, at 01:00 (am). - */ - @Scheduled(cron = "0 0 1 * * ?") - public void removeNotActivatedUsers() { - userRepository - .findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS)) - .forEach(user -> { - log.debug("Deleting not activated user {}", user.getLogin()); - userRepository.delete(user); - this.clearUserCaches(user); - }); - } - - /** - * Gets a list of all the authorities. - * @return a list of all the authorities. - */ - @Transactional(readOnly = true) - public List getAuthorities() { - return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList()); - } - - private void clearUserCaches(User user) { - Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin()); - if (user.getEmail() != null) { - Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail()); - } - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/UsernameAlreadyUsedException.java b/src/main/java/io/github/shuoros/peoplify/service/UsernameAlreadyUsedException.java deleted file mode 100644 index 3c40306..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/UsernameAlreadyUsedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.shuoros.peoplify.service; - -public class UsernameAlreadyUsedException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public UsernameAlreadyUsedException() { - super("Login name already used!"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/dto/AdminUserDTO.java b/src/main/java/io/github/shuoros/peoplify/service/dto/AdminUserDTO.java deleted file mode 100644 index d01ce90..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/dto/AdminUserDTO.java +++ /dev/null @@ -1,196 +0,0 @@ -package io.github.shuoros.peoplify.service.dto; - -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.Authority; -import io.github.shuoros.peoplify.domain.User; -import java.io.Serializable; -import java.time.Instant; -import java.util.Set; -import java.util.stream.Collectors; -import javax.validation.constraints.*; - -/** - * A DTO representing a user, with his authorities. - */ -public class AdminUserDTO implements Serializable { - - private static final long serialVersionUID = 1L; - - private Long id; - - @NotBlank - @Pattern(regexp = Constants.LOGIN_REGEX) - @Size(min = 1, max = 50) - private String login; - - @Size(max = 50) - private String firstName; - - @Size(max = 50) - private String lastName; - - @Email - @Size(min = 5, max = 254) - private String email; - - @Size(max = 256) - private String imageUrl; - - private boolean activated = false; - - @Size(min = 2, max = 10) - private String langKey; - - private String createdBy; - - private Instant createdDate; - - private String lastModifiedBy; - - private Instant lastModifiedDate; - - private Set authorities; - - public AdminUserDTO() { - // Empty constructor needed for Jackson. - } - - public AdminUserDTO(User user) { - this.id = user.getId(); - this.login = user.getLogin(); - this.firstName = user.getFirstName(); - this.lastName = user.getLastName(); - this.email = user.getEmail(); - this.activated = user.isActivated(); - this.imageUrl = user.getImageUrl(); - this.langKey = user.getLangKey(); - this.createdBy = user.getCreatedBy(); - this.createdDate = user.getCreatedDate(); - this.lastModifiedBy = user.getLastModifiedBy(); - this.lastModifiedDate = user.getLastModifiedDate(); - this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet()); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } - - public String getLangKey() { - return langKey; - } - - public void setLangKey(String langKey) { - this.langKey = langKey; - } - - public String getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(String createdBy) { - this.createdBy = createdBy; - } - - public Instant getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(Instant createdDate) { - this.createdDate = createdDate; - } - - public String getLastModifiedBy() { - return lastModifiedBy; - } - - public void setLastModifiedBy(String lastModifiedBy) { - this.lastModifiedBy = lastModifiedBy; - } - - public Instant getLastModifiedDate() { - return lastModifiedDate; - } - - public void setLastModifiedDate(Instant lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - } - - public Set getAuthorities() { - return authorities; - } - - public void setAuthorities(Set authorities) { - this.authorities = authorities; - } - - // prettier-ignore - @Override - public String toString() { - return "AdminUserDTO{" + - "login='" + login + '\'' + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", imageUrl='" + imageUrl + '\'' + - ", activated=" + activated + - ", langKey='" + langKey + '\'' + - ", createdBy=" + createdBy + - ", createdDate=" + createdDate + - ", lastModifiedBy='" + lastModifiedBy + '\'' + - ", lastModifiedDate=" + lastModifiedDate + - ", authorities=" + authorities + - "}"; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/dto/PasswordChangeDTO.java b/src/main/java/io/github/shuoros/peoplify/service/dto/PasswordChangeDTO.java deleted file mode 100644 index d53e879..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/dto/PasswordChangeDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.shuoros.peoplify.service.dto; - -import java.io.Serializable; - -/** - * A DTO representing a password change required data - current and new password. - */ -public class PasswordChangeDTO implements Serializable { - - private static final long serialVersionUID = 1L; - - private String currentPassword; - private String newPassword; - - public PasswordChangeDTO() { - // Empty constructor needed for Jackson. - } - - public PasswordChangeDTO(String currentPassword, String newPassword) { - this.currentPassword = currentPassword; - this.newPassword = newPassword; - } - - public String getCurrentPassword() { - return currentPassword; - } - - public void setCurrentPassword(String currentPassword) { - this.currentPassword = currentPassword; - } - - public String getNewPassword() { - return newPassword; - } - - public void setNewPassword(String newPassword) { - this.newPassword = newPassword; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/dto/UserDTO.java b/src/main/java/io/github/shuoros/peoplify/service/dto/UserDTO.java deleted file mode 100644 index 6dd62a7..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/dto/UserDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.shuoros.peoplify.service.dto; - -import io.github.shuoros.peoplify.domain.User; -import java.io.Serializable; - -/** - * A DTO representing a user, with only the public attributes. - */ -public class UserDTO implements Serializable { - - private static final long serialVersionUID = 1L; - - private Long id; - - private String login; - - public UserDTO() { - // Empty constructor needed for Jackson. - } - - public UserDTO(User user) { - this.id = user.getId(); - // Customize it here if you need, or not, firstName/lastName/etc - this.login = user.getLogin(); - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - // prettier-ignore - @Override - public String toString() { - return "UserDTO{" + - "id='" + id + '\'' + - ", login='" + login + '\'' + - "}"; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/dto/package-info.java b/src/main/java/io/github/shuoros/peoplify/service/dto/package-info.java deleted file mode 100644 index 6e48e46..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/dto/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Data Transfer Objects. - */ -package io.github.shuoros.peoplify.service.dto; diff --git a/src/main/java/io/github/shuoros/peoplify/service/mapper/UserMapper.java b/src/main/java/io/github/shuoros/peoplify/service/mapper/UserMapper.java deleted file mode 100644 index ff077f3..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/mapper/UserMapper.java +++ /dev/null @@ -1,147 +0,0 @@ -package io.github.shuoros.peoplify.service.mapper; - -import io.github.shuoros.peoplify.domain.Authority; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.dto.UserDTO; -import java.util.*; -import java.util.stream.Collectors; -import org.mapstruct.BeanMapping; -import org.mapstruct.Mapping; -import org.mapstruct.Named; -import org.springframework.stereotype.Service; - -/** - * Mapper for the entity {@link User} and its DTO called {@link UserDTO}. - * - * Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct - * support is still in beta, and requires a manual step with an IDE. - */ -@Service -public class UserMapper { - - public List usersToUserDTOs(List users) { - return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).collect(Collectors.toList()); - } - - public UserDTO userToUserDTO(User user) { - return new UserDTO(user); - } - - public List usersToAdminUserDTOs(List users) { - return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).collect(Collectors.toList()); - } - - public AdminUserDTO userToAdminUserDTO(User user) { - return new AdminUserDTO(user); - } - - public List userDTOsToUsers(List userDTOs) { - return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).collect(Collectors.toList()); - } - - public User userDTOToUser(AdminUserDTO userDTO) { - if (userDTO == null) { - return null; - } else { - User user = new User(); - user.setId(userDTO.getId()); - user.setLogin(userDTO.getLogin()); - user.setFirstName(userDTO.getFirstName()); - user.setLastName(userDTO.getLastName()); - user.setEmail(userDTO.getEmail()); - user.setImageUrl(userDTO.getImageUrl()); - user.setActivated(userDTO.isActivated()); - user.setLangKey(userDTO.getLangKey()); - Set authorities = this.authoritiesFromStrings(userDTO.getAuthorities()); - user.setAuthorities(authorities); - return user; - } - } - - private Set authoritiesFromStrings(Set authoritiesAsString) { - Set authorities = new HashSet<>(); - - if (authoritiesAsString != null) { - authorities = - authoritiesAsString - .stream() - .map(string -> { - Authority auth = new Authority(); - auth.setName(string); - return auth; - }) - .collect(Collectors.toSet()); - } - - return authorities; - } - - public User userFromId(Long id) { - if (id == null) { - return null; - } - User user = new User(); - user.setId(id); - return user; - } - - @Named("id") - @BeanMapping(ignoreByDefault = true) - @Mapping(target = "id", source = "id") - public UserDTO toDtoId(User user) { - if (user == null) { - return null; - } - UserDTO userDto = new UserDTO(); - userDto.setId(user.getId()); - return userDto; - } - - @Named("idSet") - @BeanMapping(ignoreByDefault = true) - @Mapping(target = "id", source = "id") - public Set toDtoIdSet(Set users) { - if (users == null) { - return Collections.emptySet(); - } - - Set userSet = new HashSet<>(); - for (User userEntity : users) { - userSet.add(this.toDtoId(userEntity)); - } - - return userSet; - } - - @Named("login") - @BeanMapping(ignoreByDefault = true) - @Mapping(target = "id", source = "id") - @Mapping(target = "login", source = "login") - public UserDTO toDtoLogin(User user) { - if (user == null) { - return null; - } - UserDTO userDto = new UserDTO(); - userDto.setId(user.getId()); - userDto.setLogin(user.getLogin()); - return userDto; - } - - @Named("loginSet") - @BeanMapping(ignoreByDefault = true) - @Mapping(target = "id", source = "id") - @Mapping(target = "login", source = "login") - public Set toDtoLoginSet(Set users) { - if (users == null) { - return Collections.emptySet(); - } - - Set userSet = new HashSet<>(); - for (User userEntity : users) { - userSet.add(this.toDtoLogin(userEntity)); - } - - return userSet; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/mapper/package-info.java b/src/main/java/io/github/shuoros/peoplify/service/mapper/package-info.java deleted file mode 100644 index 076e643..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/mapper/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * MapStruct mappers for mapping domain objects and Data Transfer Objects. - */ -package io.github.shuoros.peoplify.service.mapper; diff --git a/src/main/java/io/github/shuoros/peoplify/service/package-info.java b/src/main/java/io/github/shuoros/peoplify/service/package-info.java deleted file mode 100644 index e690b55..0000000 --- a/src/main/java/io/github/shuoros/peoplify/service/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Service layer beans. - */ -package io.github.shuoros.peoplify.service; diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/AccountResource.java b/src/main/java/io/github/shuoros/peoplify/web/rest/AccountResource.java deleted file mode 100644 index 328605f..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/AccountResource.java +++ /dev/null @@ -1,194 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.SecurityUtils; -import io.github.shuoros.peoplify.service.MailService; -import io.github.shuoros.peoplify.service.UserService; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.dto.PasswordChangeDTO; -import io.github.shuoros.peoplify.web.rest.errors.*; -import io.github.shuoros.peoplify.web.rest.vm.KeyAndPasswordVM; -import io.github.shuoros.peoplify.web.rest.vm.ManagedUserVM; -import java.util.*; -import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; - -/** - * REST controller for managing the current user's account. - */ -@RestController -@RequestMapping("/api") -public class AccountResource { - - private static class AccountResourceException extends RuntimeException { - - private AccountResourceException(String message) { - super(message); - } - } - - private final Logger log = LoggerFactory.getLogger(AccountResource.class); - - private final UserRepository userRepository; - - private final UserService userService; - - private final MailService mailService; - - public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) { - this.userRepository = userRepository; - this.userService = userService; - this.mailService = mailService; - } - - /** - * {@code POST /register} : register the user. - * - * @param managedUserVM the managed user View Model. - * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. - * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. - * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. - */ - @PostMapping("/register") - @ResponseStatus(HttpStatus.CREATED) - public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { - if (isPasswordLengthInvalid(managedUserVM.getPassword())) { - throw new InvalidPasswordException(); - } - User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); - mailService.sendActivationEmail(user); - } - - /** - * {@code GET /activate} : activate the registered user. - * - * @param key the activation key. - * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be activated. - */ - @GetMapping("/activate") - public void activateAccount(@RequestParam(value = "key") String key) { - Optional user = userService.activateRegistration(key); - if (!user.isPresent()) { - throw new AccountResourceException("No user was found for this activation key"); - } - } - - /** - * {@code GET /authenticate} : check if the user is authenticated, and return its login. - * - * @param request the HTTP request. - * @return the login if the user is authenticated. - */ - @GetMapping("/authenticate") - public String isAuthenticated(HttpServletRequest request) { - log.debug("REST request to check if the current user is authenticated"); - return request.getRemoteUser(); - } - - /** - * {@code GET /account} : get the current user. - * - * @return the current user. - * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be returned. - */ - @GetMapping("/account") - public AdminUserDTO getAccount() { - return userService - .getUserWithAuthorities() - .map(AdminUserDTO::new) - .orElseThrow(() -> new AccountResourceException("User could not be found")); - } - - /** - * {@code POST /account} : update the current user information. - * - * @param userDTO the current user information. - * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. - * @throws RuntimeException {@code 500 (Internal Server Error)} if the user login wasn't found. - */ - @PostMapping("/account") - public void saveAccount(@Valid @RequestBody AdminUserDTO userDTO) { - String userLogin = SecurityUtils - .getCurrentUserLogin() - .orElseThrow(() -> new AccountResourceException("Current user login not found")); - Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); - if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userLogin))) { - throw new EmailAlreadyUsedException(); - } - Optional user = userRepository.findOneByLogin(userLogin); - if (!user.isPresent()) { - throw new AccountResourceException("User could not be found"); - } - userService.updateUser( - userDTO.getFirstName(), - userDTO.getLastName(), - userDTO.getEmail(), - userDTO.getLangKey(), - userDTO.getImageUrl() - ); - } - - /** - * {@code POST /account/change-password} : changes the current user's password. - * - * @param passwordChangeDto current and new password. - * @throws InvalidPasswordException {@code 400 (Bad Request)} if the new password is incorrect. - */ - @PostMapping(path = "/account/change-password") - public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) { - if (isPasswordLengthInvalid(passwordChangeDto.getNewPassword())) { - throw new InvalidPasswordException(); - } - userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword()); - } - - /** - * {@code POST /account/reset-password/init} : Send an email to reset the password of the user. - * - * @param mail the mail of the user. - */ - @PostMapping(path = "/account/reset-password/init") - public void requestPasswordReset(@RequestBody String mail) { - Optional user = userService.requestPasswordReset(mail); - if (user.isPresent()) { - mailService.sendPasswordResetMail(user.get()); - } else { - // Pretend the request has been successful to prevent checking which emails really exist - // but log that an invalid attempt has been made - log.warn("Password reset requested for non existing mail"); - } - } - - /** - * {@code POST /account/reset-password/finish} : Finish to reset the password of the user. - * - * @param keyAndPassword the generated key and the new password. - * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. - * @throws RuntimeException {@code 500 (Internal Server Error)} if the password could not be reset. - */ - @PostMapping(path = "/account/reset-password/finish") - public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { - if (isPasswordLengthInvalid(keyAndPassword.getNewPassword())) { - throw new InvalidPasswordException(); - } - Optional user = userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); - - if (!user.isPresent()) { - throw new AccountResourceException("No user was found for this reset key"); - } - } - - private static boolean isPasswordLengthInvalid(String password) { - return ( - StringUtils.isEmpty(password) || - password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH || - password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH - ); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/PublicUserResource.java b/src/main/java/io/github/shuoros/peoplify/web/rest/PublicUserResource.java deleted file mode 100644 index 8d8df0f..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/PublicUserResource.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import io.github.shuoros.peoplify.service.UserService; -import io.github.shuoros.peoplify.service.dto.UserDTO; -import java.util.*; -import java.util.Collections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import tech.jhipster.web.util.PaginationUtil; - -@RestController -@RequestMapping("/api") -public class PublicUserResource { - - private static final List ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( - Arrays.asList("id", "login", "firstName", "lastName", "email", "activated", "langKey") - ); - - private final Logger log = LoggerFactory.getLogger(PublicUserResource.class); - - private final UserService userService; - - public PublicUserResource(UserService userService) { - this.userService = userService; - } - - /** - * {@code GET /users} : get all users with only the public informations - calling this are allowed for anyone. - * - * @param pageable the pagination information. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. - */ - @GetMapping("/users") - public ResponseEntity> getAllPublicUsers(@org.springdoc.api.annotations.ParameterObject Pageable pageable) { - log.debug("REST request to get all public User names"); - if (!onlyContainsAllowedProperties(pageable)) { - return ResponseEntity.badRequest().build(); - } - - final Page page = userService.getAllPublicUsers(pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); - return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); - } - - private boolean onlyContainsAllowedProperties(Pageable pageable) { - return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); - } - - /** - * Gets a list of all roles. - * @return a string list of all roles. - */ - @GetMapping("/authorities") - public List getAuthorities() { - return userService.getAuthorities(); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/UserJWTController.java b/src/main/java/io/github/shuoros/peoplify/web/rest/UserJWTController.java deleted file mode 100644 index 1e2fe91..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/UserJWTController.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.shuoros.peoplify.security.jwt.JWTFilter; -import io.github.shuoros.peoplify.security.jwt.TokenProvider; -import io.github.shuoros.peoplify.web.rest.vm.LoginVM; -import javax.validation.Valid; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.*; - -/** - * Controller to authenticate users. - */ -@RestController -@RequestMapping("/api") -public class UserJWTController { - - private final TokenProvider tokenProvider; - - private final AuthenticationManagerBuilder authenticationManagerBuilder; - - public UserJWTController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) { - this.tokenProvider = tokenProvider; - this.authenticationManagerBuilder = authenticationManagerBuilder; - } - - @PostMapping("/authenticate") - public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM) { - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - loginVM.getUsername(), - loginVM.getPassword() - ); - - Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); - SecurityContextHolder.getContext().setAuthentication(authentication); - String jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe()); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); - return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); - } - - /** - * Object to return as body in JWT Authentication. - */ - static class JWTToken { - - private String idToken; - - JWTToken(String idToken) { - this.idToken = idToken; - } - - @JsonProperty("id_token") - String getIdToken() { - return idToken; - } - - void setIdToken(String idToken) { - this.idToken = idToken; - } - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/UserResource.java b/src/main/java/io/github/shuoros/peoplify/web/rest/UserResource.java deleted file mode 100644 index cb291df..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/UserResource.java +++ /dev/null @@ -1,212 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.github.shuoros.peoplify.service.MailService; -import io.github.shuoros.peoplify.service.UserService; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.web.rest.errors.BadRequestAlertException; -import io.github.shuoros.peoplify.web.rest.errors.EmailAlreadyUsedException; -import io.github.shuoros.peoplify.web.rest.errors.LoginAlreadyUsedException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; -import java.util.Collections; -import javax.validation.Valid; -import javax.validation.constraints.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import tech.jhipster.web.util.HeaderUtil; -import tech.jhipster.web.util.PaginationUtil; -import tech.jhipster.web.util.ResponseUtil; - -/** - * REST controller for managing users. - *

- * This class accesses the {@link io.github.shuoros.peoplify.domain.User} entity, and needs to fetch its collection of authorities. - *

- * For a normal use-case, it would be better to have an eager relationship between User and Authority, - * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join - * which would be good for performance. - *

- * We use a View Model and a DTO for 3 reasons: - *

    - *
  • We want to keep a lazy association between the user and the authorities, because people will - * quite often do relationships with the user, and we don't want them to get the authorities all - * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' - * application because of this use-case.
  • - *
  • Not having an outer join causes n+1 requests to the database. This is not a real issue as - * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, - * but then all authorities come from the cache, so in fact it's much better than doing an outer join - * (which will get lots of data from the database, for each HTTP call).
  • - *
  • As this manages users, for security reasons, we'd rather have a DTO layer.
  • - *
- *

- * Another option would be to have a specific JPA entity graph to handle this case. - */ -@RestController -@RequestMapping("/api/admin") -public class UserResource { - - private static final List ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( - Arrays.asList( - "id", - "login", - "firstName", - "lastName", - "email", - "activated", - "langKey", - "createdBy", - "createdDate", - "lastModifiedBy", - "lastModifiedDate" - ) - ); - - private final Logger log = LoggerFactory.getLogger(UserResource.class); - - @Value("${jhipster.clientApp.name}") - private String applicationName; - - private final UserService userService; - - private final UserRepository userRepository; - - private final MailService mailService; - - public UserResource(UserService userService, UserRepository userRepository, MailService mailService) { - this.userService = userService; - this.userRepository = userRepository; - this.mailService = mailService; - } - - /** - * {@code POST /admin/users} : Creates a new user. - *

- * Creates a new user if the login and email are not already used, and sends an - * mail with an activation link. - * The user needs to be activated on creation. - * - * @param userDTO the user to create. - * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use. - * @throws URISyntaxException if the Location URI syntax is incorrect. - * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use. - */ - @PostMapping("/users") - @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity createUser(@Valid @RequestBody AdminUserDTO userDTO) throws URISyntaxException { - log.debug("REST request to save User : {}", userDTO); - - if (userDTO.getId() != null) { - throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists"); - // Lowercase the user login before comparing with database - } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) { - throw new LoginAlreadyUsedException(); - } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) { - throw new EmailAlreadyUsedException(); - } else { - User newUser = userService.createUser(userDTO); - mailService.sendCreationEmail(newUser); - return ResponseEntity - .created(new URI("/api/admin/users/" + newUser.getLogin())) - .headers( - HeaderUtil.createAlert(applicationName, "A user is created with identifier " + newUser.getLogin(), newUser.getLogin()) - ) - .body(newUser); - } - } - - /** - * {@code PUT /admin/users} : Updates an existing User. - * - * @param userDTO the user to update. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user. - * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use. - * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use. - */ - @PutMapping("/users") - @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity updateUser(@Valid @RequestBody AdminUserDTO userDTO) { - log.debug("REST request to update User : {}", userDTO); - Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); - if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { - throw new EmailAlreadyUsedException(); - } - existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()); - if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { - throw new LoginAlreadyUsedException(); - } - Optional updatedUser = userService.updateUser(userDTO); - - return ResponseUtil.wrapOrNotFound( - updatedUser, - HeaderUtil.createAlert(applicationName, "A user is updated with identifier " + userDTO.getLogin(), userDTO.getLogin()) - ); - } - - /** - * {@code GET /admin/users} : get all users with all the details - calling this are only allowed for the administrators. - * - * @param pageable the pagination information. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. - */ - @GetMapping("/users") - @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity> getAllUsers(@org.springdoc.api.annotations.ParameterObject Pageable pageable) { - log.debug("REST request to get all User for an admin"); - if (!onlyContainsAllowedProperties(pageable)) { - return ResponseEntity.badRequest().build(); - } - - final Page page = userService.getAllManagedUsers(pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); - return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); - } - - private boolean onlyContainsAllowedProperties(Pageable pageable) { - return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); - } - - /** - * {@code GET /admin/users/:login} : get the "login" user. - * - * @param login the login of the user to find. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. - */ - @GetMapping("/users/{login}") - @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity getUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { - log.debug("REST request to get User : {}", login); - return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(AdminUserDTO::new)); - } - - /** - * {@code DELETE /admin/users/:login} : delete the "login" User. - * - * @param login the login of the user to delete. - * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. - */ - @DeleteMapping("/users/{login}") - @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") - public ResponseEntity deleteUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { - log.debug("REST request to delete User: {}", login); - userService.deleteUser(login); - return ResponseEntity - .noContent() - .headers(HeaderUtil.createAlert(applicationName, "A user is deleted with identifier " + login, login)) - .build(); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/BadRequestAlertException.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/BadRequestAlertException.java deleted file mode 100644 index 9c56fbe..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/BadRequestAlertException.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Status; - -@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep -public class BadRequestAlertException extends AbstractThrowableProblem { - - private static final long serialVersionUID = 1L; - - private final String entityName; - - private final String errorKey; - - public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) { - this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey); - } - - public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) { - super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey)); - this.entityName = entityName; - this.errorKey = errorKey; - } - - public String getEntityName() { - return entityName; - } - - public String getErrorKey() { - return errorKey; - } - - private static Map getAlertParameters(String entityName, String errorKey) { - Map parameters = new HashMap<>(); - parameters.put("message", "error." + errorKey); - parameters.put("params", entityName); - return parameters; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/EmailAlreadyUsedException.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/EmailAlreadyUsedException.java deleted file mode 100644 index 1a87ce2..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/EmailAlreadyUsedException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep -public class EmailAlreadyUsedException extends BadRequestAlertException { - - private static final long serialVersionUID = 1L; - - public EmailAlreadyUsedException() { - super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ErrorConstants.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ErrorConstants.java deleted file mode 100644 index c9a4f43..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ErrorConstants.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import java.net.URI; - -public final class ErrorConstants { - - public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; - public static final String ERR_VALIDATION = "error.validation"; - public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; - public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); - public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); - public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); - public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); - public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); - - private ErrorConstants() {} -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslator.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslator.java deleted file mode 100644 index 707fa20..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslator.java +++ /dev/null @@ -1,222 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.env.Environment; -import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.dao.DataAccessException; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageConversionException; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.DefaultProblem; -import org.zalando.problem.Problem; -import org.zalando.problem.ProblemBuilder; -import org.zalando.problem.Status; -import org.zalando.problem.StatusType; -import org.zalando.problem.spring.web.advice.ProblemHandling; -import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; -import org.zalando.problem.violations.ConstraintViolationProblem; -import tech.jhipster.config.JHipsterConstants; -import tech.jhipster.web.util.HeaderUtil; - -/** - * Controller advice to translate the server side exceptions to client-friendly json structures. - * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). - */ -@ControllerAdvice -public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { - - private static final String FIELD_ERRORS_KEY = "fieldErrors"; - private static final String MESSAGE_KEY = "message"; - private static final String PATH_KEY = "path"; - private static final String VIOLATIONS_KEY = "violations"; - - @Value("${jhipster.clientApp.name}") - private String applicationName; - - private final Environment env; - - public ExceptionTranslator(Environment env) { - this.env = env; - } - - /** - * Post-process the Problem payload to add the message key for the front-end if needed. - */ - @Override - public ResponseEntity process(@Nullable ResponseEntity entity, NativeWebRequest request) { - if (entity == null) { - return null; - } - Problem problem = entity.getBody(); - if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { - return entity; - } - - HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); - String requestUri = nativeRequest != null ? nativeRequest.getRequestURI() : StringUtils.EMPTY; - ProblemBuilder builder = Problem - .builder() - .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()) - .withStatus(problem.getStatus()) - .withTitle(problem.getTitle()) - .with(PATH_KEY, requestUri); - - if (problem instanceof ConstraintViolationProblem) { - builder - .with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()) - .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION); - } else { - builder.withCause(((DefaultProblem) problem).getCause()).withDetail(problem.getDetail()).withInstance(problem.getInstance()); - problem.getParameters().forEach(builder::with); - if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) { - builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode()); - } - } - return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode()); - } - - @Override - public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { - BindingResult result = ex.getBindingResult(); - List fieldErrors = result - .getFieldErrors() - .stream() - .map(f -> - new FieldErrorVM( - f.getObjectName().replaceFirst("DTO$", ""), - f.getField(), - StringUtils.isNotBlank(f.getDefaultMessage()) ? f.getDefaultMessage() : f.getCode() - ) - ) - .collect(Collectors.toList()); - - Problem problem = Problem - .builder() - .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE) - .withTitle("Method argument not valid") - .withStatus(defaultConstraintViolationStatus()) - .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION) - .with(FIELD_ERRORS_KEY, fieldErrors) - .build(); - return create(ex, problem, request); - } - - @ExceptionHandler - public ResponseEntity handleEmailAlreadyUsedException( - io.github.shuoros.peoplify.service.EmailAlreadyUsedException ex, - NativeWebRequest request - ) { - EmailAlreadyUsedException problem = new EmailAlreadyUsedException(); - return create( - problem, - request, - HeaderUtil.createFailureAlert(applicationName, false, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) - ); - } - - @ExceptionHandler - public ResponseEntity handleUsernameAlreadyUsedException( - io.github.shuoros.peoplify.service.UsernameAlreadyUsedException ex, - NativeWebRequest request - ) { - LoginAlreadyUsedException problem = new LoginAlreadyUsedException(); - return create( - problem, - request, - HeaderUtil.createFailureAlert(applicationName, false, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) - ); - } - - @ExceptionHandler - public ResponseEntity handleInvalidPasswordException( - io.github.shuoros.peoplify.service.InvalidPasswordException ex, - NativeWebRequest request - ) { - return create(new InvalidPasswordException(), request); - } - - @ExceptionHandler - public ResponseEntity handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { - return create( - ex, - request, - HeaderUtil.createFailureAlert(applicationName, false, ex.getEntityName(), ex.getErrorKey(), ex.getMessage()) - ); - } - - @ExceptionHandler - public ResponseEntity handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) { - Problem problem = Problem.builder().withStatus(Status.CONFLICT).with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE).build(); - return create(ex, problem, request); - } - - @Override - public ProblemBuilder prepare(final Throwable throwable, final StatusType status, final URI type) { - Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); - - if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { - if (throwable instanceof HttpMessageConversionException) { - return Problem - .builder() - .withType(type) - .withTitle(status.getReasonPhrase()) - .withStatus(status) - .withDetail("Unable to convert http message") - .withCause( - Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) - ); - } - if (throwable instanceof DataAccessException) { - return Problem - .builder() - .withType(type) - .withTitle(status.getReasonPhrase()) - .withStatus(status) - .withDetail("Failure during data access") - .withCause( - Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) - ); - } - if (containsPackageName(throwable.getMessage())) { - return Problem - .builder() - .withType(type) - .withTitle(status.getReasonPhrase()) - .withStatus(status) - .withDetail("Unexpected runtime exception") - .withCause( - Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) - ); - } - } - - return Problem - .builder() - .withType(type) - .withTitle(status.getReasonPhrase()) - .withStatus(status) - .withDetail(throwable.getMessage()) - .withCause( - Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) - ); - } - - private boolean containsPackageName(String message) { - // This list is for sure not complete - return StringUtils.containsAny(message, "org.", "java.", "net.", "javax.", "com.", "io.", "de.", "io.github.shuoros.peoplify"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/FieldErrorVM.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/FieldErrorVM.java deleted file mode 100644 index 74e454d..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/FieldErrorVM.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import java.io.Serializable; - -public class FieldErrorVM implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String objectName; - - private final String field; - - private final String message; - - public FieldErrorVM(String dto, String field, String message) { - this.objectName = dto; - this.field = field; - this.message = message; - } - - public String getObjectName() { - return objectName; - } - - public String getField() { - return field; - } - - public String getMessage() { - return message; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/InvalidPasswordException.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/InvalidPasswordException.java deleted file mode 100644 index 85fef43..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/InvalidPasswordException.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Status; - -@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep -public class InvalidPasswordException extends AbstractThrowableProblem { - - private static final long serialVersionUID = 1L; - - public InvalidPasswordException() { - super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/LoginAlreadyUsedException.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/LoginAlreadyUsedException.java deleted file mode 100644 index 90f469c..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/LoginAlreadyUsedException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep -public class LoginAlreadyUsedException extends BadRequestAlertException { - - private static final long serialVersionUID = 1L; - - public LoginAlreadyUsedException() { - super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/package-info.java b/src/main/java/io/github/shuoros/peoplify/web/rest/errors/package-info.java deleted file mode 100644 index 43bfcac..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/errors/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Specific errors used with Zalando's "problem-spring-web" library. - * - * More information on https://github.com/zalando/problem-spring-web - */ -package io.github.shuoros.peoplify.web.rest.errors; diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/package-info.java b/src/main/java/io/github/shuoros/peoplify/web/rest/package-info.java deleted file mode 100644 index 83a7d05..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Spring MVC REST controllers. - */ -package io.github.shuoros.peoplify.web.rest; diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/KeyAndPasswordVM.java b/src/main/java/io/github/shuoros/peoplify/web/rest/vm/KeyAndPasswordVM.java deleted file mode 100644 index dd1cb5d..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/KeyAndPasswordVM.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.vm; - -/** - * View Model object for storing the user's key and password. - */ -public class KeyAndPasswordVM { - - private String key; - - private String newPassword; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getNewPassword() { - return newPassword; - } - - public void setNewPassword(String newPassword) { - this.newPassword = newPassword; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/LoginVM.java b/src/main/java/io/github/shuoros/peoplify/web/rest/vm/LoginVM.java deleted file mode 100644 index 967b025..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/LoginVM.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.vm; - -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - -/** - * View Model object for storing a user's credentials. - */ -public class LoginVM { - - @NotNull - @Size(min = 1, max = 50) - private String username; - - @NotNull - @Size(min = 4, max = 100) - private String password; - - private boolean rememberMe; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public boolean isRememberMe() { - return rememberMe; - } - - public void setRememberMe(boolean rememberMe) { - this.rememberMe = rememberMe; - } - - // prettier-ignore - @Override - public String toString() { - return "LoginVM{" + - "username='" + username + '\'' + - ", rememberMe=" + rememberMe + - '}'; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/ManagedUserVM.java b/src/main/java/io/github/shuoros/peoplify/web/rest/vm/ManagedUserVM.java deleted file mode 100644 index 48d6c09..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/ManagedUserVM.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.vm; - -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import javax.validation.constraints.Size; - -/** - * View Model extending the AdminUserDTO, which is meant to be used in the user management UI. - */ -public class ManagedUserVM extends AdminUserDTO { - - public static final int PASSWORD_MIN_LENGTH = 4; - - public static final int PASSWORD_MAX_LENGTH = 100; - - @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) - private String password; - - public ManagedUserVM() { - // Empty constructor needed for Jackson. - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - // prettier-ignore - @Override - public String toString() { - return "ManagedUserVM{" + super.toString() + "} "; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/package-info.java b/src/main/java/io/github/shuoros/peoplify/web/rest/vm/package-info.java deleted file mode 100644 index fcbbdb7..0000000 --- a/src/main/java/io/github/shuoros/peoplify/web/rest/vm/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * View Models used by Spring MVC REST controllers. - */ -package io.github.shuoros.peoplify.web.rest.vm; diff --git a/.mvn/jvm.config b/src/main/resources/application.properties similarity index 100% rename from .mvn/jvm.config rename to src/main/resources/application.properties diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt deleted file mode 100644 index e0bc55a..0000000 --- a/src/main/resources/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ - - ${AnsiColor.GREEN} β–ˆβ–ˆβ•—${AnsiColor.RED} β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— - ${AnsiColor.GREEN} β–ˆβ–ˆβ•‘${AnsiColor.RED} β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ•β•β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β•β•β• β•šβ•β•β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β•β•β•β• β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•— - ${AnsiColor.GREEN} β–ˆβ–ˆβ•‘${AnsiColor.RED} β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• - ${AnsiColor.GREEN}β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘${AnsiColor.RED} β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β•β•β• β•šβ•β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘ - ${AnsiColor.GREEN}β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•${AnsiColor.RED} β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•— - ${AnsiColor.GREEN} β•šβ•β•β•β•β•β• ${AnsiColor.RED} β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β•β•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•β•β•β•β•β• β•šβ•β• β•šβ•β• - -${AnsiColor.BRIGHT_BLUE}:: JHipster πŸ€“ :: Running Spring Boot ${spring-boot.version} :: -:: https://www.jhipster.tech ::${AnsiColor.DEFAULT} diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml deleted file mode 100644 index 7bccde3..0000000 --- a/src/main/resources/config/application-dev.yml +++ /dev/null @@ -1,107 +0,0 @@ -# =================================================================== -# Spring Boot configuration for the "dev" profile. -# -# This configuration overrides the application.yml file. -# -# More information on profiles: https://www.jhipster.tech/profiles/ -# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -logging: - level: - ROOT: DEBUG - tech.jhipster: DEBUG - org.hibernate.SQL: DEBUG - io.github.shuoros.peoplify: DEBUG - -spring: - devtools: - restart: - enabled: true - additional-exclude: static/** - livereload: - enabled: false # we use Webpack dev server + BrowserSync for livereload - jackson: - serialization: - indent-output: true - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: jdbc:postgresql://localhost:5432/peoplify - username: peoplify - password: - hikari: - poolName: Hikari - auto-commit: false - jpa: - database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect - liquibase: - # Remove 'faker' if you do not want the sample data to be loaded automatically - contexts: dev, faker - mail: - host: localhost - port: 25 - username: - password: - messages: - cache-duration: PT1S # 1 second, see the ISO 8601 standard - thymeleaf: - cache: false - -server: - port: 8080 - -# =================================================================== -# JHipster specific properties -# -# Full reference is available at: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -jhipster: - cache: # Cache configuration - ehcache: # Ehcache configuration - time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache - max-entries: 100 # Number of objects in each cache entry - # CORS is only enabled by default with the "dev" profile - cors: - # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+) - allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000' - # Enable CORS when running in GitHub Codespaces - allowed-origin-patterns: 'https://*.githubpreview.dev' - allowed-methods: '*' - allowed-headers: '*' - exposed-headers: 'Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params' - allow-credentials: true - max-age: 1800 - security: - authentication: - jwt: - # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) - base64-secret: ZDUxMTM5YWRmNjBiZDhmOTJlMTkxNWVmM2VlOTJhYTZiMjEyYjZiZDJlOGRhMmE5NmJjODFkYTI1MjQ2MzdjODdhNjk3OTg4YTA2MDk0Mjk1NDVkZWU2MGFlYzFmOTEwNzRlODVkZTI4NjVjNjM4YTE1ODY5NzA4ZmZhNzE5NDk= - # Token is valid 24 hours - token-validity-in-seconds: 86400 - token-validity-in-seconds-for-remember-me: 2592000 - mail: # specific JHipster mail property, for standard properties see MailProperties - base-url: http://127.0.0.1:8080 - logging: - use-json-format: false # By default, logs are not in Json format - logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration - enabled: false - host: localhost - port: 5000 - queue-size: 512 -# =================================================================== -# Application specific properties -# Add your own application properties here, see the ApplicationProperties class -# to have type-safe configuration, like in the JHipsterProperties above -# -# More documentation is available at: -# https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# application: diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml deleted file mode 100644 index ee62c22..0000000 --- a/src/main/resources/config/application-prod.yml +++ /dev/null @@ -1,126 +0,0 @@ -# =================================================================== -# Spring Boot configuration for the "prod" profile. -# -# This configuration overrides the application.yml file. -# -# More information on profiles: https://www.jhipster.tech/profiles/ -# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -logging: - level: - ROOT: INFO - tech.jhipster: INFO - io.github.shuoros.peoplify: INFO - -management: - metrics: - export: - prometheus: - enabled: false - -spring: - devtools: - restart: - enabled: false - livereload: - enabled: false - datasource: - type: com.zaxxer.hikari.HikariDataSource - url: jdbc:postgresql://localhost:5432/peoplify - username: peoplify - password: - hikari: - poolName: Hikari - auto-commit: false - jpa: - database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect - # Replace by 'prod, faker' to add the faker context and have sample data loaded in production - liquibase: - contexts: prod - mail: - host: localhost - port: 25 - username: - password: - thymeleaf: - cache: true - -# =================================================================== -# To enable TLS in production, generate a certificate using: -# keytool -genkey -alias peoplify -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 -# -# You can also use Let's Encrypt: -# See details in topic "Create a Java Keystore (.JKS) from Let's Encrypt Certificates" on https://maximilian-boehm.com/en-gb/blog -# -# Then, modify the server.ssl properties so your "server" configuration looks like: -# -# server: -# port: 443 -# ssl: -# key-store: classpath:config/tls/keystore.p12 -# key-store-password: password -# key-store-type: PKCS12 -# key-alias: selfsigned -# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/) -# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA -# =================================================================== -server: - port: 8080 - shutdown: graceful # see https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-graceful-shutdown - compression: - enabled: true - mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json,image/svg+xml - min-response-size: 1024 - -# =================================================================== -# JHipster specific properties -# -# Full reference is available at: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -jhipster: - http: - cache: # Used by the CachingHttpHeadersFilter - timeToLiveInDays: 1461 - cache: # Cache configuration - ehcache: # Ehcache configuration - time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache - max-entries: 1000 # Number of objects in each cache entry - security: - authentication: - jwt: - # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) - # As this is the PRODUCTION configuration, you MUST change the default key, and store it securely: - # - In the JHipster Registry (which includes a Spring Cloud Config server) - # - In a separate `application-prod.yml` file, in the same folder as your executable JAR file - # - In the `JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET` environment variable - base64-secret: ZDUxMTM5YWRmNjBiZDhmOTJlMTkxNWVmM2VlOTJhYTZiMjEyYjZiZDJlOGRhMmE5NmJjODFkYTI1MjQ2MzdjODdhNjk3OTg4YTA2MDk0Mjk1NDVkZWU2MGFlYzFmOTEwNzRlODVkZTI4NjVjNjM4YTE1ODY5NzA4ZmZhNzE5NDk= - # Token is valid 24 hours - token-validity-in-seconds: 86400 - token-validity-in-seconds-for-remember-me: 2592000 - mail: # specific JHipster mail property, for standard properties see MailProperties - base-url: http://my-server-url-to-change # Modify according to your server's URL - logging: - use-json-format: false # By default, logs are not in Json format - logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration - enabled: false - host: localhost - port: 5000 - queue-size: 512 -# =================================================================== -# Application specific properties -# Add your own application properties here, see the ApplicationProperties class -# to have type-safe configuration, like in the JHipsterProperties above -# -# More documentation is available at: -# https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# application: diff --git a/src/main/resources/config/application-tls.yml b/src/main/resources/config/application-tls.yml deleted file mode 100644 index 039f6f4..0000000 --- a/src/main/resources/config/application-tls.yml +++ /dev/null @@ -1,19 +0,0 @@ -# =================================================================== -# Activate this profile to enable TLS and HTTP/2. -# -# JHipster has generated a self-signed certificate, which will be used to encrypt traffic. -# As your browser will not understand this certificate, you will need to import it. -# -# Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag -# at chrome://flags/#allow-insecure-localhost -# =================================================================== -server: - ssl: - key-store: classpath:config/tls/keystore.p12 - key-store-password: password - key-store-type: PKCS12 - key-alias: selfsigned - ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - enabled-protocols: TLSv1.2 - http2: - enabled: true diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml deleted file mode 100644 index 60bba0c..0000000 --- a/src/main/resources/config/application.yml +++ /dev/null @@ -1,212 +0,0 @@ -# =================================================================== -# Spring Boot configuration. -# -# This configuration will be overridden by the Spring profile you use, -# for example application-dev.yml if you use the "dev" profile. -# -# More information on profiles: https://www.jhipster.tech/profiles/ -# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - ---- -# Conditionally disable springdoc on missing api-docs profile -spring: - config: - activate: - on-profile: '!api-docs' -springdoc: - api-docs: - enabled: false ---- -management: - endpoints: - web: - base-path: /management - exposure: - include: - [ - 'configprops', - 'env', - 'health', - 'info', - 'jhimetrics', - 'jhiopenapigroups', - 'logfile', - 'loggers', - 'prometheus', - 'threaddump', - 'caches', - 'liquibase', - ] - endpoint: - health: - show-details: when_authorized - roles: 'ROLE_ADMIN' - probes: - enabled: true - group: - liveness: - include: livenessState - readiness: - include: readinessState,db - jhimetrics: - enabled: true - info: - git: - mode: full - env: - enabled: true - health: - mail: - enabled: false # When using the MailService, configure an SMTP server and set this to true - metrics: - export: - # Prometheus is the default metrics backend - prometheus: - enabled: true - step: 60 - enable: - http: true - jvm: true - logback: true - process: true - system: true - distribution: - percentiles-histogram: - all: true - percentiles: - all: 0, 0.5, 0.75, 0.95, 0.99, 1.0 - tags: - application: ${spring.application.name} - web: - server: - request: - autotime: - enabled: true - -spring: - application: - name: peoplify - profiles: - # The commented value for `active` can be replaced with valid Spring profiles to load. - # Otherwise, it will be filled in by maven when building the JAR file - # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` - active: #spring.profiles.active# - group: - dev: - - dev - - api-docs - # Uncomment to activate TLS for the dev profile - #- tls - jmx: - enabled: false - data: - jpa: - repositories: - bootstrap-mode: deferred - jpa: - open-in-view: false - properties: - hibernate.jdbc.time_zone: UTC - hibernate.id.new_generator_mappings: true - hibernate.connection.provider_disables_autocommit: true - hibernate.cache.use_second_level_cache: true - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: false - # modify batch size as necessary - hibernate.jdbc.batch_size: 25 - hibernate.order_inserts: true - hibernate.order_updates: true - hibernate.query.fail_on_pagination_over_collection_fetch: true - hibernate.query.in_clause_parameter_padding: true - hibernate: - ddl-auto: none - naming: - physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - messages: - basename: i18n/messages - main: - allow-bean-definition-overriding: true - mvc: - pathmatch: - matching-strategy: ant_path_matcher - task: - execution: - thread-name-prefix: peoplify-task- - pool: - core-size: 2 - max-size: 50 - queue-capacity: 10000 - scheduling: - thread-name-prefix: peoplify-scheduling- - pool: - size: 2 - thymeleaf: - mode: HTML - output: - ansi: - console-available: true - -server: - servlet: - session: - cookie: - http-only: true - -springdoc: - show-actuator: true - -# Properties to be exposed on the /info management endpoint -info: - # Comma separated list of profiles that will trigger the ribbon to show - display-ribbon-on-profiles: 'dev' - -# =================================================================== -# JHipster specific properties -# -# Full reference is available at: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -jhipster: - clientApp: - name: 'peoplifyApp' - # By default CORS is disabled. Uncomment to enable. - # cors: - # allowed-origins: "http://localhost:8100,http://localhost:9000" - # allowed-methods: "*" - # allowed-headers: "*" - # exposed-headers: "Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params" - # allow-credentials: true - # max-age: 1800 - mail: - from: peoplify@localhost - api-docs: - default-include-pattern: ${server.servlet.context-path:}/api/** - management-include-pattern: ${server.servlet.context-path:}/management/** - title: Peoplify API - description: Peoplify API documentation - version: 0.0.1 - terms-of-service-url: - contact-name: - contact-url: - contact-email: - license: unlicensed - license-url: - security: -# =================================================================== -# Application specific properties -# Add your own application properties here, see the ApplicationProperties class -# to have type-safe configuration, like in the JHipsterProperties above -# -# More documentation is available at: -# https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# application: diff --git a/src/main/resources/config/bootstrap-prod.yml b/src/main/resources/config/bootstrap-prod.yml deleted file mode 100644 index 1047df3..0000000 --- a/src/main/resources/config/bootstrap-prod.yml +++ /dev/null @@ -1,6 +0,0 @@ -# =================================================================== -# Spring Cloud Config bootstrap configuration for the "prod" profile -# =================================================================== - -spring: - cloud: diff --git a/src/main/resources/config/bootstrap.yml b/src/main/resources/config/bootstrap.yml deleted file mode 100644 index 6efbf88..0000000 --- a/src/main/resources/config/bootstrap.yml +++ /dev/null @@ -1,14 +0,0 @@ -# =================================================================== -# Spring Cloud Config bootstrap configuration for the "dev" profile -# In prod profile, properties will be overwritten by the ones defined in bootstrap-prod.yml -# =================================================================== - -spring: - application: - name: peoplify - profiles: - # The commented value for `active` can be replaced with valid Spring profiles to load. - # Otherwise, it will be filled in by maven when building the JAR file - # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` - active: #spring.profiles.active# - cloud: diff --git a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml deleted file mode 100644 index eaac648..0000000 --- a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/config/liquibase/data/authority.csv b/src/main/resources/config/liquibase/data/authority.csv deleted file mode 100644 index af5c6df..0000000 --- a/src/main/resources/config/liquibase/data/authority.csv +++ /dev/null @@ -1,3 +0,0 @@ -name -ROLE_ADMIN -ROLE_USER diff --git a/src/main/resources/config/liquibase/data/user.csv b/src/main/resources/config/liquibase/data/user.csv deleted file mode 100644 index fbc52da..0000000 --- a/src/main/resources/config/liquibase/data/user.csv +++ /dev/null @@ -1,3 +0,0 @@ -id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by -1;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system -2;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system diff --git a/src/main/resources/config/liquibase/data/user_authority.csv b/src/main/resources/config/liquibase/data/user_authority.csv deleted file mode 100644 index 01dbdef..0000000 --- a/src/main/resources/config/liquibase/data/user_authority.csv +++ /dev/null @@ -1,4 +0,0 @@ -user_id;authority_name -1;ROLE_ADMIN -1;ROLE_USER -2;ROLE_USER diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml deleted file mode 100644 index a929b9c..0000000 --- a/src/main/resources/config/liquibase/master.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties deleted file mode 100644 index fba158b..0000000 --- a/src/main/resources/i18n/messages.properties +++ /dev/null @@ -1,21 +0,0 @@ -# Error page -error.title=Your request cannot be processed -error.subtitle=Sorry, an error has occurred. -error.status=Status: -error.message=Message: - -# Activation email -email.activation.title=peoplify account activation is required -email.activation.greeting=Dear {0} -email.activation.text1=Your peoplify account has been created, please click on the URL below to activate it: -email.activation.text2=Regards, -email.signature=peoplify Team. - -# Creation email -email.creation.text1=Your peoplify account has been created, please click on the URL below to access it: - -# Reset email -email.reset.title=peoplify password reset -email.reset.greeting=Dear {0} -email.reset.text1=For your peoplify account a password reset was requested, please click on the URL below to reset it: -email.reset.text2=Regards, diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml deleted file mode 100644 index d7aec03..0000000 --- a/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html deleted file mode 100644 index 690e856..0000000 --- a/src/main/resources/templates/error.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - Your request cannot be processed - - - -

-

Your request cannot be processed :(

- -

Sorry, an error has occurred.

- - Status:  ()
- - Message: 
-
-
- - diff --git a/src/main/resources/templates/mail/activationEmail.html b/src/main/resources/templates/mail/activationEmail.html deleted file mode 100644 index 0bb53a2..0000000 --- a/src/main/resources/templates/mail/activationEmail.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - JHipster activation - - - - -

Dear

-

Your JHipster account has been created, please click on the URL below to activate it:

-

- Activation link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/main/resources/templates/mail/creationEmail.html b/src/main/resources/templates/mail/creationEmail.html deleted file mode 100644 index 4e52898..0000000 --- a/src/main/resources/templates/mail/creationEmail.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - JHipster creation - - - - -

Dear

-

Your JHipster account has been created, please click on the URL below to access it:

-

- Login link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/main/resources/templates/mail/passwordResetEmail.html b/src/main/resources/templates/mail/passwordResetEmail.html deleted file mode 100644 index 290ca6d..0000000 --- a/src/main/resources/templates/mail/passwordResetEmail.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - JHipster password reset - - - - -

Dear

-

- For your JHipster account a password reset was requested, please click on the URL below to reset it: -

-

- Login link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/test/java/io/github/shuoros/peoplify/IntegrationTest.java b/src/test/java/io/github/shuoros/peoplify/IntegrationTest.java deleted file mode 100644 index 6b38ca4..0000000 --- a/src/test/java/io/github/shuoros/peoplify/IntegrationTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.shuoros.peoplify; - -import io.github.shuoros.peoplify.PeoplifyApp; -import io.github.shuoros.peoplify.config.AsyncSyncConfiguration; -import io.github.shuoros.peoplify.config.EmbeddedSQL; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; - -/** - * Base composite annotation for integration tests. - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@SpringBootTest(classes = { PeoplifyApp.class, AsyncSyncConfiguration.class }) -@EmbeddedSQL -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public @interface IntegrationTest { -} diff --git a/src/test/java/io/github/shuoros/peoplify/PeoplifyApplicationTests.java b/src/test/java/io/github/shuoros/peoplify/PeoplifyApplicationTests.java new file mode 100644 index 0000000..150747a --- /dev/null +++ b/src/test/java/io/github/shuoros/peoplify/PeoplifyApplicationTests.java @@ -0,0 +1,13 @@ +package io.github.shuoros.peoplify; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PeoplifyApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/io/github/shuoros/peoplify/TechnicalStructureTest.java b/src/test/java/io/github/shuoros/peoplify/TechnicalStructureTest.java deleted file mode 100644 index a79cc05..0000000 --- a/src/test/java/io/github/shuoros/peoplify/TechnicalStructureTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.shuoros.peoplify; - -import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.belongToAnyOf; -import static com.tngtech.archunit.library.Architectures.layeredArchitecture; - -import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; - -@AnalyzeClasses(packagesOf = PeoplifyApp.class, importOptions = DoNotIncludeTests.class) -class TechnicalStructureTest { - - // prettier-ignore - @ArchTest - static final ArchRule respectsTechnicalArchitectureLayers = layeredArchitecture() - .layer("Config").definedBy("..config..") - .layer("Web").definedBy("..web..") - .optionalLayer("Service").definedBy("..service..") - .layer("Security").definedBy("..security..") - .layer("Persistence").definedBy("..repository..") - .layer("Domain").definedBy("..domain..") - - .whereLayer("Config").mayNotBeAccessedByAnyLayer() - .whereLayer("Web").mayOnlyBeAccessedByLayers("Config") - .whereLayer("Service").mayOnlyBeAccessedByLayers("Web", "Config") - .whereLayer("Security").mayOnlyBeAccessedByLayers("Config", "Service", "Web") - .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service", "Security", "Web", "Config") - .whereLayer("Domain").mayOnlyBeAccessedByLayers("Persistence", "Service", "Security", "Web", "Config") - - .ignoreDependency(belongToAnyOf(PeoplifyApp.class), alwaysTrue()) - .ignoreDependency(alwaysTrue(), belongToAnyOf( - io.github.shuoros.peoplify.config.Constants.class, - io.github.shuoros.peoplify.config.ApplicationProperties.class - )); -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/AsyncSyncConfiguration.java b/src/test/java/io/github/shuoros/peoplify/config/AsyncSyncConfiguration.java deleted file mode 100644 index d689aed..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/AsyncSyncConfiguration.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.util.concurrent.Executor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.task.SyncTaskExecutor; - -@Configuration -public class AsyncSyncConfiguration { - - @Bean(name = "taskExecutor") - public Executor taskExecutor() { - return new SyncTaskExecutor(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/EmbeddedSQL.java b/src/test/java/io/github/shuoros/peoplify/config/EmbeddedSQL.java deleted file mode 100644 index 5fbfc95..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/EmbeddedSQL.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface EmbeddedSQL { -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/PostgreSqlTestContainer.java b/src/test/java/io/github/shuoros/peoplify/config/PostgreSqlTestContainer.java deleted file mode 100644 index 42ca258..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/PostgreSqlTestContainer.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.util.Collections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.JdbcDatabaseContainer; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.containers.output.Slf4jLogConsumer; - -public class PostgreSqlTestContainer implements SqlTestContainer { - - private static final Logger log = LoggerFactory.getLogger(PostgreSqlTestContainer.class); - - private PostgreSQLContainer postgreSQLContainer; - - @Override - public void destroy() { - if (null != postgreSQLContainer && postgreSQLContainer.isRunning()) { - postgreSQLContainer.stop(); - } - } - - @Override - public void afterPropertiesSet() { - if (null == postgreSQLContainer) { - postgreSQLContainer = - new PostgreSQLContainer<>("postgres:14.5") - .withDatabaseName("peoplify") - .withTmpFs(Collections.singletonMap("/testtmpfs", "rw")) - .withLogConsumer(new Slf4jLogConsumer(log)) - .withReuse(true); - } - if (!postgreSQLContainer.isRunning()) { - postgreSQLContainer.start(); - } - } - - @Override - public JdbcDatabaseContainer getTestContainer() { - return postgreSQLContainer; - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/SpringBootTestClassOrderer.java b/src/test/java/io/github/shuoros/peoplify/config/SpringBootTestClassOrderer.java deleted file mode 100644 index 82567b3..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/SpringBootTestClassOrderer.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import io.github.shuoros.peoplify.IntegrationTest; -import java.util.Comparator; -import org.junit.jupiter.api.ClassDescriptor; -import org.junit.jupiter.api.ClassOrderer; -import org.junit.jupiter.api.ClassOrdererContext; - -public class SpringBootTestClassOrderer implements ClassOrderer { - - @Override - public void orderClasses(ClassOrdererContext context) { - context.getClassDescriptors().sort(Comparator.comparingInt(SpringBootTestClassOrderer::getOrder)); - } - - private static int getOrder(ClassDescriptor classDescriptor) { - if (classDescriptor.findAnnotation(IntegrationTest.class).isPresent()) { - return 2; - } - return 1; - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/SqlTestContainer.java b/src/test/java/io/github/shuoros/peoplify/config/SqlTestContainer.java deleted file mode 100644 index 53f2714..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/SqlTestContainer.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.testcontainers.containers.JdbcDatabaseContainer; - -public interface SqlTestContainer extends InitializingBean, DisposableBean { - JdbcDatabaseContainer getTestContainer(); -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/TestContainersSpringContextCustomizerFactory.java b/src/test/java/io/github/shuoros/peoplify/config/TestContainersSpringContextCustomizerFactory.java deleted file mode 100644 index 380156b..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/TestContainersSpringContextCustomizerFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import java.util.Arrays; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.test.context.ContextConfigurationAttributes; -import org.springframework.test.context.ContextCustomizer; -import org.springframework.test.context.ContextCustomizerFactory; -import tech.jhipster.config.JHipsterConstants; - -public class TestContainersSpringContextCustomizerFactory implements ContextCustomizerFactory { - - private Logger log = LoggerFactory.getLogger(TestContainersSpringContextCustomizerFactory.class); - - private static SqlTestContainer devTestContainer; - private static SqlTestContainer prodTestContainer; - - @Override - public ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { - return (context, mergedConfig) -> { - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - TestPropertyValues testValues = TestPropertyValues.empty(); - EmbeddedSQL sqlAnnotation = AnnotatedElementUtils.findMergedAnnotation(testClass, EmbeddedSQL.class); - if (null != sqlAnnotation) { - log.debug("detected the EmbeddedSQL annotation on class {}", testClass.getName()); - log.info("Warming up the sql database"); - if ( - Arrays - .asList(context.getEnvironment().getActiveProfiles()) - .contains("test" + JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) - ) { - if (null == devTestContainer) { - try { - Class containerClass = (Class) Class.forName( - this.getClass().getPackageName() + ".PostgreSqlTestContainer" - ); - devTestContainer = beanFactory.createBean(containerClass); - beanFactory.registerSingleton(containerClass.getName(), devTestContainer); - // ((DefaultListableBeanFactory)beanFactory).registerDisposableBean(containerClass.getName(), devTestContainer); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - testValues = testValues.and("spring.datasource.url=" + devTestContainer.getTestContainer().getJdbcUrl() + ""); - testValues = testValues.and("spring.datasource.username=" + devTestContainer.getTestContainer().getUsername()); - testValues = testValues.and("spring.datasource.password=" + devTestContainer.getTestContainer().getPassword()); - } - if ( - Arrays - .asList(context.getEnvironment().getActiveProfiles()) - .contains("test" + JHipsterConstants.SPRING_PROFILE_PRODUCTION) - ) { - if (null == prodTestContainer) { - try { - Class containerClass = (Class) Class.forName( - this.getClass().getPackageName() + ".PostgreSqlTestContainer" - ); - prodTestContainer = beanFactory.createBean(containerClass); - beanFactory.registerSingleton(containerClass.getName(), prodTestContainer); - // ((DefaultListableBeanFactory)beanFactory).registerDisposableBean(containerClass.getName(), prodTestContainer); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - testValues = testValues.and("spring.datasource.url=" + prodTestContainer.getTestContainer().getJdbcUrl() + ""); - testValues = testValues.and("spring.datasource.username=" + prodTestContainer.getTestContainer().getUsername()); - testValues = testValues.and("spring.datasource.password=" + prodTestContainer.getTestContainer().getPassword()); - } - } - testValues.applyTo(context); - }; - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTest.java b/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTest.java deleted file mode 100644 index 37330d0..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.*; -import javax.servlet.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import tech.jhipster.config.JHipsterConstants; -import tech.jhipster.config.JHipsterProperties; - -/** - * Unit tests for the {@link WebConfigurer} class. - */ -class WebConfigurerTest { - - private WebConfigurer webConfigurer; - - private MockServletContext servletContext; - - private MockEnvironment env; - - private JHipsterProperties props; - - @BeforeEach - public void setup() { - servletContext = spy(new MockServletContext()); - doReturn(mock(FilterRegistration.Dynamic.class)).when(servletContext).addFilter(anyString(), any(Filter.class)); - doReturn(mock(ServletRegistration.Dynamic.class)).when(servletContext).addServlet(anyString(), any(Servlet.class)); - - env = new MockEnvironment(); - props = new JHipsterProperties(); - - webConfigurer = new WebConfigurer(env, props); - } - - @Test - void shouldCorsFilterOnApiPath() throws Exception { - props.getCors().setAllowedOrigins(Collections.singletonList("other.domain.com")); - props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); - props.getCors().setAllowedHeaders(Collections.singletonList("*")); - props.getCors().setMaxAge(1800L); - props.getCors().setAllowCredentials(true); - - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - - mockMvc - .perform( - options("/api/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST") - ) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")) - .andExpect(header().string(HttpHeaders.VARY, "Origin")) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE")) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")); - - mockMvc - .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")); - } - - @Test - void shouldCorsFilterOnOtherPath() throws Exception { - props.getCors().setAllowedOrigins(Collections.singletonList("*")); - props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); - props.getCors().setAllowedHeaders(Collections.singletonList("*")); - props.getCors().setMaxAge(1800L); - props.getCors().setAllowCredentials(true); - - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - - mockMvc - .perform(get("/test/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) - .andExpect(status().isOk()) - .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - } - - @Test - void shouldCorsFilterDeactivatedForNullAllowedOrigins() throws Exception { - props.getCors().setAllowedOrigins(null); - - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - - mockMvc - .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) - .andExpect(status().isOk()) - .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - } - - @Test - void shouldCorsFilterDeactivatedForEmptyAllowedOrigins() throws Exception { - props.getCors().setAllowedOrigins(new ArrayList<>()); - - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - - mockMvc - .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) - .andExpect(status().isOk()) - .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTestController.java b/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTestController.java deleted file mode 100644 index c5f442d..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/WebConfigurerTestController.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class WebConfigurerTestController { - - @GetMapping("/api/test-cors") - public void testCorsOnApiPath() {} - - @GetMapping("/test/test-cors") - public void testCorsOnOtherPath() {} -} diff --git a/src/test/java/io/github/shuoros/peoplify/config/timezone/HibernateTimeZoneIT.java b/src/test/java/io/github/shuoros/peoplify/config/timezone/HibernateTimeZoneIT.java deleted file mode 100644 index ae146c2..0000000 --- a/src/test/java/io/github/shuoros/peoplify/config/timezone/HibernateTimeZoneIT.java +++ /dev/null @@ -1,162 +0,0 @@ -package io.github.shuoros.peoplify.config.timezone; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.repository.timezone.DateTimeWrapper; -import io.github.shuoros.peoplify.repository.timezone.DateTimeWrapperRepository; -import java.time.*; -import java.time.format.DateTimeFormatter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.rowset.SqlRowSet; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integration tests for the ZoneId Hibernate configuration. - */ -@IntegrationTest -class HibernateTimeZoneIT { - - @Autowired - private DateTimeWrapperRepository dateTimeWrapperRepository; - - @Autowired - private JdbcTemplate jdbcTemplate; - - @Value("${spring.jpa.properties.hibernate.jdbc.time_zone:UTC}") - private String zoneId; - - private DateTimeWrapper dateTimeWrapper; - private DateTimeFormatter dateTimeFormatter; - private DateTimeFormatter timeFormatter; - private DateTimeFormatter dateFormatter; - - @BeforeEach - public void setup() { - dateTimeWrapper = new DateTimeWrapper(); - dateTimeWrapper.setInstant(Instant.parse("2014-11-12T05:50:00.0Z")); - dateTimeWrapper.setLocalDateTime(LocalDateTime.parse("2014-11-12T07:50:00.0")); - dateTimeWrapper.setOffsetDateTime(OffsetDateTime.parse("2011-12-14T08:30:00.0Z")); - dateTimeWrapper.setZonedDateTime(ZonedDateTime.parse("2011-12-14T08:30:00.0Z")); - dateTimeWrapper.setLocalTime(LocalTime.parse("14:30:00")); - dateTimeWrapper.setOffsetTime(OffsetTime.parse("14:30:00+02:00")); - dateTimeWrapper.setLocalDate(LocalDate.parse("2016-09-10")); - - dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S").withZone(ZoneId.of(zoneId)); - - timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.of(zoneId)); - - dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - } - - @Test - @Transactional - void storeInstantWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("instant", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeFormatter.format(dateTimeWrapper.getInstant()); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeLocalDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("local_date_time", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper.getLocalDateTime().atZone(ZoneId.systemDefault()).format(dateTimeFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeOffsetDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("offset_date_time", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper.getOffsetDateTime().format(dateTimeFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeZoneDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("zoned_date_time", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper.getZonedDateTime().format(dateTimeFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeLocalTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("local_time", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getLocalTime() - .atDate(LocalDate.of(1970, Month.JANUARY, 1)) - .atZone(ZoneId.systemDefault()) - .format(timeFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeOffsetTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("offset_time", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getOffsetTime() - .toLocalTime() - .atDate(LocalDate.of(1970, Month.JANUARY, 1)) - .atZone(ZoneId.systemDefault()) - .format(timeFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - @Test - @Transactional - void storeLocalDateWithZoneIdConfigShouldBeStoredWithoutTransformation() { - dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); - - String request = generateSqlRequest("local_date", dateTimeWrapper.getId()); - SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper.getLocalDate().format(dateFormatter); - - assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); - } - - private String generateSqlRequest(String fieldName, long id) { - return format("SELECT %s FROM jhi_date_time_wrapper where id=%d", fieldName, id); - } - - private void assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(SqlRowSet sqlRowSet, String expectedValue) { - while (sqlRowSet.next()) { - String dbValue = sqlRowSet.getString(1); - - assertThat(dbValue).isNotNull(); - assertThat(dbValue).isEqualTo(expectedValue); - } - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/management/SecurityMetersServiceTests.java b/src/test/java/io/github/shuoros/peoplify/management/SecurityMetersServiceTests.java deleted file mode 100644 index ed45da9..0000000 --- a/src/test/java/io/github/shuoros/peoplify/management/SecurityMetersServiceTests.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.shuoros.peoplify.management; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.util.Collection; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class SecurityMetersServiceTests { - - private static final String INVALID_TOKENS_METER_EXPECTED_NAME = "security.authentication.invalid-tokens"; - - private MeterRegistry meterRegistry; - - private SecurityMetersService securityMetersService; - - @BeforeEach - public void setup() { - meterRegistry = new SimpleMeterRegistry(); - - securityMetersService = new SecurityMetersService(meterRegistry); - } - - @Test - void testInvalidTokensCountersByCauseAreCreated() { - meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).counter(); - - meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter(); - - meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter(); - - meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter(); - - meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter(); - - Collection counters = meterRegistry.find(INVALID_TOKENS_METER_EXPECTED_NAME).counters(); - - assertThat(counters).hasSize(4); - } - - @Test - void testCountMethodsShouldBeBoundToCorrectCounters() { - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isZero(); - - securityMetersService.trackTokenExpired(); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isEqualTo(1); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isZero(); - - securityMetersService.trackTokenUnsupported(); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isEqualTo(1); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isZero(); - - securityMetersService.trackTokenInvalidSignature(); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isEqualTo(1); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isZero(); - - securityMetersService.trackTokenMalformed(); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isEqualTo(1); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapper.java b/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapper.java deleted file mode 100644 index 76e08c1..0000000 --- a/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapper.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.github.shuoros.peoplify.repository.timezone; - -import java.io.Serializable; -import java.time.*; -import java.util.Objects; -import javax.persistence.*; - -@Entity -@Table(name = "jhi_date_time_wrapper") -public class DateTimeWrapper implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") - @SequenceGenerator(name = "sequenceGenerator") - private Long id; - - @Column(name = "instant") - private Instant instant; - - @Column(name = "local_date_time") - private LocalDateTime localDateTime; - - @Column(name = "offset_date_time") - private OffsetDateTime offsetDateTime; - - @Column(name = "zoned_date_time") - private ZonedDateTime zonedDateTime; - - @Column(name = "local_time") - private LocalTime localTime; - - @Column(name = "offset_time") - private OffsetTime offsetTime; - - @Column(name = "local_date") - private LocalDate localDate; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Instant getInstant() { - return instant; - } - - public void setInstant(Instant instant) { - this.instant = instant; - } - - public LocalDateTime getLocalDateTime() { - return localDateTime; - } - - public void setLocalDateTime(LocalDateTime localDateTime) { - this.localDateTime = localDateTime; - } - - public OffsetDateTime getOffsetDateTime() { - return offsetDateTime; - } - - public void setOffsetDateTime(OffsetDateTime offsetDateTime) { - this.offsetDateTime = offsetDateTime; - } - - public ZonedDateTime getZonedDateTime() { - return zonedDateTime; - } - - public void setZonedDateTime(ZonedDateTime zonedDateTime) { - this.zonedDateTime = zonedDateTime; - } - - public LocalTime getLocalTime() { - return localTime; - } - - public void setLocalTime(LocalTime localTime) { - this.localTime = localTime; - } - - public OffsetTime getOffsetTime() { - return offsetTime; - } - - public void setOffsetTime(OffsetTime offsetTime) { - this.offsetTime = offsetTime; - } - - public LocalDate getLocalDate() { - return localDate; - } - - public void setLocalDate(LocalDate localDate) { - this.localDate = localDate; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - DateTimeWrapper dateTimeWrapper = (DateTimeWrapper) o; - return !(dateTimeWrapper.getId() == null || getId() == null) && Objects.equals(getId(), dateTimeWrapper.getId()); - } - - @Override - public int hashCode() { - return Objects.hashCode(getId()); - } - - // prettier-ignore - @Override - public String toString() { - return "TimeZoneTest{" + - "id=" + id + - ", instant=" + instant + - ", localDateTime=" + localDateTime + - ", offsetDateTime=" + offsetDateTime + - ", zonedDateTime=" + zonedDateTime + - '}'; - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapperRepository.java b/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapperRepository.java deleted file mode 100644 index de99f22..0000000 --- a/src/test/java/io/github/shuoros/peoplify/repository/timezone/DateTimeWrapperRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.shuoros.peoplify.repository.timezone; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - * Spring Data JPA repository for the {@link DateTimeWrapper} entity. - */ -@Repository -public interface DateTimeWrapperRepository extends JpaRepository {} diff --git a/src/test/java/io/github/shuoros/peoplify/security/DomainUserDetailsServiceIT.java b/src/test/java/io/github/shuoros/peoplify/security/DomainUserDetailsServiceIT.java deleted file mode 100644 index 6da84d0..0000000 --- a/src/test/java/io/github/shuoros/peoplify/security/DomainUserDetailsServiceIT.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import java.util.Locale; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integrations tests for {@link DomainUserDetailsService}. - */ -@Transactional -@IntegrationTest -class DomainUserDetailsServiceIT { - - private static final String USER_ONE_LOGIN = "test-user-one"; - private static final String USER_ONE_EMAIL = "test-user-one@localhost"; - private static final String USER_TWO_LOGIN = "test-user-two"; - private static final String USER_TWO_EMAIL = "test-user-two@localhost"; - private static final String USER_THREE_LOGIN = "test-user-three"; - private static final String USER_THREE_EMAIL = "test-user-three@localhost"; - - @Autowired - private UserRepository userRepository; - - @Autowired - @Qualifier("userDetailsService") - private UserDetailsService domainUserDetailsService; - - @BeforeEach - public void init() { - User userOne = new User(); - userOne.setLogin(USER_ONE_LOGIN); - userOne.setPassword(RandomStringUtils.randomAlphanumeric(60)); - userOne.setActivated(true); - userOne.setEmail(USER_ONE_EMAIL); - userOne.setFirstName("userOne"); - userOne.setLastName("doe"); - userOne.setLangKey("en"); - userRepository.save(userOne); - - User userTwo = new User(); - userTwo.setLogin(USER_TWO_LOGIN); - userTwo.setPassword(RandomStringUtils.randomAlphanumeric(60)); - userTwo.setActivated(true); - userTwo.setEmail(USER_TWO_EMAIL); - userTwo.setFirstName("userTwo"); - userTwo.setLastName("doe"); - userTwo.setLangKey("en"); - userRepository.save(userTwo); - - User userThree = new User(); - userThree.setLogin(USER_THREE_LOGIN); - userThree.setPassword(RandomStringUtils.randomAlphanumeric(60)); - userThree.setActivated(false); - userThree.setEmail(USER_THREE_EMAIL); - userThree.setFirstName("userThree"); - userThree.setLastName("doe"); - userThree.setLangKey("en"); - userRepository.save(userThree); - } - - @Test - void assertThatUserCanBeFoundByLogin() { - UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN); - assertThat(userDetails).isNotNull(); - assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); - } - - @Test - void assertThatUserCanBeFoundByLoginIgnoreCase() { - UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN.toUpperCase(Locale.ENGLISH)); - assertThat(userDetails).isNotNull(); - assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); - } - - @Test - void assertThatUserCanBeFoundByEmail() { - UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL); - assertThat(userDetails).isNotNull(); - assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); - } - - @Test - void assertThatUserCanBeFoundByEmailIgnoreCase() { - UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL.toUpperCase(Locale.ENGLISH)); - assertThat(userDetails).isNotNull(); - assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); - } - - @Test - void assertThatEmailIsPrioritizedOverLogin() { - UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_EMAIL); - assertThat(userDetails).isNotNull(); - assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); - } - - @Test - void assertThatUserNotActivatedExceptionIsThrownForNotActivatedUsers() { - assertThatExceptionOfType(UserNotActivatedException.class) - .isThrownBy(() -> domainUserDetailsService.loadUserByUsername(USER_THREE_LOGIN)); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/security/SecurityUtilsUnitTest.java b/src/test/java/io/github/shuoros/peoplify/security/SecurityUtilsUnitTest.java deleted file mode 100644 index bffbd8a..0000000 --- a/src/test/java/io/github/shuoros/peoplify/security/SecurityUtilsUnitTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.github.shuoros.peoplify.security; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Optional; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * Test class for the {@link SecurityUtils} utility class. - */ -class SecurityUtilsUnitTest { - - @BeforeEach - @AfterEach - void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - void testGetCurrentUserLogin() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); - SecurityContextHolder.setContext(securityContext); - Optional login = SecurityUtils.getCurrentUserLogin(); - assertThat(login).contains("admin"); - } - - @Test - void testGetCurrentUserJWT() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "token")); - SecurityContextHolder.setContext(securityContext); - Optional jwt = SecurityUtils.getCurrentUserJWT(); - assertThat(jwt).contains("token"); - } - - @Test - void testIsAuthenticated() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); - SecurityContextHolder.setContext(securityContext); - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - assertThat(isAuthenticated).isTrue(); - } - - @Test - void testAnonymousIsNotAuthenticated() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); - SecurityContextHolder.setContext(securityContext); - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - assertThat(isAuthenticated).isFalse(); - } - - @Test - void testHasCurrentUserThisAuthority() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.USER)).isTrue(); - assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)).isFalse(); - } - - @Test - void testHasCurrentUserAnyOfAuthorities() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isTrue(); - assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isFalse(); - } - - @Test - void testHasCurrentUserNoneOfAuthorities() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isFalse(); - assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isTrue(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/security/jwt/JWTFilterTest.java b/src/test/java/io/github/shuoros/peoplify/security/jwt/JWTFilterTest.java deleted file mode 100644 index 292c1c2..0000000 --- a/src/test/java/io/github/shuoros/peoplify/security/jwt/JWTFilterTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.github.shuoros.peoplify.management.SecurityMetersService; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.util.Collections; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.util.ReflectionTestUtils; -import tech.jhipster.config.JHipsterProperties; - -class JWTFilterTest { - - private TokenProvider tokenProvider; - - private JWTFilter jwtFilter; - - @BeforeEach - public void setup() { - JHipsterProperties jHipsterProperties = new JHipsterProperties(); - String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; - jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); - - SecurityMetersService securityMetersService = new SecurityMetersService(new SimpleMeterRegistry()); - - tokenProvider = new TokenProvider(jHipsterProperties, securityMetersService); - ReflectionTestUtils.setField(tokenProvider, "key", Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret))); - - ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", 60000); - jwtFilter = new JWTFilter(tokenProvider); - SecurityContextHolder.getContext().setAuthentication(null); - } - - @Test - void testJWTFilter() throws Exception { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - "test-user", - "test-password", - Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.USER)) - ); - String jwt = tokenProvider.createToken(authentication, false); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); - request.setRequestURI("/api/test"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - jwtFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("test-user"); - assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).hasToString(jwt); - } - - @Test - void testJWTFilterInvalidToken() throws Exception { - String jwt = "wrong_jwt"; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); - request.setRequestURI("/api/test"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - jwtFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } - - @Test - void testJWTFilterMissingAuthorization() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI("/api/test"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - jwtFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } - - @Test - void testJWTFilterMissingToken() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer "); - request.setRequestURI("/api/test"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - jwtFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } - - @Test - void testJWTFilterWrongScheme() throws Exception { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - "test-user", - "test-password", - Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.USER)) - ); - String jwt = tokenProvider.createToken(authentication, false); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Basic " + jwt); - request.setRequestURI("/api/test"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - jwtFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderSecurityMetersTests.java b/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderSecurityMetersTests.java deleted file mode 100644 index 0252616..0000000 --- a/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderSecurityMetersTests.java +++ /dev/null @@ -1,158 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.github.shuoros.peoplify.management.SecurityMetersService; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.security.Key; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.test.util.ReflectionTestUtils; -import tech.jhipster.config.JHipsterProperties; - -class TokenProviderSecurityMetersTests { - - private static final long ONE_MINUTE = 60000; - private static final String INVALID_TOKENS_METER_EXPECTED_NAME = "security.authentication.invalid-tokens"; - - private MeterRegistry meterRegistry; - - private TokenProvider tokenProvider; - - @BeforeEach - public void setup() { - JHipsterProperties jHipsterProperties = new JHipsterProperties(); - String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; - jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); - - meterRegistry = new SimpleMeterRegistry(); - - SecurityMetersService securityMetersService = new SecurityMetersService(meterRegistry); - - tokenProvider = new TokenProvider(jHipsterProperties, securityMetersService); - Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret)); - - ReflectionTestUtils.setField(tokenProvider, "key", key); - ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE); - } - - @Test - void testValidTokenShouldNotCountAnything() { - Collection counters = meterRegistry.find(INVALID_TOKENS_METER_EXPECTED_NAME).counters(); - - assertThat(aggregate(counters)).isZero(); - - String validToken = createValidToken(); - - tokenProvider.validateToken(validToken); - - assertThat(aggregate(counters)).isZero(); - } - - @Test - void testTokenExpiredCount() { - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isZero(); - - String expiredToken = createExpiredToken(); - - tokenProvider.validateToken(expiredToken); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isEqualTo(1); - } - - @Test - void testTokenUnsupportedCount() { - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isZero(); - - String unsupportedToken = createUnsupportedToken(); - - tokenProvider.validateToken(unsupportedToken); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isEqualTo(1); - } - - @Test - void testTokenSignatureInvalidCount() { - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isZero(); - - String tokenWithDifferentSignature = createTokenWithDifferentSignature(); - - tokenProvider.validateToken(tokenWithDifferentSignature); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isEqualTo(1); - } - - @Test - void testTokenMalformedCount() { - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isZero(); - - String malformedToken = createMalformedToken(); - - tokenProvider.validateToken(malformedToken); - - assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isEqualTo(1); - } - - private String createValidToken() { - Authentication authentication = createAuthentication(); - - return tokenProvider.createToken(authentication, false); - } - - private String createExpiredToken() { - ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", -ONE_MINUTE); - - Authentication authentication = createAuthentication(); - - return tokenProvider.createToken(authentication, false); - } - - private Authentication createAuthentication() { - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); - return new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities); - } - - private String createUnsupportedToken() { - Key key = (Key) ReflectionTestUtils.getField(tokenProvider, "key"); - - return Jwts.builder().setPayload("payload").signWith(key, SignatureAlgorithm.HS256).compact(); - } - - private String createMalformedToken() { - String validToken = createValidToken(); - - return "X" + validToken; - } - - private String createTokenWithDifferentSignature() { - Key otherKey = Keys.hmacShaKeyFor( - Decoders.BASE64.decode("Xfd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8") - ); - - return Jwts - .builder() - .setSubject("anonymous") - .signWith(otherKey, SignatureAlgorithm.HS512) - .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) - .compact(); - } - - private double aggregate(Collection counters) { - return counters.stream().mapToDouble(Counter::count).sum(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderTest.java b/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderTest.java deleted file mode 100644 index 0584844..0000000 --- a/src/test/java/io/github/shuoros/peoplify/security/jwt/TokenProviderTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package io.github.shuoros.peoplify.security.jwt; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.github.shuoros.peoplify.management.SecurityMetersService; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.test.util.ReflectionTestUtils; -import tech.jhipster.config.JHipsterProperties; - -class TokenProviderTest { - - private static final long ONE_MINUTE = 60000; - - private Key key; - private TokenProvider tokenProvider; - - @BeforeEach - public void setup() { - JHipsterProperties jHipsterProperties = new JHipsterProperties(); - String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; - jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); - - SecurityMetersService securityMetersService = new SecurityMetersService(new SimpleMeterRegistry()); - - tokenProvider = new TokenProvider(jHipsterProperties, securityMetersService); - key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret)); - - ReflectionTestUtils.setField(tokenProvider, "key", key); - ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE); - } - - @Test - void testReturnFalseWhenJWThasInvalidSignature() { - boolean isTokenValid = tokenProvider.validateToken(createTokenWithDifferentSignature()); - - assertThat(isTokenValid).isFalse(); - } - - @Test - void testReturnFalseWhenJWTisMalformed() { - Authentication authentication = createAuthentication(); - String token = tokenProvider.createToken(authentication, false); - String invalidToken = token.substring(1); - boolean isTokenValid = tokenProvider.validateToken(invalidToken); - - assertThat(isTokenValid).isFalse(); - } - - @Test - void testReturnFalseWhenJWTisExpired() { - ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", -ONE_MINUTE); - - Authentication authentication = createAuthentication(); - String token = tokenProvider.createToken(authentication, false); - - boolean isTokenValid = tokenProvider.validateToken(token); - - assertThat(isTokenValid).isFalse(); - } - - @Test - void testReturnFalseWhenJWTisUnsupported() { - String unsupportedToken = createUnsupportedToken(); - - boolean isTokenValid = tokenProvider.validateToken(unsupportedToken); - - assertThat(isTokenValid).isFalse(); - } - - @Test - void testReturnFalseWhenJWTisInvalid() { - boolean isTokenValid = tokenProvider.validateToken(""); - - assertThat(isTokenValid).isFalse(); - } - - @Test - void testKeyIsSetFromSecretWhenSecretIsNotEmpty() { - final String secret = "NwskoUmKHZtzGRKJKVjsJF7BtQMMxNWi"; - JHipsterProperties jHipsterProperties = new JHipsterProperties(); - jHipsterProperties.getSecurity().getAuthentication().getJwt().setSecret(secret); - - SecurityMetersService securityMetersService = new SecurityMetersService(new SimpleMeterRegistry()); - - TokenProvider tokenProvider = new TokenProvider(jHipsterProperties, securityMetersService); - - Key key = (Key) ReflectionTestUtils.getField(tokenProvider, "key"); - assertThat(key).isNotNull().isEqualTo(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))); - } - - @Test - void testKeyIsSetFromBase64SecretWhenSecretIsEmpty() { - final String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; - JHipsterProperties jHipsterProperties = new JHipsterProperties(); - jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); - - SecurityMetersService securityMetersService = new SecurityMetersService(new SimpleMeterRegistry()); - - TokenProvider tokenProvider = new TokenProvider(jHipsterProperties, securityMetersService); - - Key key = (Key) ReflectionTestUtils.getField(tokenProvider, "key"); - assertThat(key).isNotNull().isEqualTo(Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret))); - } - - private Authentication createAuthentication() { - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); - return new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities); - } - - private String createUnsupportedToken() { - return Jwts.builder().setPayload("payload").signWith(key, SignatureAlgorithm.HS512).compact(); - } - - private String createTokenWithDifferentSignature() { - Key otherKey = Keys.hmacShaKeyFor( - Decoders.BASE64.decode("Xfd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8") - ); - - return Jwts - .builder() - .setSubject("anonymous") - .signWith(otherKey, SignatureAlgorithm.HS512) - .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) - .compact(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/service/MailServiceIT.java b/src/test/java/io/github/shuoros/peoplify/service/MailServiceIT.java deleted file mode 100644 index 1612db3..0000000 --- a/src/test/java/io/github/shuoros/peoplify/service/MailServiceIT.java +++ /dev/null @@ -1,235 +0,0 @@ -package io.github.shuoros.peoplify.service; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.User; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.mail.MailSendException; -import org.springframework.mail.javamail.JavaMailSender; -import tech.jhipster.config.JHipsterProperties; - -/** - * Integration tests for {@link MailService}. - */ -@IntegrationTest -class MailServiceIT { - - private static final String[] languages = { - // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array - }; - private static final Pattern PATTERN_LOCALE_3 = Pattern.compile("([a-z]{2})-([a-zA-Z]{4})-([a-z]{2})"); - private static final Pattern PATTERN_LOCALE_2 = Pattern.compile("([a-z]{2})-([a-z]{2})"); - - @Autowired - private JHipsterProperties jHipsterProperties; - - @MockBean - private JavaMailSender javaMailSender; - - @Captor - private ArgumentCaptor messageCaptor; - - @Autowired - private MailService mailService; - - @BeforeEach - public void setup() { - doNothing().when(javaMailSender).send(any(MimeMessage.class)); - when(javaMailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null)); - } - - @Test - void testSendEmail() throws Exception { - mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getSubject()).isEqualTo("testSubject"); - assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent()).isInstanceOf(String.class); - assertThat(message.getContent()).hasToString("testContent"); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); - } - - @Test - void testSendHtmlEmail() throws Exception { - mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, true); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getSubject()).isEqualTo("testSubject"); - assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent()).isInstanceOf(String.class); - assertThat(message.getContent()).hasToString("testContent"); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testSendMultipartEmail() throws Exception { - mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, false); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - MimeMultipart mp = (MimeMultipart) message.getContent(); - MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); - ByteArrayOutputStream aos = new ByteArrayOutputStream(); - part.writeTo(aos); - assertThat(message.getSubject()).isEqualTo("testSubject"); - assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent()).isInstanceOf(Multipart.class); - assertThat(aos).hasToString("\r\ntestContent"); - assertThat(part.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); - } - - @Test - void testSendMultipartHtmlEmail() throws Exception { - mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, true); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - MimeMultipart mp = (MimeMultipart) message.getContent(); - MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); - ByteArrayOutputStream aos = new ByteArrayOutputStream(); - part.writeTo(aos); - assertThat(message.getSubject()).isEqualTo("testSubject"); - assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent()).isInstanceOf(Multipart.class); - assertThat(aos).hasToString("\r\ntestContent"); - assertThat(part.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testSendEmailFromTemplate() throws Exception { - User user = new User(); - user.setLangKey(Constants.DEFAULT_LANGUAGE); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getSubject()).isEqualTo("test title"); - assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent().toString()).isEqualToNormalizingNewlines("test title, http://127.0.0.1:8080, john\n"); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testSendActivationEmail() throws Exception { - User user = new User(); - user.setLangKey(Constants.DEFAULT_LANGUAGE); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - mailService.sendActivationEmail(user); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent().toString()).isNotEmpty(); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testCreationEmail() throws Exception { - User user = new User(); - user.setLangKey(Constants.DEFAULT_LANGUAGE); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - mailService.sendCreationEmail(user); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent().toString()).isNotEmpty(); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testSendPasswordResetMail() throws Exception { - User user = new User(); - user.setLangKey(Constants.DEFAULT_LANGUAGE); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - mailService.sendPasswordResetMail(user); - verify(javaMailSender).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); - assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); - assertThat(message.getContent().toString()).isNotEmpty(); - assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); - } - - @Test - void testSendEmailWithException() { - doThrow(MailSendException.class).when(javaMailSender).send(any(MimeMessage.class)); - try { - mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); - } catch (Exception e) { - fail("Exception shouldn't have been thrown"); - } - } - - @Test - void testSendLocalizedEmailForAllSupportedLanguages() throws Exception { - User user = new User(); - user.setLogin("john"); - user.setEmail("john.doe@example.com"); - for (String langKey : languages) { - user.setLangKey(langKey); - mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); - verify(javaMailSender, atLeastOnce()).send(messageCaptor.capture()); - MimeMessage message = messageCaptor.getValue(); - - String propertyFilePath = "i18n/messages_" + getJavaLocale(langKey) + ".properties"; - URL resource = this.getClass().getClassLoader().getResource(propertyFilePath); - File file = new File(new URI(resource.getFile()).getPath()); - Properties properties = new Properties(); - properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))); - - String emailTitle = (String) properties.get("email.test.title"); - assertThat(message.getSubject()).isEqualTo(emailTitle); - assertThat(message.getContent().toString()) - .isEqualToNormalizingNewlines("" + emailTitle + ", http://127.0.0.1:8080, john\n"); - } - } - - /** - * Convert a lang key to the Java locale. - */ - private String getJavaLocale(String langKey) { - String javaLangKey = langKey; - Matcher matcher2 = PATTERN_LOCALE_2.matcher(langKey); - if (matcher2.matches()) { - javaLangKey = matcher2.group(1) + "_" + matcher2.group(2).toUpperCase(); - } - Matcher matcher3 = PATTERN_LOCALE_3.matcher(langKey); - if (matcher3.matches()) { - javaLangKey = matcher3.group(1) + "_" + matcher3.group(2) + "_" + matcher3.group(3).toUpperCase(); - } - return javaLangKey; - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/service/UserServiceIT.java b/src/test/java/io/github/shuoros/peoplify/service/UserServiceIT.java deleted file mode 100644 index 2c192be..0000000 --- a/src/test/java/io/github/shuoros/peoplify/service/UserServiceIT.java +++ /dev/null @@ -1,185 +0,0 @@ -package io.github.shuoros.peoplify.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Optional; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.auditing.AuditingHandler; -import org.springframework.data.auditing.DateTimeProvider; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.transaction.annotation.Transactional; -import tech.jhipster.security.RandomUtil; - -/** - * Integration tests for {@link UserService}. - */ -@IntegrationTest -@Transactional -class UserServiceIT { - - private static final String DEFAULT_LOGIN = "johndoe"; - - private static final String DEFAULT_EMAIL = "johndoe@localhost"; - - private static final String DEFAULT_FIRSTNAME = "john"; - - private static final String DEFAULT_LASTNAME = "doe"; - - private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; - - private static final String DEFAULT_LANGKEY = "dummy"; - - @Autowired - private UserRepository userRepository; - - @Autowired - private UserService userService; - - @Autowired - private AuditingHandler auditingHandler; - - @MockBean - private DateTimeProvider dateTimeProvider; - - private User user; - - @BeforeEach - public void init() { - user = new User(); - user.setLogin(DEFAULT_LOGIN); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - user.setEmail(DEFAULT_EMAIL); - user.setFirstName(DEFAULT_FIRSTNAME); - user.setLastName(DEFAULT_LASTNAME); - user.setImageUrl(DEFAULT_IMAGEURL); - user.setLangKey(DEFAULT_LANGKEY); - - when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.now())); - auditingHandler.setDateTimeProvider(dateTimeProvider); - } - - @Test - @Transactional - void assertThatUserMustExistToResetPassword() { - userRepository.saveAndFlush(user); - Optional maybeUser = userService.requestPasswordReset("invalid.login@localhost"); - assertThat(maybeUser).isNotPresent(); - - maybeUser = userService.requestPasswordReset(user.getEmail()); - assertThat(maybeUser).isPresent(); - assertThat(maybeUser.orElse(null).getEmail()).isEqualTo(user.getEmail()); - assertThat(maybeUser.orElse(null).getResetDate()).isNotNull(); - assertThat(maybeUser.orElse(null).getResetKey()).isNotNull(); - } - - @Test - @Transactional - void assertThatOnlyActivatedUserCanRequestPasswordReset() { - user.setActivated(false); - userRepository.saveAndFlush(user); - - Optional maybeUser = userService.requestPasswordReset(user.getLogin()); - assertThat(maybeUser).isNotPresent(); - userRepository.delete(user); - } - - @Test - @Transactional - void assertThatResetKeyMustNotBeOlderThan24Hours() { - Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); - String resetKey = RandomUtil.generateResetKey(); - user.setActivated(true); - user.setResetDate(daysAgo); - user.setResetKey(resetKey); - userRepository.saveAndFlush(user); - - Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); - assertThat(maybeUser).isNotPresent(); - userRepository.delete(user); - } - - @Test - @Transactional - void assertThatResetKeyMustBeValid() { - Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); - user.setActivated(true); - user.setResetDate(daysAgo); - user.setResetKey("1234"); - userRepository.saveAndFlush(user); - - Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); - assertThat(maybeUser).isNotPresent(); - userRepository.delete(user); - } - - @Test - @Transactional - void assertThatUserCanResetPassword() { - String oldPassword = user.getPassword(); - Instant daysAgo = Instant.now().minus(2, ChronoUnit.HOURS); - String resetKey = RandomUtil.generateResetKey(); - user.setActivated(true); - user.setResetDate(daysAgo); - user.setResetKey(resetKey); - userRepository.saveAndFlush(user); - - Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); - assertThat(maybeUser).isPresent(); - assertThat(maybeUser.orElse(null).getResetDate()).isNull(); - assertThat(maybeUser.orElse(null).getResetKey()).isNull(); - assertThat(maybeUser.orElse(null).getPassword()).isNotEqualTo(oldPassword); - - userRepository.delete(user); - } - - @Test - @Transactional - void assertThatNotActivatedUsersWithNotNullActivationKeyCreatedBefore3DaysAreDeleted() { - Instant now = Instant.now(); - when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); - user.setActivated(false); - user.setActivationKey(RandomStringUtils.random(20)); - User dbUser = userRepository.saveAndFlush(user); - dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); - userRepository.saveAndFlush(user); - Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); - List users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); - assertThat(users).isNotEmpty(); - userService.removeNotActivatedUsers(); - users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); - assertThat(users).isEmpty(); - } - - @Test - @Transactional - void assertThatNotActivatedUsersWithNullActivationKeyCreatedBefore3DaysAreNotDeleted() { - Instant now = Instant.now(); - when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); - user.setActivated(false); - User dbUser = userRepository.saveAndFlush(user); - dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); - userRepository.saveAndFlush(user); - Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); - List users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); - assertThat(users).isEmpty(); - userService.removeNotActivatedUsers(); - Optional maybeDbUser = userRepository.findById(dbUser.getId()); - assertThat(maybeDbUser).contains(dbUser); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/service/mapper/UserMapperTest.java b/src/test/java/io/github/shuoros/peoplify/service/mapper/UserMapperTest.java deleted file mode 100644 index 95531e0..0000000 --- a/src/test/java/io/github/shuoros/peoplify/service/mapper/UserMapperTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package io.github.shuoros.peoplify.service.mapper; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.dto.UserDTO; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link UserMapper}. - */ -class UserMapperTest { - - private static final String DEFAULT_LOGIN = "johndoe"; - private static final Long DEFAULT_ID = 1L; - - private UserMapper userMapper; - private User user; - private AdminUserDTO userDto; - - @BeforeEach - public void init() { - userMapper = new UserMapper(); - user = new User(); - user.setLogin(DEFAULT_LOGIN); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - user.setEmail("johndoe@localhost"); - user.setFirstName("john"); - user.setLastName("doe"); - user.setImageUrl("image_url"); - user.setLangKey("en"); - - userDto = new AdminUserDTO(user); - } - - @Test - void usersToUserDTOsShouldMapOnlyNonNullUsers() { - List users = new ArrayList<>(); - users.add(user); - users.add(null); - - List userDTOS = userMapper.usersToUserDTOs(users); - - assertThat(userDTOS).isNotEmpty().size().isEqualTo(1); - } - - @Test - void userDTOsToUsersShouldMapOnlyNonNullUsers() { - List usersDto = new ArrayList<>(); - usersDto.add(userDto); - usersDto.add(null); - - List users = userMapper.userDTOsToUsers(usersDto); - - assertThat(users).isNotEmpty().size().isEqualTo(1); - } - - @Test - void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { - Set authoritiesAsString = new HashSet<>(); - authoritiesAsString.add("ADMIN"); - userDto.setAuthorities(authoritiesAsString); - - List usersDto = new ArrayList<>(); - usersDto.add(userDto); - - List users = userMapper.userDTOsToUsers(usersDto); - - assertThat(users).isNotEmpty().size().isEqualTo(1); - assertThat(users.get(0).getAuthorities()).isNotNull(); - assertThat(users.get(0).getAuthorities()).isNotEmpty(); - assertThat(users.get(0).getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); - } - - @Test - void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { - userDto.setAuthorities(null); - - List usersDto = new ArrayList<>(); - usersDto.add(userDto); - - List users = userMapper.userDTOsToUsers(usersDto); - - assertThat(users).isNotEmpty().size().isEqualTo(1); - assertThat(users.get(0).getAuthorities()).isNotNull(); - assertThat(users.get(0).getAuthorities()).isEmpty(); - } - - @Test - void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { - Set authoritiesAsString = new HashSet<>(); - authoritiesAsString.add("ADMIN"); - userDto.setAuthorities(authoritiesAsString); - - User user = userMapper.userDTOToUser(userDto); - - assertThat(user).isNotNull(); - assertThat(user.getAuthorities()).isNotNull(); - assertThat(user.getAuthorities()).isNotEmpty(); - assertThat(user.getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); - } - - @Test - void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { - userDto.setAuthorities(null); - - User user = userMapper.userDTOToUser(userDto); - - assertThat(user).isNotNull(); - assertThat(user.getAuthorities()).isNotNull(); - assertThat(user.getAuthorities()).isEmpty(); - } - - @Test - void userDTOToUserMapWithNullUserShouldReturnNull() { - assertThat(userMapper.userDTOToUser(null)).isNull(); - } - - @Test - void testUserFromId() { - assertThat(userMapper.userFromId(DEFAULT_ID).getId()).isEqualTo(DEFAULT_ID); - assertThat(userMapper.userFromId(null)).isNull(); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/AccountResourceIT.java b/src/test/java/io/github/shuoros/peoplify/web/rest/AccountResourceIT.java deleted file mode 100644 index 8e2a272..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/AccountResourceIT.java +++ /dev/null @@ -1,762 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import static io.github.shuoros.peoplify.web.rest.AccountResourceIT.TEST_USER_LOGIN; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.config.Constants; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.AuthorityRepository; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.github.shuoros.peoplify.service.UserService; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.dto.PasswordChangeDTO; -import io.github.shuoros.peoplify.service.dto.UserDTO; -import io.github.shuoros.peoplify.web.rest.vm.KeyAndPasswordVM; -import io.github.shuoros.peoplify.web.rest.vm.ManagedUserVM; -import java.time.Instant; -import java.util.*; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integration tests for the {@link AccountResource} REST controller. - */ -@AutoConfigureMockMvc -@WithMockUser(value = TEST_USER_LOGIN) -@IntegrationTest -class AccountResourceIT { - - static final String TEST_USER_LOGIN = "test"; - - @Autowired - private UserRepository userRepository; - - @Autowired - private AuthorityRepository authorityRepository; - - @Autowired - private UserService userService; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Autowired - private MockMvc restAccountMockMvc; - - @Test - @WithUnauthenticatedMockUser - void testNonAuthenticatedUser() throws Exception { - restAccountMockMvc - .perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string("")); - } - - @Test - void testAuthenticatedUser() throws Exception { - restAccountMockMvc - .perform( - get("/api/authenticate") - .with(request -> { - request.setRemoteUser(TEST_USER_LOGIN); - return request; - }) - .accept(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk()) - .andExpect(content().string(TEST_USER_LOGIN)); - } - - @Test - void testGetExistingAccount() throws Exception { - Set authorities = new HashSet<>(); - authorities.add(AuthoritiesConstants.ADMIN); - - AdminUserDTO user = new AdminUserDTO(); - user.setLogin(TEST_USER_LOGIN); - user.setFirstName("john"); - user.setLastName("doe"); - user.setEmail("john.doe@jhipster.com"); - user.setImageUrl("http://placehold.it/50x50"); - user.setLangKey("en"); - user.setAuthorities(authorities); - userService.createUser(user); - - restAccountMockMvc - .perform(get("/api/account").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.login").value(TEST_USER_LOGIN)) - .andExpect(jsonPath("$.firstName").value("john")) - .andExpect(jsonPath("$.lastName").value("doe")) - .andExpect(jsonPath("$.email").value("john.doe@jhipster.com")) - .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50")) - .andExpect(jsonPath("$.langKey").value("en")) - .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN)); - } - - @Test - void testGetUnknownAccount() throws Exception { - restAccountMockMvc - .perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(status().isInternalServerError()); - } - - @Test - @Transactional - void testRegisterValid() throws Exception { - ManagedUserVM validUser = new ManagedUserVM(); - validUser.setLogin("test-register-valid"); - validUser.setPassword("password"); - validUser.setFirstName("Alice"); - validUser.setLastName("Test"); - validUser.setEmail("test-register-valid@example.com"); - validUser.setImageUrl("http://placehold.it/50x50"); - validUser.setLangKey(Constants.DEFAULT_LANGUAGE); - validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - assertThat(userRepository.findOneByLogin("test-register-valid")).isEmpty(); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) - .andExpect(status().isCreated()); - - assertThat(userRepository.findOneByLogin("test-register-valid")).isPresent(); - } - - @Test - @Transactional - void testRegisterInvalidLogin() throws Exception { - ManagedUserVM invalidUser = new ManagedUserVM(); - invalidUser.setLogin("funky-log(n"); // <-- invalid - invalidUser.setPassword("password"); - invalidUser.setFirstName("Funky"); - invalidUser.setLastName("One"); - invalidUser.setEmail("funky@example.com"); - invalidUser.setActivated(true); - invalidUser.setImageUrl("http://placehold.it/50x50"); - invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); - invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) - .andExpect(status().isBadRequest()); - - Optional user = userRepository.findOneByEmailIgnoreCase("funky@example.com"); - assertThat(user).isEmpty(); - } - - @Test - @Transactional - void testRegisterInvalidEmail() throws Exception { - ManagedUserVM invalidUser = new ManagedUserVM(); - invalidUser.setLogin("bob"); - invalidUser.setPassword("password"); - invalidUser.setFirstName("Bob"); - invalidUser.setLastName("Green"); - invalidUser.setEmail("invalid"); // <-- invalid - invalidUser.setActivated(true); - invalidUser.setImageUrl("http://placehold.it/50x50"); - invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); - invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) - .andExpect(status().isBadRequest()); - - Optional user = userRepository.findOneByLogin("bob"); - assertThat(user).isEmpty(); - } - - @Test - @Transactional - void testRegisterInvalidPassword() throws Exception { - ManagedUserVM invalidUser = new ManagedUserVM(); - invalidUser.setLogin("bob"); - invalidUser.setPassword("123"); // password with only 3 digits - invalidUser.setFirstName("Bob"); - invalidUser.setLastName("Green"); - invalidUser.setEmail("bob@example.com"); - invalidUser.setActivated(true); - invalidUser.setImageUrl("http://placehold.it/50x50"); - invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); - invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) - .andExpect(status().isBadRequest()); - - Optional user = userRepository.findOneByLogin("bob"); - assertThat(user).isEmpty(); - } - - @Test - @Transactional - void testRegisterNullPassword() throws Exception { - ManagedUserVM invalidUser = new ManagedUserVM(); - invalidUser.setLogin("bob"); - invalidUser.setPassword(null); // invalid null password - invalidUser.setFirstName("Bob"); - invalidUser.setLastName("Green"); - invalidUser.setEmail("bob@example.com"); - invalidUser.setActivated(true); - invalidUser.setImageUrl("http://placehold.it/50x50"); - invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); - invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) - .andExpect(status().isBadRequest()); - - Optional user = userRepository.findOneByLogin("bob"); - assertThat(user).isEmpty(); - } - - @Test - @Transactional - void testRegisterDuplicateLogin() throws Exception { - // First registration - ManagedUserVM firstUser = new ManagedUserVM(); - firstUser.setLogin("alice"); - firstUser.setPassword("password"); - firstUser.setFirstName("Alice"); - firstUser.setLastName("Something"); - firstUser.setEmail("alice@example.com"); - firstUser.setImageUrl("http://placehold.it/50x50"); - firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); - firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - // Duplicate login, different email - ManagedUserVM secondUser = new ManagedUserVM(); - secondUser.setLogin(firstUser.getLogin()); - secondUser.setPassword(firstUser.getPassword()); - secondUser.setFirstName(firstUser.getFirstName()); - secondUser.setLastName(firstUser.getLastName()); - secondUser.setEmail("alice2@example.com"); - secondUser.setImageUrl(firstUser.getImageUrl()); - secondUser.setLangKey(firstUser.getLangKey()); - secondUser.setCreatedBy(firstUser.getCreatedBy()); - secondUser.setCreatedDate(firstUser.getCreatedDate()); - secondUser.setLastModifiedBy(firstUser.getLastModifiedBy()); - secondUser.setLastModifiedDate(firstUser.getLastModifiedDate()); - secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); - - // First user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) - .andExpect(status().isCreated()); - - // Second (non activated) user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) - .andExpect(status().isCreated()); - - Optional testUser = userRepository.findOneByEmailIgnoreCase("alice2@example.com"); - assertThat(testUser).isPresent(); - testUser.get().setActivated(true); - userRepository.save(testUser.get()); - - // Second (already activated) user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) - .andExpect(status().is4xxClientError()); - } - - @Test - @Transactional - void testRegisterDuplicateEmail() throws Exception { - // First user - ManagedUserVM firstUser = new ManagedUserVM(); - firstUser.setLogin("test-register-duplicate-email"); - firstUser.setPassword("password"); - firstUser.setFirstName("Alice"); - firstUser.setLastName("Test"); - firstUser.setEmail("test-register-duplicate-email@example.com"); - firstUser.setImageUrl("http://placehold.it/50x50"); - firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); - firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - // Register first user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) - .andExpect(status().isCreated()); - - Optional testUser1 = userRepository.findOneByLogin("test-register-duplicate-email"); - assertThat(testUser1).isPresent(); - - // Duplicate email, different login - ManagedUserVM secondUser = new ManagedUserVM(); - secondUser.setLogin("test-register-duplicate-email-2"); - secondUser.setPassword(firstUser.getPassword()); - secondUser.setFirstName(firstUser.getFirstName()); - secondUser.setLastName(firstUser.getLastName()); - secondUser.setEmail(firstUser.getEmail()); - secondUser.setImageUrl(firstUser.getImageUrl()); - secondUser.setLangKey(firstUser.getLangKey()); - secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); - - // Register second (non activated) user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) - .andExpect(status().isCreated()); - - Optional testUser2 = userRepository.findOneByLogin("test-register-duplicate-email"); - assertThat(testUser2).isEmpty(); - - Optional testUser3 = userRepository.findOneByLogin("test-register-duplicate-email-2"); - assertThat(testUser3).isPresent(); - - // Duplicate email - with uppercase email address - ManagedUserVM userWithUpperCaseEmail = new ManagedUserVM(); - userWithUpperCaseEmail.setId(firstUser.getId()); - userWithUpperCaseEmail.setLogin("test-register-duplicate-email-3"); - userWithUpperCaseEmail.setPassword(firstUser.getPassword()); - userWithUpperCaseEmail.setFirstName(firstUser.getFirstName()); - userWithUpperCaseEmail.setLastName(firstUser.getLastName()); - userWithUpperCaseEmail.setEmail("TEST-register-duplicate-email@example.com"); - userWithUpperCaseEmail.setImageUrl(firstUser.getImageUrl()); - userWithUpperCaseEmail.setLangKey(firstUser.getLangKey()); - userWithUpperCaseEmail.setAuthorities(new HashSet<>(firstUser.getAuthorities())); - - // Register third (not activated) user - restAccountMockMvc - .perform( - post("/api/register") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(userWithUpperCaseEmail)) - ) - .andExpect(status().isCreated()); - - Optional testUser4 = userRepository.findOneByLogin("test-register-duplicate-email-3"); - assertThat(testUser4).isPresent(); - assertThat(testUser4.get().getEmail()).isEqualTo("test-register-duplicate-email@example.com"); - - testUser4.get().setActivated(true); - userService.updateUser((new AdminUserDTO(testUser4.get()))); - - // Register 4th (already activated) user - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) - .andExpect(status().is4xxClientError()); - } - - @Test - @Transactional - void testRegisterAdminIsIgnored() throws Exception { - ManagedUserVM validUser = new ManagedUserVM(); - validUser.setLogin("badguy"); - validUser.setPassword("password"); - validUser.setFirstName("Bad"); - validUser.setLastName("Guy"); - validUser.setEmail("badguy@example.com"); - validUser.setActivated(true); - validUser.setImageUrl("http://placehold.it/50x50"); - validUser.setLangKey(Constants.DEFAULT_LANGUAGE); - validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - - restAccountMockMvc - .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) - .andExpect(status().isCreated()); - - Optional userDup = userRepository.findOneWithAuthoritiesByLogin("badguy"); - assertThat(userDup).isPresent(); - assertThat(userDup.get().getAuthorities()) - .hasSize(1) - .containsExactly(authorityRepository.findById(AuthoritiesConstants.USER).get()); - } - - @Test - @Transactional - void testActivateAccount() throws Exception { - final String activationKey = "some activation key"; - User user = new User(); - user.setLogin("activate-account"); - user.setEmail("activate-account@example.com"); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(false); - user.setActivationKey(activationKey); - - userRepository.saveAndFlush(user); - - restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); - - user = userRepository.findOneByLogin(user.getLogin()).orElse(null); - assertThat(user.isActivated()).isTrue(); - } - - @Test - @Transactional - void testActivateAccountWithWrongKey() throws Exception { - restAccountMockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError()); - } - - @Test - @Transactional - @WithMockUser("save-account") - void testSaveAccount() throws Exception { - User user = new User(); - user.setLogin("save-account"); - user.setEmail("save-account@example.com"); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - userRepository.saveAndFlush(user); - - AdminUserDTO userDTO = new AdminUserDTO(); - userDTO.setLogin("not-used"); - userDTO.setFirstName("firstname"); - userDTO.setLastName("lastname"); - userDTO.setEmail("save-account@example.com"); - userDTO.setActivated(false); - userDTO.setImageUrl("http://placehold.it/50x50"); - userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); - userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - - restAccountMockMvc - .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) - .andExpect(status().isOk()); - - User updatedUser = userRepository.findOneWithAuthoritiesByLogin(user.getLogin()).orElse(null); - assertThat(updatedUser.getFirstName()).isEqualTo(userDTO.getFirstName()); - assertThat(updatedUser.getLastName()).isEqualTo(userDTO.getLastName()); - assertThat(updatedUser.getEmail()).isEqualTo(userDTO.getEmail()); - assertThat(updatedUser.getLangKey()).isEqualTo(userDTO.getLangKey()); - assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); - assertThat(updatedUser.getImageUrl()).isEqualTo(userDTO.getImageUrl()); - assertThat(updatedUser.isActivated()).isTrue(); - assertThat(updatedUser.getAuthorities()).isEmpty(); - } - - @Test - @Transactional - @WithMockUser("save-invalid-email") - void testSaveInvalidEmail() throws Exception { - User user = new User(); - user.setLogin("save-invalid-email"); - user.setEmail("save-invalid-email@example.com"); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - - userRepository.saveAndFlush(user); - - AdminUserDTO userDTO = new AdminUserDTO(); - userDTO.setLogin("not-used"); - userDTO.setFirstName("firstname"); - userDTO.setLastName("lastname"); - userDTO.setEmail("invalid email"); - userDTO.setActivated(false); - userDTO.setImageUrl("http://placehold.it/50x50"); - userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); - userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - - restAccountMockMvc - .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) - .andExpect(status().isBadRequest()); - - assertThat(userRepository.findOneByEmailIgnoreCase("invalid email")).isNotPresent(); - } - - @Test - @Transactional - @WithMockUser("save-existing-email") - void testSaveExistingEmail() throws Exception { - User user = new User(); - user.setLogin("save-existing-email"); - user.setEmail("save-existing-email@example.com"); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - userRepository.saveAndFlush(user); - - User anotherUser = new User(); - anotherUser.setLogin("save-existing-email2"); - anotherUser.setEmail("save-existing-email2@example.com"); - anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); - anotherUser.setActivated(true); - - userRepository.saveAndFlush(anotherUser); - - AdminUserDTO userDTO = new AdminUserDTO(); - userDTO.setLogin("not-used"); - userDTO.setFirstName("firstname"); - userDTO.setLastName("lastname"); - userDTO.setEmail("save-existing-email2@example.com"); - userDTO.setActivated(false); - userDTO.setImageUrl("http://placehold.it/50x50"); - userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); - userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - - restAccountMockMvc - .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin("save-existing-email").orElse(null); - assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email@example.com"); - } - - @Test - @Transactional - @WithMockUser("save-existing-email-and-login") - void testSaveExistingEmailAndLogin() throws Exception { - User user = new User(); - user.setLogin("save-existing-email-and-login"); - user.setEmail("save-existing-email-and-login@example.com"); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - userRepository.saveAndFlush(user); - - AdminUserDTO userDTO = new AdminUserDTO(); - userDTO.setLogin("not-used"); - userDTO.setFirstName("firstname"); - userDTO.setLastName("lastname"); - userDTO.setEmail("save-existing-email-and-login@example.com"); - userDTO.setActivated(false); - userDTO.setImageUrl("http://placehold.it/50x50"); - userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); - userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); - - restAccountMockMvc - .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) - .andExpect(status().isOk()); - - User updatedUser = userRepository.findOneByLogin("save-existing-email-and-login").orElse(null); - assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email-and-login@example.com"); - } - - @Test - @Transactional - @WithMockUser("change-password-wrong-existing-password") - void testChangePasswordWrongExistingPassword() throws Exception { - User user = new User(); - String currentPassword = RandomStringUtils.randomAlphanumeric(60); - user.setPassword(passwordEncoder.encode(currentPassword)); - user.setLogin("change-password-wrong-existing-password"); - user.setEmail("change-password-wrong-existing-password@example.com"); - userRepository.saveAndFlush(user); - - restAccountMockMvc - .perform( - post("/api/account/change-password") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO("1" + currentPassword, "new password"))) - ) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin("change-password-wrong-existing-password").orElse(null); - assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isFalse(); - assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isTrue(); - } - - @Test - @Transactional - @WithMockUser("change-password") - void testChangePassword() throws Exception { - User user = new User(); - String currentPassword = RandomStringUtils.randomAlphanumeric(60); - user.setPassword(passwordEncoder.encode(currentPassword)); - user.setLogin("change-password"); - user.setEmail("change-password@example.com"); - userRepository.saveAndFlush(user); - - restAccountMockMvc - .perform( - post("/api/account/change-password") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, "new password"))) - ) - .andExpect(status().isOk()); - - User updatedUser = userRepository.findOneByLogin("change-password").orElse(null); - assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isTrue(); - } - - @Test - @Transactional - @WithMockUser("change-password-too-small") - void testChangePasswordTooSmall() throws Exception { - User user = new User(); - String currentPassword = RandomStringUtils.randomAlphanumeric(60); - user.setPassword(passwordEncoder.encode(currentPassword)); - user.setLogin("change-password-too-small"); - user.setEmail("change-password-too-small@example.com"); - userRepository.saveAndFlush(user); - - String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1); - - restAccountMockMvc - .perform( - post("/api/account/change-password") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) - ) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin("change-password-too-small").orElse(null); - assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); - } - - @Test - @Transactional - @WithMockUser("change-password-too-long") - void testChangePasswordTooLong() throws Exception { - User user = new User(); - String currentPassword = RandomStringUtils.randomAlphanumeric(60); - user.setPassword(passwordEncoder.encode(currentPassword)); - user.setLogin("change-password-too-long"); - user.setEmail("change-password-too-long@example.com"); - userRepository.saveAndFlush(user); - - String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1); - - restAccountMockMvc - .perform( - post("/api/account/change-password") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) - ) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin("change-password-too-long").orElse(null); - assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); - } - - @Test - @Transactional - @WithMockUser("change-password-empty") - void testChangePasswordEmpty() throws Exception { - User user = new User(); - String currentPassword = RandomStringUtils.randomAlphanumeric(60); - user.setPassword(passwordEncoder.encode(currentPassword)); - user.setLogin("change-password-empty"); - user.setEmail("change-password-empty@example.com"); - userRepository.saveAndFlush(user); - - restAccountMockMvc - .perform( - post("/api/account/change-password") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, ""))) - ) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin("change-password-empty").orElse(null); - assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); - } - - @Test - @Transactional - void testRequestPasswordReset() throws Exception { - User user = new User(); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - user.setLogin("password-reset"); - user.setEmail("password-reset@example.com"); - user.setLangKey("en"); - userRepository.saveAndFlush(user); - - restAccountMockMvc - .perform(post("/api/account/reset-password/init").content("password-reset@example.com")) - .andExpect(status().isOk()); - } - - @Test - @Transactional - void testRequestPasswordResetUpperCaseEmail() throws Exception { - User user = new User(); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - user.setLogin("password-reset-upper-case"); - user.setEmail("password-reset-upper-case@example.com"); - user.setLangKey("en"); - userRepository.saveAndFlush(user); - - restAccountMockMvc - .perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")) - .andExpect(status().isOk()); - } - - @Test - void testRequestPasswordResetWrongEmail() throws Exception { - restAccountMockMvc - .perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")) - .andExpect(status().isOk()); - } - - @Test - @Transactional - void testFinishPasswordReset() throws Exception { - User user = new User(); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setLogin("finish-password-reset"); - user.setEmail("finish-password-reset@example.com"); - user.setResetDate(Instant.now().plusSeconds(60)); - user.setResetKey("reset key"); - userRepository.saveAndFlush(user); - - KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); - keyAndPassword.setKey(user.getResetKey()); - keyAndPassword.setNewPassword("new password"); - - restAccountMockMvc - .perform( - post("/api/account/reset-password/finish") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) - ) - .andExpect(status().isOk()); - - User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); - assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isTrue(); - } - - @Test - @Transactional - void testFinishPasswordResetTooSmall() throws Exception { - User user = new User(); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setLogin("finish-password-reset-too-small"); - user.setEmail("finish-password-reset-too-small@example.com"); - user.setResetDate(Instant.now().plusSeconds(60)); - user.setResetKey("reset key too small"); - userRepository.saveAndFlush(user); - - KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); - keyAndPassword.setKey(user.getResetKey()); - keyAndPassword.setNewPassword("foo"); - - restAccountMockMvc - .perform( - post("/api/account/reset-password/finish") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) - ) - .andExpect(status().isBadRequest()); - - User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); - assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isFalse(); - } - - @Test - @Transactional - void testFinishPasswordResetWrongKey() throws Exception { - KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); - keyAndPassword.setKey("wrong reset key"); - keyAndPassword.setNewPassword("new password"); - - restAccountMockMvc - .perform( - post("/api/account/reset-password/finish") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) - ) - .andExpect(status().isInternalServerError()); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/PublicUserResourceIT.java b/src/test/java/io/github/shuoros/peoplify/web/rest/PublicUserResourceIT.java deleted file mode 100644 index 2504711..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/PublicUserResourceIT.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import javax.persistence.EntityManager; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.cache.CacheManager; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integration tests for the {@link UserResource} REST controller. - */ -@AutoConfigureMockMvc -@WithMockUser(authorities = AuthoritiesConstants.ADMIN) -@IntegrationTest -class PublicUserResourceIT { - - private static final String DEFAULT_LOGIN = "johndoe"; - - @Autowired - private UserRepository userRepository; - - @Autowired - private EntityManager em; - - @Autowired - private CacheManager cacheManager; - - @Autowired - private MockMvc restUserMockMvc; - - private User user; - - @BeforeEach - public void setup() { - cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); - cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); - } - - @BeforeEach - public void initTest() { - user = UserResourceIT.initTestUser(userRepository, em); - } - - @Test - @Transactional - void getAllPublicUsers() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - - // Get all the users - restUserMockMvc - .perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) - .andExpect(jsonPath("$.[*].email").doesNotExist()) - .andExpect(jsonPath("$.[*].imageUrl").doesNotExist()) - .andExpect(jsonPath("$.[*].langKey").doesNotExist()); - } - - @Test - @Transactional - void getAllAuthorities() throws Exception { - restUserMockMvc - .perform(get("/api/authorities").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$").isArray()) - .andExpect(jsonPath("$").value(hasItems(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN))); - } - - @Test - @Transactional - void getAllUsersSortedByParameters() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - - restUserMockMvc.perform(get("/api/users?sort=resetKey,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); - restUserMockMvc.perform(get("/api/users?sort=password,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); - restUserMockMvc - .perform(get("/api/users?sort=resetKey,desc&sort=id,desc").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); - restUserMockMvc.perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/TestUtil.java b/src/test/java/io/github/shuoros/peoplify/web/rest/TestUtil.java deleted file mode 100644 index 13fea1a..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/TestUtil.java +++ /dev/null @@ -1,206 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.ZonedDateTime; -import java.time.format.DateTimeParseException; -import java.util.List; -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; -import org.hamcrest.Description; -import org.hamcrest.TypeSafeDiagnosingMatcher; -import org.hamcrest.TypeSafeMatcher; -import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; -import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.format.support.FormattingConversionService; - -/** - * Utility class for testing REST controllers. - */ -public final class TestUtil { - - private static final ObjectMapper mapper = createObjectMapper(); - - private static ObjectMapper createObjectMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); - mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - mapper.registerModule(new JavaTimeModule()); - return mapper; - } - - /** - * Convert an object to JSON byte array. - * - * @param object the object to convert. - * @return the JSON byte array. - * @throws IOException - */ - public static byte[] convertObjectToJsonBytes(Object object) throws IOException { - return mapper.writeValueAsBytes(object); - } - - /** - * Create a byte array with a specific size filled with specified data. - * - * @param size the size of the byte array. - * @param data the data to put in the byte array. - * @return the JSON byte array. - */ - public static byte[] createByteArray(int size, String data) { - byte[] byteArray = new byte[size]; - for (int i = 0; i < size; i++) { - byteArray[i] = Byte.parseByte(data, 2); - } - return byteArray; - } - - /** - * A matcher that tests that the examined string represents the same instant as the reference datetime. - */ - public static class ZonedDateTimeMatcher extends TypeSafeDiagnosingMatcher { - - private final ZonedDateTime date; - - public ZonedDateTimeMatcher(ZonedDateTime date) { - this.date = date; - } - - @Override - protected boolean matchesSafely(String item, Description mismatchDescription) { - try { - if (!date.isEqual(ZonedDateTime.parse(item))) { - mismatchDescription.appendText("was ").appendValue(item); - return false; - } - return true; - } catch (DateTimeParseException e) { - mismatchDescription.appendText("was ").appendValue(item).appendText(", which could not be parsed as a ZonedDateTime"); - return false; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("a String representing the same Instant as ").appendValue(date); - } - } - - /** - * Creates a matcher that matches when the examined string represents the same instant as the reference datetime. - * - * @param date the reference datetime against which the examined string is checked. - */ - public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) { - return new ZonedDateTimeMatcher(date); - } - - /** - * A matcher that tests that the examined number represents the same value - it can be Long, Double, etc - as the reference BigDecimal. - */ - public static class NumberMatcher extends TypeSafeMatcher { - - final BigDecimal value; - - public NumberMatcher(BigDecimal value) { - this.value = value; - } - - @Override - public void describeTo(Description description) { - description.appendText("a numeric value is ").appendValue(value); - } - - @Override - protected boolean matchesSafely(Number item) { - BigDecimal bigDecimal = asDecimal(item); - return bigDecimal != null && value.compareTo(bigDecimal) == 0; - } - - private static BigDecimal asDecimal(Number item) { - if (item == null) { - return null; - } - if (item instanceof BigDecimal) { - return (BigDecimal) item; - } else if (item instanceof Long) { - return BigDecimal.valueOf((Long) item); - } else if (item instanceof Integer) { - return BigDecimal.valueOf((Integer) item); - } else if (item instanceof Double) { - return BigDecimal.valueOf((Double) item); - } else if (item instanceof Float) { - return BigDecimal.valueOf((Float) item); - } else { - return BigDecimal.valueOf(item.doubleValue()); - } - } - } - - /** - * Creates a matcher that matches when the examined number represents the same value as the reference BigDecimal. - * - * @param number the reference BigDecimal against which the examined number is checked. - */ - public static NumberMatcher sameNumber(BigDecimal number) { - return new NumberMatcher(number); - } - - /** - * Verifies the equals/hashcode contract on the domain object. - */ - public static void equalsVerifier(Class clazz) throws Exception { - T domainObject1 = clazz.getConstructor().newInstance(); - assertThat(domainObject1.toString()).isNotNull(); - assertThat(domainObject1).isEqualTo(domainObject1); - assertThat(domainObject1).hasSameHashCodeAs(domainObject1); - // Test with an instance of another class - Object testOtherObject = new Object(); - assertThat(domainObject1).isNotEqualTo(testOtherObject); - assertThat(domainObject1).isNotEqualTo(null); - // Test with an instance of the same class - T domainObject2 = clazz.getConstructor().newInstance(); - assertThat(domainObject1).isNotEqualTo(domainObject2); - // HashCodes are equals because the objects are not persisted yet - assertThat(domainObject1).hasSameHashCodeAs(domainObject2); - } - - /** - * Create a {@link FormattingConversionService} which use ISO date format, instead of the localized one. - * @return the {@link FormattingConversionService}. - */ - public static FormattingConversionService createFormattingConversionService() { - DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService(); - DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); - registrar.setUseIsoFormat(true); - registrar.registerFormatters(dfcs); - return dfcs; - } - - /** - * Makes a an executes a query to the EntityManager finding all stored objects. - * @param The type of objects to be searched - * @param em The instance of the EntityManager - * @param clss The class type to be searched - * @return A list of all found objects - */ - public static List findAll(EntityManager em, Class clss) { - CriteriaBuilder cb = em.getCriteriaBuilder(); - CriteriaQuery cq = cb.createQuery(clss); - Root rootEntry = cq.from(clss); - CriteriaQuery all = cq.select(rootEntry); - TypedQuery allQuery = em.createQuery(all); - return allQuery.getResultList(); - } - - private TestUtil() {} -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/UserJWTControllerIT.java b/src/test/java/io/github/shuoros/peoplify/web/rest/UserJWTControllerIT.java deleted file mode 100644 index 00b9b70..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/UserJWTControllerIT.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import static org.hamcrest.Matchers.emptyString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.web.rest.vm.LoginVM; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integration tests for the {@link UserJWTController} REST controller. - */ -@AutoConfigureMockMvc -@IntegrationTest -class UserJWTControllerIT { - - @Autowired - private UserRepository userRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Autowired - private MockMvc mockMvc; - - @Test - @Transactional - void testAuthorize() throws Exception { - User user = new User(); - user.setLogin("user-jwt-controller"); - user.setEmail("user-jwt-controller@example.com"); - user.setActivated(true); - user.setPassword(passwordEncoder.encode("test")); - - userRepository.saveAndFlush(user); - - LoginVM login = new LoginVM(); - login.setUsername("user-jwt-controller"); - login.setPassword("test"); - mockMvc - .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id_token").isString()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andExpect(header().string("Authorization", not(nullValue()))) - .andExpect(header().string("Authorization", not(is(emptyString())))); - } - - @Test - @Transactional - void testAuthorizeWithRememberMe() throws Exception { - User user = new User(); - user.setLogin("user-jwt-controller-remember-me"); - user.setEmail("user-jwt-controller-remember-me@example.com"); - user.setActivated(true); - user.setPassword(passwordEncoder.encode("test")); - - userRepository.saveAndFlush(user); - - LoginVM login = new LoginVM(); - login.setUsername("user-jwt-controller-remember-me"); - login.setPassword("test"); - login.setRememberMe(true); - mockMvc - .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id_token").isString()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andExpect(header().string("Authorization", not(nullValue()))) - .andExpect(header().string("Authorization", not(is(emptyString())))); - } - - @Test - void testAuthorizeFails() throws Exception { - LoginVM login = new LoginVM(); - login.setUsername("wrong-user"); - login.setPassword("wrong password"); - mockMvc - .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) - .andExpect(status().isUnauthorized()) - .andExpect(jsonPath("$.id_token").doesNotExist()) - .andExpect(header().doesNotExist("Authorization")); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/UserResourceIT.java b/src/test/java/io/github/shuoros/peoplify/web/rest/UserResourceIT.java deleted file mode 100644 index 5484d52..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/UserResourceIT.java +++ /dev/null @@ -1,582 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import io.github.shuoros.peoplify.IntegrationTest; -import io.github.shuoros.peoplify.domain.Authority; -import io.github.shuoros.peoplify.domain.User; -import io.github.shuoros.peoplify.repository.UserRepository; -import io.github.shuoros.peoplify.security.AuthoritiesConstants; -import io.github.shuoros.peoplify.service.dto.AdminUserDTO; -import io.github.shuoros.peoplify.service.mapper.UserMapper; -import io.github.shuoros.peoplify.web.rest.vm.ManagedUserVM; -import java.time.Instant; -import java.util.*; -import java.util.function.Consumer; -import javax.persistence.EntityManager; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.cache.CacheManager; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; - -/** - * Integration tests for the {@link UserResource} REST controller. - */ -@AutoConfigureMockMvc -@WithMockUser(authorities = AuthoritiesConstants.ADMIN) -@IntegrationTest -class UserResourceIT { - - private static final String DEFAULT_LOGIN = "johndoe"; - private static final String UPDATED_LOGIN = "jhipster"; - - private static final Long DEFAULT_ID = 1L; - - private static final String DEFAULT_PASSWORD = "passjohndoe"; - private static final String UPDATED_PASSWORD = "passjhipster"; - - private static final String DEFAULT_EMAIL = "johndoe@localhost"; - private static final String UPDATED_EMAIL = "jhipster@localhost"; - - private static final String DEFAULT_FIRSTNAME = "john"; - private static final String UPDATED_FIRSTNAME = "jhipsterFirstName"; - - private static final String DEFAULT_LASTNAME = "doe"; - private static final String UPDATED_LASTNAME = "jhipsterLastName"; - - private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; - private static final String UPDATED_IMAGEURL = "http://placehold.it/40x40"; - - private static final String DEFAULT_LANGKEY = "en"; - private static final String UPDATED_LANGKEY = "fr"; - - @Autowired - private UserRepository userRepository; - - @Autowired - private UserMapper userMapper; - - @Autowired - private EntityManager em; - - @Autowired - private CacheManager cacheManager; - - @Autowired - private MockMvc restUserMockMvc; - - private User user; - - @BeforeEach - public void setup() { - cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); - cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); - } - - /** - * Create a User. - * - * This is a static method, as tests for other entities might also need it, - * if they test an entity which has a required relationship to the User entity. - */ - public static User createEntity(EntityManager em) { - User user = new User(); - user.setLogin(DEFAULT_LOGIN + RandomStringUtils.randomAlphabetic(5)); - user.setPassword(RandomStringUtils.randomAlphanumeric(60)); - user.setActivated(true); - user.setEmail(RandomStringUtils.randomAlphabetic(5) + DEFAULT_EMAIL); - user.setFirstName(DEFAULT_FIRSTNAME); - user.setLastName(DEFAULT_LASTNAME); - user.setImageUrl(DEFAULT_IMAGEURL); - user.setLangKey(DEFAULT_LANGKEY); - return user; - } - - /** - * Setups the database with one user. - */ - public static User initTestUser(UserRepository userRepository, EntityManager em) { - userRepository.deleteAll(); - User user = createEntity(em); - user.setLogin(DEFAULT_LOGIN); - user.setEmail(DEFAULT_EMAIL); - return user; - } - - @BeforeEach - public void initTest() { - user = initTestUser(userRepository, em); - } - - @Test - @Transactional - void createUser() throws Exception { - int databaseSizeBeforeCreate = userRepository.findAll().size(); - - // Create the User - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setLogin(DEFAULT_LOGIN); - managedUserVM.setPassword(DEFAULT_PASSWORD); - managedUserVM.setFirstName(DEFAULT_FIRSTNAME); - managedUserVM.setLastName(DEFAULT_LASTNAME); - managedUserVM.setEmail(DEFAULT_EMAIL); - managedUserVM.setActivated(true); - managedUserVM.setImageUrl(DEFAULT_IMAGEURL); - managedUserVM.setLangKey(DEFAULT_LANGKEY); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restUserMockMvc - .perform( - post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isCreated()); - - // Validate the User in the database - assertPersistedUsers(users -> { - assertThat(users).hasSize(databaseSizeBeforeCreate + 1); - User testUser = users.get(users.size() - 1); - assertThat(testUser.getLogin()).isEqualTo(DEFAULT_LOGIN); - assertThat(testUser.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); - assertThat(testUser.getLastName()).isEqualTo(DEFAULT_LASTNAME); - assertThat(testUser.getEmail()).isEqualTo(DEFAULT_EMAIL); - assertThat(testUser.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); - assertThat(testUser.getLangKey()).isEqualTo(DEFAULT_LANGKEY); - }); - } - - @Test - @Transactional - void createUserWithExistingId() throws Exception { - int databaseSizeBeforeCreate = userRepository.findAll().size(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setId(DEFAULT_ID); - managedUserVM.setLogin(DEFAULT_LOGIN); - managedUserVM.setPassword(DEFAULT_PASSWORD); - managedUserVM.setFirstName(DEFAULT_FIRSTNAME); - managedUserVM.setLastName(DEFAULT_LASTNAME); - managedUserVM.setEmail(DEFAULT_EMAIL); - managedUserVM.setActivated(true); - managedUserVM.setImageUrl(DEFAULT_IMAGEURL); - managedUserVM.setLangKey(DEFAULT_LANGKEY); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - // An entity with an existing ID cannot be created, so this API call must fail - restUserMockMvc - .perform( - post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isBadRequest()); - - // Validate the User in the database - assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); - } - - @Test - @Transactional - void createUserWithExistingLogin() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - int databaseSizeBeforeCreate = userRepository.findAll().size(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setLogin(DEFAULT_LOGIN); // this login should already be used - managedUserVM.setPassword(DEFAULT_PASSWORD); - managedUserVM.setFirstName(DEFAULT_FIRSTNAME); - managedUserVM.setLastName(DEFAULT_LASTNAME); - managedUserVM.setEmail("anothermail@localhost"); - managedUserVM.setActivated(true); - managedUserVM.setImageUrl(DEFAULT_IMAGEURL); - managedUserVM.setLangKey(DEFAULT_LANGKEY); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - // Create the User - restUserMockMvc - .perform( - post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isBadRequest()); - - // Validate the User in the database - assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); - } - - @Test - @Transactional - void createUserWithExistingEmail() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - int databaseSizeBeforeCreate = userRepository.findAll().size(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setLogin("anotherlogin"); - managedUserVM.setPassword(DEFAULT_PASSWORD); - managedUserVM.setFirstName(DEFAULT_FIRSTNAME); - managedUserVM.setLastName(DEFAULT_LASTNAME); - managedUserVM.setEmail(DEFAULT_EMAIL); // this email should already be used - managedUserVM.setActivated(true); - managedUserVM.setImageUrl(DEFAULT_IMAGEURL); - managedUserVM.setLangKey(DEFAULT_LANGKEY); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - // Create the User - restUserMockMvc - .perform( - post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isBadRequest()); - - // Validate the User in the database - assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); - } - - @Test - @Transactional - void getAllUsers() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - - // Get all the users - restUserMockMvc - .perform(get("/api/admin/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) - .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME))) - .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME))) - .andExpect(jsonPath("$.[*].email").value(hasItem(DEFAULT_EMAIL))) - .andExpect(jsonPath("$.[*].imageUrl").value(hasItem(DEFAULT_IMAGEURL))) - .andExpect(jsonPath("$.[*].langKey").value(hasItem(DEFAULT_LANGKEY))); - } - - @Test - @Transactional - void getUser() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - - assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); - - // Get the user - restUserMockMvc - .perform(get("/api/admin/users/{login}", user.getLogin())) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.login").value(user.getLogin())) - .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME)) - .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME)) - .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL)) - .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL)) - .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY)); - - assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNotNull(); - } - - @Test - @Transactional - void getNonExistingUser() throws Exception { - restUserMockMvc.perform(get("/api/admin/users/unknown")).andExpect(status().isNotFound()); - } - - @Test - @Transactional - void updateUser() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - int databaseSizeBeforeUpdate = userRepository.findAll().size(); - - // Update the user - User updatedUser = userRepository.findById(user.getId()).get(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setId(updatedUser.getId()); - managedUserVM.setLogin(updatedUser.getLogin()); - managedUserVM.setPassword(UPDATED_PASSWORD); - managedUserVM.setFirstName(UPDATED_FIRSTNAME); - managedUserVM.setLastName(UPDATED_LASTNAME); - managedUserVM.setEmail(UPDATED_EMAIL); - managedUserVM.setActivated(updatedUser.isActivated()); - managedUserVM.setImageUrl(UPDATED_IMAGEURL); - managedUserVM.setLangKey(UPDATED_LANGKEY); - managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); - managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); - managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); - managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restUserMockMvc - .perform( - put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isOk()); - - // Validate the User in the database - assertPersistedUsers(users -> { - assertThat(users).hasSize(databaseSizeBeforeUpdate); - User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().get(); - assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); - assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); - assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); - assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); - assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); - }); - } - - @Test - @Transactional - void updateUserLogin() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - int databaseSizeBeforeUpdate = userRepository.findAll().size(); - - // Update the user - User updatedUser = userRepository.findById(user.getId()).get(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setId(updatedUser.getId()); - managedUserVM.setLogin(UPDATED_LOGIN); - managedUserVM.setPassword(UPDATED_PASSWORD); - managedUserVM.setFirstName(UPDATED_FIRSTNAME); - managedUserVM.setLastName(UPDATED_LASTNAME); - managedUserVM.setEmail(UPDATED_EMAIL); - managedUserVM.setActivated(updatedUser.isActivated()); - managedUserVM.setImageUrl(UPDATED_IMAGEURL); - managedUserVM.setLangKey(UPDATED_LANGKEY); - managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); - managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); - managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); - managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restUserMockMvc - .perform( - put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isOk()); - - // Validate the User in the database - assertPersistedUsers(users -> { - assertThat(users).hasSize(databaseSizeBeforeUpdate); - User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().get(); - assertThat(testUser.getLogin()).isEqualTo(UPDATED_LOGIN); - assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); - assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); - assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); - assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); - assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); - }); - } - - @Test - @Transactional - void updateUserExistingEmail() throws Exception { - // Initialize the database with 2 users - userRepository.saveAndFlush(user); - - User anotherUser = new User(); - anotherUser.setLogin("jhipster"); - anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); - anotherUser.setActivated(true); - anotherUser.setEmail("jhipster@localhost"); - anotherUser.setFirstName("java"); - anotherUser.setLastName("hipster"); - anotherUser.setImageUrl(""); - anotherUser.setLangKey("en"); - userRepository.saveAndFlush(anotherUser); - - // Update the user - User updatedUser = userRepository.findById(user.getId()).get(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setId(updatedUser.getId()); - managedUserVM.setLogin(updatedUser.getLogin()); - managedUserVM.setPassword(updatedUser.getPassword()); - managedUserVM.setFirstName(updatedUser.getFirstName()); - managedUserVM.setLastName(updatedUser.getLastName()); - managedUserVM.setEmail("jhipster@localhost"); // this email should already be used by anotherUser - managedUserVM.setActivated(updatedUser.isActivated()); - managedUserVM.setImageUrl(updatedUser.getImageUrl()); - managedUserVM.setLangKey(updatedUser.getLangKey()); - managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); - managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); - managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); - managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restUserMockMvc - .perform( - put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isBadRequest()); - } - - @Test - @Transactional - void updateUserExistingLogin() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - - User anotherUser = new User(); - anotherUser.setLogin("jhipster"); - anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); - anotherUser.setActivated(true); - anotherUser.setEmail("jhipster@localhost"); - anotherUser.setFirstName("java"); - anotherUser.setLastName("hipster"); - anotherUser.setImageUrl(""); - anotherUser.setLangKey("en"); - userRepository.saveAndFlush(anotherUser); - - // Update the user - User updatedUser = userRepository.findById(user.getId()).get(); - - ManagedUserVM managedUserVM = new ManagedUserVM(); - managedUserVM.setId(updatedUser.getId()); - managedUserVM.setLogin("jhipster"); // this login should already be used by anotherUser - managedUserVM.setPassword(updatedUser.getPassword()); - managedUserVM.setFirstName(updatedUser.getFirstName()); - managedUserVM.setLastName(updatedUser.getLastName()); - managedUserVM.setEmail(updatedUser.getEmail()); - managedUserVM.setActivated(updatedUser.isActivated()); - managedUserVM.setImageUrl(updatedUser.getImageUrl()); - managedUserVM.setLangKey(updatedUser.getLangKey()); - managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); - managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); - managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); - managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); - managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - restUserMockMvc - .perform( - put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) - ) - .andExpect(status().isBadRequest()); - } - - @Test - @Transactional - void deleteUser() throws Exception { - // Initialize the database - userRepository.saveAndFlush(user); - int databaseSizeBeforeDelete = userRepository.findAll().size(); - - // Delete the user - restUserMockMvc - .perform(delete("/api/admin/users/{login}", user.getLogin()).accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); - - assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); - - // Validate the database is empty - assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeDelete - 1)); - } - - @Test - void testUserEquals() throws Exception { - TestUtil.equalsVerifier(User.class); - User user1 = new User(); - user1.setId(DEFAULT_ID); - User user2 = new User(); - user2.setId(user1.getId()); - assertThat(user1).isEqualTo(user2); - user2.setId(2L); - assertThat(user1).isNotEqualTo(user2); - user1.setId(null); - assertThat(user1).isNotEqualTo(user2); - } - - @Test - void testUserDTOtoUser() { - AdminUserDTO userDTO = new AdminUserDTO(); - userDTO.setId(DEFAULT_ID); - userDTO.setLogin(DEFAULT_LOGIN); - userDTO.setFirstName(DEFAULT_FIRSTNAME); - userDTO.setLastName(DEFAULT_LASTNAME); - userDTO.setEmail(DEFAULT_EMAIL); - userDTO.setActivated(true); - userDTO.setImageUrl(DEFAULT_IMAGEURL); - userDTO.setLangKey(DEFAULT_LANGKEY); - userDTO.setCreatedBy(DEFAULT_LOGIN); - userDTO.setLastModifiedBy(DEFAULT_LOGIN); - userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); - - User user = userMapper.userDTOToUser(userDTO); - assertThat(user.getId()).isEqualTo(DEFAULT_ID); - assertThat(user.getLogin()).isEqualTo(DEFAULT_LOGIN); - assertThat(user.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); - assertThat(user.getLastName()).isEqualTo(DEFAULT_LASTNAME); - assertThat(user.getEmail()).isEqualTo(DEFAULT_EMAIL); - assertThat(user.isActivated()).isTrue(); - assertThat(user.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); - assertThat(user.getLangKey()).isEqualTo(DEFAULT_LANGKEY); - assertThat(user.getCreatedBy()).isNull(); - assertThat(user.getCreatedDate()).isNotNull(); - assertThat(user.getLastModifiedBy()).isNull(); - assertThat(user.getLastModifiedDate()).isNotNull(); - assertThat(user.getAuthorities()).extracting("name").containsExactly(AuthoritiesConstants.USER); - } - - @Test - void testUserToUserDTO() { - user.setId(DEFAULT_ID); - user.setCreatedBy(DEFAULT_LOGIN); - user.setCreatedDate(Instant.now()); - user.setLastModifiedBy(DEFAULT_LOGIN); - user.setLastModifiedDate(Instant.now()); - Set authorities = new HashSet<>(); - Authority authority = new Authority(); - authority.setName(AuthoritiesConstants.USER); - authorities.add(authority); - user.setAuthorities(authorities); - - AdminUserDTO userDTO = userMapper.userToAdminUserDTO(user); - - assertThat(userDTO.getId()).isEqualTo(DEFAULT_ID); - assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); - assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); - assertThat(userDTO.getLastName()).isEqualTo(DEFAULT_LASTNAME); - assertThat(userDTO.getEmail()).isEqualTo(DEFAULT_EMAIL); - assertThat(userDTO.isActivated()).isTrue(); - assertThat(userDTO.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); - assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); - assertThat(userDTO.getCreatedBy()).isEqualTo(DEFAULT_LOGIN); - assertThat(userDTO.getCreatedDate()).isEqualTo(user.getCreatedDate()); - assertThat(userDTO.getLastModifiedBy()).isEqualTo(DEFAULT_LOGIN); - assertThat(userDTO.getLastModifiedDate()).isEqualTo(user.getLastModifiedDate()); - assertThat(userDTO.getAuthorities()).containsExactly(AuthoritiesConstants.USER); - assertThat(userDTO.toString()).isNotNull(); - } - - @Test - void testAuthorityEquals() { - Authority authorityA = new Authority(); - assertThat(authorityA).isNotEqualTo(null).isNotEqualTo(new Object()); - assertThat(authorityA.hashCode()).isZero(); - assertThat(authorityA.toString()).isNotNull(); - - Authority authorityB = new Authority(); - assertThat(authorityA).isEqualTo(authorityB); - - authorityB.setName(AuthoritiesConstants.ADMIN); - assertThat(authorityA).isNotEqualTo(authorityB); - - authorityA.setName(AuthoritiesConstants.USER); - assertThat(authorityA).isNotEqualTo(authorityB); - - authorityB.setName(AuthoritiesConstants.USER); - assertThat(authorityA).isEqualTo(authorityB).hasSameHashCodeAs(authorityB); - } - - private void assertPersistedUsers(Consumer> userAssertion) { - userAssertion.accept(userRepository.findAll()); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/WithUnauthenticatedMockUser.java b/src/test/java/io/github/shuoros/peoplify/web/rest/WithUnauthenticatedMockUser.java deleted file mode 100644 index 34a426b..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/WithUnauthenticatedMockUser.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.shuoros.peoplify.web.rest; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.test.context.support.WithSecurityContext; -import org.springframework.security.test.context.support.WithSecurityContextFactory; - -@Target({ ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@WithSecurityContext(factory = WithUnauthenticatedMockUser.Factory.class) -public @interface WithUnauthenticatedMockUser { - class Factory implements WithSecurityContextFactory { - - @Override - public SecurityContext createSecurityContext(WithUnauthenticatedMockUser annotation) { - return SecurityContextHolder.createEmptyContext(); - } - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorIT.java b/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorIT.java deleted file mode 100644 index c7e44e2..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorIT.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import io.github.shuoros.peoplify.IntegrationTest; -import org.junit.jupiter.api.Test; -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.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; - -/** - * Integration tests {@link ExceptionTranslator} controller advice. - */ -@WithMockUser -@AutoConfigureMockMvc -@IntegrationTest -class ExceptionTranslatorIT { - - @Autowired - private MockMvc mockMvc; - - @Test - void testConcurrencyFailure() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/concurrency-failure")) - .andExpect(status().isConflict()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); - } - - @Test - void testMethodArgumentNotValid() throws Exception { - mockMvc - .perform(post("/api/exception-translator-test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) - .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].message").value("must not be null")); - } - - @Test - void testMissingServletRequestPartException() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/missing-servlet-request-part")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")); - } - - @Test - void testMissingServletRequestParameterException() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/missing-servlet-request-parameter")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")); - } - - @Test - void testAccessDenied() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/access-denied")) - .andExpect(status().isForbidden()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.403")) - .andExpect(jsonPath("$.detail").value("test access denied!")); - } - - @Test - void testUnauthorized() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/unauthorized")) - .andExpect(status().isUnauthorized()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.401")) - .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized")) - .andExpect(jsonPath("$.detail").value("test authentication failed!")); - } - - @Test - void testMethodNotSupported() throws Exception { - mockMvc - .perform(post("/api/exception-translator-test/access-denied")) - .andExpect(status().isMethodNotAllowed()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.405")) - .andExpect(jsonPath("$.detail").value("Request method 'POST' not supported")); - } - - @Test - void testExceptionWithResponseStatus() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/response-status")) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.400")) - .andExpect(jsonPath("$.title").value("test response status")); - } - - @Test - void testInternalServerError() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/internal-server-error")) - .andExpect(status().isInternalServerError()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value("error.http.500")) - .andExpect(jsonPath("$.title").value("Internal Server Error")); - } -} diff --git a/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorTestController.java b/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorTestController.java deleted file mode 100644 index 9e6243f..0000000 --- a/src/test/java/io/github/shuoros/peoplify/web/rest/errors/ExceptionTranslatorTestController.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.shuoros.peoplify.web.rest.errors; - -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/exception-translator-test") -public class ExceptionTranslatorTestController { - - @GetMapping("/concurrency-failure") - public void concurrencyFailure() { - throw new ConcurrencyFailureException("test concurrency failure"); - } - - @PostMapping("/method-argument") - public void methodArgument(@Valid @RequestBody TestDTO testDTO) {} - - @GetMapping("/missing-servlet-request-part") - public void missingServletRequestPartException(@RequestPart String part) {} - - @GetMapping("/missing-servlet-request-parameter") - public void missingServletRequestParameterException(@RequestParam String param) {} - - @GetMapping("/access-denied") - public void accessdenied() { - throw new AccessDeniedException("test access denied!"); - } - - @GetMapping("/unauthorized") - public void unauthorized() { - throw new BadCredentialsException("test authentication failed!"); - } - - @GetMapping("/response-status") - public void exceptionWithResponseStatus() { - throw new TestResponseStatusException(); - } - - @GetMapping("/internal-server-error") - public void internalServerError() { - throw new RuntimeException(); - } - - public static class TestDTO { - - @NotNull - private String test; - - public String getTest() { - return test; - } - - public void setTest(String test) { - this.test = test; - } - } - - @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status") - @SuppressWarnings("serial") - public static class TestResponseStatusException extends RuntimeException {} -} diff --git a/src/test/resources/META-INF/spring.factories b/src/test/resources/META-INF/spring.factories deleted file mode 100644 index 7298973..0000000 --- a/src/test/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.test.context.ContextCustomizerFactory = io.github.shuoros.peoplify.config.TestContainersSpringContextCustomizerFactory diff --git a/src/test/resources/config/application-testdev.yml b/src/test/resources/config/application-testdev.yml deleted file mode 100644 index 30e843c..0000000 --- a/src/test/resources/config/application-testdev.yml +++ /dev/null @@ -1,34 +0,0 @@ -# =================================================================== -# Spring Boot configuration. -# -# This configuration is used for unit/integration tests with testcontainers database containers. -# -# To activate this configuration launch integration tests with the 'testcontainers' profile -# -# More information on database containers: https://www.testcontainers.org/modules/databases/ -# =================================================================== - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - hikari: - auto-commit: false - poolName: Hikari - maximum-pool-size: 1 - jpa: - database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect - open-in-view: false - hibernate: - ddl-auto: none - naming: - physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - properties: - hibernate.id.new_generator_mappings: true - hibernate.connection.provider_disables_autocommit: true - hibernate.cache.use_second_level_cache: false - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: false - hibernate.hbm2ddl.auto: validate - hibernate.jdbc.time_zone: UTC - hibernate.query.fail_on_pagination_over_collection_fetch: true diff --git a/src/test/resources/config/application-testprod.yml b/src/test/resources/config/application-testprod.yml deleted file mode 100644 index 484eda8..0000000 --- a/src/test/resources/config/application-testprod.yml +++ /dev/null @@ -1,34 +0,0 @@ -# =================================================================== -# Spring Boot configuration. -# -# This configuration is used for unit/integration tests with testcontainers database containers. -# -# To activate this configuration launch integration tests with the 'testcontainers' profile -# -# More information on database containers: https://www.testcontainers.org/modules/databases/ -# =================================================================== - -spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - hikari: - poolName: Hikari - auto-commit: false - maximum-pool-size: 1 - jpa: - database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect - open-in-view: false - hibernate: - ddl-auto: none - naming: - physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy - properties: - hibernate.id.new_generator_mappings: true - hibernate.connection.provider_disables_autocommit: true - hibernate.cache.use_second_level_cache: false - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: false - hibernate.hbm2ddl.auto: validate - hibernate.jdbc.time_zone: UTC - hibernate.query.fail_on_pagination_over_collection_fetch: true diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml deleted file mode 100644 index 585b315..0000000 --- a/src/test/resources/config/application.yml +++ /dev/null @@ -1,92 +0,0 @@ -# =================================================================== -# Spring Boot configuration. -# -# This configuration is used for unit/integration tests. -# -# More information on profiles: https://www.jhipster.tech/profiles/ -# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# =================================================================== -# Standard Spring Boot properties. -# Full reference is available at: -# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html -# =================================================================== - -spring: - application: - name: peoplify - # Replace by 'prod, faker' to add the faker context and have sample data loaded in production - liquibase: - contexts: test - jackson: - serialization: - write-durations-as-timestamps: false - mail: - host: localhost - main: - allow-bean-definition-overriding: true - mvc: - pathmatch: - matching-strategy: ant_path_matcher - messages: - basename: i18n/messages - task: - execution: - thread-name-prefix: peoplify-task- - pool: - core-size: 1 - max-size: 50 - queue-capacity: 10000 - scheduling: - thread-name-prefix: peoplify-scheduling- - pool: - size: 20 - thymeleaf: - mode: HTML - -server: - port: 10344 - address: localhost - -# =================================================================== -# JHipster specific properties -# -# Full reference is available at: https://www.jhipster.tech/common-application-properties/ -# =================================================================== -jhipster: - clientApp: - name: 'peoplifyApp' - mail: - from: peoplify@localhost.com - base-url: http://127.0.0.1:8080 - logging: - # To test json console appender - use-json-format: false - logstash: - enabled: false - host: localhost - port: 5000 - queue-size: 512 - security: - authentication: - jwt: - # This token must be encoded using Base64 (you can type `echo 'secret-key'|base64` on your command line) - base64-secret: ZDUxMTM5YWRmNjBiZDhmOTJlMTkxNWVmM2VlOTJhYTZiMjEyYjZiZDJlOGRhMmE5NmJjODFkYTI1MjQ2MzdjODdhNjk3OTg4YTA2MDk0Mjk1NDVkZWU2MGFlYzFmOTEwNzRlODVkZTI4NjVjNjM4YTE1ODY5NzA4ZmZhNzE5NDk= - # Token is valid 24 hours - token-validity-in-seconds: 86400 - -# =================================================================== -# Application specific properties -# Add your own application properties here, see the ApplicationProperties class -# to have type-safe configuration, like in the JHipsterProperties above -# -# More documentation is available at: -# https://www.jhipster.tech/common-application-properties/ -# =================================================================== - -# application: -management: - health: - mail: - enabled: false diff --git a/src/test/resources/config/bootstrap.yml b/src/test/resources/config/bootstrap.yml deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/resources/i18n/messages_en.properties b/src/test/resources/i18n/messages_en.properties deleted file mode 100644 index f19db86..0000000 --- a/src/test/resources/i18n/messages_en.properties +++ /dev/null @@ -1 +0,0 @@ -email.test.title=test title diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties deleted file mode 100644 index 706b17a..0000000 --- a/src/test/resources/junit-platform.properties +++ /dev/null @@ -1,4 +0,0 @@ -junit.jupiter.execution.timeout.default = 15 s -junit.jupiter.execution.timeout.testable.method.default = 15 s -junit.jupiter.execution.timeout.beforeall.method.default = 60 s -junit.jupiter.testclass.order.default=io.github.shuoros.peoplify.config.SpringBootTestClassOrderer diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml deleted file mode 100644 index ea4a435..0000000 --- a/src/test/resources/logback.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WARN - - - - diff --git a/src/test/resources/templates/mail/activationEmail.html b/src/test/resources/templates/mail/activationEmail.html deleted file mode 100644 index 01ad64c..0000000 --- a/src/test/resources/templates/mail/activationEmail.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - JHipster activation - - - -

Dear

-

Your JHipster account has been created, please click on the URL below to activate it:

-

- Activation link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/test/resources/templates/mail/creationEmail.html b/src/test/resources/templates/mail/creationEmail.html deleted file mode 100644 index e96075f..0000000 --- a/src/test/resources/templates/mail/creationEmail.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - JHipster creation - - - -

Dear

-

Your JHipster account has been created, please click on the URL below to access it:

-

- Login link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/test/resources/templates/mail/passwordResetEmail.html b/src/test/resources/templates/mail/passwordResetEmail.html deleted file mode 100644 index ca7727a..0000000 --- a/src/test/resources/templates/mail/passwordResetEmail.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - JHipster password reset - - - -

Dear

-

- For your JHipster account a password reset was requested, please click on the URL below to reset it: -

-

- Login link -

-

- Regards, -
- JHipster. -

- - diff --git a/src/test/resources/templates/mail/testEmail.html b/src/test/resources/templates/mail/testEmail.html deleted file mode 100644 index a4ca16a..0000000 --- a/src/test/resources/templates/mail/testEmail.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/test/resources/testcontainers.properties b/src/test/resources/testcontainers.properties deleted file mode 100644 index f0e6e42..0000000 --- a/src/test/resources/testcontainers.properties +++ /dev/null @@ -1 +0,0 @@ -testcontainers.reuse.enable=true From 5baa0ae8eb7ee82be23d2a8c7d6fd3a5e4cd5cdd Mon Sep 17 00:00:00 2001 From: Soroush Date: Mon, 7 Aug 2023 08:20:57 +0200 Subject: [PATCH 2/6] IMPLEMENT[setup canvas and its background color by random] --- .../controller/GeneratorController.java | 25 +++++++++++ .../model/enumeration/DefaultColor.java | 41 ++++++++++++++++++ .../service/AvatarGeneratorService.java | 42 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/main/java/io/github/shuoros/peoplify/controller/GeneratorController.java create mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java create mode 100644 src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java diff --git a/src/main/java/io/github/shuoros/peoplify/controller/GeneratorController.java b/src/main/java/io/github/shuoros/peoplify/controller/GeneratorController.java new file mode 100644 index 0000000..3d338ec --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/controller/GeneratorController.java @@ -0,0 +1,25 @@ +package io.github.shuoros.peoplify.controller; + +import io.github.shuoros.peoplify.service.AvatarGeneratorService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +@RestController +@RequestMapping("/api/generate") +public class GeneratorController { + + @Autowired + private AvatarGeneratorService avatarGeneratorService; + + @GetMapping(path = "/avatar", produces = MediaType.IMAGE_PNG_VALUE) + public ResponseEntity generateAvatar() { + return ResponseEntity + .ok() + .body(outputStream -> avatarGeneratorService.generateAvatar(outputStream)); + } +} diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java new file mode 100644 index 0000000..2bb9708 --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java @@ -0,0 +1,41 @@ +package io.github.shuoros.peoplify.model.enumeration; + +import java.awt.*; + +public enum DefaultColor { + RED("red", new Color(190, 30, 45)), + BLUE("blue", new Color(28, 117, 188)), + GREEN("green", new Color(120, 193, 67)), + YELLOW("yellow", new Color(231, 209, 35)), + ORANGE("orange", new Color(245, 119, 31)), + PURPLE("purple", new Color(127, 63, 152)), + PINK("pink", new Color(255, 17, 125)), + BROWN("brown", new Color(117, 76, 41)), + INDIGO("indigo", new Color(38, 34, 98)), + GRAY("gray", new Color(115, 115, 115)), + LAVENDER("lavender", new Color(199, 153, 198)), + CYAN("cyan", new Color(111, 204, 221)), + BLACK("black", new Color(0, 0, 0)), + WHITE("white", new Color(255, 255, 255)); + + private String name; + private Color color; + + DefaultColor(String name, Color color) { + this.name = name; + this.color = color; + } + + public static DefaultColor findByName(String name) { + for (DefaultColor color : DefaultColor.values()) { + if (color.name.equalsIgnoreCase(name)) { + return color; + } + } + return null; + } + + public Color getColor() { + return color; + } +} diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java new file mode 100644 index 0000000..c3f43e7 --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java @@ -0,0 +1,42 @@ +package io.github.shuoros.peoplify.service; + +import io.github.shuoros.peoplify.model.enumeration.DefaultColor; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +@Service +public class AvatarGeneratorService { + + protected static final int CANVAS_SIZE = 32; + + public void generateAvatar(OutputStream outputStream) throws IOException { + final BufferedImage canvas = setUpCanvas(); + writeToOutputStream(canvas, outputStream); + } + + private BufferedImage setUpCanvas() { + final BufferedImage canvas = new BufferedImage(CANVAS_SIZE, CANVAS_SIZE, BufferedImage.TYPE_INT_RGB); + final Color backgroundColor = resolveRandomColor(); + for (int i = 0; i < CANVAS_SIZE; i++) { + for (int j = 0; j < CANVAS_SIZE; j++) { + canvas.setRGB(i, j, backgroundColor.getRGB()); + } + } + return canvas; + } + + private Color resolveRandomColor() { + final Random random = new Random(); + return DefaultColor.values()[random.nextInt(DefaultColor.values().length)].getColor(); + } + + private void writeToOutputStream(BufferedImage canvas, OutputStream outputStream) throws IOException { + ImageIO.write(canvas, "jpg", outputStream); + } +} From 7c888318fe6ea66685873da6af76991dbc1acf83 Mon Sep 17 00:00:00 2001 From: Soroush Date: Mon, 7 Aug 2023 10:28:56 +0200 Subject: [PATCH 3/6] IMPLEMENT[render random body] --- .../model/enumeration/BackgroundColor.java | 39 +++++++++++++++++ .../peoplify/model/enumeration/BodyColor.java | 11 +++++ .../model/enumeration/DefaultColor.java | 41 ------------------ .../service/AvatarComponentsProvider.java | 34 +++++++++++++++ .../service/AvatarGeneratorService.java | 32 +++++++++++--- src/main/resources/static/body-black.png | Bin 0 -> 7254 bytes src/main/resources/static/body-brown.png | Bin 0 -> 7180 bytes src/main/resources/static/body-nude.png | Bin 0 -> 7102 bytes src/main/resources/static/body-pink.png | Bin 0 -> 7158 bytes src/main/resources/static/body-white.png | Bin 0 -> 7267 bytes src/main/resources/static/body-yellow.png | Bin 0 -> 7091 bytes 11 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/BackgroundColor.java create mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/BodyColor.java delete mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java create mode 100644 src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java create mode 100644 src/main/resources/static/body-black.png create mode 100644 src/main/resources/static/body-brown.png create mode 100644 src/main/resources/static/body-nude.png create mode 100644 src/main/resources/static/body-pink.png create mode 100644 src/main/resources/static/body-white.png create mode 100644 src/main/resources/static/body-yellow.png diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/BackgroundColor.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/BackgroundColor.java new file mode 100644 index 0000000..0b57ae4 --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/model/enumeration/BackgroundColor.java @@ -0,0 +1,39 @@ +package io.github.shuoros.peoplify.model.enumeration; + +import java.awt.*; + +public enum BackgroundColor { + RED(new Color(190, 30, 45)), + BLUE(new Color(28, 117, 188)), + GREEN(new Color(120, 193, 67)), + YELLOW(new Color(231, 209, 35)), + ORANGE(new Color(245, 119, 31)), + PURPLE(new Color(127, 63, 152)), + PINK(new Color(255, 17, 125)), + BROWN(new Color(117, 76, 41)), + INDIGO(new Color(38, 34, 98)), + GRAY(new Color(115, 115, 115)), + LAVENDER(new Color(199, 153, 198)), + CYAN(new Color(111, 204, 221)), + BLACK(new Color(0, 0, 0)), + WHITE(new Color(255, 255, 255)); + + private Color color; + + BackgroundColor(Color color) { + this.color = color; + } + + public static BackgroundColor findByName(String name) { + for (BackgroundColor color : BackgroundColor.values()) { + if (color.name().equalsIgnoreCase(name)) { + return color; + } + } + return null; + } + + public Color getColor() { + return color; + } +} diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/BodyColor.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/BodyColor.java new file mode 100644 index 0000000..2e1b64e --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/model/enumeration/BodyColor.java @@ -0,0 +1,11 @@ +package io.github.shuoros.peoplify.model.enumeration; + +public enum BodyColor { + + YELLOW, + BLACK, + BROWN, + WHITE, + PINK, + NUDE +} diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java deleted file mode 100644 index 2bb9708..0000000 --- a/src/main/java/io/github/shuoros/peoplify/model/enumeration/DefaultColor.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.shuoros.peoplify.model.enumeration; - -import java.awt.*; - -public enum DefaultColor { - RED("red", new Color(190, 30, 45)), - BLUE("blue", new Color(28, 117, 188)), - GREEN("green", new Color(120, 193, 67)), - YELLOW("yellow", new Color(231, 209, 35)), - ORANGE("orange", new Color(245, 119, 31)), - PURPLE("purple", new Color(127, 63, 152)), - PINK("pink", new Color(255, 17, 125)), - BROWN("brown", new Color(117, 76, 41)), - INDIGO("indigo", new Color(38, 34, 98)), - GRAY("gray", new Color(115, 115, 115)), - LAVENDER("lavender", new Color(199, 153, 198)), - CYAN("cyan", new Color(111, 204, 221)), - BLACK("black", new Color(0, 0, 0)), - WHITE("white", new Color(255, 255, 255)); - - private String name; - private Color color; - - DefaultColor(String name, Color color) { - this.name = name; - this.color = color; - } - - public static DefaultColor findByName(String name) { - for (DefaultColor color : DefaultColor.values()) { - if (color.name.equalsIgnoreCase(name)) { - return color; - } - } - return null; - } - - public Color getColor() { - return color; - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java new file mode 100644 index 0000000..b494efb --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java @@ -0,0 +1,34 @@ +package io.github.shuoros.peoplify.service; + +import io.github.shuoros.peoplify.model.enumeration.BodyColor; +import org.springframework.util.ResourceUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class AvatarComponentsProvider { + + protected static final Map body; + + static { + body = Map.of( + BodyColor.YELLOW, loadComponent("body-yellow"), + BodyColor.BLACK, loadComponent("body-black"), + BodyColor.BROWN, loadComponent("body-brown"), + BodyColor.NUDE, loadComponent("body-nude"), + BodyColor.PINK, loadComponent("body-pink"), + BodyColor.WHITE, loadComponent("body-white") + ); + } + + private static BufferedImage loadComponent(String name) { + try { + return ImageIO.read(ResourceUtils.getFile("classpath:static/" + name + ".png")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java index c3f43e7..f66ea74 100644 --- a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java @@ -1,6 +1,7 @@ package io.github.shuoros.peoplify.service; -import io.github.shuoros.peoplify.model.enumeration.DefaultColor; +import io.github.shuoros.peoplify.model.enumeration.BackgroundColor; +import io.github.shuoros.peoplify.model.enumeration.BodyColor; import org.springframework.stereotype.Service; import javax.imageio.ImageIO; @@ -13,16 +14,27 @@ @Service public class AvatarGeneratorService { - protected static final int CANVAS_SIZE = 32; + protected static final int CANVAS_SIZE = 1024; + protected static final Random RANDOM = new Random(); public void generateAvatar(OutputStream outputStream) throws IOException { final BufferedImage canvas = setUpCanvas(); + + renderBody(canvas); + writeToOutputStream(canvas, outputStream); } + private void renderBody(BufferedImage canvas) { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final BufferedImage body = resolveRandomBody(); + graphics.drawImage(body, 20, 50, null); + graphics.dispose(); + } + private BufferedImage setUpCanvas() { final BufferedImage canvas = new BufferedImage(CANVAS_SIZE, CANVAS_SIZE, BufferedImage.TYPE_INT_RGB); - final Color backgroundColor = resolveRandomColor(); + final Color backgroundColor = resolveRandomBackgroundColor(); for (int i = 0; i < CANVAS_SIZE; i++) { for (int j = 0; j < CANVAS_SIZE; j++) { canvas.setRGB(i, j, backgroundColor.getRGB()); @@ -31,12 +43,18 @@ private BufferedImage setUpCanvas() { return canvas; } - private Color resolveRandomColor() { - final Random random = new Random(); - return DefaultColor.values()[random.nextInt(DefaultColor.values().length)].getColor(); + private BufferedImage resolveRandomBody() { + return AvatarComponentsProvider.body.get( + BodyColor.values()[RANDOM.nextInt(BodyColor.values().length)] + ); + } + + private Color resolveRandomBackgroundColor() { + return BackgroundColor.values()[RANDOM.nextInt(BackgroundColor.values().length)].getColor(); } private void writeToOutputStream(BufferedImage canvas, OutputStream outputStream) throws IOException { - ImageIO.write(canvas, "jpg", outputStream); + ImageIO.write(canvas, "png", outputStream); + outputStream.close(); } } diff --git a/src/main/resources/static/body-black.png b/src/main/resources/static/body-black.png new file mode 100644 index 0000000000000000000000000000000000000000..6da2d885c9b0fc07c3061926d0b48f21ecdb0c36 GIT binary patch literal 7254 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 OK}NUCZ&vEvi}_!$ur5*n literal 0 HcmV?d00001 diff --git a/src/main/resources/static/body-brown.png b/src/main/resources/static/body-brown.png new file mode 100644 index 0000000000000000000000000000000000000000..677d9a62c22b8704d4a809396165d22b8098bf51 GIT binary patch literal 7180 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/body-nude.png b/src/main/resources/static/body-nude.png new file mode 100644 index 0000000000000000000000000000000000000000..077bba6b7abc770d67a47c4863c844a5dc6c23f3 GIT binary patch literal 7102 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/body-pink.png b/src/main/resources/static/body-pink.png new file mode 100644 index 0000000000000000000000000000000000000000..6d3361695cc051c736b923b6771865591ce9aed7 GIT binary patch literal 7158 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/body-yellow.png b/src/main/resources/static/body-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..947a44a9b1c8f13dd9d12725cc42681054619756 GIT binary patch literal 7091 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 IntI0m7hj<97XSbN literal 0 HcmV?d00001 From baafa3602313127f291d6ea09c012420741e8dd3 Mon Sep 17 00:00:00 2001 From: Soroush Date: Mon, 7 Aug 2023 11:03:04 +0200 Subject: [PATCH 4/6] IMPLEMENT[render random cloth] --- .../model/enumeration/ClothColor.java | 15 +++++++ .../service/AvatarComponentsProvider.java | 16 +++++++- .../service/AvatarGeneratorService.java | 38 +++++++++++++----- src/main/resources/static/cloth-black.png | Bin 0 -> 2818 bytes src/main/resources/static/cloth-blue.png | Bin 0 -> 2800 bytes src/main/resources/static/cloth-gray.png | Bin 0 -> 2775 bytes src/main/resources/static/cloth-green.png | Bin 0 -> 2793 bytes src/main/resources/static/cloth-orange.png | Bin 0 -> 2799 bytes src/main/resources/static/cloth-pink.png | Bin 0 -> 2787 bytes src/main/resources/static/cloth-purple.png | Bin 0 -> 2797 bytes src/main/resources/static/cloth-red.png | Bin 0 -> 2811 bytes src/main/resources/static/cloth-white.png | Bin 0 -> 2588 bytes src/main/resources/static/cloth-yellow.png | Bin 0 -> 2823 bytes 13 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/ClothColor.java create mode 100644 src/main/resources/static/cloth-black.png create mode 100644 src/main/resources/static/cloth-blue.png create mode 100644 src/main/resources/static/cloth-gray.png create mode 100644 src/main/resources/static/cloth-green.png create mode 100644 src/main/resources/static/cloth-orange.png create mode 100644 src/main/resources/static/cloth-pink.png create mode 100644 src/main/resources/static/cloth-purple.png create mode 100644 src/main/resources/static/cloth-red.png create mode 100644 src/main/resources/static/cloth-white.png create mode 100644 src/main/resources/static/cloth-yellow.png diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/ClothColor.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/ClothColor.java new file mode 100644 index 0000000..ed2ac85 --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/model/enumeration/ClothColor.java @@ -0,0 +1,15 @@ +package io.github.shuoros.peoplify.model.enumeration; + +public enum ClothColor { + + BLACK, + BLUE, + GRAY, + GREEN, + ORANGE, + PINK, + PURPLE, + RED, + WHITE, + YELLOW +} diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java index b494efb..68e840f 100644 --- a/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java @@ -1,17 +1,19 @@ package io.github.shuoros.peoplify.service; import io.github.shuoros.peoplify.model.enumeration.BodyColor; +import io.github.shuoros.peoplify.model.enumeration.ClothColor; import org.springframework.util.ResourceUtils; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; -import java.util.List; import java.util.Map; public class AvatarComponentsProvider { protected static final Map body; + protected static final Map cloth; + static { body = Map.of( @@ -22,6 +24,18 @@ BodyColor.NUDE, loadComponent("body-nude"), BodyColor.PINK, loadComponent("body-pink"), BodyColor.WHITE, loadComponent("body-white") ); + cloth = Map.of( + ClothColor.BLACK, loadComponent("cloth-black"), + ClothColor.BLUE, loadComponent("cloth-blue"), + ClothColor.GRAY, loadComponent("cloth-gray"), + ClothColor.GREEN, loadComponent("cloth-green"), + ClothColor.ORANGE, loadComponent("cloth-orange"), + ClothColor.PINK, loadComponent("cloth-pink"), + ClothColor.PURPLE, loadComponent("cloth-purple"), + ClothColor.RED, loadComponent("cloth-red"), + ClothColor.WHITE, loadComponent("cloth-white"), + ClothColor.YELLOW, loadComponent("cloth-yellow") + ); } private static BufferedImage loadComponent(String name) { diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java index f66ea74..c617892 100644 --- a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java @@ -2,6 +2,7 @@ import io.github.shuoros.peoplify.model.enumeration.BackgroundColor; import io.github.shuoros.peoplify.model.enumeration.BodyColor; +import io.github.shuoros.peoplify.model.enumeration.ClothColor; import org.springframework.stereotype.Service; import javax.imageio.ImageIO; @@ -14,22 +15,17 @@ @Service public class AvatarGeneratorService { - protected static final int CANVAS_SIZE = 1024; - protected static final Random RANDOM = new Random(); + private static final int CANVAS_SIZE = 600; + private static final Random RANDOM = new Random(); public void generateAvatar(OutputStream outputStream) throws IOException { final BufferedImage canvas = setUpCanvas(); renderBody(canvas); - writeToOutputStream(canvas, outputStream); - } + renderCloth(canvas); - private void renderBody(BufferedImage canvas) { - final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); - final BufferedImage body = resolveRandomBody(); - graphics.drawImage(body, 20, 50, null); - graphics.dispose(); + writeToOutputStream(canvas, outputStream); } private BufferedImage setUpCanvas() { @@ -43,14 +39,34 @@ private BufferedImage setUpCanvas() { return canvas; } + private void renderBody(BufferedImage canvas) { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final BufferedImage body = resolveRandomBody(); + graphics.drawImage(body, 116, 22, null); + graphics.dispose(); + } + + private void renderCloth(BufferedImage canvas) { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final BufferedImage body = resolveRandomCloth(); + graphics.drawImage(body, 109, 384, null); + graphics.dispose(); + } + + private Color resolveRandomBackgroundColor() { + return BackgroundColor.values()[RANDOM.nextInt(BackgroundColor.values().length)].getColor(); + } + private BufferedImage resolveRandomBody() { return AvatarComponentsProvider.body.get( BodyColor.values()[RANDOM.nextInt(BodyColor.values().length)] ); } - private Color resolveRandomBackgroundColor() { - return BackgroundColor.values()[RANDOM.nextInt(BackgroundColor.values().length)].getColor(); + private BufferedImage resolveRandomCloth() { + return AvatarComponentsProvider.cloth.get( + ClothColor.values()[RANDOM.nextInt(ClothColor.values().length)] + ); } private void writeToOutputStream(BufferedImage canvas, OutputStream outputStream) throws IOException { diff --git a/src/main/resources/static/cloth-black.png b/src/main/resources/static/cloth-black.png new file mode 100644 index 0000000000000000000000000000000000000000..e366c368f20a60d010cfa38f76ef992b804eb6bc GIT binary patch literal 2818 zcmds3`9IWq7ynMSQcT?&-B7w(L?kW9Hf7%nGug7F%uv}W*@h$uac`ECXlU$=Fm^L) zVjjE59^)#RF`F_o#*CSHrssM7isy&VIq%nbf6h6t^E&5sUMKykleL_zk}LoKa<(=W zE&u>Z5aahdrN#27v>#Qhc7@q^L;}F+qrV;~1`W9f0J6=t7MGzhMf3$~^p`0G>8m}1 zqAKVm^bSo0o2nisDX3qx#ATQC;=#q)+xdx%4#sdJhFsc3>tL`=Lo1p5dC|1xb{C@~B--3SRalW5QM~2f zYD0FbX7XW`_$LGOpl!Z0=MI53wL4vPd9^^<{>u?l;;>(gcb%|3H-cVPojr!>sY~(| z>}NDDpA6ZfUwoF26GhVDVT`Ib@@67H?VE3Hx6`<{|hQ+eNCsSh&zU(xX z8*(6zzGd=J4@J5@X2rEm%j{}CHBLA4KvAz;5Abu(BjF<+P86<9#@<<~#{{DSW$Hoz zME&IXN5X$en$Tzl#efexL3A+$U}i~W+LN=7cj@!jUgJU>$Rsry>H+bC=!O|&XPkQxdwqHXOfg4tZ$1es z_;5I%Vh}4_eOG=0+l+!Zu$8O?X%Oj&(|@MkQ0rq7a;ZZb+Y_mYTGdMOQd`X}`UW|~ zprH+s##cnKCaVl{zn$4{c4_1B10nw7`BA7qxF4lDDX7jX3kBB}S>Q#%`T3*E=8${c zUE>3w!@9K$M`yN<9&5UqpWWgB=!F%b)_iY~i3^_5V46RStrHQux@?AQv$tmYh%IXd z<@a$Oa|+iT$iy!G;Nr4jAhQDp~_w>Wbs5-wj7 z@|4*Z|EzR5bnQ((+zj%45LdJ0)^9`B@A3H|6TV*td;a%r$4A05Vg87rl!&#f%3q4W ztKLJ$YJJ!!)nugSd(7X3vW;@46@*xY2Qchyb7U~#d@?ddQ=rE!l4mw^mM{?D7#f67cpKGIcz#Cxtj3kW9)i=N$Ctm^om( z)cVz1g%@>3X18EQd&0+%9wgkGo*Mpp$SB{O@jNPrGcuRR{jShT@kQr0(GH-b6>nX* zGn2nX`O_2Ku9a_&qQO$e?C*d@>0)(GK=1sT7JA~7#Cb@+r5t)ROE0mp%#VwP1+m#*ZjN6h+8kjrA^Kkh6+`ECy{Z2KW_d5XK<>53-(&f5^ zR|s8fcgjK$c^;px-6v)0I$PC>k49q#5OCE}lCPk&zRgxHPhcOc0&k4Q;$dW?_h}Lx ze@I`p*O;V#QzB&){K(xAIe(kF5^PQqE~?Bmye6Dk`9aH(d1pO_@u`lLrA&x8Q6k16(^LL{Y{SK`MpmSuYa79cKfjrTVqWUfivYG zX|wvH#tY*~CH5N1^pqB7vJtIFs=bYUt$2q2Sc=<}+xY2jD^oun!^_u7t$uyn@Ni$D z-oe$msA8P=KG0grCqIV%ype7ayA!PZCYGc!w(xeG6r;HpkYNuCV)Uw581@F}<@zva z!TOZIJxOgHG#93#y(N=S)7>;N^%_taD%0Mwo;UI|0)`(n_ip&1F<6PQx7J?9+1UHd z_kcN;Um|2?@zWeAa4H2sUi6|6%aVI!0w?;-)kjS}|A(0GR=>R~At|6kpU7mfZYr;c zct?T2xmA~e=kuXUI6EYDViHt2@+rYpKh?A+Vxd101#Y;0|L3;V6<>&|pEHBS4t3*Y zGe)E@x&FdwxGDLbMq?$OrZ{N3^U!Xw>U?*IFr9oor41N_+ELs>?{G0HQxzxwG=XFgq}_2k8BJP4 zJKVc;R##*bl8!xwwa4;+^mxQ>=_F&Q814LJbew);9f-G@NjRpE`-+x^!{`Yks3Y_y~?Ejel7N^QNd0KJ(@SAG|riFh+d2d3t X>{BFi2}a^$765E5oh<6j{2u-rS5dLq literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-blue.png b/src/main/resources/static/cloth-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..51a2473e198b92d403493cd26399935c80bc6bb8 GIT binary patch literal 2800 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 QuCO3`^ablTme_=U0cX^9xBvhE literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-gray.png b/src/main/resources/static/cloth-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..f46fc5bb3777e14514f38192bfcb9612267dfd7b GIT binary patch literal 2775 zcmds3`BxHX7e*bo(#)AjopBV$%0?=6vRo3RT$4sE%oPwF_ifZ1M-dUsQcKI4q#;+3 zu##NLH7%JIby7>*#x)_yz!2OJ6y!&pr3*IUi5mUEl5k z006pPXWZZbfC^5DUubD4<9>}WzVg#fITM@)09ft+_o-ykpuYhCyRLeDp$04|@c4+T^m_)a@?@5@SMh(uzzmYLpAv zsM&qeZ|W%n9eX>BNhD_V8B1Ct8{K#IuiNA2pUqjxw?jo7b;DE@8vf z5g-J-d-TmzC6fi$=@pMlADTjn<2Uw@fm+UdYgLWh_WdIJul;2Y#kx!G=r9~Fk5PKD zLBgtFqWwgleLaI#cO?qLXc7hFC3+dJ>!Q%6mENArXE%Me--X%W1454?ps0z;9aJWX zi6=A_Ol?m~ENLRv%;rl)j*DVXxL$L{4^p6oD6gsD)1!RF)hY#aq#!5JFFShr;BmU1 zqU7v+F&mhB3Y!B~8F ze_XF#HAqAyrnT~KUm;BKx!VdP?CH1cTQ$wU*F66UVNpR}%5}O?!&00b6vcHfjAqk- zhvOR6omUET(vz>%F{WVXbiG8Ljq5lok_HS+7>6`nj~Z9h^iU^|yg%TQI1*4m&|YHO z)Mj)i3(V|B=dNNl4Gn@-)UpgK^Cs$Buw@TK5lvzkTW!C?4UEiNXYHM{h+4T`{@c6Q zDtpP(kcI5)95W3}41=7|Ca5G;rhTATP`@%?pU=L|=Prz{f0`EFjiLcHWpK9deDI|q zt#V#_YDaPE>#4gao+nxWSL7VKvHtA1p=Rr##a4{IgjSm76e0AlLuthf_F(YNuYY>^ z*&is;-%18$9=`j~jQavJ-PnFB(4TH6;)EYERAuY}$uKEZ;$?5a3v?8(?>p&P<63{t zDMqG21@+_w6Y2OBIgDKn3NrMM?3u4g{Nw|v)0Nk`pxJ@myotBl4}FO*&x*mMI@ zcK^A$!WksnowgdY+(9gG8$#}DK0O&yop*bA&@qnJ_cv9ea@s6K{6n4eRf{IsB)K+c zQW8l4;@IteGF-7wD+6uSDcHjF=f3xHfz}fJ8AHd7%X?h+hkQk(fEY(_Z*1PL6Itsx zoLG$gj2K-qH@dJba3sZhaCl0SY_)blRx(j|Z|*R;EFJ8weW_U%9@;WeRa~+ZX<=O0-}@`KbneE9gIO!$8*M&Wj9*bP9z&;U7}&*~(h-*I zm9XHK_2To33H*=ZwtUDdL|Ap5XiwiHH=fBXnbA)&c_)6Fp6I&d*V9E|oOKP9)CwO5 zO`Sfsl5A=EeS9^@z_l?Octpf`To7&8)G`5i#T~SzK8;|yBl2`U_RgIQ%qp3U7<25a z*WUuO2Y!B>>HK;#_5n=s6MLPozrJciE88^HW^7hgd01xjOy=;qoDzqwnP*%qV;ouu z9%Xbkbj>Y<>7HMlSuIIFf|ZKd=;0DfDZJqe=Y(m@lR+N}aQ*RHn=lk^1>a(_4y{>8 zj|#Ccp4|T6-W4FJ%WfN9K9G3}6YY!KlG|{uA6+W#LTrkt{R(#Rx^ei}bar@$eQb99($N&Zo;@ODBx4jzIV;?L3%?QDvPxQG;;7qY2ZuwSdF_! zTWT`CoXN1`5VSWw;Oz?BQNz89d2+W&+grYJ( zU{il4+<|OwTDT9_LU2X#lYel6NEZ!ogGD&!Ldd0@ZTNVz??Bf;>m$@+Up*h~bCkFZ zF+G&&7`^+4fT3(zNt93f z82%m4^drhcs4oB)IK#qXre^E_^{Fh3mmmNPj!=(Q^?`8y&VPa-B$iSPDZi34`)#a} z8a=;MEUhf~5BcoZzI>58W5G4CLHo+;aysTUY^U&+y#H$Axx z@OBK9+0cpIARzfPJ?|-i=Z_w=~poDsOA-&lo}o h%F$LW_=tf*GjB!*A711jQC_M5FLxie2A8l){{W&lhy(xt literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-green.png b/src/main/resources/static/cloth-green.png new file mode 100644 index 0000000000000000000000000000000000000000..43753619c5d5e8796c8345b9843360a3d2b12d3d GIT binary patch literal 2793 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 Wr|?X|1<4N<;CktbQ=>!3gZ}_v{CqJ0 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-pink.png b/src/main/resources/static/cloth-pink.png new file mode 100644 index 0000000000000000000000000000000000000000..299cb3bef9e396448a16e24f8a375207468bf432 GIT binary patch literal 2787 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 G=6?Wuo`ER< literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-purple.png b/src/main/resources/static/cloth-purple.png new file mode 100644 index 0000000000000000000000000000000000000000..97ebac88befa4614b48581ce2135b4cbb65b97b4 GIT binary patch literal 2797 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 GJO2PyzZK~K literal 0 HcmV?d00001 diff --git a/src/main/resources/static/cloth-yellow.png b/src/main/resources/static/cloth-yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..3afa0dfaa4aaf7897b7f7eadcfd59bb83fd9ff41 GIT binary patch literal 2823 zcmds3`#aQWAO6ldQ;oJFrO0keB|=VR4hs1OP~)JnmFgmg5h~c%FPxi*&{k0Knvje+J}EIU*VW)LUE}&UoG_x(Z6v4yg^b1JpgZ>lz|^r#T(Ffb&f(roen38RkaKl#S0fH6Kg43 z4?bduC7i-ux?bDi)tVYyd36)7H7#>sLOZ^rg%`x~-Pm*FU3=pA$pI#eG)ftG*``kH zzq@WLff!v>7Z|!R?Rem3u#~1<4BgBpg^|Dd&N?;zL@9U?+xBVWNi$nE6etsiF;CWL zboB^^urWJVoVCx+aYCo-P*O*~78qZaxLe9N(IQ681j;OZy@!=%aG=>6pJ|=CEjX{^ z6lQ&=#P-dBr5Z|C(M4UccG^VD6X8k;HLLTNw!3z!QhVn^GJiht`$c_C`Yl;pOS^@0 zjFO*(#c%`p}HvW`rU)k#7Q=!&{DX910ieIjq%FEYz5WgM%goj+pP&x zlaTh+{y(--x9W!R-=2}dGAC%64^3gMew(bvk*>ewPCB|241ei}K|~-{gufwF6CQtb zk+rnl{L0_hQVVF(7gXyz^EJVx^A0qUd zKSq4X_H9E4!|!sr$Kn>Gis%AH0X+Bq13i1j0fxRYCGFtWA31l1gBY%22b>r#QZ&y z5PL}}?)Ly!El_CZzJBl+aj zYxiT@j=avE^+>qLLapIBG<&QeOJ_#YpD`l zv8`?EvOPk)ynT|zE2_9{D0YWnYB?UeFML@MF}LN=dN-53l)|yS6K1k1;EwdV`oL1XB;ohs;tYau*id@6K zyf+_1dfIS>x3#1v6bV)=g$LcuF^$A$8)yq&|C~-HvD#oGxMfUA4=*-5niyU*IvC|` z(XP&_%3QdUO{`0+6%Cl^ud|>IZi3XS)UW(s0?+M@vl?RLbyVC8VR>oVjgtSpLHUwk^Pa{@m~cv}{<2E4gOrP#)2PuzEeeJaH>* z%D?~Gw7dtc8dn3JNKuG^_g39WZU>Gpsvmz}_&onp9_IR3*EB9~0mrQojP)!Iw8NmV z21DO9c)@b-j+MTtDSP(8qHmnp`o4Rt1s8syaP}8ybi*YoZ39^cJEb!i@j(YqMe|%W zDzIM3hY~`9rra%NqH9E$^=-WH5sYlcHox(Cmtpb{(p_TUAJ&GER44QbIZ?k=!79oF z58cDIb4^1}G=2zTh5H(X%O8k7*=LbiJtAMh>u{RPZIpIl%%a|pjE#9c&^&rrF6+w8 z>0p=0?0rLQA38EfrdRt5B{VQ*`ljhL|m`^n+(^c@U`A2-=K;FO{Ecu z5j*%e+VL;zXRQ6vXJ~O%^R~|sWNhJPWkZ7%uSWx7V^I>uN z&s|~c^T{9?yLvmfd0CjYh+#kUsT{BRYdNYgB;{6}Tge~PqXx)nt9|{_Tk(e5@rlv( zkowhb$dubr>Ai~YAd%-0mve%1&&p~@aO6&s&C`uL zoBC5Y0ZWu7B#|e%JkXDeXg#$baBN|IlEFpPXkcD&eXd3XnUAV@tSteEZB*Uv0c72o z%SopOTBF4stYe87IPxA#nefS;t#5)X+G5XOp;D)q^D0lhSG^?iY~5GWI*Q@QqJ{9q z(IXm9-3@DE@=)OPzIRHMSaQB>u6GXj_+>2a-&0-;tEm3%RUYwm2NI}Z&>CV*089&p zt8l&<2a|H#t&mbr31D`85m2nZOguBEpeV>aycd``&s9(~5J8?jZbqlVk-9^2&`0U% zKWO5e;>7z2S!Yt0FIzPX8h0mGiXh+ZlmN-9iAl)+?b0lTYW~VlQFV~Z$wmHu>GiQc o555ajT7*OqXZyE34 Date: Mon, 7 Aug 2023 11:13:26 +0200 Subject: [PATCH 5/6] IMPLEMENT[render random face] --- .../model/enumeration/FaceExpression.java | 6 ++++ .../service/AvatarComponentsProvider.java | 6 ++++ .../service/AvatarGeneratorService.java | 26 ++++++++++++++---- src/main/resources/static/face-happy.png | Bin 0 -> 2769 bytes 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/github/shuoros/peoplify/model/enumeration/FaceExpression.java create mode 100644 src/main/resources/static/face-happy.png diff --git a/src/main/java/io/github/shuoros/peoplify/model/enumeration/FaceExpression.java b/src/main/java/io/github/shuoros/peoplify/model/enumeration/FaceExpression.java new file mode 100644 index 0000000..0477f21 --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/model/enumeration/FaceExpression.java @@ -0,0 +1,6 @@ +package io.github.shuoros.peoplify.model.enumeration; + +public enum FaceExpression { + + HAPPY +} diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java index 68e840f..09ca6b3 100644 --- a/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarComponentsProvider.java @@ -2,6 +2,7 @@ import io.github.shuoros.peoplify.model.enumeration.BodyColor; import io.github.shuoros.peoplify.model.enumeration.ClothColor; +import io.github.shuoros.peoplify.model.enumeration.FaceExpression; import org.springframework.util.ResourceUtils; import javax.imageio.ImageIO; @@ -12,6 +13,8 @@ public class AvatarComponentsProvider { protected static final Map body; + protected static final Map face; + protected static final Map cloth; @@ -24,6 +27,9 @@ BodyColor.NUDE, loadComponent("body-nude"), BodyColor.PINK, loadComponent("body-pink"), BodyColor.WHITE, loadComponent("body-white") ); + face = Map.of( + FaceExpression.HAPPY, loadComponent("face-happy") + ); cloth = Map.of( ClothColor.BLACK, loadComponent("cloth-black"), ClothColor.BLUE, loadComponent("cloth-blue"), diff --git a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java index c617892..ec11463 100644 --- a/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java +++ b/src/main/java/io/github/shuoros/peoplify/service/AvatarGeneratorService.java @@ -3,6 +3,7 @@ import io.github.shuoros.peoplify.model.enumeration.BackgroundColor; import io.github.shuoros.peoplify.model.enumeration.BodyColor; import io.github.shuoros.peoplify.model.enumeration.ClothColor; +import io.github.shuoros.peoplify.model.enumeration.FaceExpression; import org.springframework.stereotype.Service; import javax.imageio.ImageIO; @@ -23,6 +24,8 @@ public void generateAvatar(OutputStream outputStream) throws IOException { renderBody(canvas); + renderFace(canvas); + renderCloth(canvas); writeToOutputStream(canvas, outputStream); @@ -39,17 +42,24 @@ private BufferedImage setUpCanvas() { return canvas; } - private void renderBody(BufferedImage canvas) { + private void renderBody(final BufferedImage canvas) { final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); final BufferedImage body = resolveRandomBody(); graphics.drawImage(body, 116, 22, null); graphics.dispose(); } - private void renderCloth(BufferedImage canvas) { + private void renderFace(final BufferedImage canvas) { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final BufferedImage face = resolveRandomFace(); + graphics.drawImage(face, 216, 160, null); + graphics.dispose(); + } + + private void renderCloth(final BufferedImage canvas) { final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); - final BufferedImage body = resolveRandomCloth(); - graphics.drawImage(body, 109, 384, null); + final BufferedImage cloth = resolveRandomCloth(); + graphics.drawImage(cloth, 109, 384, null); graphics.dispose(); } @@ -63,13 +73,19 @@ private BufferedImage resolveRandomBody() { ); } + private BufferedImage resolveRandomFace() { + return AvatarComponentsProvider.face.get( + FaceExpression.values()[RANDOM.nextInt(FaceExpression.values().length)] + ); + } + private BufferedImage resolveRandomCloth() { return AvatarComponentsProvider.cloth.get( ClothColor.values()[RANDOM.nextInt(ClothColor.values().length)] ); } - private void writeToOutputStream(BufferedImage canvas, OutputStream outputStream) throws IOException { + private void writeToOutputStream(final BufferedImage canvas, final OutputStream outputStream) throws IOException { ImageIO.write(canvas, "png", outputStream); outputStream.close(); } diff --git a/src/main/resources/static/face-happy.png b/src/main/resources/static/face-happy.png new file mode 100644 index 0000000000000000000000000000000000000000..09ca1508c3ca93853b2356730f4d29dc91bb3e63 GIT binary patch literal 2769 zcmcgudo&Xc8>SMesYR6gawjBH#^*j}!&THBn-eS%*(;S0f3uAZy#~_5lVPY z9@X^(zWpOQy+^&fDCl$p$1kVq}As)Jw(UFdZXTa&#npb@fDC! z@HD70TQ)j7t&j0KpRT{#NZ8P@QCl1~YH9<+l+MY=h`aLhTr@1b2e)7pC5Zkq!JzHF zg0l++%*}@Bt@1f8>tDyHO=I~R!86^&yMfjH6CTBUM`VTy zTu+@hKTjjSO^K>GM^gmWCY3C=R~N~h*p8dmO8F}!m$YNWrtK?90NY5w|+9dod3}N zwaZn{!sDs)3yeQnWUgy70@W%oXpeSK7{X6nld+hn~I4O$`n z0@zm)hz zL{v)6r8D@WO`}Qu2?KuE*|q{j9Bz+POqB%Kba|*4&R#+s4MOflU~Z!tQoW$(6%hU($X;_(L90K^Y$pMh zD3B~6z^M5IvEhXB0CwyZw|BBKOiB)g)6BEUzc1KGZgFv8MB- ztk4rfF?H7k=~?4JNRxHjMm}w=3YDWzm|hnVEqG|p7fx2m%ueA%ab~4( zVkG69PA{DBi6KQRr}H;(;f}TFBfA9qPE8zx{Qt$wrThl(I}`?=gYGg|BUWrITAcH( zR%`6kE2Wn>{#~R<5CLE* zqr{h?G1<}Dlsb*;nE1j%k$eXhpx8Lx;^1cgRe=7D=C_E@sJ+?=ul;)|D~Ei-h*^&D zI*;(|2gKiD0>AI;b2q=EHv7=)5k(TypLWhVNIboy@(Wd6{@IaUoT)1y2Hlc&#&XTE^8S4LgGRpf?GHcf z4mHGhPefOc?~qN)U4N#%!Uuz9)rqCUrz10?3@!=cY&5u(db0lUfJFYKdwJ_11PA7Q zqY`Ms&g1-O;_Kp7Ym@t9mr|0p-OLWRS!M|r=DVWrcut1bAAfSqK(6`SCJb+LLCdW2 z7WwAjVhdIOHf@_K0m{z~ij%YgY`ev{mN2X>hbr@|Csjw6vnyc&@dGG^v(5qP=FxbO9O#5AG=s|blLnKLOYCw3di{nM)o^yX!W<159 zk?+65iSoT?^iX*a<)pwY`Tkw|OeUwxlTy-~hiltCT3fSzsG3*`53twnNS^-wrt@BU za!1Vku#B4HF`&1)i9Ump^8BDb3zF)24^xAU+*c5+Oo`@x{ea8ZDV^S#&s{U|Rw!K?=0nriuN`4&dphPK8;Frvq^t_+5X{{d=Z?#m3Sn;K;{ z6oZ=#hajIZ2HRj0&!MfFiQkiDEE2xpeuJz;Z3aK>3y;|~iQ2A~yba@{3!Gi$TbgE# zqqftHPr@5BxF)xp0V^>4*ESM@(PJ101q=2bzRS=r* z(U3}o3Xf1>`(AY)g(;v5uL0Y>O^m`YUXHk)d>R(BiW9DlA{4*5x*5@{_PtoP@?`YLY4|Z|w^E~aZKTgb3QbAD7oIGxSiCO$+SVNLm_$~nkZ+gk zWGM@aUpyn{&r4jNDH)x5Rux36O3oeAi2e$XBa!9hVQP`kJ0n!eVBup~6LSO|4d0A6 zrwVObX(lP0TiT~BCK({%#9U%?{M*Moio&{fJ=;J4_?FOG436GSmae-d=ZG?BvC+P) zHQHMM5yTpXL&0%BYvI71AR2L8xC%-Ra~&_iz7O&jQkC(?|8VDJ%w>Q2`40XWPM@o_bpZ*<)TL@0XM_Q zD3)7tSBTuVK}rDOX>LtspT!`X)ZrU0xxZlYPQEA;m|&e(~8L8;HvQKIqq nAS}CcCvx2ppQ@t|b>xIKmsz`T7swy|s2p%(B(%=Z>G8h-me)}+ literal 0 HcmV?d00001 From 5695b2086a632bb4eabb7ea2bdf528c5da907cf0 Mon Sep 17 00:00:00 2001 From: Soroush Date: Mon, 7 Aug 2023 13:49:45 +0200 Subject: [PATCH 6/6] ADD[LICENSE & README] --- LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7e2bc80 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Soroush Shemshadi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a1ee2c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Peoplify πŸ§‘πŸ»πŸ§‘πŸΌπŸ§‘πŸ½πŸ§‘πŸΎπŸ§‘πŸΏ +A random people (avatar and name) generator

- * You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/. - */ - @PostConstruct - public void initApplication() { - Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); - if ( - activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && - activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION) - ) { - log.error( - "You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time." - ); - } - if ( - activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && - activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD) - ) { - log.error( - "You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time." - ); - } - } - - /** - * Main method, used to run the application. - * - * @param args the command line arguments. - */ - public static void main(String[] args) { - SpringApplication app = new SpringApplication(PeoplifyApp.class); - DefaultProfileUtil.addDefaultProfile(app); - Environment env = app.run(args).getEnvironment(); - logApplicationStartup(env); - } - - private static void logApplicationStartup(Environment env) { - String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store")).map(key -> "https").orElse("http"); - String serverPort = env.getProperty("server.port"); - String contextPath = Optional - .ofNullable(env.getProperty("server.servlet.context-path")) - .filter(StringUtils::isNotBlank) - .orElse("/"); - String hostAddress = "localhost"; - try { - hostAddress = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - log.warn("The host name could not be determined, using `localhost` as fallback"); - } - log.info( - CRLFLogConverter.CRLF_SAFE_MARKER, - "\n----------------------------------------------------------\n\t" + - "Application '{}' is running! Access URLs:\n\t" + - "Local: \t\t{}://localhost:{}{}\n\t" + - "External: \t{}://{}:{}{}\n\t" + - "Profile(s): \t{}\n----------------------------------------------------------", - env.getProperty("spring.application.name"), - protocol, - serverPort, - contextPath, - protocol, - hostAddress, - serverPort, - contextPath, - env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles() - ); - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/PeoplifyApplication.java b/src/main/java/io/github/shuoros/peoplify/PeoplifyApplication.java new file mode 100644 index 0000000..d02d4ee --- /dev/null +++ b/src/main/java/io/github/shuoros/peoplify/PeoplifyApplication.java @@ -0,0 +1,13 @@ +package io.github.shuoros.peoplify; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PeoplifyApplication { + + public static void main(String[] args) { + SpringApplication.run(PeoplifyApplication.class, args); + } + +} diff --git a/src/main/java/io/github/shuoros/peoplify/aop/logging/LoggingAspect.java b/src/main/java/io/github/shuoros/peoplify/aop/logging/LoggingAspect.java deleted file mode 100644 index b54d547..0000000 --- a/src/main/java/io/github/shuoros/peoplify/aop/logging/LoggingAspect.java +++ /dev/null @@ -1,115 +0,0 @@ -package io.github.shuoros.peoplify.aop.logging; - -import java.util.Arrays; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.AfterThrowing; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.env.Environment; -import org.springframework.core.env.Profiles; -import tech.jhipster.config.JHipsterConstants; - -/** - * Aspect for logging execution of service and repository Spring components. - * - * By default, it only runs with the "dev" profile. - */ -@Aspect -public class LoggingAspect { - - private final Environment env; - - public LoggingAspect(Environment env) { - this.env = env; - } - - /** - * Pointcut that matches all repositories, services and Web REST endpoints. - */ - @Pointcut( - "within(@org.springframework.stereotype.Repository *)" + - " || within(@org.springframework.stereotype.Service *)" + - " || within(@org.springframework.web.bind.annotation.RestController *)" - ) - public void springBeanPointcut() { - // Method is empty as this is just a Pointcut, the implementations are in the advices. - } - - /** - * Pointcut that matches all Spring beans in the application's main packages. - */ - @Pointcut( - "within(io.github.shuoros.peoplify.repository..*)" + - " || within(io.github.shuoros.peoplify.service..*)" + - " || within(io.github.shuoros.peoplify.web.rest..*)" - ) - public void applicationPackagePointcut() { - // Method is empty as this is just a Pointcut, the implementations are in the advices. - } - - /** - * Retrieves the {@link Logger} associated to the given {@link JoinPoint}. - * - * @param joinPoint join point we want the logger for. - * @return {@link Logger} associated to the given {@link JoinPoint}. - */ - private Logger logger(JoinPoint joinPoint) { - return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName()); - } - - /** - * Advice that logs methods throwing exceptions. - * - * @param joinPoint join point for advice. - * @param e exception. - */ - @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e") - public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { - if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { - logger(joinPoint) - .error( - "Exception in {}() with cause = '{}' and exception = '{}'", - joinPoint.getSignature().getName(), - e.getCause() != null ? e.getCause() : "NULL", - e.getMessage(), - e - ); - } else { - logger(joinPoint) - .error( - "Exception in {}() with cause = {}", - joinPoint.getSignature().getName(), - e.getCause() != null ? e.getCause() : "NULL" - ); - } - } - - /** - * Advice that logs when a method is entered and exited. - * - * @param joinPoint join point for advice. - * @return result. - * @throws Throwable throws {@link IllegalArgumentException}. - */ - @Around("applicationPackagePointcut() && springBeanPointcut()") - public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { - Logger log = logger(joinPoint); - if (log.isDebugEnabled()) { - log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); - } - try { - Object result = joinPoint.proceed(); - if (log.isDebugEnabled()) { - log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result); - } - return result; - } catch (IllegalArgumentException e) { - log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName()); - throw e; - } - } -} diff --git a/src/main/java/io/github/shuoros/peoplify/config/ApplicationProperties.java b/src/main/java/io/github/shuoros/peoplify/config/ApplicationProperties.java deleted file mode 100644 index dad094a..0000000 --- a/src/main/java/io/github/shuoros/peoplify/config/ApplicationProperties.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.shuoros.peoplify.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Properties specific to Peoplify. - *