├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.cjs ├── .mvn ├── jvm.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .prettierignore ├── .prettierrc ├── .yo-rc.json ├── LICENSE.txt ├── README.md ├── angular.json ├── checkstyle.xml ├── cypress-audits.config.ts ├── cypress.config.ts ├── eslint.config.mjs ├── jest.conf.js ├── mvnw ├── mvnw.cmd ├── ngsw-config.json ├── npmw ├── npmw.cmd ├── package.json ├── pom.xml ├── sonar-project.properties ├── src ├── main │ ├── docker │ │ ├── app.yml │ │ ├── cassandra-cluster.yml │ │ ├── cassandra-migration.yml │ │ ├── cassandra.yml │ │ ├── cassandra │ │ │ ├── Cassandra-Migration.Dockerfile │ │ │ └── scripts │ │ │ │ ├── autoMigrate.sh │ │ │ │ └── execute-cql.sh │ │ ├── grafana │ │ │ └── provisioning │ │ │ │ ├── dashboards │ │ │ │ ├── JVM.json │ │ │ │ └── dashboard.yml │ │ │ │ └── datasources │ │ │ │ └── datasource.yml │ │ ├── jhipster-control-center.yml │ │ ├── jib │ │ │ └── entrypoint.sh │ │ ├── monitoring.yml │ │ ├── prometheus │ │ │ └── prometheus.yml │ │ ├── services.yml │ │ └── sonar.yml │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── jhipster │ │ │ └── sample │ │ │ ├── ApplicationWebXml.java │ │ │ ├── GeneratedByJHipster.java │ │ │ ├── JhipsterCassandraSampleApplicationApp.java │ │ │ ├── aop │ │ │ └── logging │ │ │ │ ├── LoggingAspect.java │ │ │ │ └── package-info.java │ │ │ ├── config │ │ │ ├── ApplicationProperties.java │ │ │ ├── AsyncConfiguration.java │ │ │ ├── CRLFLogConverter.java │ │ │ ├── Constants.java │ │ │ ├── DatabaseConfiguration.java │ │ │ ├── DateTimeFormatConfiguration.java │ │ │ ├── JacksonConfiguration.java │ │ │ ├── LoggingAspectConfiguration.java │ │ │ ├── LoggingConfiguration.java │ │ │ ├── SecurityConfiguration.java │ │ │ ├── SecurityJwtConfiguration.java │ │ │ ├── StaticResourcesWebConfiguration.java │ │ │ ├── WebConfigurer.java │ │ │ └── package-info.java │ │ │ ├── domain │ │ │ ├── User.java │ │ │ └── package-info.java │ │ │ ├── management │ │ │ ├── SecurityMetersService.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── repository │ │ │ ├── UserRepository.java │ │ │ └── package-info.java │ │ │ ├── security │ │ │ ├── AuthoritiesConstants.java │ │ │ ├── DomainUserDetailsService.java │ │ │ ├── SecurityUtils.java │ │ │ ├── UserNotActivatedException.java │ │ │ └── package-info.java │ │ │ ├── service │ │ │ ├── EmailAlreadyUsedException.java │ │ │ ├── InvalidPasswordException.java │ │ │ ├── MailService.java │ │ │ ├── UserService.java │ │ │ ├── UsernameAlreadyUsedException.java │ │ │ ├── dto │ │ │ │ ├── AdminUserDTO.java │ │ │ │ ├── PasswordChangeDTO.java │ │ │ │ ├── UserDTO.java │ │ │ │ └── package-info.java │ │ │ ├── mapper │ │ │ │ ├── UserMapper.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ └── web │ │ │ ├── filter │ │ │ ├── SpaWebFilter.java │ │ │ └── package-info.java │ │ │ └── rest │ │ │ ├── AccountResource.java │ │ │ ├── AuthenticateController.java │ │ │ ├── PublicUserResource.java │ │ │ ├── UserResource.java │ │ │ ├── errors │ │ │ ├── BadRequestAlertException.java │ │ │ ├── EmailAlreadyUsedException.java │ │ │ ├── ErrorConstants.java │ │ │ ├── ExceptionTranslator.java │ │ │ ├── FieldErrorVM.java │ │ │ ├── InvalidPasswordException.java │ │ │ ├── LoginAlreadyUsedException.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── vm │ │ │ ├── KeyAndPasswordVM.java │ │ │ ├── LoginVM.java │ │ │ ├── ManagedUserVM.java │ │ │ └── package-info.java │ ├── resources │ │ ├── banner.txt │ │ ├── config │ │ │ ├── application-dev.yml │ │ │ ├── application-prod.yml │ │ │ ├── application-tls.yml │ │ │ ├── application.yml │ │ │ ├── cql │ │ │ │ ├── changelog │ │ │ │ │ ├── 00000000000000_create-tables.cql │ │ │ │ │ ├── 00000000000001_insert_default_users.cql │ │ │ │ │ └── README.md │ │ │ │ ├── create-keyspace-prod.cql │ │ │ │ ├── create-keyspace.cql │ │ │ │ └── drop-keyspace.cql │ │ │ └── tls │ │ │ │ └── keystore.p12 │ │ ├── i18n │ │ │ ├── messages.properties │ │ │ └── messages_en.properties │ │ ├── logback-spring.xml │ │ └── templates │ │ │ ├── error.html │ │ │ └── mail │ │ │ ├── activationEmail.html │ │ │ ├── creationEmail.html │ │ │ └── passwordResetEmail.html │ └── webapp │ │ ├── 404.html │ │ ├── WEB-INF │ │ └── web.xml │ │ ├── app │ │ ├── account │ │ │ ├── account.route.ts │ │ │ ├── activate │ │ │ │ ├── activate.component.html │ │ │ │ ├── activate.component.spec.ts │ │ │ │ ├── activate.component.ts │ │ │ │ ├── activate.route.ts │ │ │ │ ├── activate.service.spec.ts │ │ │ │ └── activate.service.ts │ │ │ ├── password-reset │ │ │ │ ├── finish │ │ │ │ │ ├── password-reset-finish.component.html │ │ │ │ │ ├── password-reset-finish.component.spec.ts │ │ │ │ │ ├── password-reset-finish.component.ts │ │ │ │ │ ├── password-reset-finish.route.ts │ │ │ │ │ ├── password-reset-finish.service.spec.ts │ │ │ │ │ └── password-reset-finish.service.ts │ │ │ │ └── init │ │ │ │ │ ├── password-reset-init.component.html │ │ │ │ │ ├── password-reset-init.component.spec.ts │ │ │ │ │ ├── password-reset-init.component.ts │ │ │ │ │ ├── password-reset-init.route.ts │ │ │ │ │ ├── password-reset-init.service.spec.ts │ │ │ │ │ └── password-reset-init.service.ts │ │ │ ├── password │ │ │ │ ├── password-strength-bar │ │ │ │ │ ├── password-strength-bar.component.html │ │ │ │ │ ├── password-strength-bar.component.scss │ │ │ │ │ ├── password-strength-bar.component.spec.ts │ │ │ │ │ └── password-strength-bar.component.ts │ │ │ │ ├── password.component.html │ │ │ │ ├── password.component.spec.ts │ │ │ │ ├── password.component.ts │ │ │ │ ├── password.route.ts │ │ │ │ ├── password.service.spec.ts │ │ │ │ └── password.service.ts │ │ │ ├── register │ │ │ │ ├── register.component.html │ │ │ │ ├── register.component.spec.ts │ │ │ │ ├── register.component.ts │ │ │ │ ├── register.model.ts │ │ │ │ ├── register.route.ts │ │ │ │ ├── register.service.spec.ts │ │ │ │ └── register.service.ts │ │ │ └── settings │ │ │ │ ├── settings.component.html │ │ │ │ ├── settings.component.spec.ts │ │ │ │ ├── settings.component.ts │ │ │ │ └── settings.route.ts │ │ ├── admin │ │ │ ├── admin.routes.ts │ │ │ ├── configuration │ │ │ │ ├── configuration.component.html │ │ │ │ ├── configuration.component.spec.ts │ │ │ │ ├── configuration.component.ts │ │ │ │ ├── configuration.model.ts │ │ │ │ ├── configuration.service.spec.ts │ │ │ │ └── configuration.service.ts │ │ │ ├── docs │ │ │ │ ├── docs.component.html │ │ │ │ ├── docs.component.scss │ │ │ │ └── docs.component.ts │ │ │ ├── health │ │ │ │ ├── health.component.html │ │ │ │ ├── health.component.spec.ts │ │ │ │ ├── health.component.ts │ │ │ │ ├── health.model.ts │ │ │ │ ├── health.service.spec.ts │ │ │ │ ├── health.service.ts │ │ │ │ └── modal │ │ │ │ │ ├── health-modal.component.html │ │ │ │ │ ├── health-modal.component.spec.ts │ │ │ │ │ └── health-modal.component.ts │ │ │ ├── logs │ │ │ │ ├── log.model.ts │ │ │ │ ├── logs.component.html │ │ │ │ ├── logs.component.spec.ts │ │ │ │ ├── logs.component.ts │ │ │ │ ├── logs.service.spec.ts │ │ │ │ └── logs.service.ts │ │ │ ├── metrics │ │ │ │ ├── blocks │ │ │ │ │ ├── jvm-memory │ │ │ │ │ │ ├── jvm-memory.component.html │ │ │ │ │ │ └── jvm-memory.component.ts │ │ │ │ │ ├── jvm-threads │ │ │ │ │ │ ├── jvm-threads.component.html │ │ │ │ │ │ └── jvm-threads.component.ts │ │ │ │ │ ├── metrics-cache │ │ │ │ │ │ ├── metrics-cache.component.html │ │ │ │ │ │ └── metrics-cache.component.ts │ │ │ │ │ ├── metrics-datasource │ │ │ │ │ │ ├── metrics-datasource.component.html │ │ │ │ │ │ └── metrics-datasource.component.ts │ │ │ │ │ ├── metrics-endpoints-requests │ │ │ │ │ │ ├── metrics-endpoints-requests.component.html │ │ │ │ │ │ └── metrics-endpoints-requests.component.ts │ │ │ │ │ ├── metrics-garbagecollector │ │ │ │ │ │ ├── metrics-garbagecollector.component.html │ │ │ │ │ │ └── metrics-garbagecollector.component.ts │ │ │ │ │ ├── metrics-modal-threads │ │ │ │ │ │ ├── metrics-modal-threads.component.html │ │ │ │ │ │ ├── metrics-modal-threads.component.spec.ts │ │ │ │ │ │ └── metrics-modal-threads.component.ts │ │ │ │ │ ├── metrics-request │ │ │ │ │ │ ├── metrics-request.component.html │ │ │ │ │ │ └── metrics-request.component.ts │ │ │ │ │ └── metrics-system │ │ │ │ │ │ ├── metrics-system.component.html │ │ │ │ │ │ └── metrics-system.component.ts │ │ │ │ ├── metrics.component.html │ │ │ │ ├── metrics.component.spec.ts │ │ │ │ ├── metrics.component.ts │ │ │ │ ├── metrics.model.ts │ │ │ │ ├── metrics.service.spec.ts │ │ │ │ └── metrics.service.ts │ │ │ └── user-management │ │ │ │ ├── delete │ │ │ │ ├── user-management-delete-dialog.component.html │ │ │ │ ├── user-management-delete-dialog.component.spec.ts │ │ │ │ └── user-management-delete-dialog.component.ts │ │ │ │ ├── detail │ │ │ │ ├── user-management-detail.component.html │ │ │ │ ├── user-management-detail.component.spec.ts │ │ │ │ └── user-management-detail.component.ts │ │ │ │ ├── list │ │ │ │ ├── user-management.component.html │ │ │ │ ├── user-management.component.spec.ts │ │ │ │ └── user-management.component.ts │ │ │ │ ├── service │ │ │ │ ├── user-management.service.spec.ts │ │ │ │ └── user-management.service.ts │ │ │ │ ├── update │ │ │ │ ├── user-management-update.component.html │ │ │ │ ├── user-management-update.component.spec.ts │ │ │ │ └── user-management-update.component.ts │ │ │ │ ├── user-management.model.ts │ │ │ │ └── user-management.route.ts │ │ ├── app-page-title-strategy.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.routes.ts │ │ ├── config │ │ │ ├── authority.constants.ts │ │ │ ├── datepicker-adapter.ts │ │ │ ├── dayjs.ts │ │ │ ├── error.constants.ts │ │ │ ├── font-awesome-icons.ts │ │ │ ├── input.constants.ts │ │ │ ├── language.constants.ts │ │ │ ├── navigation.constants.ts │ │ │ ├── pagination.constants.ts │ │ │ ├── translation.config.ts │ │ │ └── uib-pagination.config.ts │ │ ├── core │ │ │ ├── auth │ │ │ │ ├── account.model.ts │ │ │ │ ├── account.service.spec.ts │ │ │ │ ├── account.service.ts │ │ │ │ ├── auth-jwt.service.spec.ts │ │ │ │ ├── auth-jwt.service.ts │ │ │ │ ├── state-storage.service.ts │ │ │ │ └── user-route-access.service.ts │ │ │ ├── config │ │ │ │ ├── application-config.service.spec.ts │ │ │ │ └── application-config.service.ts │ │ │ ├── interceptor │ │ │ │ ├── auth-expired.interceptor.ts │ │ │ │ ├── auth.interceptor.ts │ │ │ │ ├── error-handler.interceptor.ts │ │ │ │ ├── index.ts │ │ │ │ └── notification.interceptor.ts │ │ │ ├── request │ │ │ │ ├── request-util.ts │ │ │ │ └── request.model.ts │ │ │ └── util │ │ │ │ ├── alert.service.spec.ts │ │ │ │ ├── alert.service.ts │ │ │ │ ├── data-util.service.spec.ts │ │ │ │ ├── data-util.service.ts │ │ │ │ ├── event-manager.service.spec.ts │ │ │ │ ├── event-manager.service.ts │ │ │ │ ├── operators.spec.ts │ │ │ │ ├── operators.ts │ │ │ │ ├── parse-links.service.spec.ts │ │ │ │ └── parse-links.service.ts │ │ ├── entities │ │ │ ├── entity-navbar-items.ts │ │ │ ├── entity.routes.ts │ │ │ └── user │ │ │ │ ├── service │ │ │ │ ├── user.service.spec.ts │ │ │ │ └── user.service.ts │ │ │ │ ├── user.model.ts │ │ │ │ └── user.test-samples.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── layouts │ │ │ ├── error │ │ │ │ ├── error.component.html │ │ │ │ ├── error.component.ts │ │ │ │ └── error.route.ts │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ └── footer.component.ts │ │ │ ├── main │ │ │ │ ├── main.component.html │ │ │ │ ├── main.component.spec.ts │ │ │ │ └── main.component.ts │ │ │ ├── navbar │ │ │ │ ├── active-menu.directive.ts │ │ │ │ ├── navbar-item.model.d.ts │ │ │ │ ├── navbar.component.html │ │ │ │ ├── navbar.component.scss │ │ │ │ ├── navbar.component.spec.ts │ │ │ │ └── navbar.component.ts │ │ │ └── profiles │ │ │ │ ├── page-ribbon.component.scss │ │ │ │ ├── page-ribbon.component.spec.ts │ │ │ │ ├── page-ribbon.component.ts │ │ │ │ ├── profile-info.model.ts │ │ │ │ └── profile.service.ts │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.spec.ts │ │ │ ├── login.component.ts │ │ │ ├── login.model.ts │ │ │ └── login.service.ts │ │ └── shared │ │ │ ├── alert │ │ │ ├── alert-error.component.html │ │ │ ├── alert-error.component.spec.ts │ │ │ ├── alert-error.component.ts │ │ │ ├── alert-error.model.ts │ │ │ ├── alert.component.html │ │ │ ├── alert.component.spec.ts │ │ │ └── alert.component.ts │ │ │ ├── auth │ │ │ ├── has-any-authority.directive.spec.ts │ │ │ └── has-any-authority.directive.ts │ │ │ ├── date │ │ │ ├── duration.pipe.ts │ │ │ ├── format-medium-date.pipe.spec.ts │ │ │ ├── format-medium-date.pipe.ts │ │ │ ├── format-medium-datetime.pipe.spec.ts │ │ │ ├── format-medium-datetime.pipe.ts │ │ │ └── index.ts │ │ │ ├── filter │ │ │ ├── filter.component.html │ │ │ ├── filter.component.ts │ │ │ ├── filter.model.spec.ts │ │ │ ├── filter.model.ts │ │ │ └── index.ts │ │ │ ├── language │ │ │ ├── find-language-from-key.pipe.ts │ │ │ ├── index.ts │ │ │ ├── translate.directive.spec.ts │ │ │ ├── translate.directive.ts │ │ │ └── translation.module.ts │ │ │ ├── pagination │ │ │ ├── index.ts │ │ │ ├── item-count.component.spec.ts │ │ │ └── item-count.component.ts │ │ │ ├── shared.module.ts │ │ │ └── sort │ │ │ ├── index.ts │ │ │ ├── sort-by.directive.spec.ts │ │ │ ├── sort-by.directive.ts │ │ │ ├── sort-state.ts │ │ │ ├── sort.directive.spec.ts │ │ │ ├── sort.directive.ts │ │ │ ├── sort.service.spec.ts │ │ │ └── sort.service.ts │ │ ├── bootstrap.ts │ │ ├── content │ │ ├── css │ │ │ └── loading.css │ │ ├── images │ │ │ ├── jhipster_family_member_0.svg │ │ │ ├── jhipster_family_member_0_head-192.png │ │ │ ├── jhipster_family_member_0_head-256.png │ │ │ ├── jhipster_family_member_0_head-384.png │ │ │ ├── jhipster_family_member_0_head-512.png │ │ │ ├── jhipster_family_member_1.svg │ │ │ ├── jhipster_family_member_1_head-192.png │ │ │ ├── jhipster_family_member_1_head-256.png │ │ │ ├── jhipster_family_member_1_head-384.png │ │ │ ├── jhipster_family_member_1_head-512.png │ │ │ ├── jhipster_family_member_2.svg │ │ │ ├── jhipster_family_member_2_head-192.png │ │ │ ├── jhipster_family_member_2_head-256.png │ │ │ ├── jhipster_family_member_2_head-384.png │ │ │ ├── jhipster_family_member_2_head-512.png │ │ │ ├── jhipster_family_member_3.svg │ │ │ ├── jhipster_family_member_3_head-192.png │ │ │ ├── jhipster_family_member_3_head-256.png │ │ │ ├── jhipster_family_member_3_head-384.png │ │ │ ├── jhipster_family_member_3_head-512.png │ │ │ └── logo-jhipster.png │ │ └── scss │ │ │ ├── _bootstrap-variables.scss │ │ │ ├── global.scss │ │ │ └── vendor.scss │ │ ├── declarations.d.ts │ │ ├── environments │ │ ├── environment.development.ts │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── i18n │ │ └── en │ │ │ ├── activate.json │ │ │ ├── configuration.json │ │ │ ├── error.json │ │ │ ├── global.json │ │ │ ├── health.json │ │ │ ├── home.json │ │ │ ├── login.json │ │ │ ├── logs.json │ │ │ ├── metrics.json │ │ │ ├── password.json │ │ │ ├── register.json │ │ │ ├── reset.json │ │ │ ├── sessions.json │ │ │ ├── settings.json │ │ │ └── user-management.json │ │ ├── index.html │ │ ├── main.ts │ │ ├── manifest.webapp │ │ ├── robots.txt │ │ └── swagger-ui │ │ └── index.html └── test │ ├── gatling │ └── conf │ │ ├── gatling.conf │ │ └── logback.xml │ ├── java │ └── io │ │ └── github │ │ └── jhipster │ │ └── sample │ │ ├── CassandraKeyspaceIT.java │ │ ├── IntegrationTest.java │ │ ├── TechnicalStructureTest.java │ │ ├── config │ │ ├── AsyncSyncConfiguration.java │ │ ├── CRLFLogConverterTest.java │ │ ├── CassandraTestContainer.java │ │ ├── CassandraTestContainersSpringContextCustomizerFactory.java │ │ ├── EmbeddedCassandra.java │ │ ├── SpringBootTestClassOrderer.java │ │ ├── StaticResourcesWebConfigurerTest.java │ │ ├── WebConfigurerTest.java │ │ └── WebConfigurerTestController.java │ │ ├── domain │ │ └── AssertUtils.java │ │ ├── management │ │ └── SecurityMetersServiceTests.java │ │ ├── security │ │ ├── DomainUserDetailsServiceIT.java │ │ ├── SecurityUtilsUnitTest.java │ │ └── jwt │ │ │ ├── AuthenticationIntegrationTest.java │ │ │ ├── JwtAuthenticationTestUtils.java │ │ │ ├── TokenAuthenticationIT.java │ │ │ └── TokenAuthenticationSecurityMetersIT.java │ │ ├── service │ │ ├── MailServiceIT.java │ │ ├── UserServiceIT.java │ │ └── mapper │ │ │ └── UserMapperTest.java │ │ └── web │ │ ├── filter │ │ └── SpaWebFilterIT.java │ │ └── rest │ │ ├── AccountResourceIT.java │ │ ├── AuthenticateControllerIT.java │ │ ├── PublicUserResourceIT.java │ │ ├── TestUtil.java │ │ ├── UserResourceIT.java │ │ ├── WithUnauthenticatedMockUser.java │ │ └── errors │ │ ├── ExceptionTranslatorIT.java │ │ └── ExceptionTranslatorTestController.java │ ├── javascript │ └── cypress │ │ ├── e2e │ │ ├── account │ │ │ ├── login-page.cy.ts │ │ │ ├── logout.cy.ts │ │ │ ├── password-page.cy.ts │ │ │ ├── register-page.cy.ts │ │ │ ├── reset-password-page.cy.ts │ │ │ └── settings-page.cy.ts │ │ ├── administration │ │ │ └── administration.cy.ts │ │ └── lighthouse.audits.ts │ │ ├── fixtures │ │ └── integration-test.png │ │ ├── plugins │ │ └── index.ts │ │ ├── support │ │ ├── account.ts │ │ ├── commands.ts │ │ ├── entity.ts │ │ ├── index.ts │ │ ├── management.ts │ │ └── navbar.ts │ │ └── tsconfig.json │ └── resources │ ├── META-INF │ └── spring.factories │ ├── config │ └── application.yml │ ├── i18n │ └── messages_en.properties │ ├── junit-platform.properties │ ├── logback.xml │ └── templates │ └── mail │ ├── activationEmail.html │ ├── creationEmail.html │ ├── passwordResetEmail.html │ └── testEmail.html ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── webpack ├── environment.js ├── logo-jhipster.png ├── proxy.conf.js └── webpack.custom.js /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 17, 17-bullseye, 17-buster 4 | ARG VARIANT="17" 5 | FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} 6 | 7 | # [Option] Install Maven 8 | ARG INSTALL_MAVEN="false" 9 | ARG MAVEN_VERSION="" 10 | # [Option] Install Gradle 11 | ARG INSTALL_GRADLE="false" 12 | ARG GRADLE_VERSION="" 13 | RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ 14 | && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi 15 | 16 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 17 | ARG NODE_VERSION="none" 18 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 19 | 20 | # [Optional] Uncomment this section to install additional OS packages. 21 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 22 | # && apt-get -y install --no-install-recommends 23 | 24 | # [Optional] Uncomment this line to install global node packages. 25 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # We recommend you to keep these unchanged 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | # Change these settings to your own preference 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | # Generated by jhipster:java:bootstrap generator 23 | [*.java] 24 | indent_size = 4 25 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '{,**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,css,scss}': ['prettier --write'], 3 | }; 4 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 19 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .git 4 | 5 | # Generated by jhipster:maven 6 | target 7 | .mvn 8 | 9 | # Generated by jhipster:client 10 | target/classes/static/ 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 140 2 | singleQuote: true 3 | tabWidth: 2 4 | useTabs: false 5 | arrowParens: avoid 6 | bracketSameLine: false 7 | plugins: 8 | - prettier-plugin-packagejson 9 | - prettier-plugin-java 10 | overrides: 11 | - files: "*.java" 12 | options: 13 | tabWidth: 4 14 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /cypress-audits.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | import defaultConfig from './cypress.config'; 3 | 4 | export default defineConfig({ 5 | ...defaultConfig, 6 | e2e: { 7 | ...defaultConfig.e2e, 8 | specPattern: 'src/test/javascript/cypress/e2e/**/*.audits.ts', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | 3 | export default defineConfig({ 4 | video: false, 5 | fixturesFolder: 'src/test/javascript/cypress/fixtures', 6 | screenshotsFolder: 'target/cypress/screenshots', 7 | downloadsFolder: 'target/cypress/downloads', 8 | videosFolder: 'target/cypress/videos', 9 | chromeWebSecurity: true, 10 | viewportWidth: 1200, 11 | viewportHeight: 720, 12 | retries: 2, 13 | env: { 14 | authenticationUrl: '/api/authenticate', 15 | jwtStorageName: 'jhi-authenticationToken', 16 | }, 17 | e2e: { 18 | // We've imported your old cypress plugins here. 19 | // You may want to clean this up later by importing these. 20 | async setupNodeEvents(on, config) { 21 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 22 | return (await import('./src/test/javascript/cypress/plugins/index')).default(on, config); 23 | }, 24 | baseUrl: 'http://localhost:8080/', 25 | specPattern: 'src/test/javascript/cypress/e2e/**/*.cy.ts', 26 | supportFile: 'src/test/javascript/cypress/support/index.ts', 27 | experimentalRunAllSpecs: true, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /jest.conf.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest'); 2 | 3 | const { 4 | compilerOptions: { paths = {}, baseUrl = './' }, 5 | } = require('./tsconfig.json'); 6 | 7 | module.exports = { 8 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$|dayjs/esm)'], 9 | resolver: 'jest-preset-angular/build/resolvers/ng-jest-resolver.js', 10 | globals: { 11 | __VERSION__: 'test', 12 | }, 13 | roots: ['', `/${baseUrl}`], 14 | modulePaths: [`/${baseUrl}`], 15 | setupFiles: ['jest-date-mock'], 16 | cacheDirectory: '/target/jest-cache', 17 | coverageDirectory: '/target/test-results/', 18 | moduleNameMapper: pathsToModuleNameMapper(paths, { prefix: `/${baseUrl}/` }), 19 | reporters: [ 20 | 'default', 21 | ['jest-junit', { outputDirectory: '/target/test-results/', outputName: 'TESTS-results-jest.xml' }], 22 | ['jest-sonar', { outputDirectory: './target/test-results/jest', outputName: 'TESTS-results-sonar.xml' }], 23 | ], 24 | testMatch: ['/src/main/webapp/app/**/@(*.)@(spec.ts)'], 25 | testEnvironmentOptions: { 26 | url: 'https://jhipster.tech', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": ["/favicon.ico", "/index.html", "/manifest.webapp", "/*.css", "/*.js"] 10 | } 11 | }, 12 | { 13 | "name": "assets", 14 | "installMode": "lazy", 15 | "updateMode": "prefetch", 16 | "resources": { 17 | "files": ["/content/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"] 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /npmw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | basedir=$(dirname "$0") 4 | 5 | if [ -f "$basedir/mvnw" ]; then 6 | bindir="$basedir/target/node" 7 | repodir="$basedir/target/node/node_modules" 8 | installCommand="$basedir/mvnw --batch-mode -ntp -Pwebapp frontend:install-node-and-npm@install-node-and-npm" 9 | else 10 | echo "Using npm installed globally" 11 | exec npm "$@" 12 | fi 13 | 14 | NPM_EXE="$repodir/npm/bin/npm-cli.js" 15 | NODE_EXE="$bindir/node" 16 | 17 | if [ ! -x "$NPM_EXE" ] || [ ! -x "$NODE_EXE" ]; then 18 | $installCommand || true 19 | fi 20 | 21 | if [ -x "$NODE_EXE" ]; then 22 | echo "Using node installed locally $($NODE_EXE --version)" 23 | PATH="$bindir:$PATH" 24 | else 25 | NODE_EXE='node' 26 | fi 27 | 28 | if [ ! -x "$NPM_EXE" ]; then 29 | echo "Local npm not found, using npm installed globally" 30 | npm "$@" 31 | else 32 | echo "Using npm installed locally $($NODE_EXE $NPM_EXE --version)" 33 | $NODE_EXE $NPM_EXE "$@" 34 | fi 35 | -------------------------------------------------------------------------------- /npmw.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | set NPMW_DIR=%~dp0 6 | 7 | set NODE_EXE=^"^" 8 | set NODE_PATH=%NPMW_DIR%target\node\ 9 | set NPM_EXE=^"%NPMW_DIR%target\node\npm.cmd^" 10 | set INSTALL_NPM_COMMAND=^"%NPMW_DIR%mvnw.cmd^" -Pwebapp frontend:install-node-and-npm@install-node-and-npm 11 | 12 | if not exist %NPM_EXE% ( 13 | call %INSTALL_NPM_COMMAND% 14 | ) 15 | 16 | if exist %NODE_EXE% ( 17 | Rem execute local npm with local node, whilst adding local node location to the PATH for this CMD session 18 | endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NODE_EXE% %NPM_EXE% %* 19 | ) else if exist %NPM_EXE% ( 20 | Rem execute local npm, whilst adding local npm location to the PATH for this CMD session 21 | endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NPM_EXE% %* 22 | ) else ( 23 | call npm %* 24 | ) 25 | -------------------------------------------------------------------------------- /src/main/docker/app.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | app: 5 | image: jhipstercassandrasampleapplication 6 | environment: 7 | - _JAVA_OPTIONS=-Xmx512m -Xms256m 8 | - SPRING_PROFILES_ACTIVE=prod,api-docs 9 | - MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true 10 | - SPRING_CASSANDRA_CONTACTPOINTS=cassandra 11 | - JHIPSTER_SLEEP=10 12 | ports: 13 | - 127.0.0.1:8080:8080 14 | healthcheck: 15 | test: 16 | - CMD 17 | - curl 18 | - -f 19 | - http://localhost:8080/management/health 20 | interval: 5s 21 | timeout: 5s 22 | retries: 40 23 | depends_on: 24 | cassandra: 25 | condition: service_healthy 26 | cassandra-migration: 27 | condition: service_completed_successfully 28 | cassandra: 29 | extends: 30 | file: ./cassandra.yml 31 | service: cassandra 32 | cassandra-migration: 33 | extends: 34 | file: ./cassandra.yml 35 | service: cassandra-migration 36 | -------------------------------------------------------------------------------- /src/main/docker/cassandra-cluster.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | cassandra: 5 | container_name: jhipstercassandrasampleapplication-cassandra 6 | image: cassandra:5.0.2 7 | # volumes: 8 | # - ~/volumes/jhipster/jhipsterCassandraSampleApplication/cassandra/:/var/lib/cassandra/data 9 | # If you want to expose these ports outside your dev PC, 10 | # remove the "127.0.0.1:" prefix 11 | ports: 12 | - 127.0.0.1:7000:7000 13 | - 127.0.0.1:7001:7001 14 | - 127.0.0.1:7199:7199 15 | - 127.0.0.1:9042:9042 16 | - 127.0.0.1:9160:9160 17 | cassandra-node: 18 | image: cassandra:5.0.2 19 | environment: 20 | - CASSANDRA_SEEDS=jhipstercassandrasampleapplication-cassandra 21 | cassandra-migration: 22 | environment: 23 | - CASSANDRA_CONTACT_POINT=jhipstercassandrasampleapplication-cassandra 24 | - USER=docker-cassandra-migration 25 | # - DEBUG_LOG=1 # uncomment to show debug logs during the migration process 26 | - CREATE_KEYSPACE_SCRIPT=create-keyspace-prod.cql 27 | build: 28 | context: . 29 | dockerfile: cassandra/Cassandra-Migration.Dockerfile 30 | volumes: 31 | - ../resources/config/cql:/cql:ro 32 | -------------------------------------------------------------------------------- /src/main/docker/cassandra-migration.yml: -------------------------------------------------------------------------------- 1 | name: jhipstercassandrasampleapplication 2 | services: 3 | cassandra-migration: 4 | environment: 5 | - CASSANDRA_CONTACT_POINT=jhipstercassandrasampleapplication-cassandra 6 | - USER=docker-cassandra-migration 7 | # - DEBUG_LOG=1 # uncomment to show debug logs during the migration process 8 | - CREATE_KEYSPACE_SCRIPT=create-keyspace.cql 9 | build: 10 | context: . 11 | dockerfile: cassandra/Cassandra-Migration.Dockerfile 12 | volumes: 13 | - ../resources/config/cql:/cql:ro 14 | -------------------------------------------------------------------------------- /src/main/docker/cassandra.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | cassandra: 5 | image: cassandra:5.0.2 6 | container_name: jhipstercassandrasampleapplication-cassandra 7 | # volumes: 8 | # - ~/volumes/jhipster/jhipsterCassandraSampleApplication/cassandra/:/var/lib/cassandra/data 9 | # If you want to expose these ports outside your dev PC, 10 | # remove the "127.0.0.1:" prefix 11 | ports: 12 | - 127.0.0.1:7000:7000 13 | - 127.0.0.1:7001:7001 14 | - 127.0.0.1:7199:7199 15 | - 127.0.0.1:9042:9042 16 | - 127.0.0.1:9160:9160 17 | healthcheck: 18 | test: ['CMD', 'cqlsh', '-e', 'describe keyspaces'] 19 | interval: 5s 20 | timeout: 25s 21 | retries: 20 22 | labels: 23 | org.springframework.boot.ignore: true 24 | cassandra-migration: 25 | environment: 26 | - CASSANDRA_CONTACT_POINT=jhipstercassandrasampleapplication-cassandra 27 | - USER=docker-cassandra-migration 28 | # - DEBUG_LOG=1 # uncomment to show debug logs during the migration process 29 | - CREATE_KEYSPACE_SCRIPT=create-keyspace-prod.cql 30 | build: 31 | context: . 32 | dockerfile: cassandra/Cassandra-Migration.Dockerfile 33 | volumes: 34 | - ../resources/config/cql:/cql:ro 35 | labels: 36 | org.springframework.boot.ignore: true 37 | -------------------------------------------------------------------------------- /src/main/docker/cassandra/Cassandra-Migration.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cassandra:5.0.2 2 | 3 | # script to orchestrate the automatic keyspace creation and apply all migration scripts 4 | ADD cassandra/scripts/autoMigrate.sh /usr/local/bin/autoMigrate 5 | RUN chmod 755 /usr/local/bin/autoMigrate 6 | 7 | # script to run any cql script from src/main/resources/config/cql 8 | ADD cassandra/scripts/execute-cql.sh /usr/local/bin/execute-cql 9 | RUN chmod 755 /usr/local/bin/execute-cql 10 | 11 | ENTRYPOINT ["autoMigrate"] 12 | -------------------------------------------------------------------------------- /src/main/docker/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Prometheus' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | editable: true 10 | options: 11 | path: /etc/grafana/provisioning/dashboards 12 | -------------------------------------------------------------------------------- /src/main/docker/jib/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP} 4 | 5 | # usage: file_env VAR [DEFAULT] 6 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 7 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 8 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 9 | file_env() { 10 | local var="$1" 11 | local fileVar="${var}_FILE" 12 | local def="${2:-}" 13 | if [[ ${!var:-} && ${!fileVar:-} ]]; then 14 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 15 | exit 1 16 | fi 17 | local val="$def" 18 | if [[ ${!var:-} ]]; then 19 | val="${!var}" 20 | elif [[ ${!fileVar:-} ]]; then 21 | val="$(< "${!fileVar}")" 22 | fi 23 | 24 | if [[ -n $val ]]; then 25 | export "$var"="$val" 26 | fi 27 | 28 | unset "$fileVar" 29 | } 30 | 31 | file_env 'SPRING_DATASOURCE_URL' 32 | file_env 'SPRING_DATASOURCE_USERNAME' 33 | file_env 'SPRING_DATASOURCE_PASSWORD' 34 | file_env 'SPRING_LIQUIBASE_URL' 35 | file_env 'SPRING_LIQUIBASE_USER' 36 | file_env 'SPRING_LIQUIBASE_PASSWORD' 37 | file_env 'JHIPSTER_REGISTRY_PASSWORD' 38 | 39 | exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "io.github.jhipster.sample.JhipsterCassandraSampleApplicationApp" "$@" 40 | -------------------------------------------------------------------------------- /src/main/docker/monitoring.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | prometheus: 5 | image: prom/prometheus:v3.3.1 6 | volumes: 7 | - ./prometheus/:/etc/prometheus/ 8 | command: 9 | - '--config.file=/etc/prometheus/prometheus.yml' 10 | # If you want to expose these ports outside your dev PC, 11 | # remove the "127.0.0.1:" prefix 12 | ports: 13 | - 127.0.0.1:9090:9090 14 | # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and 15 | # grafana/provisioning/datasources/datasource.yml 16 | network_mode: 'host' # to test locally running service 17 | grafana: 18 | image: grafana/grafana:12.0.0 19 | volumes: 20 | - ./grafana/provisioning/:/etc/grafana/provisioning/ 21 | environment: 22 | - GF_SECURITY_ADMIN_PASSWORD=admin 23 | - GF_USERS_ALLOW_SIGN_UP=false 24 | - GF_INSTALL_PLUGINS=grafana-piechart-panel 25 | # If you want to expose these ports outside your dev PC, 26 | # remove the "127.0.0.1:" prefix 27 | ports: 28 | - 127.0.0.1:3000:3000 29 | # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and 30 | # grafana/provisioning/datasources/datasource.yml 31 | network_mode: 'host' # to test locally running service 32 | -------------------------------------------------------------------------------- /src/main/docker/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # Sample global config for monitoring JHipster applications 2 | global: 3 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 4 | evaluation_interval: 15s # By default, scrape targets every 15 seconds. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Attach these labels to any time series or alerts when communicating with 8 | # external systems (federation, remote storage, Alertmanager). 9 | external_labels: 10 | monitor: 'jhipster' 11 | 12 | # A scrape configuration containing exactly one endpoint to scrape: 13 | # Here it's Prometheus itself. 14 | scrape_configs: 15 | # The job name is added as a label `job=` to any timeseries scraped from this config. 16 | - job_name: 'prometheus' 17 | 18 | # Override the global default and scrape targets from this job every 5 seconds. 19 | scrape_interval: 5s 20 | 21 | # scheme defaults to 'http' enable https in case your application is server via https 22 | #scheme: https 23 | # basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details 24 | #basic_auth: 25 | # username: admin 26 | # password: admin 27 | metrics_path: /management/prometheus 28 | static_configs: 29 | - targets: 30 | # On MacOS, replace localhost by host.docker.internal 31 | - localhost:8080 32 | -------------------------------------------------------------------------------- /src/main/docker/services.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | cassandra: 5 | extends: 6 | file: ./cassandra.yml 7 | service: cassandra 8 | cassandra-migration: 9 | extends: 10 | file: ./cassandra.yml 11 | service: cassandra-migration 12 | -------------------------------------------------------------------------------- /src/main/docker/sonar.yml: -------------------------------------------------------------------------------- 1 | # This configuration is intended for development purpose, it's **your** responsibility to harden it for production 2 | name: jhipstercassandrasampleapplication 3 | services: 4 | sonar: 5 | container_name: sonarqube 6 | image: sonarqube:25.5.0.107428-community 7 | # Forced authentication redirect for UI is turned off for out of the box experience while trying out SonarQube 8 | # For real use cases delete SONAR_FORCEAUTHENTICATION variable or set SONAR_FORCEAUTHENTICATION=true 9 | environment: 10 | - SONAR_FORCEAUTHENTICATION=false 11 | # If you want to expose these ports outside your dev PC, 12 | # remove the "127.0.0.1:" prefix 13 | ports: 14 | - 127.0.0.1:9001:9000 15 | - 127.0.0.1:9000:9000 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/ApplicationWebXml.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | import tech.jhipster.config.DefaultProfileUtil; 6 | 7 | /** 8 | * This is a helper Java class that provides an alternative to creating a {@code web.xml}. 9 | * This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc. 10 | */ 11 | public class ApplicationWebXml extends SpringBootServletInitializer { 12 | 13 | @Override 14 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 15 | // set a default to use when no profile is configured. 16 | DefaultProfileUtil.addDefaultProfile(application.application()); 17 | return application.sources(JhipsterCassandraSampleApplicationApp.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/GeneratedByJHipster.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample; 2 | 3 | import jakarta.annotation.Generated; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Generated(value = "JHipster", comments = "Generated by JHipster 8.11.0") 10 | @Retention(RetentionPolicy.SOURCE) 11 | @Target({ ElementType.TYPE }) 12 | public @interface GeneratedByJHipster { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/aop/logging/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Logging aspect. 3 | */ 4 | package io.github.jhipster.sample.aop.logging; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * Properties specific to Jhipster Cassandra Sample Application. 7 | *

8 | * Properties are configured in the {@code application.yml} file. 9 | * See {@link tech.jhipster.config.JHipsterProperties} for a good example. 10 | */ 11 | @ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) 12 | public class ApplicationProperties { 13 | // jhipster-needle-application-properties-property 14 | 15 | // jhipster-needle-application-properties-property-getter 16 | 17 | // jhipster-needle-application-properties-property-class 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/Constants.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | /** 4 | * Application constants. 5 | */ 6 | public final class Constants { 7 | 8 | // Regex for acceptable logins 9 | public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; 10 | 11 | public static final String SYSTEM = "system"; 12 | public static final String DEFAULT_LANGUAGE = "en"; 13 | 14 | private Constants() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/DateTimeFormatConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.format.FormatterRegistry; 5 | import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * Configure the converters to use the ISO format for dates by default. 10 | */ 11 | @Configuration 12 | public class DateTimeFormatConfiguration implements WebMvcConfigurer { 13 | 14 | @Override 15 | public void addFormatters(FormatterRegistry registry) { 16 | DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); 17 | registrar.setUseIsoFormat(true); 18 | registrar.registerFormatters(registry); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/JacksonConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 7 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 8 | import java.io.IOException; 9 | import java.time.LocalTime; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | public class JacksonConfiguration { 15 | 16 | /** 17 | * Support for Java date and time API. 18 | * @return the corresponding Jackson module. 19 | */ 20 | @Bean 21 | public JavaTimeModule javaTimeModule() { 22 | final JavaTimeModule javaTime = new JavaTimeModule(); 23 | javaTime.addSerializer( 24 | LocalTime.class, 25 | new JsonSerializer() { 26 | @Override 27 | public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 28 | gen.writeString(value.toString()); 29 | } 30 | } 31 | ); 32 | return javaTime; 33 | } 34 | 35 | @Bean 36 | public Jdk8Module jdk8TimeModule() { 37 | return new Jdk8Module(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/LoggingAspectConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import io.github.jhipster.sample.aop.logging.LoggingAspect; 4 | import org.springframework.context.annotation.*; 5 | import org.springframework.core.env.Environment; 6 | import tech.jhipster.config.JHipsterConstants; 7 | 8 | @Configuration 9 | @EnableAspectJAutoProxy 10 | public class LoggingAspectConfiguration { 11 | 12 | @Bean 13 | @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) 14 | public LoggingAspect loggingAspect(Environment env) { 15 | return new LoggingAspect(env); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Application configuration. 3 | */ 4 | package io.github.jhipster.sample.config; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/domain/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Domain objects. 3 | */ 4 | package io.github.jhipster.sample.domain; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/management/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Application management. 3 | */ 4 | package io.github.jhipster.sample.management; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Application root. 3 | */ 4 | package io.github.jhipster.sample; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/repository/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Repository layer. 3 | */ 4 | package io.github.jhipster.sample.repository; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/security/AuthoritiesConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.security; 2 | 3 | /** 4 | * Constants for Spring Security authorities. 5 | */ 6 | public final class AuthoritiesConstants { 7 | 8 | public static final String ADMIN = "ROLE_ADMIN"; 9 | 10 | public static final String USER = "ROLE_USER"; 11 | 12 | public static final String ANONYMOUS = "ROLE_ANONYMOUS"; 13 | 14 | private AuthoritiesConstants() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/security/UserNotActivatedException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.security; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | /** 6 | * This exception is thrown in case of a not activated user trying to authenticate. 7 | */ 8 | public class UserNotActivatedException extends AuthenticationException { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | public UserNotActivatedException(String message) { 13 | super(message); 14 | } 15 | 16 | public UserNotActivatedException(String message, Throwable t) { 17 | super(message, t); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/security/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Application security utilities. 3 | */ 4 | package io.github.jhipster.sample.security; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/EmailAlreadyUsedException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.service; 2 | 3 | public class EmailAlreadyUsedException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public EmailAlreadyUsedException() { 8 | super("Email is already in use!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/InvalidPasswordException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.service; 2 | 3 | public class InvalidPasswordException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public InvalidPasswordException() { 8 | super("Incorrect password"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/UsernameAlreadyUsedException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.service; 2 | 3 | public class UsernameAlreadyUsedException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public UsernameAlreadyUsedException() { 8 | super("Login name already used!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/dto/PasswordChangeDTO.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.service.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A DTO representing a password change required data - current and new password. 7 | */ 8 | public class PasswordChangeDTO implements Serializable { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | private String currentPassword; 13 | private String newPassword; 14 | 15 | public PasswordChangeDTO() { 16 | // Empty constructor needed for Jackson. 17 | } 18 | 19 | public PasswordChangeDTO(String currentPassword, String newPassword) { 20 | this.currentPassword = currentPassword; 21 | this.newPassword = newPassword; 22 | } 23 | 24 | public String getCurrentPassword() { 25 | return currentPassword; 26 | } 27 | 28 | public void setCurrentPassword(String currentPassword) { 29 | this.currentPassword = currentPassword; 30 | } 31 | 32 | public String getNewPassword() { 33 | return newPassword; 34 | } 35 | 36 | public void setNewPassword(String newPassword) { 37 | this.newPassword = newPassword; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/dto/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Data transfer objects for rest mapping. 3 | */ 4 | package io.github.jhipster.sample.service.dto; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/mapper/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Data transfer objects mappers. 3 | */ 4 | package io.github.jhipster.sample.service.mapper; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/service/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Service layer. 3 | */ 4 | package io.github.jhipster.sample.service; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/filter/SpaWebFilter.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.filter; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import org.springframework.web.filter.OncePerRequestFilter; 9 | 10 | public class SpaWebFilter extends OncePerRequestFilter { 11 | 12 | /** 13 | * Forwards any unmapped paths (except those containing a period) to the client {@code index.html}. 14 | */ 15 | @Override 16 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 17 | throws ServletException, IOException { 18 | // Request URI includes the contextPath if any, removed it. 19 | String path = request.getRequestURI().substring(request.getContextPath().length()); 20 | if ( 21 | !path.startsWith("/api") && 22 | !path.startsWith("/management") && 23 | !path.startsWith("/v3/api-docs") && 24 | !path.contains(".") && 25 | path.matches("/(.*)") 26 | ) { 27 | request.getRequestDispatcher("/index.html").forward(request, response); 28 | return; 29 | } 30 | 31 | filterChain.doFilter(request, response); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/filter/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Request chain filters. 3 | */ 4 | package io.github.jhipster.sample.web.filter; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/PublicUserResource.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest; 2 | 3 | import io.github.jhipster.sample.service.UserService; 4 | import io.github.jhipster.sample.service.dto.UserDTO; 5 | import java.util.*; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | @RestController 11 | @RequestMapping("/api") 12 | public class PublicUserResource { 13 | 14 | private static final List ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( 15 | Arrays.asList("id", "login", "firstName", "lastName", "email", "activated", "langKey") 16 | ); 17 | 18 | private static final Logger LOG = LoggerFactory.getLogger(PublicUserResource.class); 19 | 20 | private final UserService userService; 21 | 22 | public PublicUserResource(UserService userService) { 23 | this.userService = userService; 24 | } 25 | 26 | /** 27 | * {@code GET /users} : get all users with only public information - calling this method is allowed for anyone. 28 | */ 29 | @GetMapping("/users") 30 | public List getAllPublicUsers() { 31 | return userService.getAllPublicUsers(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/EmailAlreadyUsedException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.errors; 2 | 3 | @SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep 4 | public class EmailAlreadyUsedException extends BadRequestAlertException { 5 | 6 | private static final long serialVersionUID = 1L; 7 | 8 | public EmailAlreadyUsedException() { 9 | super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/ErrorConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.errors; 2 | 3 | import java.net.URI; 4 | 5 | public final class ErrorConstants { 6 | 7 | public static final String ERR_VALIDATION = "error.validation"; 8 | public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; 9 | public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); 10 | public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); 11 | public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); 12 | public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); 13 | public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); 14 | 15 | private ErrorConstants() {} 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/FieldErrorVM.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.errors; 2 | 3 | import java.io.Serializable; 4 | 5 | public class FieldErrorVM implements Serializable { 6 | 7 | private static final long serialVersionUID = 1L; 8 | 9 | private final String objectName; 10 | 11 | private final String field; 12 | 13 | private final String message; 14 | 15 | public FieldErrorVM(String dto, String field, String message) { 16 | this.objectName = dto; 17 | this.field = field; 18 | this.message = message; 19 | } 20 | 21 | public String getObjectName() { 22 | return objectName; 23 | } 24 | 25 | public String getField() { 26 | return field; 27 | } 28 | 29 | public String getMessage() { 30 | return message; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/InvalidPasswordException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.errors; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.ErrorResponseException; 5 | import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder; 6 | 7 | @SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep 8 | public class InvalidPasswordException extends ErrorResponseException { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | public InvalidPasswordException() { 13 | super( 14 | HttpStatus.BAD_REQUEST, 15 | ProblemDetailWithCauseBuilder.instance() 16 | .withStatus(HttpStatus.BAD_REQUEST.value()) 17 | .withType(ErrorConstants.INVALID_PASSWORD_TYPE) 18 | .withTitle("Incorrect password") 19 | .build(), 20 | null 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/LoginAlreadyUsedException.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.errors; 2 | 3 | @SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep 4 | public class LoginAlreadyUsedException extends BadRequestAlertException { 5 | 6 | private static final long serialVersionUID = 1L; 7 | 8 | public LoginAlreadyUsedException() { 9 | super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/errors/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Rest layer error handling. 3 | */ 4 | package io.github.jhipster.sample.web.rest.errors; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Rest layer. 3 | */ 4 | package io.github.jhipster.sample.web.rest; 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/vm/KeyAndPasswordVM.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.vm; 2 | 3 | /** 4 | * View Model object for storing the user's key and password. 5 | */ 6 | public class KeyAndPasswordVM { 7 | 8 | private String key; 9 | 10 | private String newPassword; 11 | 12 | public String getKey() { 13 | return key; 14 | } 15 | 16 | public void setKey(String key) { 17 | this.key = key; 18 | } 19 | 20 | public String getNewPassword() { 21 | return newPassword; 22 | } 23 | 24 | public void setNewPassword(String newPassword) { 25 | this.newPassword = newPassword; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/vm/LoginVM.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.vm; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Size; 5 | 6 | /** 7 | * View Model object for storing a user's credentials. 8 | */ 9 | public class LoginVM { 10 | 11 | @NotNull 12 | @Size(min = 1, max = 50) 13 | private String username; 14 | 15 | @NotNull 16 | @Size(min = 4, max = 100) 17 | private String password; 18 | 19 | private boolean rememberMe; 20 | 21 | public String getUsername() { 22 | return username; 23 | } 24 | 25 | public void setUsername(String username) { 26 | this.username = username; 27 | } 28 | 29 | public String getPassword() { 30 | return password; 31 | } 32 | 33 | public void setPassword(String password) { 34 | this.password = password; 35 | } 36 | 37 | public boolean isRememberMe() { 38 | return rememberMe; 39 | } 40 | 41 | public void setRememberMe(boolean rememberMe) { 42 | this.rememberMe = rememberMe; 43 | } 44 | 45 | // prettier-ignore 46 | @Override 47 | public String toString() { 48 | return "LoginVM{" + 49 | "username='" + username + '\'' + 50 | ", rememberMe=" + rememberMe + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/vm/ManagedUserVM.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest.vm; 2 | 3 | import io.github.jhipster.sample.service.dto.AdminUserDTO; 4 | import jakarta.validation.constraints.Size; 5 | 6 | /** 7 | * View Model extending the AdminUserDTO, which is meant to be used in the user management UI. 8 | */ 9 | public class ManagedUserVM extends AdminUserDTO { 10 | 11 | public static final int PASSWORD_MIN_LENGTH = 4; 12 | 13 | public static final int PASSWORD_MAX_LENGTH = 100; 14 | 15 | @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) 16 | private String password; 17 | 18 | public ManagedUserVM() { 19 | // Empty constructor needed for Jackson. 20 | } 21 | 22 | public String getPassword() { 23 | return password; 24 | } 25 | 26 | public void setPassword(String password) { 27 | this.password = password; 28 | } 29 | 30 | // prettier-ignore 31 | @Override 32 | public String toString() { 33 | return "ManagedUserVM{" + super.toString() + "} "; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/jhipster/sample/web/rest/vm/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Rest layer visual models. 3 | */ 4 | package io.github.jhipster.sample.web.rest.vm; 5 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗ 3 | ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗ 4 | ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╝ ╚█████╗ ██║ ██████╗ ███████╔╝ 5 | ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔═══██║ ██║ ██╔════╝ ╚═══██╗ ██║ ██╔═══╝ ██╔══██║ 6 | ${AnsiColor.GREEN}╚██████╔╝${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╝ ██║ ████████╗ ██║ ╚██╗ 7 | ${AnsiColor.GREEN} ╚═════╝ ${AnsiColor.RED} ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝ 8 | 9 | ${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} :: Startup profile(s) ${spring.profiles.active} :: 10 | :: https://www.jhipster.tech ::${AnsiColor.DEFAULT} 11 | -------------------------------------------------------------------------------- /src/main/resources/config/application-tls.yml: -------------------------------------------------------------------------------- 1 | # =================================================================== 2 | # Activate this profile to enable TLS and HTTP/2. 3 | # 4 | # JHipster has generated a self-signed certificate, which will be used to encrypt traffic. 5 | # As your browser will not understand this certificate, you will need to import it. 6 | # 7 | # Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag 8 | # at chrome://flags/#allow-insecure-localhost 9 | # =================================================================== 10 | server: 11 | ssl: 12 | key-store: classpath:config/tls/keystore.p12 13 | key-store-password: password 14 | key-store-type: PKCS12 15 | key-alias: selfsigned 16 | ciphers: 17 | - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 18 | - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 19 | - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 20 | - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 21 | - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 22 | - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 23 | - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 24 | - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 25 | enabled-protocols: TLSv1.2 26 | http2: 27 | enabled: true 28 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/changelog/00000000000000_create-tables.cql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS user ( 2 | id text, 3 | login text, 4 | password text, 5 | firstname text, 6 | lastname text, 7 | email text, 8 | activated boolean, 9 | lang_key text, 10 | activation_key text, 11 | reset_key text, 12 | reset_date timestamp, 13 | authorities set, 14 | PRIMARY KEY(id) 15 | ); 16 | 17 | CREATE TABLE IF NOT EXISTS user_by_login ( 18 | login text, 19 | id text, 20 | PRIMARY KEY(login, id) 21 | ); 22 | 23 | CREATE TABLE IF NOT EXISTS user_by_email ( 24 | email text, 25 | id text, 26 | PRIMARY KEY(email, id) 27 | ); 28 | 29 | CREATE TABLE IF NOT EXISTS user_by_activation_key ( 30 | activation_key text, 31 | id text, 32 | PRIMARY KEY(activation_key, id) 33 | ); 34 | 35 | CREATE TABLE IF NOT EXISTS user_by_reset_key ( 36 | reset_key text, 37 | id text, 38 | PRIMARY KEY(reset_key, id) 39 | ); 40 | 41 | CREATE TABLE IF NOT EXISTS activation_key_by_creation_date ( 42 | creation_date timeuuid, 43 | activation_key text, 44 | PRIMARY KEY(creation_date, activation_key) 45 | ); 46 | 47 | CREATE TABLE IF NOT EXISTS persistent_token ( 48 | series text, 49 | token_date timestamp, 50 | user_agent text, 51 | token_value text, 52 | login text, 53 | user_id text, 54 | ip_address text, 55 | PRIMARY KEY(series) 56 | ); 57 | 58 | CREATE TABLE IF NOT EXISTS persistent_token_by_user ( 59 | user_id text, 60 | persistent_token_series text, 61 | PRIMARY KEY(user_id, persistent_token_series) 62 | ); 63 | 64 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/changelog/00000000000001_insert_default_users.cql: -------------------------------------------------------------------------------- 1 | INSERT INTO user (id,login,password,firstname,lastname,email,activated,lang_key,activation_key,authorities) 2 | VALUES('1','admin','$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC','Administrator','Administrator','admin@localhost',true,'en','',{'ROLE_USER','ROLE_ADMIN'}); 3 | 4 | INSERT INTO user_by_login (login, id) 5 | VALUES('admin','1'); 6 | 7 | INSERT INTO user_by_email (email, id) 8 | VALUES('admin@localhost','1'); 9 | 10 | INSERT INTO user (id,login,password,firstname,lastname,email,activated,lang_key,activation_key,authorities) 11 | VALUES('2','user','$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K','User','User','user@localhost',true,'en','',{'ROLE_USER'}); 12 | 13 | INSERT INTO user_by_login (login, id) 14 | VALUES('user','2'); 15 | 16 | INSERT INTO user_by_email (email, id) 17 | VALUES('user@localhost','2'); 18 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/changelog/README.md: -------------------------------------------------------------------------------- 1 | The changelog folder for cassandra/cql files is similar to Liquibase (for SQL databases), but with a minimal tooling. 2 | 3 | - The script name should follow the pattern yyyyMMddHHmmss\_{script-name}.cql 4 | - eg: 20150805124838_added_entity_BankAccount.cql 5 | - The scripts will be applied sequentially in alphabetical order 6 | - The scripts will be applied automatically only in two contexts: 7 | - Unit tests 8 | - Docker-compose for to start a [cassandra cluster for development](https://www.jhipster.tech/docker-compose/#cassandra-in-development) 9 | 10 | Unlike Liquibase, the scripts are not currently automatically applied to the database when deployed with a production profile 11 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/create-keyspace-prod.cql: -------------------------------------------------------------------------------- 1 | CREATE KEYSPACE IF NOT EXISTS jhipsterCassandraSampleApplication 2 | WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 } 3 | AND DURABLE_WRITES = true; 4 | 5 | CREATE TABLE IF NOT EXISTS jhipsterCassandraSampleApplication.schema_version ( 6 | script_name text, 7 | checksum text, 8 | executed_by text, 9 | executed_on timestamp, 10 | execution_time int, 11 | status text, 12 | PRIMARY KEY(script_name) 13 | ); 14 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/create-keyspace.cql: -------------------------------------------------------------------------------- 1 | CREATE KEYSPACE IF NOT EXISTS jhipsterCassandraSampleApplication 2 | WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } 3 | AND DURABLE_WRITES = true; 4 | 5 | CREATE TABLE IF NOT EXISTS jhipsterCassandraSampleApplication.schema_version ( 6 | script_name text, 7 | checksum text, 8 | executed_by text, 9 | executed_on timestamp, 10 | execution_time int, 11 | status text, 12 | PRIMARY KEY(script_name) 13 | ); 14 | -------------------------------------------------------------------------------- /src/main/resources/config/cql/drop-keyspace.cql: -------------------------------------------------------------------------------- 1 | DROP KEYSPACE IF EXISTS jhipsterCassandraSampleApplication; 2 | -------------------------------------------------------------------------------- /src/main/resources/config/tls/keystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/resources/config/tls/keystore.p12 -------------------------------------------------------------------------------- /src/main/resources/i18n/messages.properties: -------------------------------------------------------------------------------- 1 | # Error page 2 | error.title=Your request cannot be processed 3 | error.subtitle=Sorry, an error has occurred. 4 | error.status=Status: 5 | error.message=Message: 6 | 7 | # Activation email 8 | email.activation.title=jhipsterCassandraSampleApplication account activation is required 9 | email.activation.greeting=Dear {0} 10 | email.activation.text1=Your jhipsterCassandraSampleApplication account has been created, please click on the URL below to activate it: 11 | email.activation.text2=Regards, 12 | email.signature=jhipsterCassandraSampleApplication Team. 13 | 14 | # Creation email 15 | email.creation.text1=Your jhipsterCassandraSampleApplication account has been created, please click on the URL below to access it: 16 | 17 | # Reset email 18 | email.reset.title=jhipsterCassandraSampleApplication password reset 19 | email.reset.greeting=Dear {0} 20 | email.reset.text1=For your jhipsterCassandraSampleApplication account a password reset was requested, please click on the URL below to reset it: 21 | email.reset.text2=Regards, 22 | -------------------------------------------------------------------------------- /src/main/resources/i18n/messages_en.properties: -------------------------------------------------------------------------------- 1 | # Error page 2 | error.title=Your request cannot be processed 3 | error.subtitle=Sorry, an error has occurred. 4 | error.status=Status: 5 | error.message=Message: 6 | 7 | # Activation email 8 | email.activation.title=jhipsterCassandraSampleApplication account activation 9 | email.activation.greeting=Dear {0} 10 | email.activation.text1=Your jhipsterCassandraSampleApplication account has been created, please click on the URL below to activate it: 11 | email.activation.text2=Regards, 12 | email.signature=jhipsterCassandraSampleApplication Team. 13 | 14 | # Creation email 15 | email.creation.text1=Your jhipsterCassandraSampleApplication account has been created, please click on the URL below to access it: 16 | 17 | # Reset email 18 | email.reset.title=jhipsterCassandraSampleApplication password reset 19 | email.reset.greeting=Dear {0} 20 | email.reset.text1=For your jhipsterCassandraSampleApplication account a password reset was requested, please click on the URL below to reset it: 21 | email.reset.text2=Regards, 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/activationEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster activation 5 | 6 | 7 | 8 | 9 |

Dear

10 |

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

11 |

12 | Activation link 13 |

14 |

15 | Regards, 16 |
17 | JHipster. 18 |

19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/creationEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster creation 5 | 6 | 7 | 8 | 9 |

Dear

10 |

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

11 |

12 | Login link 13 |

14 |

15 | Regards, 16 |
17 | JHipster. 18 |

19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/passwordResetEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster password reset 5 | 6 | 7 | 8 | 9 |

Dear

10 |

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

13 |

14 | Login link 15 |

16 |

17 | Regards, 18 |
19 | JHipster. 20 |

21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found 6 | 7 | 8 | 52 | 53 | 54 |

Page Not Found

55 |

Sorry, but the page you were trying to view does not exist.

56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | html 10 | text/html;charset=utf-8 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/account.route.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import activateRoute from './activate/activate.route'; 4 | import passwordRoute from './password/password.route'; 5 | import passwordResetFinishRoute from './password-reset/finish/password-reset-finish.route'; 6 | import passwordResetInitRoute from './password-reset/init/password-reset-init.route'; 7 | import registerRoute from './register/register.route'; 8 | import settingsRoute from './settings/settings.route'; 9 | 10 | const accountRoutes: Routes = [ 11 | activateRoute, 12 | passwordRoute, 13 | passwordResetFinishRoute, 14 | passwordResetInitRoute, 15 | registerRoute, 16 | settingsRoute, 17 | ]; 18 | 19 | export default accountRoutes; 20 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/activate/activate.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Activation

5 | @if (success()) { 6 |
7 | Your user account has been activated. Please 8 | sign in. 9 |
10 | } 11 | @if (error()) { 12 |
13 | Your user could not be activated. Please use the registration form to sign up. 14 |
15 | } 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/activate/activate.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, inject, signal } from '@angular/core'; 2 | import { ActivatedRoute, RouterModule } from '@angular/router'; 3 | import { mergeMap } from 'rxjs/operators'; 4 | 5 | import SharedModule from 'app/shared/shared.module'; 6 | import { ActivateService } from './activate.service'; 7 | 8 | @Component({ 9 | selector: 'jhi-activate', 10 | imports: [SharedModule, RouterModule], 11 | templateUrl: './activate.component.html', 12 | }) 13 | export default class ActivateComponent implements OnInit { 14 | error = signal(false); 15 | success = signal(false); 16 | 17 | private readonly activateService = inject(ActivateService); 18 | private readonly route = inject(ActivatedRoute); 19 | 20 | ngOnInit(): void { 21 | this.route.queryParams.pipe(mergeMap(params => this.activateService.get(params.key))).subscribe({ 22 | next: () => this.success.set(true), 23 | error: () => this.error.set(true), 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/activate/activate.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import ActivateComponent from './activate.component'; 4 | 5 | const activateRoute: Route = { 6 | path: 'activate', 7 | component: ActivateComponent, 8 | title: 'activate.title', 9 | }; 10 | 11 | export default activateRoute; 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/activate/activate.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient, HttpParams } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class ActivateService { 9 | private readonly http = inject(HttpClient); 10 | private readonly applicationConfigService = inject(ApplicationConfigService); 11 | 12 | get(key: string): Observable<{}> { 13 | return this.http.get(this.applicationConfigService.getEndpointFor('api/activate'), { 14 | params: new HttpParams().set('key', key), 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import PasswordResetFinishComponent from './password-reset-finish.component'; 4 | 5 | const passwordResetFinishRoute: Route = { 6 | path: 'reset/finish', 7 | component: PasswordResetFinishComponent, 8 | title: 'global.menu.account.password', 9 | }; 10 | 11 | export default passwordResetFinishRoute; 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class PasswordResetFinishService { 9 | private readonly http = inject(HttpClient); 10 | private readonly applicationConfigService = inject(ApplicationConfigService); 11 | 12 | save(key: string, newPassword: string): Observable<{}> { 13 | return this.http.post(this.applicationConfigService.getEndpointFor('api/account/reset-password/finish'), { key, newPassword }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, ElementRef, inject, signal, viewChild } from '@angular/core'; 2 | import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; 3 | import SharedModule from 'app/shared/shared.module'; 4 | 5 | import { PasswordResetInitService } from './password-reset-init.service'; 6 | 7 | @Component({ 8 | selector: 'jhi-password-reset-init', 9 | imports: [SharedModule, FormsModule, ReactiveFormsModule], 10 | templateUrl: './password-reset-init.component.html', 11 | }) 12 | export default class PasswordResetInitComponent implements AfterViewInit { 13 | email = viewChild.required('email'); 14 | 15 | success = signal(false); 16 | resetRequestForm; 17 | 18 | private readonly passwordResetInitService = inject(PasswordResetInitService); 19 | private readonly fb = inject(FormBuilder); 20 | 21 | constructor() { 22 | this.resetRequestForm = this.fb.group({ 23 | email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email]], 24 | }); 25 | } 26 | 27 | ngAfterViewInit(): void { 28 | this.email().nativeElement.focus(); 29 | } 30 | 31 | requestReset(): void { 32 | this.passwordResetInitService.save(this.resetRequestForm.get(['email'])!.value).subscribe(() => this.success.set(true)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import PasswordResetInitComponent from './password-reset-init.component'; 4 | 5 | const passwordResetInitRoute: Route = { 6 | path: 'reset/request', 7 | component: PasswordResetInitComponent, 8 | title: 'global.menu.account.password', 9 | }; 10 | 11 | export default passwordResetInitRoute; 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/init/password-reset-init.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 3 | import { provideHttpClient } from '@angular/common/http'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { PasswordResetInitService } from './password-reset-init.service'; 7 | 8 | describe('PasswordResetInit Service', () => { 9 | let service: PasswordResetInitService; 10 | let httpMock: HttpTestingController; 11 | let applicationConfigService: ApplicationConfigService; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | providers: [provideHttpClient(), provideHttpClientTesting()], 16 | }); 17 | 18 | service = TestBed.inject(PasswordResetInitService); 19 | applicationConfigService = TestBed.inject(ApplicationConfigService); 20 | httpMock = TestBed.inject(HttpTestingController); 21 | }); 22 | 23 | afterEach(() => { 24 | httpMock.verify(); 25 | }); 26 | 27 | describe('Service methods', () => { 28 | it('should call reset-password/init endpoint with correct values', () => { 29 | // GIVEN 30 | const mail = 'test@test.com'; 31 | 32 | // WHEN 33 | service.save(mail).subscribe(); 34 | 35 | const testRequest = httpMock.expectOne({ 36 | method: 'POST', 37 | url: applicationConfigService.getEndpointFor('api/account/reset-password/init'), 38 | }); 39 | 40 | // THEN 41 | expect(testRequest.request.body).toEqual(mail); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class PasswordResetInitService { 9 | private readonly http = inject(HttpClient); 10 | private readonly applicationConfigService = inject(ApplicationConfigService); 11 | 12 | save(mail: string): Observable<{}> { 13 | return this.http.post(this.applicationConfigService.getEndpointFor('api/account/reset-password/init'), mail); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 | Password strength: 3 |
    4 |
  • 5 |
  • 6 |
  • 7 |
  • 8 |
  • 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | start Password strength bar style 3 | ========================================================================== */ 4 | ul#strength { 5 | display: inline; 6 | list-style: none; 7 | margin: 0 0 0 15px; 8 | padding: 0; 9 | vertical-align: 2px; 10 | } 11 | 12 | .point { 13 | background: #ddd; 14 | border-radius: 2px; 15 | display: inline-block; 16 | height: 5px; 17 | margin-right: 1px; 18 | width: 20px; 19 | &:last-child { 20 | margin: 0 !important; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password/password.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; 4 | import PasswordComponent from './password.component'; 5 | 6 | const passwordRoute: Route = { 7 | path: 'password', 8 | component: PasswordComponent, 9 | title: 'global.menu.account.password', 10 | canActivate: [UserRouteAccessService], 11 | }; 12 | 13 | export default passwordRoute; 14 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password/password.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 3 | import { provideHttpClient } from '@angular/common/http'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { PasswordService } from './password.service'; 7 | 8 | describe('Password Service', () => { 9 | let service: PasswordService; 10 | let httpMock: HttpTestingController; 11 | let applicationConfigService: ApplicationConfigService; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | providers: [provideHttpClient(), provideHttpClientTesting()], 16 | }); 17 | 18 | service = TestBed.inject(PasswordService); 19 | applicationConfigService = TestBed.inject(ApplicationConfigService); 20 | httpMock = TestBed.inject(HttpTestingController); 21 | }); 22 | 23 | afterEach(() => { 24 | httpMock.verify(); 25 | }); 26 | 27 | describe('Service methods', () => { 28 | it('should call change-password endpoint with correct values', () => { 29 | // GIVEN 30 | const password1 = 'password1'; 31 | const password2 = 'password2'; 32 | 33 | // WHEN 34 | service.save(password2, password1).subscribe(); 35 | 36 | const testRequest = httpMock.expectOne({ 37 | method: 'POST', 38 | url: applicationConfigService.getEndpointFor('api/account/change-password'), 39 | }); 40 | 41 | // THEN 42 | expect(testRequest.request.body).toEqual({ currentPassword: password1, newPassword: password2 }); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/password/password.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class PasswordService { 9 | private readonly http = inject(HttpClient); 10 | private readonly applicationConfigService = inject(ApplicationConfigService); 11 | 12 | save(newPassword: string, currentPassword: string): Observable<{}> { 13 | return this.http.post(this.applicationConfigService.getEndpointFor('api/account/change-password'), { currentPassword, newPassword }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/register/register.model.ts: -------------------------------------------------------------------------------- 1 | export class Registration { 2 | constructor( 3 | public login: string, 4 | public email: string, 5 | public password: string, 6 | public langKey: string, 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/register/register.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import RegisterComponent from './register.component'; 4 | 5 | const registerRoute: Route = { 6 | path: 'register', 7 | component: RegisterComponent, 8 | title: 'register.title', 9 | }; 10 | 11 | export default registerRoute; 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/register/register.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { Registration } from './register.model'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class RegisterService { 10 | private readonly http = inject(HttpClient); 11 | private readonly applicationConfigService = inject(ApplicationConfigService); 12 | 13 | save(registration: Registration): Observable<{}> { 14 | return this.http.post(this.applicationConfigService.getEndpointFor('api/register'), registration); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/app/account/settings/settings.route.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; 4 | import SettingsComponent from './settings.component'; 5 | 6 | const settingsRoute: Route = { 7 | path: 'settings', 8 | component: SettingsComponent, 9 | title: 'global.menu.account.settings', 10 | canActivate: [UserRouteAccessService], 11 | }; 12 | 13 | export default settingsRoute; 14 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/admin.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | /* jhipster-needle-add-admin-module-import - JHipster will add admin modules imports here */ 3 | 4 | const routes: Routes = [ 5 | { 6 | path: 'user-management', 7 | loadChildren: () => import('./user-management/user-management.route'), 8 | title: 'userManagement.home.title', 9 | }, 10 | { 11 | path: 'docs', 12 | loadComponent: () => import('./docs/docs.component'), 13 | title: 'global.menu.admin.apidocs', 14 | }, 15 | { 16 | path: 'configuration', 17 | loadComponent: () => import('./configuration/configuration.component'), 18 | title: 'configuration.title', 19 | }, 20 | { 21 | path: 'health', 22 | loadComponent: () => import('./health/health.component'), 23 | title: 'health.title', 24 | }, 25 | { 26 | path: 'logs', 27 | loadComponent: () => import('./logs/logs.component'), 28 | title: 'logs.title', 29 | }, 30 | { 31 | path: 'metrics', 32 | loadComponent: () => import('./metrics/metrics.component'), 33 | title: 'metrics.title', 34 | }, 35 | /* jhipster-needle-add-admin-route - JHipster will add admin routes here */ 36 | ]; 37 | 38 | export default routes; 39 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/configuration/configuration.model.ts: -------------------------------------------------------------------------------- 1 | export interface ConfigProps { 2 | contexts: Contexts; 3 | } 4 | 5 | export type Contexts = Record; 6 | 7 | export interface Context { 8 | beans: Beans; 9 | parentId?: any; 10 | } 11 | 12 | export type Beans = Record; 13 | 14 | export interface Bean { 15 | prefix: string; 16 | properties: any; 17 | } 18 | 19 | export interface Env { 20 | activeProfiles?: string[]; 21 | propertySources: PropertySource[]; 22 | } 23 | 24 | export interface PropertySource { 25 | name: string; 26 | properties: Properties; 27 | } 28 | 29 | export type Properties = Record; 30 | 31 | export interface Property { 32 | value: string; 33 | origin?: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/configuration/configuration.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 7 | import { Bean, Beans, ConfigProps, Env, PropertySource } from './configuration.model'; 8 | 9 | @Injectable({ providedIn: 'root' }) 10 | export class ConfigurationService { 11 | private readonly http = inject(HttpClient); 12 | private readonly applicationConfigService = inject(ApplicationConfigService); 13 | 14 | getBeans(): Observable { 15 | return this.http.get(this.applicationConfigService.getEndpointFor('management/configprops')).pipe( 16 | map(configProps => 17 | Object.values( 18 | Object.values(configProps.contexts) 19 | .map(context => context.beans) 20 | .reduce((allBeans: Beans, contextBeans: Beans) => ({ ...allBeans, ...contextBeans }), {}), 21 | ), 22 | ), 23 | ); 24 | } 25 | 26 | getPropertySources(): Observable { 27 | return this.http.get(this.applicationConfigService.getEndpointFor('management/env')).pipe(map(env => env.propertySources)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/docs/docs.component.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/docs/docs.component.scss: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/scss/functions'; 2 | @import 'bootstrap/scss/variables'; 3 | 4 | iframe { 5 | background: white; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/docs/docs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'jhi-docs', 5 | templateUrl: './docs.component.html', 6 | styleUrl: './docs.component.scss', 7 | }) 8 | export default class DocsComponent {} 9 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/health/health.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, inject } from '@angular/core'; 2 | import { HttpErrorResponse } from '@angular/common/http'; 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; 4 | 5 | import SharedModule from 'app/shared/shared.module'; 6 | import { HealthService } from './health.service'; 7 | import { Health, HealthDetails, HealthStatus } from './health.model'; 8 | import HealthModalComponent from './modal/health-modal.component'; 9 | 10 | @Component({ 11 | selector: 'jhi-health', 12 | templateUrl: './health.component.html', 13 | imports: [SharedModule], 14 | }) 15 | export default class HealthComponent implements OnInit { 16 | health?: Health; 17 | 18 | private readonly modalService = inject(NgbModal); 19 | private readonly healthService = inject(HealthService); 20 | 21 | ngOnInit(): void { 22 | this.refresh(); 23 | } 24 | 25 | getBadgeClass(statusState: HealthStatus): string { 26 | if (statusState === 'UP') { 27 | return 'bg-success'; 28 | } 29 | return 'bg-danger'; 30 | } 31 | 32 | refresh(): void { 33 | this.healthService.checkHealth().subscribe({ 34 | next: health => (this.health = health), 35 | error: (error: HttpErrorResponse) => { 36 | if (error.status === 503) { 37 | this.health = error.error; 38 | } 39 | }, 40 | }); 41 | } 42 | 43 | showHealth(health: { key: string; value: HealthDetails }): void { 44 | const modalRef = this.modalService.open(HealthModalComponent); 45 | modalRef.componentInstance.health = health; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/health/health.model.ts: -------------------------------------------------------------------------------- 1 | export type HealthStatus = 'UP' | 'DOWN' | 'UNKNOWN' | 'OUT_OF_SERVICE'; 2 | 3 | export type HealthKey = 'diskSpace' | 'mail' | 'ping' | 'livenessState' | 'readinessState' | 'cassandra'; 4 | 5 | export interface Health { 6 | status: HealthStatus; 7 | components?: Partial>; 8 | } 9 | 10 | export interface HealthDetails { 11 | status: HealthStatus; 12 | details?: Record; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/health/health.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { Health } from './health.model'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class HealthService { 10 | private readonly http = inject(HttpClient); 11 | private readonly applicationConfigService = inject(ApplicationConfigService); 12 | 13 | checkHealth(): Observable { 14 | return this.http.get(this.applicationConfigService.getEndpointFor('management/health')); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/health/modal/health-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; 3 | 4 | import SharedModule from 'app/shared/shared.module'; 5 | import { HealthDetails, HealthKey } from '../health.model'; 6 | 7 | @Component({ 8 | selector: 'jhi-health-modal', 9 | templateUrl: './health-modal.component.html', 10 | imports: [SharedModule], 11 | }) 12 | export default class HealthModalComponent { 13 | health?: { key: HealthKey; value: HealthDetails }; 14 | 15 | private readonly activeModal = inject(NgbActiveModal); 16 | 17 | readableValue(value: any): string { 18 | if (this.health?.key === 'diskSpace') { 19 | // should display storage space in a human readable unit 20 | const val = value / 1073741824; 21 | if (val > 1) { 22 | return `${val.toFixed(2)} GB`; 23 | } 24 | return `${(value / 1048576).toFixed(2)} MB`; 25 | } 26 | 27 | if (typeof value === 'object') { 28 | return JSON.stringify(value); 29 | } 30 | return String(value); 31 | } 32 | 33 | dismiss(): void { 34 | this.activeModal.dismiss(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/logs/log.model.ts: -------------------------------------------------------------------------------- 1 | export type Level = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'OFF'; 2 | 3 | export interface Logger { 4 | configuredLevel: Level | null; 5 | effectiveLevel: Level; 6 | } 7 | 8 | export interface LoggersResponse { 9 | levels: Level[]; 10 | loggers: Record; 11 | } 12 | 13 | export class Log { 14 | constructor( 15 | public name: string, 16 | public level: Level, 17 | ) {} 18 | } 19 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/logs/logs.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 3 | import { provideHttpClient } from '@angular/common/http'; 4 | 5 | import { LogsService } from './logs.service'; 6 | 7 | describe('Logs Service', () => { 8 | let service: LogsService; 9 | let httpMock: HttpTestingController; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [provideHttpClient(), provideHttpClientTesting()], 14 | }); 15 | 16 | service = TestBed.inject(LogsService); 17 | httpMock = TestBed.inject(HttpTestingController); 18 | }); 19 | 20 | afterEach(() => { 21 | httpMock.verify(); 22 | }); 23 | 24 | describe('Service methods', () => { 25 | it('should change log level', () => { 26 | service.changeLevel('main', 'ERROR').subscribe(); 27 | 28 | const req = httpMock.expectOne({ method: 'POST' }); 29 | expect(req.request.body).toEqual({ configuredLevel: 'ERROR' }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/logs/logs.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { Level, LoggersResponse } from './log.model'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class LogsService { 10 | private readonly http = inject(HttpClient); 11 | private readonly applicationConfigService = inject(ApplicationConfigService); 12 | 13 | changeLevel(name: string, configuredLevel: Level): Observable<{}> { 14 | return this.http.post(this.applicationConfigService.getEndpointFor(`management/loggers/${name}`), { configuredLevel }); 15 | } 16 | 17 | findAll(): Observable { 18 | return this.http.get(this.applicationConfigService.getEndpointFor('management/loggers')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html: -------------------------------------------------------------------------------- 1 |

Memory

2 | 3 | @if (!updating() && jvmMemoryMetrics()) { 4 |
5 | @for (entry of jvmMemoryMetrics() | keyvalue; track $index) { 6 |
7 | @if (entry.value.max !== -1) { 8 | 9 | {{ entry.key }} 10 | ({{ entry.value.used / 1048576 | number: '1.0-0' }}M / {{ entry.value.max / 1048576 | number: '1.0-0' }}M) 11 | 12 | 13 |
Committed : {{ entry.value.committed / 1048576 | number: '1.0-0' }}M
14 | 15 | {{ (entry.value.used * 100) / entry.value.max | number: '1.0-0' }}% 16 | 17 | } @else { 18 | {{ entry.key }} {{ entry.value.used / 1048576 | number: '1.0-0' }}M 21 | } 22 |
23 | } 24 |
25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { JvmMetrics } from 'app/admin/metrics/metrics.model'; 5 | 6 | @Component({ 7 | selector: 'jhi-jvm-memory', 8 | templateUrl: './jvm-memory.component.html', 9 | imports: [SharedModule], 10 | }) 11 | export class JvmMemoryComponent { 12 | /** 13 | * object containing all jvm memory metrics 14 | */ 15 | jvmMemoryMetrics = input>(); 16 | 17 | /** 18 | * boolean field saying if the metrics are in the process of being updated 19 | */ 20 | updating = input(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { CacheMetrics } from 'app/admin/metrics/metrics.model'; 5 | import { filterNaN } from 'app/core/util/operators'; 6 | 7 | @Component({ 8 | selector: 'jhi-metrics-cache', 9 | templateUrl: './metrics-cache.component.html', 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | imports: [SharedModule], 12 | }) 13 | export class MetricsCacheComponent { 14 | /** 15 | * object containing all cache related metrics 16 | */ 17 | cacheMetrics = input>(); 18 | 19 | /** 20 | * boolean field saying if the metrics are in the process of being updated 21 | */ 22 | updating = input(); 23 | 24 | filterNaN = (n: number): number => filterNaN(n); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { Databases } from 'app/admin/metrics/metrics.model'; 5 | import { filterNaN } from 'app/core/util/operators'; 6 | 7 | @Component({ 8 | selector: 'jhi-metrics-datasource', 9 | templateUrl: './metrics-datasource.component.html', 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | imports: [SharedModule], 12 | }) 13 | export class MetricsDatasourceComponent { 14 | /** 15 | * object containing all datasource related metrics 16 | */ 17 | datasourceMetrics = input(); 18 | 19 | /** 20 | * boolean field saying if the metrics are in the process of being updated 21 | */ 22 | updating = input(); 23 | 24 | filterNaN = (n: number): number => filterNaN(n); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html: -------------------------------------------------------------------------------- 1 |

Endpoints requests (time in millisecond)

2 | 3 | @if (!updating() && endpointsRequestsMetrics()) { 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @for (entry of endpointsRequestsMetrics() | keyvalue; track entry.key) { 16 | @for (method of entry.value | keyvalue; track method.key) { 17 | 18 | 19 | 20 | 21 | 22 | 23 | } 24 | } 25 | 26 |
MethodEndpoint urlCountMean
{{ method.key }}{{ entry.key }}{{ method.value!.count }}{{ method.value!.mean | number: '1.0-3' }}
27 |
28 | } 29 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { Services } from 'app/admin/metrics/metrics.model'; 5 | 6 | @Component({ 7 | selector: 'jhi-metrics-endpoints-requests', 8 | templateUrl: './metrics-endpoints-requests.component.html', 9 | imports: [SharedModule], 10 | }) 11 | export class MetricsEndpointsRequestsComponent { 12 | /** 13 | * object containing service related metrics 14 | */ 15 | endpointsRequestsMetrics = input(); 16 | 17 | /** 18 | * boolean field saying if the metrics are in the process of being updated 19 | */ 20 | updating = input(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { GarbageCollector } from 'app/admin/metrics/metrics.model'; 5 | 6 | @Component({ 7 | selector: 'jhi-metrics-garbagecollector', 8 | templateUrl: './metrics-garbagecollector.component.html', 9 | imports: [SharedModule], 10 | }) 11 | export class MetricsGarbageCollectorComponent { 12 | /** 13 | * object containing garbage collector related metrics 14 | */ 15 | garbageCollectorMetrics = input(); 16 | 17 | /** 18 | * boolean field saying if the metrics are in the process of being updated 19 | */ 20 | updating = input(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.html: -------------------------------------------------------------------------------- 1 |

HTTP requests (time in millisecond)

2 | 3 | @let requestMetricsRef = requestMetrics(); 4 | @if (!updating() && requestMetricsRef) { 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @for (entry of requestMetricsRef['percode'] | keyvalue; track entry.key) { 16 | 17 | 18 | 29 | 32 | 33 | 34 | } 35 | 36 |
CodeCountMeanMax
{{ entry.key }} 19 | 26 | {{ entry.value.count }} 27 | 28 | 30 | {{ filterNaN(entry.value.mean) | number: '1.0-2' }} 31 | {{ entry.value.max | number: '1.0-2' }}
37 | } 38 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { HttpServerRequests } from 'app/admin/metrics/metrics.model'; 5 | import { filterNaN } from 'app/core/util/operators'; 6 | 7 | @Component({ 8 | selector: 'jhi-metrics-request', 9 | templateUrl: './metrics-request.component.html', 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | imports: [SharedModule], 12 | }) 13 | export class MetricsRequestComponent { 14 | /** 15 | * object containing http request related metrics 16 | */ 17 | requestMetrics = input(); 18 | 19 | /** 20 | * boolean field saying if the metrics are in the process of being updated 21 | */ 22 | updating = input(); 23 | 24 | filterNaN = (n: number): number => filterNaN(n); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, input } from '@angular/core'; 2 | 3 | import SharedModule from 'app/shared/shared.module'; 4 | import { ProcessMetrics } from 'app/admin/metrics/metrics.model'; 5 | 6 | @Component({ 7 | selector: 'jhi-metrics-system', 8 | templateUrl: './metrics-system.component.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | imports: [SharedModule], 11 | }) 12 | export class MetricsSystemComponent { 13 | /** 14 | * object containing thread related metrics 15 | */ 16 | systemMetrics = input(); 17 | 18 | /** 19 | * boolean field saying if the metrics are in the process of being updated 20 | */ 21 | updating = input(); 22 | 23 | convertMillisecondsToDuration(ms: number): string { 24 | const times = { 25 | year: 31557600000, 26 | month: 2629746000, 27 | day: 86400000, 28 | hour: 3600000, 29 | minute: 60000, 30 | second: 1000, 31 | }; 32 | let timeString = ''; 33 | for (const [key, value] of Object.entries(times)) { 34 | if (Math.floor(ms / value) > 0) { 35 | let plural = ''; 36 | if (Math.floor(ms / value) > 1) { 37 | plural = 's'; 38 | } 39 | timeString += `${Math.floor(ms / value).toString()} ${key.toString()}${plural} `; 40 | ms = ms - value * Math.floor(ms / value); 41 | } 42 | } 43 | return timeString; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/metrics/metrics.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 6 | import { Metrics, ThreadDump } from './metrics.model'; 7 | 8 | @Injectable({ providedIn: 'root' }) 9 | export class MetricsService { 10 | private readonly http = inject(HttpClient); 11 | private readonly applicationConfigService = inject(ApplicationConfigService); 12 | 13 | getMetrics(): Observable { 14 | return this.http.get(this.applicationConfigService.getEndpointFor('management/jhimetrics')); 15 | } 16 | 17 | threadDump(): Observable { 18 | return this.http.get(this.applicationConfigService.getEndpointFor('management/threaddump')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.html: -------------------------------------------------------------------------------- 1 | @if (user) { 2 |
3 | 6 | 7 | 14 | 15 | 24 |
25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; 4 | 5 | import SharedModule from 'app/shared/shared.module'; 6 | import { User } from '../user-management.model'; 7 | import { UserManagementService } from '../service/user-management.service'; 8 | 9 | @Component({ 10 | selector: 'jhi-user-mgmt-delete-dialog', 11 | templateUrl: './user-management-delete-dialog.component.html', 12 | imports: [SharedModule, FormsModule], 13 | }) 14 | export default class UserManagementDeleteDialogComponent { 15 | user?: User; 16 | 17 | private readonly userService = inject(UserManagementService); 18 | private readonly activeModal = inject(NgbActiveModal); 19 | 20 | cancel(): void { 21 | this.activeModal.dismiss(); 22 | } 23 | 24 | confirmDelete(login: string): void { 25 | this.userService.delete(login).subscribe(() => { 26 | this.activeModal.close('deleted'); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/user-management/detail/user-management-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import SharedModule from 'app/shared/shared.module'; 4 | 5 | import { User } from '../user-management.model'; 6 | 7 | @Component({ 8 | selector: 'jhi-user-mgmt-detail', 9 | templateUrl: './user-management-detail.component.html', 10 | imports: [RouterModule, SharedModule], 11 | }) 12 | export default class UserManagementDetailComponent { 13 | user = input(null); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/user-management/user-management.model.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string | null; 3 | login?: string; 4 | firstName?: string | null; 5 | lastName?: string | null; 6 | email?: string; 7 | activated?: boolean; 8 | langKey?: string; 9 | authorities?: string[]; 10 | createdBy?: string; 11 | createdDate?: Date; 12 | lastModifiedBy?: string; 13 | lastModifiedDate?: Date; 14 | } 15 | 16 | export class User implements IUser { 17 | constructor( 18 | public id: string | null, 19 | public login?: string, 20 | public firstName?: string | null, 21 | public lastName?: string | null, 22 | public email?: string, 23 | public activated?: boolean, 24 | public langKey?: string, 25 | public authorities?: string[], 26 | public createdBy?: string, 27 | public createdDate?: Date, 28 | public lastModifiedBy?: string, 29 | public lastModifiedDate?: Date, 30 | ) {} 31 | } 32 | -------------------------------------------------------------------------------- /src/main/webapp/app/admin/user-management/user-management.route.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, ResolveFn, Routes } from '@angular/router'; 3 | import { of } from 'rxjs'; 4 | 5 | import { IUser } from './user-management.model'; 6 | import { UserManagementService } from './service/user-management.service'; 7 | 8 | export const userManagementResolve: ResolveFn = (route: ActivatedRouteSnapshot) => { 9 | const login = route.paramMap.get('login'); 10 | if (login) { 11 | return inject(UserManagementService).find(login); 12 | } 13 | return of(null); 14 | }; 15 | 16 | const userManagementRoute: Routes = [ 17 | { 18 | path: '', 19 | loadComponent: () => import('./list/user-management.component'), 20 | data: { 21 | defaultSort: 'id,asc', 22 | }, 23 | }, 24 | { 25 | path: ':login/view', 26 | loadComponent: () => import('./detail/user-management-detail.component'), 27 | resolve: { 28 | user: userManagementResolve, 29 | }, 30 | }, 31 | { 32 | path: 'new', 33 | loadComponent: () => import('./update/user-management-update.component'), 34 | resolve: { 35 | user: userManagementResolve, 36 | }, 37 | }, 38 | { 39 | path: ':login/edit', 40 | loadComponent: () => import('./update/user-management-update.component'), 41 | resolve: { 42 | user: userManagementResolve, 43 | }, 44 | }, 45 | ]; 46 | 47 | export default userManagementRoute; 48 | -------------------------------------------------------------------------------- /src/main/webapp/app/app-page-title-strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { RouterStateSnapshot, TitleStrategy } from '@angular/router'; 3 | import { TranslateService } from '@ngx-translate/core'; 4 | 5 | @Injectable() 6 | export class AppPageTitleStrategy extends TitleStrategy { 7 | private readonly translateService = inject(TranslateService); 8 | 9 | override updateTitle(routerState: RouterStateSnapshot): void { 10 | let pageTitle = this.buildTitle(routerState); 11 | pageTitle ??= 'global.title'; 12 | this.translateService.get(pageTitle).subscribe(title => { 13 | document.title = title; 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { registerLocaleData } from '@angular/common'; 3 | import dayjs from 'dayjs/esm'; 4 | import { FaIconLibrary } from '@fortawesome/angular-fontawesome'; 5 | import { NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap'; 6 | import locale from '@angular/common/locales/en'; 7 | // jhipster-needle-angular-add-module-import JHipster will add new module here 8 | 9 | import { ApplicationConfigService } from 'app/core/config/application-config.service'; 10 | import { fontAwesomeIcons } from './config/font-awesome-icons'; 11 | import MainComponent from './layouts/main/main.component'; 12 | 13 | @Component({ 14 | selector: 'jhi-app', 15 | template: '', 16 | imports: [ 17 | MainComponent, 18 | // jhipster-needle-angular-add-module JHipster will add new module here 19 | ], 20 | }) 21 | export default class AppComponent { 22 | private readonly applicationConfigService = inject(ApplicationConfigService); 23 | private readonly iconLibrary = inject(FaIconLibrary); 24 | private readonly dpConfig = inject(NgbDatepickerConfig); 25 | 26 | constructor() { 27 | this.applicationConfigService.setEndpointPrefix(SERVER_API_URL); 28 | registerLocaleData(locale); 29 | this.iconLibrary.addIcons(...fontAwesomeIcons); 30 | this.dpConfig.minDate = { year: dayjs().subtract(100, 'year').year(), month: 1, day: 1 }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/webapp/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { Authority } from 'app/config/authority.constants'; 4 | 5 | import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; 6 | import { errorRoute } from './layouts/error/error.route'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: '', 11 | loadComponent: () => import('./home/home.component'), 12 | title: 'home.title', 13 | }, 14 | { 15 | path: '', 16 | loadComponent: () => import('./layouts/navbar/navbar.component'), 17 | outlet: 'navbar', 18 | }, 19 | { 20 | path: 'admin', 21 | data: { 22 | authorities: [Authority.ADMIN], 23 | }, 24 | canActivate: [UserRouteAccessService], 25 | loadChildren: () => import('./admin/admin.routes'), 26 | }, 27 | { 28 | path: 'account', 29 | loadChildren: () => import('./account/account.route'), 30 | }, 31 | { 32 | path: 'login', 33 | loadComponent: () => import('./login/login.component'), 34 | title: 'login.title', 35 | }, 36 | { 37 | path: '', 38 | loadChildren: () => import(`./entities/entity.routes`), 39 | }, 40 | ...errorRoute, 41 | ]; 42 | 43 | export default routes; 44 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/authority.constants.ts: -------------------------------------------------------------------------------- 1 | export enum Authority { 2 | ADMIN = 'ROLE_ADMIN', 3 | USER = 'ROLE_USER', 4 | } 5 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/datepicker-adapter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Angular bootstrap Date adapter 3 | */ 4 | import { Injectable } from '@angular/core'; 5 | import { NgbDateAdapter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; 6 | import dayjs from 'dayjs/esm'; 7 | 8 | @Injectable() 9 | export class NgbDateDayjsAdapter extends NgbDateAdapter { 10 | fromModel(date: dayjs.Dayjs | null): NgbDateStruct | null { 11 | if (date && dayjs.isDayjs(date) && date.isValid()) { 12 | return { year: date.year(), month: date.month() + 1, day: date.date() }; 13 | } 14 | return null; 15 | } 16 | 17 | toModel(date: NgbDateStruct | null): dayjs.Dayjs | null { 18 | return date ? dayjs(`${date.year}-${date.month}-${date.day}`) : null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/dayjs.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs/esm'; 2 | import customParseFormat from 'dayjs/esm/plugin/customParseFormat'; 3 | import duration from 'dayjs/esm/plugin/duration'; 4 | import relativeTime from 'dayjs/esm/plugin/relativeTime'; 5 | 6 | // jhipster-needle-i18n-language-dayjs-imports - JHipster will import languages from dayjs here 7 | import 'dayjs/esm/locale/en'; 8 | 9 | // DAYJS CONFIGURATION 10 | dayjs.extend(customParseFormat); 11 | dayjs.extend(duration); 12 | dayjs.extend(relativeTime); 13 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/error.constants.ts: -------------------------------------------------------------------------------- 1 | export const PROBLEM_BASE_URL = 'https://www.jhipster.tech/problem'; 2 | export const EMAIL_ALREADY_USED_TYPE = `${PROBLEM_BASE_URL}/email-already-used`; 3 | export const LOGIN_ALREADY_USED_TYPE = `${PROBLEM_BASE_URL}/login-already-used`; 4 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/font-awesome-icons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | faArrowLeft, 3 | faAsterisk, 4 | faBan, 5 | faBars, 6 | faBell, 7 | faBook, 8 | faCalendarAlt, 9 | faCheck, 10 | faCloud, 11 | faCogs, 12 | faDatabase, 13 | faEye, 14 | faFlag, 15 | faHeart, 16 | faHome, 17 | faList, 18 | faLock, 19 | faPencilAlt, 20 | faPlus, 21 | faRoad, 22 | faSave, 23 | faSearch, 24 | faSignInAlt, 25 | faSignOutAlt, 26 | faSort, 27 | faSortDown, 28 | faSortUp, 29 | faSync, 30 | faTachometerAlt, 31 | faTasks, 32 | faThList, 33 | faTimes, 34 | faTrashAlt, 35 | faUser, 36 | faUserPlus, 37 | faUsers, 38 | faUsersCog, 39 | faWrench, 40 | // jhipster-needle-add-icon-import 41 | } from '@fortawesome/free-solid-svg-icons'; 42 | 43 | export const fontAwesomeIcons = [ 44 | faArrowLeft, 45 | faAsterisk, 46 | faBan, 47 | faBars, 48 | faBell, 49 | faBook, 50 | faCalendarAlt, 51 | faCheck, 52 | faCloud, 53 | faCogs, 54 | faDatabase, 55 | faEye, 56 | faFlag, 57 | faHeart, 58 | faHome, 59 | faList, 60 | faLock, 61 | faPencilAlt, 62 | faPlus, 63 | faRoad, 64 | faSave, 65 | faSearch, 66 | faSignOutAlt, 67 | faSignInAlt, 68 | faSort, 69 | faSortDown, 70 | faSortUp, 71 | faSync, 72 | faTachometerAlt, 73 | faTasks, 74 | faThList, 75 | faTimes, 76 | faTrashAlt, 77 | faUser, 78 | faUserPlus, 79 | faUsers, 80 | faUsersCog, 81 | faWrench, 82 | // jhipster-needle-add-icon-import 83 | ]; 84 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/input.constants.ts: -------------------------------------------------------------------------------- 1 | export const DATE_FORMAT = 'YYYY-MM-DD'; 2 | export const DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm'; 3 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/language.constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Languages codes are ISO_639-1 codes, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 3 | They are written in English to avoid character encoding issues (not a perfect solution) 4 | */ 5 | export const LANGUAGES: string[] = [ 6 | 'en', 7 | // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array 8 | ]; 9 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/navigation.constants.ts: -------------------------------------------------------------------------------- 1 | export const ASC = 'asc'; 2 | export const DESC = 'desc'; 3 | export const SORT = 'sort'; 4 | export const ITEM_DELETED_EVENT = 'deleted'; 5 | export const DEFAULT_SORT_DATA = 'defaultSort'; 6 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/pagination.constants.ts: -------------------------------------------------------------------------------- 1 | export const TOTAL_COUNT_RESPONSE_HEADER = 'X-Total-Count'; 2 | export const PAGE_HEADER = 'page'; 3 | export const ITEMS_PER_PAGE = 20; 4 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/translation.config.ts: -------------------------------------------------------------------------------- 1 | import { MissingTranslationHandler, MissingTranslationHandlerParams, TranslateLoader } from '@ngx-translate/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 4 | 5 | export const translationNotFoundMessage = 'translation-not-found'; 6 | 7 | export class MissingTranslationHandlerImpl implements MissingTranslationHandler { 8 | handle(params: MissingTranslationHandlerParams): string { 9 | const { key } = params; 10 | return `${translationNotFoundMessage}[${key}]`; 11 | } 12 | } 13 | 14 | export function translatePartialLoader(http: HttpClient): TranslateLoader { 15 | return new TranslateHttpLoader(http, 'i18n/', `.json?_=${I18N_HASH}`); 16 | } 17 | 18 | export function missingTranslationHandler(): MissingTranslationHandler { 19 | return new MissingTranslationHandlerImpl(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/config/uib-pagination.config.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { NgbPaginationConfig } from '@ng-bootstrap/ng-bootstrap'; 3 | 4 | import { ITEMS_PER_PAGE } from 'app/config/pagination.constants'; 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class PaginationConfig { 8 | private readonly config = inject(NgbPaginationConfig); 9 | constructor() { 10 | this.config.boundaryLinks = true; 11 | this.config.maxSize = 5; 12 | this.config.pageSize = ITEMS_PER_PAGE; 13 | this.config.size = 'sm'; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/auth/account.model.ts: -------------------------------------------------------------------------------- 1 | export class Account { 2 | constructor( 3 | public activated: boolean, 4 | public authorities: string[], 5 | public email: string, 6 | public firstName: string | null, 7 | public langKey: string, 8 | public lastName: string | null, 9 | public login: string, 10 | public imageUrl: string | null, 11 | ) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/auth/auth-jwt.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | import { Login } from 'app/login/login.model'; 7 | import { ApplicationConfigService } from '../config/application-config.service'; 8 | import { StateStorageService } from './state-storage.service'; 9 | 10 | type JwtToken = { 11 | id_token: string; 12 | }; 13 | 14 | @Injectable({ providedIn: 'root' }) 15 | export class AuthServerProvider { 16 | private readonly http = inject(HttpClient); 17 | private readonly stateStorageService = inject(StateStorageService); 18 | private readonly applicationConfigService = inject(ApplicationConfigService); 19 | 20 | getToken(): string { 21 | return this.stateStorageService.getAuthenticationToken() ?? ''; 22 | } 23 | 24 | login(credentials: Login): Observable { 25 | return this.http 26 | .post(this.applicationConfigService.getEndpointFor('api/authenticate'), credentials) 27 | .pipe(map(response => this.authenticateSuccess(response, credentials.rememberMe))); 28 | } 29 | 30 | logout(): Observable { 31 | return new Observable(observer => { 32 | this.stateStorageService.clearAuthenticationToken(); 33 | observer.complete(); 34 | }); 35 | } 36 | 37 | private authenticateSuccess(response: JwtToken, rememberMe: boolean): void { 38 | this.stateStorageService.storeAuthenticationToken(response.id_token, rememberMe); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/auth/user-route-access.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, isDevMode } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; 3 | import { map } from 'rxjs/operators'; 4 | 5 | import { AccountService } from 'app/core/auth/account.service'; 6 | import { StateStorageService } from './state-storage.service'; 7 | 8 | export const UserRouteAccessService: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { 9 | const accountService = inject(AccountService); 10 | const router = inject(Router); 11 | const stateStorageService = inject(StateStorageService); 12 | return accountService.identity().pipe( 13 | map(account => { 14 | if (account) { 15 | const { authorities } = next.data; 16 | 17 | if (!authorities || authorities.length === 0 || accountService.hasAnyAuthority(authorities)) { 18 | return true; 19 | } 20 | 21 | if (isDevMode()) { 22 | console.error('User does not have any of the required authorities:', authorities); 23 | } 24 | router.navigate(['accessdenied']); 25 | return false; 26 | } 27 | 28 | stateStorageService.storeUrl(state.url); 29 | router.navigate(['/login']); 30 | return false; 31 | }), 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/config/application-config.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ApplicationConfigService } from './application-config.service'; 4 | 5 | describe('ApplicationConfigService', () => { 6 | let service: ApplicationConfigService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ApplicationConfigService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | 17 | describe('without prefix', () => { 18 | it('should return correctly', () => { 19 | expect(service.getEndpointFor('api')).toEqual('api'); 20 | }); 21 | 22 | it('should return correctly when passing microservice', () => { 23 | expect(service.getEndpointFor('api', 'microservice')).toEqual('services/microservice/api'); 24 | }); 25 | }); 26 | 27 | describe('with prefix', () => { 28 | beforeEach(() => { 29 | service.setEndpointPrefix('prefix/'); 30 | }); 31 | 32 | it('should return correctly', () => { 33 | expect(service.getEndpointFor('api')).toEqual('prefix/api'); 34 | }); 35 | 36 | it('should return correctly when passing microservice', () => { 37 | expect(service.getEndpointFor('api', 'microservice')).toEqual('prefix/services/microservice/api'); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/config/application-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class ApplicationConfigService { 7 | private endpointPrefix = ''; 8 | private microfrontend = false; 9 | 10 | setEndpointPrefix(endpointPrefix: string): void { 11 | this.endpointPrefix = endpointPrefix; 12 | } 13 | 14 | setMicrofrontend(microfrontend = true): void { 15 | this.microfrontend = microfrontend; 16 | } 17 | 18 | isMicrofrontend(): boolean { 19 | return this.microfrontend; 20 | } 21 | 22 | getEndpointFor(api: string, microservice?: string): string { 23 | if (microservice) { 24 | return `${this.endpointPrefix}services/${microservice}/${api}`; 25 | } 26 | return `${this.endpointPrefix}${api}`; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/interceptor/auth-expired.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { tap } from 'rxjs/operators'; 5 | import { Router } from '@angular/router'; 6 | 7 | import { LoginService } from 'app/login/login.service'; 8 | import { StateStorageService } from 'app/core/auth/state-storage.service'; 9 | 10 | @Injectable() 11 | export class AuthExpiredInterceptor implements HttpInterceptor { 12 | private readonly loginService = inject(LoginService); 13 | private readonly stateStorageService = inject(StateStorageService); 14 | private readonly router = inject(Router); 15 | 16 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 17 | return next.handle(request).pipe( 18 | tap({ 19 | error: (err: HttpErrorResponse) => { 20 | if (err.status === 401 && err.url && !err.url.includes('api/account')) { 21 | this.stateStorageService.storeUrl(this.router.routerState.snapshot.url); 22 | this.loginService.logout(); 23 | this.router.navigate(['/login']); 24 | } 25 | }, 26 | }), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/interceptor/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { StateStorageService } from 'app/core/auth/state-storage.service'; 6 | import { ApplicationConfigService } from '../config/application-config.service'; 7 | 8 | @Injectable() 9 | export class AuthInterceptor implements HttpInterceptor { 10 | private readonly stateStorageService = inject(StateStorageService); 11 | private readonly applicationConfigService = inject(ApplicationConfigService); 12 | 13 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 14 | const serverApiUrl = this.applicationConfigService.getEndpointFor(''); 15 | if (!request.url || (request.url.startsWith('http') && !(serverApiUrl && request.url.startsWith(serverApiUrl)))) { 16 | return next.handle(request); 17 | } 18 | 19 | const token: string | null = this.stateStorageService.getAuthenticationToken(); 20 | if (token) { 21 | request = request.clone({ 22 | setHeaders: { 23 | Authorization: `Bearer ${token}`, 24 | }, 25 | }); 26 | } 27 | return next.handle(request); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/interceptor/error-handler.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { tap } from 'rxjs/operators'; 5 | 6 | import { EventManager, EventWithContent } from 'app/core/util/event-manager.service'; 7 | 8 | @Injectable() 9 | export class ErrorHandlerInterceptor implements HttpInterceptor { 10 | private readonly eventManager = inject(EventManager); 11 | 12 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 13 | return next.handle(request).pipe( 14 | tap({ 15 | error: (err: HttpErrorResponse) => { 16 | if (!(err.status === 401 && (err.message === '' || err.url?.includes('api/account')))) { 17 | this.eventManager.broadcast(new EventWithContent('jhipsterCassandraSampleApplicationApp.httpError', err)); 18 | } 19 | }, 20 | }), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/interceptor/index.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 2 | 3 | import { AuthInterceptor } from 'app/core/interceptor/auth.interceptor'; 4 | import { AuthExpiredInterceptor } from 'app/core/interceptor/auth-expired.interceptor'; 5 | import { ErrorHandlerInterceptor } from 'app/core/interceptor/error-handler.interceptor'; 6 | import { NotificationInterceptor } from 'app/core/interceptor/notification.interceptor'; 7 | 8 | export const httpInterceptorProviders = [ 9 | { 10 | provide: HTTP_INTERCEPTORS, 11 | useClass: AuthInterceptor, 12 | multi: true, 13 | }, 14 | { 15 | provide: HTTP_INTERCEPTORS, 16 | useClass: AuthExpiredInterceptor, 17 | multi: true, 18 | }, 19 | { 20 | provide: HTTP_INTERCEPTORS, 21 | useClass: ErrorHandlerInterceptor, 22 | multi: true, 23 | }, 24 | { 25 | provide: HTTP_INTERCEPTORS, 26 | useClass: NotificationInterceptor, 27 | multi: true, 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/interceptor/notification.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http'; 2 | import { Injectable, inject } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { tap } from 'rxjs/operators'; 5 | 6 | import { AlertService } from 'app/core/util/alert.service'; 7 | 8 | @Injectable() 9 | export class NotificationInterceptor implements HttpInterceptor { 10 | private readonly alertService = inject(AlertService); 11 | 12 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 13 | return next.handle(request).pipe( 14 | tap((event: HttpEvent) => { 15 | if (event instanceof HttpResponse) { 16 | let alert: string | null = null; 17 | let alertParams: string | null = null; 18 | 19 | for (const headerKey of event.headers.keys()) { 20 | if (headerKey.toLowerCase().endsWith('app-alert')) { 21 | alert = event.headers.get(headerKey); 22 | } else if (headerKey.toLowerCase().endsWith('app-params')) { 23 | alertParams = decodeURIComponent(event.headers.get(headerKey)!.replace(/\+/g, ' ')); 24 | } 25 | } 26 | 27 | if (alert) { 28 | this.alertService.addAlert({ 29 | type: 'success', 30 | translationKey: alert, 31 | translationParams: { param: alertParams }, 32 | }); 33 | } 34 | } 35 | }), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/request/request-util.ts: -------------------------------------------------------------------------------- 1 | import { HttpParams } from '@angular/common/http'; 2 | 3 | export const createRequestOption = (req?: any): HttpParams => { 4 | let options: HttpParams = new HttpParams(); 5 | 6 | if (req) { 7 | Object.entries(req).forEach(([key, val]) => { 8 | if (val !== undefined && val !== null) { 9 | for (const value of [].concat(req[key]).filter(v => v !== '')) { 10 | options = options.append(key, value); 11 | } 12 | } 13 | }); 14 | } 15 | 16 | return options; 17 | }; 18 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/request/request.model.ts: -------------------------------------------------------------------------------- 1 | export interface Pagination { 2 | page: number; 3 | size: number; 4 | sort: string[]; 5 | } 6 | 7 | export interface Search { 8 | query: string; 9 | } 10 | 11 | export interface SearchWithPagination extends Search, Pagination {} 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/util/data-util.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { DataUtils } from './data-util.service'; 4 | 5 | describe('Data Utils Service Test', () => { 6 | let service: DataUtils; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [DataUtils], 11 | }); 12 | service = TestBed.inject(DataUtils); 13 | }); 14 | 15 | describe('byteSize', () => { 16 | it('should return the bytesize of the text', () => { 17 | expect(service.byteSize('Hello JHipster')).toBe(`10.5 bytes`); 18 | }); 19 | }); 20 | 21 | describe('openFile', () => { 22 | it('should open the file in the new window', () => { 23 | const newWindow = { ...window }; 24 | window.open = jest.fn(() => newWindow); 25 | window.URL.createObjectURL = jest.fn(); 26 | // 'JHipster' in base64 is 'SkhpcHN0ZXI=' 27 | const data = 'SkhpcHN0ZXI='; 28 | const contentType = 'text/plain'; 29 | service.openFile(data, contentType); 30 | expect(window.open).toHaveBeenCalledTimes(1); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/util/operators.spec.ts: -------------------------------------------------------------------------------- 1 | import { filterNaN, isPresent } from './operators'; 2 | 3 | describe('Operators Test', () => { 4 | describe('isPresent', () => { 5 | it('should remove null and undefined values', () => { 6 | expect([1, null, undefined].filter(isPresent)).toEqual([1]); 7 | }); 8 | }); 9 | 10 | describe('filterNaN', () => { 11 | it('should return 0 for NaN', () => { 12 | expect(filterNaN(NaN)).toBe(0); 13 | }); 14 | it('should return number for a number', () => { 15 | expect(filterNaN(12345)).toBe(12345); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/main/webapp/app/core/util/operators.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Function used to workaround https://github.com/microsoft/TypeScript/issues/16069 3 | * es2019 alternative `const filteredArr = myArr.flatMap((x) => x ? x : []);` 4 | */ 5 | export function isPresent(t: T | undefined | null): t is T { 6 | return t !== undefined && t !== null; 7 | } 8 | 9 | export const filterNaN = (input: number): number => (isNaN(input) ? 0 : input); 10 | -------------------------------------------------------------------------------- /src/main/webapp/app/entities/entity-navbar-items.ts: -------------------------------------------------------------------------------- 1 | import NavbarItem from 'app/layouts/navbar/navbar-item.model'; 2 | 3 | export const EntityNavbarItems: NavbarItem[] = []; 4 | -------------------------------------------------------------------------------- /src/main/webapp/app/entities/entity.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | const routes: Routes = [ 4 | /* jhipster-needle-add-entity-route - JHipster will add entity modules routes here */ 5 | ]; 6 | 7 | export default routes; 8 | -------------------------------------------------------------------------------- /src/main/webapp/app/entities/user/user.model.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: string; 3 | login?: string | null; 4 | } 5 | -------------------------------------------------------------------------------- /src/main/webapp/app/entities/user/user.test-samples.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from './user.model'; 2 | 3 | export const sampleWithRequiredData: IUser = { 4 | id: 'cdbac2c8-5af0-4c05-9390-f838bceaee96', 5 | login: 'M@-OK\\8Q7\\KE7Lmnl\\-9\\,gz8P\\@0wAqB', 6 | }; 7 | 8 | export const sampleWithPartialData: IUser = { 9 | id: '09076bdf-799b-4280-a46d-d5d1b0596d4e', 10 | login: 'ohu@Z', 11 | }; 12 | 13 | export const sampleWithFullData: IUser = { 14 | id: '2c184532-359e-417e-8339-7736058f89c7', 15 | login: 'tXsOip', 16 | }; 17 | Object.freeze(sampleWithRequiredData); 18 | Object.freeze(sampleWithPartialData); 19 | Object.freeze(sampleWithFullData); 20 | -------------------------------------------------------------------------------- /src/main/webapp/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Main page styles 3 | ========================================================================== */ 4 | 5 | .hipster { 6 | display: inline-block; 7 | width: 347px; 8 | height: 497px; 9 | background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; 10 | background-size: contain; 11 | } 12 | 13 | /* wait autoprefixer update to allow simple generation of high pixel density media query */ 14 | @media only screen and (-webkit-min-device-pixel-ratio: 2), 15 | only screen and (-moz-min-device-pixel-ratio: 2), 16 | only screen and (-o-min-device-pixel-ratio: 2/1), 17 | only screen and (min-resolution: 192dpi), 18 | only screen and (min-resolution: 2dppx) { 19 | .hipster { 20 | background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; 21 | background-size: contain; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/webapp/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; 2 | import { Router, RouterModule } from '@angular/router'; 3 | import { Subject } from 'rxjs'; 4 | import { takeUntil } from 'rxjs/operators'; 5 | 6 | import SharedModule from 'app/shared/shared.module'; 7 | import { AccountService } from 'app/core/auth/account.service'; 8 | import { Account } from 'app/core/auth/account.model'; 9 | 10 | @Component({ 11 | selector: 'jhi-home', 12 | templateUrl: './home.component.html', 13 | styleUrl: './home.component.scss', 14 | imports: [SharedModule, RouterModule], 15 | }) 16 | export default class HomeComponent implements OnInit, OnDestroy { 17 | account = signal(null); 18 | 19 | private readonly destroy$ = new Subject(); 20 | 21 | private readonly accountService = inject(AccountService); 22 | private readonly router = inject(Router); 23 | 24 | ngOnInit(): void { 25 | this.accountService 26 | .getAuthenticationState() 27 | .pipe(takeUntil(this.destroy$)) 28 | .subscribe(account => this.account.set(account)); 29 | } 30 | 31 | login(): void { 32 | this.router.navigate(['/login']); 33 | } 34 | 35 | ngOnDestroy(): void { 36 | this.destroy$.next(); 37 | this.destroy$.complete(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/error/error.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 |

Error page!

9 | 10 | @if (errorMessage(); as errMessage) { 11 |
{{ errMessage }}
12 | } 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/error/error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | import { TranslateService } from '@ngx-translate/core'; 5 | import SharedModule from 'app/shared/shared.module'; 6 | 7 | @Component({ 8 | selector: 'jhi-error', 9 | templateUrl: './error.component.html', 10 | imports: [SharedModule], 11 | }) 12 | export default class ErrorComponent implements OnInit, OnDestroy { 13 | errorMessage = signal(undefined); 14 | errorKey?: string; 15 | langChangeSubscription?: Subscription; 16 | 17 | private readonly translateService = inject(TranslateService); 18 | private readonly route = inject(ActivatedRoute); 19 | 20 | ngOnInit(): void { 21 | this.route.data.subscribe(routeData => { 22 | if (routeData.errorMessage) { 23 | this.errorKey = routeData.errorMessage; 24 | this.getErrorMessageTranslation(); 25 | this.langChangeSubscription = this.translateService.onLangChange.subscribe(() => this.getErrorMessageTranslation()); 26 | } 27 | }); 28 | } 29 | 30 | ngOnDestroy(): void { 31 | if (this.langChangeSubscription) { 32 | this.langChangeSubscription.unsubscribe(); 33 | } 34 | } 35 | 36 | private getErrorMessageTranslation(): void { 37 | this.errorMessage.set(''); 38 | if (this.errorKey) { 39 | this.translateService.get(this.errorKey).subscribe(translatedErrorMessage => { 40 | this.errorMessage.set(translatedErrorMessage); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/error/error.route.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const errorRoute: Routes = [ 4 | { 5 | path: 'error', 6 | loadComponent: () => import('./error.component'), 7 | title: 'error.title', 8 | }, 9 | { 10 | path: 'accessdenied', 11 | loadComponent: () => import('./error.component'), 12 | data: { 13 | errorMessage: 'error.http.403', 14 | }, 15 | title: 'error.title', 16 | }, 17 | { 18 | path: '404', 19 | loadComponent: () => import('./error.component'), 20 | data: { 21 | errorMessage: 'error.http.404', 22 | }, 23 | title: 'error.title', 24 | }, 25 | { 26 | path: '**', 27 | redirectTo: '/404', 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TranslateDirective } from 'app/shared/language'; 3 | 4 | @Component({ 5 | selector: 'jhi-footer', 6 | templateUrl: './footer.component.html', 7 | imports: [TranslateDirective], 8 | }) 9 | export default class FooterComponent {} 10 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/main/main.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/navbar/active-menu.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, OnInit, Renderer2, inject, input } from '@angular/core'; 2 | import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; 3 | 4 | @Directive({ 5 | selector: '[jhiActiveMenu]', 6 | }) 7 | export default class ActiveMenuDirective implements OnInit { 8 | jhiActiveMenu = input(); 9 | 10 | private readonly el = inject(ElementRef); 11 | private readonly renderer = inject(Renderer2); 12 | private readonly translateService = inject(TranslateService); 13 | 14 | ngOnInit(): void { 15 | this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { 16 | this.updateActiveFlag(event.lang); 17 | }); 18 | 19 | this.updateActiveFlag(this.translateService.currentLang); 20 | } 21 | 22 | updateActiveFlag(selectedLanguage: string): void { 23 | if (this.jhiActiveMenu() === selectedLanguage) { 24 | this.renderer.addClass(this.el.nativeElement, 'active'); 25 | } else { 26 | this.renderer.removeClass(this.el.nativeElement, 'active'); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/navbar/navbar-item.model.d.ts: -------------------------------------------------------------------------------- 1 | type NavbarItem = { 2 | name: string; 3 | route: string; 4 | translationKey: string; 5 | }; 6 | 7 | export default NavbarItem; 8 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/navbar/navbar.component.scss: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/scss/functions'; 2 | @import 'bootstrap/scss/variables'; 3 | 4 | /* ========================================================================== 5 | Navbar 6 | ========================================================================== */ 7 | 8 | .navbar-version { 9 | font-size: 0.65em; 10 | color: $navbar-dark-color; 11 | } 12 | 13 | .profile-image { 14 | height: 1.75em; 15 | width: 1.75em; 16 | } 17 | 18 | .navbar { 19 | padding: 0.2rem 1rem; 20 | 21 | a.nav-link { 22 | font-weight: 400; 23 | } 24 | } 25 | 26 | /* ========================================================================== 27 | Logo styles 28 | ========================================================================== */ 29 | .logo-img { 30 | height: 45px; 31 | width: 45px; 32 | display: inline-block; 33 | vertical-align: middle; 34 | background: url('/content/images/logo-jhipster.png') no-repeat center center; 35 | background-size: contain; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/profiles/page-ribbon.component.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Development Ribbon 3 | ========================================================================== */ 4 | .ribbon { 5 | background-color: rgba(170, 0, 0, 0.5); 6 | overflow: hidden; 7 | position: absolute; 8 | top: 40px; 9 | white-space: nowrap; 10 | width: 15em; 11 | z-index: 9999; 12 | pointer-events: none; 13 | opacity: 0.75; 14 | a { 15 | color: #fff; 16 | display: block; 17 | font-weight: 400; 18 | margin: 1px 0; 19 | padding: 10px 50px; 20 | text-align: center; 21 | text-decoration: none; 22 | text-shadow: 0 0 5px #444; 23 | pointer-events: none; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/profiles/page-ribbon.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { provideHttpClient } from '@angular/common/http'; 3 | import { of } from 'rxjs'; 4 | 5 | import { ProfileInfo } from 'app/layouts/profiles/profile-info.model'; 6 | import { ProfileService } from 'app/layouts/profiles/profile.service'; 7 | 8 | import PageRibbonComponent from './page-ribbon.component'; 9 | 10 | describe('Page Ribbon Component', () => { 11 | let comp: PageRibbonComponent; 12 | let fixture: ComponentFixture; 13 | let profileService: ProfileService; 14 | 15 | beforeEach(waitForAsync(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [PageRibbonComponent], 18 | providers: [provideHttpClient()], 19 | }) 20 | .overrideTemplate(PageRibbonComponent, '') 21 | .compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(PageRibbonComponent); 26 | comp = fixture.componentInstance; 27 | profileService = TestBed.inject(ProfileService); 28 | }); 29 | 30 | it('should call profileService.getProfileInfo on init', () => { 31 | // GIVEN 32 | jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(new ProfileInfo())); 33 | 34 | // WHEN 35 | comp.ngOnInit(); 36 | 37 | // THEN 38 | expect(profileService.getProfileInfo).toHaveBeenCalled(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/profiles/page-ribbon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Injector, OnInit, Signal, inject } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { toSignal } from '@angular/core/rxjs-interop'; 5 | 6 | import SharedModule from 'app/shared/shared.module'; 7 | import { ProfileService } from './profile.service'; 8 | 9 | @Component({ 10 | selector: 'jhi-page-ribbon', 11 | template: ` 12 | @if (ribbonEnvSignal; as ribbonEnv) { 13 | 16 | } 17 | `, 18 | styleUrl: './page-ribbon.component.scss', 19 | imports: [SharedModule], 20 | }) 21 | export default class PageRibbonComponent implements OnInit { 22 | ribbonEnvSignal?: Signal; 23 | private readonly injector = inject(Injector); 24 | private readonly profileService = inject(ProfileService); 25 | 26 | ngOnInit(): void { 27 | const ribbonEnv$: Observable = this.profileService.getProfileInfo().pipe(map(profileInfo => profileInfo.ribbonEnv)); 28 | this.ribbonEnvSignal = toSignal(ribbonEnv$, { injector: this.injector }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/webapp/app/layouts/profiles/profile-info.model.ts: -------------------------------------------------------------------------------- 1 | export interface InfoResponse { 2 | 'display-ribbon-on-profiles'?: string; 3 | git?: any; 4 | build?: any; 5 | activeProfiles?: string[]; 6 | } 7 | 8 | export class ProfileInfo { 9 | constructor( 10 | public activeProfiles?: string[], 11 | public ribbonEnv?: string, 12 | public inProduction?: boolean, 13 | public openAPIEnabled?: boolean, 14 | ) {} 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/login/login.model.ts: -------------------------------------------------------------------------------- 1 | export class Login { 2 | constructor( 3 | public username: string, 4 | public password: string, 5 | public rememberMe: boolean, 6 | ) {} 7 | } 8 | -------------------------------------------------------------------------------- /src/main/webapp/app/login/login.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, inject } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { mergeMap } from 'rxjs/operators'; 4 | 5 | import { Account } from 'app/core/auth/account.model'; 6 | import { AccountService } from 'app/core/auth/account.service'; 7 | import { AuthServerProvider } from 'app/core/auth/auth-jwt.service'; 8 | import { Login } from './login.model'; 9 | 10 | @Injectable({ providedIn: 'root' }) 11 | export class LoginService { 12 | private readonly accountService = inject(AccountService); 13 | private readonly authServerProvider = inject(AuthServerProvider); 14 | 15 | login(credentials: Login): Observable { 16 | return this.authServerProvider.login(credentials).pipe(mergeMap(() => this.accountService.identity(true))); 17 | } 18 | 19 | logout(): void { 20 | this.authServerProvider.logout().subscribe({ complete: () => this.accountService.authenticate(null) }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/alert/alert-error.component.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/alert/alert-error.model.ts: -------------------------------------------------------------------------------- 1 | export class AlertError { 2 | constructor( 3 | public message: string, 4 | public key?: string, 5 | public params?: Record, 6 | ) {} 7 | } 8 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/alert/alert.component.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/alert/alert.component.spec.ts: -------------------------------------------------------------------------------- 1 | jest.mock('app/core/util/alert.service'); 2 | 3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 4 | 5 | import { AlertService } from 'app/core/util/alert.service'; 6 | 7 | import { AlertComponent } from './alert.component'; 8 | 9 | describe('Alert Component', () => { 10 | let comp: AlertComponent; 11 | let fixture: ComponentFixture; 12 | let mockAlertService: AlertService; 13 | 14 | beforeEach(waitForAsync(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [AlertComponent], 17 | providers: [AlertService], 18 | }) 19 | .overrideTemplate(AlertComponent, '') 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(AlertComponent); 25 | comp = fixture.componentInstance; 26 | mockAlertService = TestBed.inject(AlertService); 27 | }); 28 | 29 | it('should call alertService.get on init', () => { 30 | // WHEN 31 | comp.ngOnInit(); 32 | 33 | // THEN 34 | expect(mockAlertService.get).toHaveBeenCalled(); 35 | }); 36 | 37 | it('should call alertService.clear on destroy', () => { 38 | // WHEN 39 | comp.ngOnDestroy(); 40 | 41 | // THEN 42 | expect(mockAlertService.clear).toHaveBeenCalled(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/alert/alert.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 4 | 5 | import { Alert, AlertService } from 'app/core/util/alert.service'; 6 | 7 | @Component({ 8 | selector: 'jhi-alert', 9 | templateUrl: './alert.component.html', 10 | imports: [CommonModule, NgbModule], 11 | }) 12 | export class AlertComponent implements OnInit, OnDestroy { 13 | alerts = signal([]); 14 | 15 | private readonly alertService = inject(AlertService); 16 | 17 | ngOnInit(): void { 18 | this.alerts.set(this.alertService.get()); 19 | } 20 | 21 | setClasses(alert: Alert): Record { 22 | const classes = { 'jhi-toast': Boolean(alert.toast) }; 23 | if (alert.position) { 24 | return { ...classes, [alert.position]: true }; 25 | } 26 | return classes; 27 | } 28 | 29 | ngOnDestroy(): void { 30 | this.alertService.clear(); 31 | } 32 | 33 | close(alert: Alert): void { 34 | alert.close?.(this.alerts()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/auth/has-any-authority.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, TemplateRef, ViewContainerRef, computed, effect, inject, input } from '@angular/core'; 2 | 3 | import { AccountService } from 'app/core/auth/account.service'; 4 | 5 | /** 6 | * @whatItDoes Conditionally includes an HTML element if current user has any 7 | * of the authorities passed as the `expression`. 8 | * 9 | * @howToUse 10 | * ``` 11 | * ... 12 | * 13 | * ... 14 | * ``` 15 | */ 16 | @Directive({ 17 | selector: '[jhiHasAnyAuthority]', 18 | }) 19 | export default class HasAnyAuthorityDirective { 20 | public authorities = input([], { alias: 'jhiHasAnyAuthority' }); 21 | 22 | private readonly templateRef = inject(TemplateRef); 23 | private readonly viewContainerRef = inject(ViewContainerRef); 24 | 25 | constructor() { 26 | const accountService = inject(AccountService); 27 | const currentAccount = accountService.trackCurrentAccount(); 28 | const hasPermission = computed(() => currentAccount()?.authorities && accountService.hasAnyAuthority(this.authorities())); 29 | 30 | effect(() => { 31 | if (hasPermission()) { 32 | this.viewContainerRef.createEmbeddedView(this.templateRef); 33 | } else { 34 | this.viewContainerRef.clear(); 35 | } 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/duration.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | import dayjs from 'dayjs/esm'; 4 | 5 | @Pipe({ 6 | name: 'duration', 7 | }) 8 | export default class DurationPipe implements PipeTransform { 9 | transform(value: any): string { 10 | if (value) { 11 | return dayjs.duration(value).humanize(); 12 | } 13 | return ''; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/format-medium-date.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs/esm'; 2 | 3 | import FormatMediumDatePipe from './format-medium-date.pipe'; 4 | 5 | describe('FormatMediumDatePipe', () => { 6 | const formatMediumDatePipe = new FormatMediumDatePipe(); 7 | 8 | it('should return an empty string when receive undefined', () => { 9 | expect(formatMediumDatePipe.transform(undefined)).toBe(''); 10 | }); 11 | 12 | it('should return an empty string when receive null', () => { 13 | expect(formatMediumDatePipe.transform(null)).toBe(''); 14 | }); 15 | 16 | it('should format date like this D MMM YYYY', () => { 17 | expect(formatMediumDatePipe.transform(dayjs('2020-11-16').locale('fr'))).toBe('16 Nov 2020'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/format-medium-date.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | import dayjs from 'dayjs/esm'; 4 | 5 | @Pipe({ 6 | name: 'formatMediumDate', 7 | }) 8 | export default class FormatMediumDatePipe implements PipeTransform { 9 | transform(day: dayjs.Dayjs | null | undefined): string { 10 | return day ? day.format('D MMM YYYY') : ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/format-medium-datetime.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs/esm'; 2 | 3 | import FormatMediumDatetimePipe from './format-medium-datetime.pipe'; 4 | 5 | describe('FormatMediumDatePipe', () => { 6 | const formatMediumDatetimePipe = new FormatMediumDatetimePipe(); 7 | 8 | it('should return an empty string when receive undefined', () => { 9 | expect(formatMediumDatetimePipe.transform(undefined)).toBe(''); 10 | }); 11 | 12 | it('should return an empty string when receive null', () => { 13 | expect(formatMediumDatetimePipe.transform(null)).toBe(''); 14 | }); 15 | 16 | it('should format date like this D MMM YYYY', () => { 17 | expect(formatMediumDatetimePipe.transform(dayjs('2020-11-16').locale('fr'))).toBe('16 Nov 2020 00:00:00'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/format-medium-datetime.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | import dayjs from 'dayjs/esm'; 4 | 5 | @Pipe({ 6 | name: 'formatMediumDatetime', 7 | }) 8 | export default class FormatMediumDatetimePipe implements PipeTransform { 9 | transform(day: dayjs.Dayjs | null | undefined): string { 10 | return day ? day.format('D MMM YYYY HH:mm:ss') : ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/date/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DurationPipe } from './duration.pipe'; 2 | export { default as FormatMediumDatePipe } from './format-medium-date.pipe'; 3 | export { default as FormatMediumDatetimePipe } from './format-medium-datetime.pipe'; 4 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/filter/filter.component.html: -------------------------------------------------------------------------------- 1 | @if (filters().hasAnyFilterSet()) { 2 |
3 | Following filters are set 4 | 7 |
    8 | @for (filterOption of filters().filterOptions; track filterOption.name) { 9 | @for (value of filterOption.values; track value) { 10 |
  • 11 | {{ filterOption.name }}: {{ value }} 12 | 15 |
  • 16 | } 17 | } 18 |
19 |
20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/filter/filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, input } from '@angular/core'; 2 | import SharedModule from '../shared.module'; 3 | import { IFilterOptions } from './filter.model'; 4 | 5 | @Component({ 6 | selector: 'jhi-filter', 7 | imports: [SharedModule], 8 | templateUrl: './filter.component.html', 9 | }) 10 | export default class FilterComponent { 11 | readonly filters = input.required(); 12 | 13 | clearAllFilters(): void { 14 | this.filters().clear(); 15 | } 16 | 17 | clearFilter(filterName: string, value: string): void { 18 | this.filters().removeFilter(filterName, value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/filter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FilterComponent } from './filter.component'; 2 | export * from './filter.model'; 3 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/language/find-language-from-key.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'findLanguageFromKey', 5 | }) 6 | export default class FindLanguageFromKeyPipe implements PipeTransform { 7 | private readonly languages: Record = { 8 | en: { name: 'English' }, 9 | // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object 10 | }; 11 | 12 | transform(lang: string): string { 13 | return this.languages[lang].name; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/language/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TranslateDirective } from './translate.directive'; 2 | export { default as FindLanguageFromKeyPipe } from './find-language-from-key.pipe'; 3 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/language/translate.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { TranslateModule, TranslateService } from '@ngx-translate/core'; 4 | 5 | import TranslateDirective from './translate.directive'; 6 | 7 | @Component({ 8 | imports: [TranslateDirective], 9 | template: `
`, 10 | }) 11 | class TestTranslateDirectiveComponent {} 12 | 13 | describe('TranslateDirective Tests', () => { 14 | let fixture: ComponentFixture; 15 | let translateService: TranslateService; 16 | 17 | beforeEach(waitForAsync(() => { 18 | TestBed.configureTestingModule({ 19 | imports: [TranslateModule.forRoot(), TestTranslateDirectiveComponent], 20 | }); 21 | })); 22 | 23 | beforeEach(() => { 24 | translateService = TestBed.inject(TranslateService); 25 | fixture = TestBed.createComponent(TestTranslateDirectiveComponent); 26 | }); 27 | 28 | it('should change HTML', () => { 29 | const spy = jest.spyOn(translateService, 'get'); 30 | 31 | fixture.detectChanges(); 32 | 33 | expect(spy).toHaveBeenCalled(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/language/translation.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { MissingTranslationHandler, TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; 4 | import { missingTranslationHandler, translatePartialLoader } from 'app/config/translation.config'; 5 | import { StateStorageService } from 'app/core/auth/state-storage.service'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | TranslateModule.forRoot({ 10 | loader: { 11 | provide: TranslateLoader, 12 | useFactory: translatePartialLoader, 13 | deps: [HttpClient], 14 | }, 15 | missingTranslationHandler: { 16 | provide: MissingTranslationHandler, 17 | useFactory: missingTranslationHandler, 18 | }, 19 | }), 20 | ], 21 | }) 22 | export class TranslationModule { 23 | private readonly translateService = inject(TranslateService); 24 | private readonly stateStorageService = inject(StateStorageService); 25 | 26 | constructor() { 27 | this.translateService.setDefaultLang('en'); 28 | // if user have changed language and navigates away from the application and back to the application then use previously chosen language 29 | const langKey = this.stateStorageService.getLocale() ?? 'en'; 30 | this.translateService.use(langKey); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ItemCountComponent } from './item-count.component'; 2 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/pagination/item-count.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, computed, input } from '@angular/core'; 2 | import TranslateDirective from '../language/translate.directive'; 3 | 4 | /** 5 | * A component that will take care of item count statistics of a pagination. 6 | */ 7 | @Component({ 8 | selector: 'jhi-item-count', 9 | template: `
`, 10 | imports: [TranslateDirective], 11 | }) 12 | export default class ItemCountComponent { 13 | /** 14 | * @param params Contains parameters for component: 15 | * page Current page number 16 | * totalItems Total number of items 17 | * itemsPerPage Number of items per page 18 | */ 19 | params = input<{ 20 | page?: number; 21 | totalItems?: number; 22 | itemsPerPage?: number; 23 | }>(); 24 | 25 | first = computed(() => { 26 | const params = this.params(); 27 | if (params?.page && params.totalItems !== undefined && params.itemsPerPage) { 28 | return (params.page - 1) * params.itemsPerPage + 1; 29 | } 30 | return undefined; 31 | }); 32 | 33 | second = computed(() => { 34 | const params = this.params(); 35 | if (params?.page && params.totalItems !== undefined && params.itemsPerPage) { 36 | return params.page * params.itemsPerPage < params.totalItems ? params.page * params.itemsPerPage : params.totalItems; 37 | } 38 | return undefined; 39 | }); 40 | 41 | total = computed(() => this.params()?.totalItems); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 4 | import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; 5 | import { TranslateModule } from '@ngx-translate/core'; 6 | 7 | import FindLanguageFromKeyPipe from './language/find-language-from-key.pipe'; 8 | import TranslateDirective from './language/translate.directive'; 9 | import { AlertComponent } from './alert/alert.component'; 10 | import { AlertErrorComponent } from './alert/alert-error.component'; 11 | 12 | /** 13 | * Application wide Module 14 | */ 15 | @NgModule({ 16 | imports: [AlertComponent, AlertErrorComponent, FindLanguageFromKeyPipe, TranslateDirective], 17 | exports: [ 18 | CommonModule, 19 | NgbModule, 20 | FontAwesomeModule, 21 | AlertComponent, 22 | AlertErrorComponent, 23 | TranslateModule, 24 | FindLanguageFromKeyPipe, 25 | TranslateDirective, 26 | ], 27 | }) 28 | export default class SharedModule {} 29 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/sort/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sort-by.directive'; 2 | export * from './sort-state'; 3 | export * from './sort.directive'; 4 | export * from './sort.service'; 5 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/sort/sort-by.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostListener, contentChild, effect, inject, input } from '@angular/core'; 2 | import { FaIconComponent } from '@fortawesome/angular-fontawesome'; 3 | import { IconDefinition, faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'; 4 | 5 | import { SortDirective } from './sort.directive'; 6 | 7 | @Directive({ 8 | selector: '[jhiSortBy]', 9 | }) 10 | export class SortByDirective { 11 | readonly jhiSortBy = input.required(); 12 | 13 | iconComponent = contentChild(FaIconComponent); 14 | 15 | protected sortIcon = faSort; 16 | protected sortAscIcon = faSortUp; 17 | protected sortDescIcon = faSortDown; 18 | 19 | private readonly sort = inject(SortDirective, { host: true }); 20 | 21 | constructor() { 22 | effect(() => { 23 | if (this.iconComponent()) { 24 | let icon: IconDefinition = this.sortIcon; 25 | const { predicate, order } = this.sort.sortState(); 26 | if (predicate === this.jhiSortBy() && order !== undefined) { 27 | icon = order === 'asc' ? this.sortAscIcon : this.sortDescIcon; 28 | } 29 | this.iconComponent()!.icon = icon.iconName; 30 | this.iconComponent()!.render(); 31 | } 32 | }); 33 | } 34 | 35 | @HostListener('click') 36 | onClick(): void { 37 | if (this.iconComponent()) { 38 | this.sort.sort(this.jhiSortBy()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/sort/sort-state.ts: -------------------------------------------------------------------------------- 1 | import { WritableSignal, signal } from '@angular/core'; 2 | 3 | export type SortOrder = 'asc' | 'desc'; 4 | 5 | export type SortState = { predicate?: string; order?: SortOrder }; 6 | 7 | export const sortStateSignal = (state: SortState): WritableSignal => 8 | signal(state, { 9 | equal: (a, b) => a.predicate === b.predicate && a.order === b.order, 10 | }); 11 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/sort/sort.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, model, output } from '@angular/core'; 2 | import { SortOrder, SortState } from './sort-state'; 3 | 4 | @Directive({ 5 | selector: '[jhiSort]', 6 | }) 7 | export class SortDirective { 8 | readonly sortState = model.required(); 9 | 10 | readonly sortChange = output(); 11 | 12 | sort(field: string): void { 13 | const { predicate, order } = this.sortState(); 14 | const toggle = (): SortOrder => (order === 'asc' ? 'desc' : 'asc'); 15 | const newSortState = { predicate: field, order: field !== predicate ? 'asc' : toggle() }; 16 | this.sortState.update(() => newSortState); 17 | this.sortChange.emit(newSortState); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/app/shared/sort/sort.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { SortState } from './sort-state'; 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class SortService { 6 | private readonly collator = new Intl.Collator(undefined, { 7 | numeric: true, 8 | sensitivity: 'base', 9 | }); 10 | 11 | startSort({ predicate, order }: Required, fallback?: Required): (a: any, b: any) => number { 12 | const multiply = order === 'desc' ? -1 : 1; 13 | return (a: any, b: any) => { 14 | const compare = this.collator.compare(a[predicate], b[predicate]); 15 | if (compare === 0 && fallback) { 16 | return this.startSort(fallback)(a, b); 17 | } 18 | return compare * multiply; 19 | }; 20 | } 21 | 22 | parseSortParam(sortParam: string | undefined): SortState { 23 | if (sortParam?.includes(',')) { 24 | const split = sortParam.split(','); 25 | if (split[0]) { 26 | return { predicate: split[0], order: split[1] as any }; 27 | } 28 | } 29 | return { predicate: sortParam?.length ? sortParam : undefined }; 30 | } 31 | 32 | buildSortParam({ predicate, order }: SortState, fallback?: string): string[] { 33 | const sortParam = predicate && order ? [`${predicate},${order}`] : []; 34 | if (fallback && predicate !== fallback) { 35 | sortParam.push(`${fallback},asc`); 36 | } 37 | return sortParam; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/webapp/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { bootstrapApplication } from '@angular/platform-browser'; 3 | import { appConfig } from './app/app.config'; 4 | import AppComponent from './app/app.component'; 5 | 6 | import { environment } from './environments/environment'; 7 | 8 | // disable debug data on prod profile to improve performance 9 | if (!environment.DEBUG_INFO_ENABLED) { 10 | enableProdMode(); 11 | } 12 | 13 | bootstrapApplication(AppComponent, appConfig) 14 | // eslint-disable-next-line no-console 15 | .then(() => console.log('Application started')) 16 | .catch((err: unknown) => console.error(err)); 17 | -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_0_head-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_0_head-192.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_0_head-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_0_head-256.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_0_head-384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_0_head-384.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_0_head-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_0_head-512.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_1_head-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_1_head-192.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_1_head-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_1_head-256.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_1_head-384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_1_head-384.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_1_head-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_1_head-512.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_2_head-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_2_head-192.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_2_head-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_2_head-256.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_2_head-384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_2_head-384.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_2_head-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_2_head-512.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_3_head-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_3_head-192.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_3_head-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_3_head-256.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_3_head-384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_3_head-384.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/jhipster_family_member_3_head-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/jhipster_family_member_3_head-512.png -------------------------------------------------------------------------------- /src/main/webapp/content/images/logo-jhipster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/content/images/logo-jhipster.png -------------------------------------------------------------------------------- /src/main/webapp/content/scss/_bootstrap-variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Bootstrap overrides https://getbootstrap.com/docs/5.1/customize/sass/ 3 | * All values defined in bootstrap source 4 | * https://github.com/twbs/bootstrap/blob/v5.1.3/scss/_variables.scss can be overwritten here 5 | * Make sure not to add !default to values here 6 | */ 7 | 8 | // Options: 9 | // Quickly modify global styling by enabling or disabling optional features. 10 | $enable-rounded: true; 11 | $enable-shadows: false; 12 | $enable-gradients: false; 13 | $enable-transitions: true; 14 | $enable-hover-media-query: false; 15 | $enable-grid-classes: true; 16 | $enable-print-styles: true; 17 | 18 | // Components: 19 | // Define common padding and border radius sizes and more. 20 | 21 | $border-radius: 0.15rem; 22 | $border-radius-lg: 0.125rem; 23 | $border-radius-sm: 0.1rem; 24 | 25 | // Body: 26 | // Settings for the `` element. 27 | 28 | $body-bg: #ffffff; 29 | 30 | // Typography: 31 | // Font, line-height, and color for body text, headings, and more. 32 | 33 | $font-size-base: 1rem; 34 | 35 | $dropdown-link-hover-color: white; 36 | $dropdown-link-hover-bg: #343a40; 37 | -------------------------------------------------------------------------------- /src/main/webapp/content/scss/vendor.scss: -------------------------------------------------------------------------------- 1 | /* after changing this file run 'npm run webapp:build' */ 2 | 3 | /*************************** 4 | put Sass variables here: 5 | eg $input-color: red; 6 | ****************************/ 7 | // Override Bootstrap variables 8 | @import 'bootstrap-variables'; 9 | // Import Bootstrap source files from node_modules 10 | @import 'bootstrap/scss/bootstrap'; 11 | 12 | /* jhipster-needle-scss-add-vendor JHipster will add new css style */ 13 | -------------------------------------------------------------------------------- /src/main/webapp/declarations.d.ts: -------------------------------------------------------------------------------- 1 | // These constants are injected via webpack DefinePlugin variables. 2 | // You can add more variables in webpack.common.js or in profile specific webpack..js files. 3 | // If you change the values in the webpack config files, you need to re run webpack to update the application 4 | 5 | declare const __VERSION__: string; 6 | declare const SERVER_API_URL: string; 7 | declare const I18N_HASH: string; 8 | -------------------------------------------------------------------------------- /src/main/webapp/environments/environment.development.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | VERSION: 'DEV', 3 | DEBUG_INFO_ENABLED: true, 4 | }; 5 | -------------------------------------------------------------------------------- /src/main/webapp/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | VERSION: __VERSION__, 3 | DEBUG_INFO_ENABLED: false, 4 | }; 5 | -------------------------------------------------------------------------------- /src/main/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/main/webapp/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/activate.json: -------------------------------------------------------------------------------- 1 | { 2 | "activate": { 3 | "title": "Activation", 4 | "messages": { 5 | "success": "Your user account has been activated. Please ", 6 | "error": "Your user could not be activated. Please use the registration form to sign up." 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "configuration": { 3 | "title": "Configuration", 4 | "filter": "Filter (by prefix)", 5 | "table": { 6 | "prefix": "Prefix", 7 | "properties": "Properties" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "title": "Error page!", 4 | "http": { 5 | "400": "Bad request.", 6 | "403": "You are not authorized to access this page.", 7 | "404": "The page does not exist.", 8 | "405": "The HTTP verb you used is not supported for this URL.", 9 | "500": "Internal server error." 10 | }, 11 | "concurrencyFailure": "Another user modified this data at the same time as you. Your changes were rejected.", 12 | "validation": "Validation error on the server." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/health.json: -------------------------------------------------------------------------------- 1 | { 2 | "health": { 3 | "title": "Health Checks", 4 | "refresh.button": "Refresh", 5 | "stacktrace": "Stacktrace", 6 | "details": { 7 | "details": "Details", 8 | "properties": "Properties", 9 | "name": "Name", 10 | "value": "Value", 11 | "error": "Error" 12 | }, 13 | "indicator": { 14 | "diskSpace": "Disk space", 15 | "mail": "Email", 16 | "livenessState": "Liveness state", 17 | "readinessState": "Readiness state", 18 | "ping": "Application", 19 | "cassandra": "Cassandra" 20 | }, 21 | "table": { 22 | "service": "Service name", 23 | "status": "Status" 24 | }, 25 | "status": { 26 | "UNKNOWN": "UNKNOWN", 27 | "UP": "UP", 28 | "OUT_OF_SERVICE": "OUT_OF_SERVICE", 29 | "DOWN": "DOWN" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "Welcome, Java Hipster!", 4 | "subtitle": "This is your homepage", 5 | "logged": { 6 | "message": "You are logged in as user \"{{username}}\"." 7 | }, 8 | "question": "If you have any question on JHipster:", 9 | "link": { 10 | "homepage": "JHipster homepage", 11 | "stackoverflow": "JHipster on Stack Overflow", 12 | "bugtracker": "JHipster bug tracker", 13 | "chat": "JHipster public chat room", 14 | "follow": "follow @jhipster on Twitter" 15 | }, 16 | "like": "If you like JHipster, don't forget to give us a star on", 17 | "github": "GitHub" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/login.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": { 3 | "title": "Sign in", 4 | "form": { 5 | "password": "Password", 6 | "password.placeholder": "Your password", 7 | "rememberme": "Remember me", 8 | "button": "Sign in" 9 | }, 10 | "messages": { 11 | "error": { 12 | "authentication": "Failed to sign in! Please check your credentials and try again." 13 | } 14 | }, 15 | "password": { 16 | "forgot": "Did you forget your password?" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": { 3 | "title": "Logs", 4 | "nbloggers": "There are {{ total }} loggers.", 5 | "filter": "Filter", 6 | "table": { 7 | "name": "Name", 8 | "level": "Level" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/password.json: -------------------------------------------------------------------------------- 1 | { 2 | "password": { 3 | "title": "Password for [{{username}}]", 4 | "form": { 5 | "button": "Save" 6 | }, 7 | "messages": { 8 | "error": "An error has occurred! The password could not be changed.", 9 | "success": "Password changed!" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/register.json: -------------------------------------------------------------------------------- 1 | { 2 | "register": { 3 | "title": "Registration", 4 | "form": { 5 | "button": "Register" 6 | }, 7 | "messages": { 8 | "validate": { 9 | "login": { 10 | "required": "Your username is required.", 11 | "minlength": "Your username is required to be at least 1 character.", 12 | "maxlength": "Your username cannot be longer than 50 characters.", 13 | "pattern": "Your username is invalid." 14 | } 15 | }, 16 | "success": "Registration saved! Please check your email for confirmation.", 17 | "error": { 18 | "fail": "Registration failed! Please try again later.", 19 | "userexists": "Login name already registered! Please choose another one.", 20 | "emailexists": "Email is already in use! Please choose another one." 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/reset.json: -------------------------------------------------------------------------------- 1 | { 2 | "reset": { 3 | "request": { 4 | "title": "Reset your password", 5 | "form": { 6 | "button": "Reset password" 7 | }, 8 | "messages": { 9 | "info": "Enter the email address you used to register", 10 | "success": "Check your email for details on how to reset your password." 11 | } 12 | }, 13 | "finish": { 14 | "title": "Reset password", 15 | "form": { 16 | "button": "Validate new password" 17 | }, 18 | "messages": { 19 | "info": "Choose a new password", 20 | "success": "Your password has been reset. Please ", 21 | "keymissing": "The reset key is missing.", 22 | "error": "Your password couldn't be reset. Remember a password request is only valid for 24 hours." 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/sessions.json: -------------------------------------------------------------------------------- 1 | { 2 | "sessions": { 3 | "title": "Active sessions for [{{username}}]", 4 | "table": { 5 | "ipaddress": "IP address", 6 | "useragent": "User Agent", 7 | "date": "Date", 8 | "button": "Invalidate" 9 | }, 10 | "messages": { 11 | "success": "Session invalidated!", 12 | "error": "An error has occurred! The session could not be invalidated." 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "title": "User settings for [{{username}}]", 4 | "form": { 5 | "firstname": "First Name", 6 | "firstname.placeholder": "Your first name", 7 | "lastname": "Last Name", 8 | "lastname.placeholder": "Your last name", 9 | "language": "Language", 10 | "button": "Save" 11 | }, 12 | "messages": { 13 | "error": { 14 | "fail": "An error has occurred! Settings could not be saved.", 15 | "emailexists": "Email is already in use! Please choose another one." 16 | }, 17 | "success": "Settings saved!", 18 | "validate": { 19 | "firstname": { 20 | "required": "Your first name is required.", 21 | "minlength": "Your first name is required to be at least 1 character", 22 | "maxlength": "Your first name cannot be longer than 50 characters" 23 | }, 24 | "lastname": { 25 | "required": "Your last name is required.", 26 | "minlength": "Your last name is required to be at least 1 character", 27 | "maxlength": "Your last name cannot be longer than 50 characters" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/webapp/i18n/en/user-management.json: -------------------------------------------------------------------------------- 1 | { 2 | "userManagement": { 3 | "home": { 4 | "title": "Users", 5 | "refreshListLabel": "Refresh list", 6 | "createLabel": "Create a new user", 7 | "createOrEditLabel": "Create or edit a user" 8 | }, 9 | "created": "A new user is created with identifier {{ param }}", 10 | "updated": "A user is updated with identifier {{ param }}", 11 | "deleted": "A user is deleted with identifier {{ param }}", 12 | "delete": { 13 | "question": "Are you sure you want to delete user {{ login }}?" 14 | }, 15 | "detail": { 16 | "title": "User" 17 | }, 18 | "login": "Login", 19 | "firstName": "First name", 20 | "lastName": "Last name", 21 | "email": "Email", 22 | "activated": "Activated", 23 | "deactivated": "Deactivated", 24 | "profiles": "Profiles", 25 | "langKey": "Language", 26 | "createdBy": "Created by", 27 | "createdDate": "Created date", 28 | "lastModifiedBy": "Modified by", 29 | "lastModifiedDate": "Modified date" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/webapp/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').catch((err: unknown) => console.error(err)); 2 | -------------------------------------------------------------------------------- /src/main/webapp/manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JhipsterCassandraSampleApplication", 3 | "short_name": "JhipsterCassandraSampleApplication", 4 | "icons": [ 5 | { 6 | "src": "./content/images/jhipster_family_member_1_head-192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./content/images/jhipster_family_member_1_head-256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "./content/images/jhipster_family_member_1_head-384.png", 17 | "sizes": "384x384", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "./content/images/jhipster_family_member_1_head-512.png", 22 | "sizes": "512x512", 23 | "type": "image/png" 24 | } 25 | ], 26 | "theme_color": "#000000", 27 | "background_color": "#e0e0e0", 28 | "start_url": ".", 29 | "display": "standalone", 30 | "orientation": "portrait" 31 | } 32 | -------------------------------------------------------------------------------- /src/main/webapp/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: /api/account 5 | Disallow: /api/account/change-password 6 | Disallow: /api/account/sessions 7 | Disallow: /api/logs/ 8 | Disallow: /api/users/ 9 | Disallow: /management/ 10 | Disallow: /v3/api-docs/ 11 | -------------------------------------------------------------------------------- /src/test/gatling/conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/CassandraKeyspaceIT.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.datastax.oss.driver.api.core.CqlSession; 6 | import com.datastax.oss.driver.api.core.metadata.Metadata; 7 | import io.github.jhipster.sample.config.CassandraTestContainer; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | @IntegrationTest 12 | public class CassandraKeyspaceIT { 13 | 14 | @Autowired 15 | private CqlSession session; 16 | 17 | @Test 18 | void shouldListCassandraTestKeyspace() { 19 | Metadata metadata = session.getMetadata(); 20 | assertThat(metadata.getKeyspace(CassandraTestContainer.DEFAULT_KEYSPACE_NAME)).isNotNull(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample; 2 | 3 | import io.github.jhipster.sample.config.AsyncSyncConfiguration; 4 | import io.github.jhipster.sample.config.EmbeddedCassandra; 5 | import io.github.jhipster.sample.config.JacksonConfiguration; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | /** 13 | * Base composite annotation for integration tests. 14 | */ 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @SpringBootTest(classes = { JhipsterCassandraSampleApplicationApp.class, JacksonConfiguration.class, AsyncSyncConfiguration.class }) 18 | @EmbeddedCassandra 19 | public @interface IntegrationTest { 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/config/AsyncSyncConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import java.util.concurrent.Executor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.task.SyncTaskExecutor; 7 | 8 | @Configuration 9 | public class AsyncSyncConfiguration { 10 | 11 | @Bean(name = "taskExecutor") 12 | public Executor taskExecutor() { 13 | return new SyncTaskExecutor(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/config/EmbeddedCassandra.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface EmbeddedCassandra { 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/config/SpringBootTestClassOrderer.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import io.github.jhipster.sample.IntegrationTest; 4 | import java.util.Comparator; 5 | import org.junit.jupiter.api.ClassDescriptor; 6 | import org.junit.jupiter.api.ClassOrderer; 7 | import org.junit.jupiter.api.ClassOrdererContext; 8 | 9 | public class SpringBootTestClassOrderer implements ClassOrderer { 10 | 11 | @Override 12 | public void orderClasses(ClassOrdererContext context) { 13 | context.getClassDescriptors().sort(Comparator.comparingInt(SpringBootTestClassOrderer::getOrder)); 14 | } 15 | 16 | private static int getOrder(ClassDescriptor classDescriptor) { 17 | if (classDescriptor.findAnnotation(IntegrationTest.class).isPresent()) { 18 | return 2; 19 | } 20 | return 1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/config/WebConfigurerTestController.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.config; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class WebConfigurerTestController { 8 | 9 | @GetMapping("/api/test-cors") 10 | public void testCorsOnApiPath() { 11 | // empty method 12 | } 13 | 14 | @GetMapping("/test/test-cors") 15 | public void testCorsOnOtherPath() { 16 | // empty method 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/domain/AssertUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.ZoneOffset; 5 | import java.time.ZonedDateTime; 6 | import java.util.Comparator; 7 | 8 | public class AssertUtils { 9 | 10 | public static Comparator zonedDataTimeSameInstant = Comparator.nullsFirst((e1, a2) -> 11 | e1.withZoneSameInstant(ZoneOffset.UTC).compareTo(a2.withZoneSameInstant(ZoneOffset.UTC)) 12 | ); 13 | 14 | public static Comparator bigDecimalCompareTo = Comparator.nullsFirst(BigDecimal::compareTo); 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/security/jwt/AuthenticationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.security.jwt; 2 | 3 | import io.github.jhipster.sample.config.SecurityConfiguration; 4 | import io.github.jhipster.sample.config.SecurityJwtConfiguration; 5 | import io.github.jhipster.sample.config.WebConfigurer; 6 | import io.github.jhipster.sample.management.SecurityMetersService; 7 | import io.github.jhipster.sample.web.rest.AuthenticateController; 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import tech.jhipster.config.JHipsterProperties; 14 | 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @SpringBootTest( 18 | properties = { 19 | "jhipster.security.authentication.jwt.base64-secret=fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8", 20 | "jhipster.security.authentication.jwt.token-validity-in-seconds=60000", 21 | }, 22 | classes = { 23 | JHipsterProperties.class, 24 | WebConfigurer.class, 25 | SecurityConfiguration.class, 26 | SecurityJwtConfiguration.class, 27 | SecurityMetersService.class, 28 | AuthenticateController.class, 29 | JwtAuthenticationTestUtils.class, 30 | } 31 | ) 32 | public @interface AuthenticationIntegrationTest { 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/io/github/jhipster/sample/web/rest/WithUnauthenticatedMockUser.java: -------------------------------------------------------------------------------- 1 | package io.github.jhipster.sample.web.rest; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.springframework.security.core.context.SecurityContext; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.test.context.support.WithSecurityContext; 10 | import org.springframework.security.test.context.support.WithSecurityContextFactory; 11 | 12 | @Target({ ElementType.METHOD, ElementType.TYPE }) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @WithSecurityContext(factory = WithUnauthenticatedMockUser.Factory.class) 15 | public @interface WithUnauthenticatedMockUser { 16 | class Factory implements WithSecurityContextFactory { 17 | 18 | @Override 19 | public SecurityContext createSecurityContext(WithUnauthenticatedMockUser annotation) { 20 | return SecurityContextHolder.createEmptyContext(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/e2e/account/logout.cy.ts: -------------------------------------------------------------------------------- 1 | import { accountMenuSelector, loginItemSelector, navbarSelector } from '../../support/commands'; 2 | 3 | describe('logout', () => { 4 | const username = Cypress.env('E2E_USERNAME') ?? 'user'; 5 | const password = Cypress.env('E2E_PASSWORD') ?? 'user'; 6 | 7 | it('go to home page when successfully logs out', () => { 8 | cy.login(username, password); 9 | cy.visit(''); 10 | 11 | cy.clickOnLogoutItem(); 12 | 13 | cy.get(navbarSelector).get(accountMenuSelector).click(); 14 | cy.get(navbarSelector).get(accountMenuSelector).get(loginItemSelector).should('be.visible'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/e2e/account/reset-password-page.cy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | classInvalid, 3 | classValid, 4 | emailResetPasswordSelector, 5 | forgetYourPasswordSelector, 6 | submitInitResetPasswordSelector, 7 | usernameLoginSelector, 8 | } from '../../support/commands'; 9 | 10 | describe('forgot your password', () => { 11 | const username = Cypress.env('E2E_USERNAME') ?? 'user'; 12 | 13 | beforeEach(() => { 14 | cy.visit(''); 15 | cy.clickOnLoginItem(); 16 | cy.get(usernameLoginSelector).type(username); 17 | cy.get(forgetYourPasswordSelector).click(); 18 | }); 19 | 20 | beforeEach(() => { 21 | cy.intercept('POST', '/api/account/reset-password/init').as('initResetPassword'); 22 | }); 23 | 24 | it('requires email', () => { 25 | cy.get(emailResetPasswordSelector).should('have.class', classInvalid); 26 | cy.get(emailResetPasswordSelector).type('user@gmail.com'); 27 | cy.get(emailResetPasswordSelector).should('have.class', classValid); 28 | }); 29 | 30 | it('should be able to init reset password', () => { 31 | cy.get(emailResetPasswordSelector).type('user@gmail.com'); 32 | cy.get(submitInitResetPasswordSelector).click({ force: true }); 33 | cy.wait('@initResetPassword').then(({ response }) => expect(response?.statusCode).to.equal(200)); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/e2e/lighthouse.audits.ts: -------------------------------------------------------------------------------- 1 | describe('Lighthouse Audits', () => { 2 | beforeEach(() => { 3 | cy.visit('/'); 4 | }); 5 | 6 | it('homepage', () => { 7 | const customThresholds = { 8 | performance: 80, 9 | accessibility: 90, 10 | seo: 90, 11 | 'best-practices': 90, 12 | // If you have enabled PWA you should set this threshold to 100 13 | pwa: 0, 14 | }; 15 | 16 | const desktopConfig = { 17 | extends: 'lighthouse:default', 18 | formFactor: 'desktop', 19 | // Change the CPU slowdown multiplier to emulate different kind of devices 20 | // See https://github.com/GoogleChrome/lighthouse/blob/master/docs/throttling.md#cpu-throttling for details 21 | throttling: { 22 | cpuSlowdownMultiplier: 1, 23 | }, 24 | screenEmulation: { disabled: true }, 25 | }; 26 | cy.lighthouse(customThresholds, desktopConfig); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/fixtures/integration-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/src/test/javascript/cypress/fixtures/integration-test.png -------------------------------------------------------------------------------- /src/test/javascript/cypress/support/account.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace */ 2 | export type Account = Record; 3 | 4 | Cypress.Commands.add('getAccount', () => { 5 | return cy 6 | .authenticatedRequest({ 7 | method: 'GET', 8 | url: '/api/account', 9 | }) 10 | .then(response => response.body as Account); 11 | }); 12 | 13 | Cypress.Commands.add('saveAccount', (account: Account) => { 14 | return cy.authenticatedRequest({ 15 | method: 'POST', 16 | url: '/api/account', 17 | body: account, 18 | }); 19 | }); 20 | 21 | declare global { 22 | namespace Cypress { 23 | interface Chainable { 24 | getAccount(): Cypress.Chainable; 25 | saveAccount(account: Account): Cypress.Chainable>; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import './account'; 17 | import './commands'; 18 | import './navbar'; 19 | import './entity'; 20 | import './management'; 21 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/support/management.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-return */ 3 | 4 | Cypress.Commands.add('getManagementInfo', () => { 5 | return cy 6 | .request({ 7 | method: 'GET', 8 | url: '/management/info', 9 | }) 10 | .then(response => response.body); 11 | }); 12 | 13 | declare global { 14 | namespace Cypress { 15 | interface Chainable { 16 | getManagementInfo(): Cypress.Chainable; 17 | } 18 | } 19 | } 20 | 21 | // Convert this to a module instead of a script (allows import/export) 22 | export {}; 23 | -------------------------------------------------------------------------------- /src/test/javascript/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "sourceMap": false, 6 | "outDir": "../../../../target/cypress/tsc", 7 | "target": "es2018", 8 | "types": ["cypress", "node"] 9 | }, 10 | "include": ["./../../../../cypress*.config.ts", "./**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.test.context.ContextCustomizerFactory = \ 2 | io.github.jhipster.sample.config.\ 3 | CassandraTestContainersSpringContextCustomizerFactory -------------------------------------------------------------------------------- /src/test/resources/i18n/messages_en.properties: -------------------------------------------------------------------------------- 1 | email.test.title=test title 2 | # Value used for English locale unit test in MailServiceIT 3 | # as this file is loaded instead of real file 4 | email.activation.title=jhipsterCassandraSampleApplication account activation 5 | -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.execution.timeout.default = 15 s 2 | junit.jupiter.execution.timeout.testable.method.default = 15 s 3 | junit.jupiter.execution.timeout.beforeall.method.default = 60 s 4 | junit.jupiter.testclass.order.default=io.github.jhipster.sample.config.SpringBootTestClassOrderer 5 | -------------------------------------------------------------------------------- /src/test/resources/templates/mail/activationEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster activation 5 | 6 | 7 | 8 |

Dear

9 |

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

10 |

11 | Activation link 12 |

13 |

14 | Regards, 15 |
16 | JHipster. 17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/resources/templates/mail/creationEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster creation 5 | 6 | 7 | 8 |

Dear

9 |

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

10 |

11 | Login link 12 |

13 |

14 | Regards, 15 |
16 | JHipster. 17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/resources/templates/mail/passwordResetEmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JHipster password reset 5 | 6 | 7 | 8 |

Dear

9 |

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

12 |

13 | Login link 14 |

15 |

16 | Regards, 17 |
18 | JHipster. 19 |

20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/templates/mail/testEmail.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./target/out-tsc/app", 5 | "types": ["@angular/localize"] 6 | }, 7 | "files": ["src/main/webapp/main.ts"], 8 | "include": ["src/main/webapp/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src/main/webapp/", 4 | "outDir": "./target/out-tsc/root", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "sourceMap": true, 11 | "declaration": false, 12 | "downlevelIteration": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "node", 15 | "importHelpers": true, 16 | "esModuleInterop": true, 17 | "allowSyntheticDefaultImports": true, 18 | "useDefineForClassFields": false, 19 | "target": "es2022", 20 | "module": "es2020", 21 | "types": [], 22 | "lib": ["es2018", "es2020", "dom"] 23 | }, 24 | "references": [ 25 | { 26 | "path": "tsconfig.spec.json" 27 | } 28 | ], 29 | "angularCompilerOptions": { 30 | "strictInjectionParameters": true, 31 | "strictInputAccessModifiers": true, 32 | "strictTemplates": true, 33 | "preserveWhitespaces": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/main/webapp/**/*.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "target/out-tsc/spec", 7 | "types": ["jest", "node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webpack/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | I18N_HASH: 'generated_hash', 3 | SERVER_API_URL: '', 4 | __VERSION__: process.env.APP_VERSION || 'DEV', 5 | }; 6 | -------------------------------------------------------------------------------- /webpack/logo-jhipster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhipster/jhipster-sample-app-cassandra/1839073dc0ef2650a0acaa77d555f2212fad2851/webpack/logo-jhipster.png -------------------------------------------------------------------------------- /webpack/proxy.conf.js: -------------------------------------------------------------------------------- 1 | function setupProxy({ tls }) { 2 | const serverResources = ['/api', '/services', '/management', '/v3/api-docs', '/h2-console', '/health']; 3 | return [ 4 | { 5 | context: serverResources, 6 | target: `http${tls ? 's' : ''}://localhost:8080`, 7 | secure: false, 8 | changeOrigin: tls, 9 | }, 10 | ]; 11 | } 12 | 13 | module.exports = setupProxy; 14 | --------------------------------------------------------------------------------