From e467d6dc9c5cb256e125ba3f8832e6dfd01b42c5 Mon Sep 17 00:00:00 2001 From: JHipster Bot Date: Wed, 30 Dec 2020 15:51:34 +0000 Subject: [PATCH 1/2] Add JDL Model `CCWAPP` See https://start.jhipster.tech/jdl-studio/#!/view/ef85c763-04ea-464f-8b58-e9723b993d50 --- ccwapp.jh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 ccwapp.jh diff --git a/ccwapp.jh b/ccwapp.jh new file mode 100644 index 0000000..4e743c1 --- /dev/null +++ b/ccwapp.jh @@ -0,0 +1,48 @@ + +application { + config { + baseName CCWApplication, + applicationType monolith, + packageName com.finca.ccw, + authenticationType jwt, + prodDatabaseType mysql, + clientFramework angular + } + entities * +} + +entity Employee { + EmployeeID String required + UnitID String required + FirstName String required + LastName String required + EmailAddress String required + Login String +} + +entity CollectionTracking { + EmployeeID String required + UnitID String required + EmployeeName String required + BusinessProposal String required + SubProposal String required + MobileNo String required + RelationId String required + AccountNo String required + AccountTitle String required + NoOfVisits Integer required + OsAmount BigDecimal required + OsProfit BigDecimal required + OdDays String required + LoanOfficer String required + VisitedBy String required + PptDate LocalDate required + Remakrs String required + + +} + +relationship OneToMany { + Employee to CollectionTracking +} + \ No newline at end of file From 249fe0fd991919c5149383671d11c801dc732647 Mon Sep 17 00:00:00 2001 From: JHipster Bot Date: Wed, 30 Dec 2020 15:51:52 +0000 Subject: [PATCH 2/2] Generate entities from JDL Model `CCWAPP` See https://start.jhipster.tech/jdl-studio/#!/view/ef85c763-04ea-464f-8b58-e9723b993d50 --- .jhipster/CollectionTracking.json | 110 +++ .jhipster/Employee.json | 53 ++ .yo-rc.json | 21 +- README.md | 4 +- angular.json | 4 +- package.json | 4 +- pom.xml | 39 +- sonar-project.properties | 4 +- src/main/docker/app.yml | 10 +- src/main/docker/monitoring.yml | 4 +- src/main/docker/mysql.yml | 6 +- src/main/docker/sonar.yml | 2 +- .../java/com/finca/ccw/ApplicationWebXml.java | 19 + .../java/com/finca/ccw/CcwApplicationApp.java | 104 +++ .../finca/ccw/aop/logging/LoggingAspect.java | 110 +++ .../ccw/config/ApplicationProperties.java | 12 + .../finca/ccw/config/AsyncConfiguration.java | 45 ++ .../finca/ccw/config/CacheConfiguration.java | 78 ++ .../config/CloudDatabaseConfiguration.java | 24 + .../java/com/finca/ccw/config/Constants.java | 15 + .../ccw/config/DatabaseConfiguration.java | 56 ++ .../config/DateTimeFormatConfiguration.java | 20 + .../ccw/config/JacksonConfiguration.java | 51 ++ .../ccw/config/LiquibaseConfiguration.java | 68 ++ .../finca/ccw/config/LocaleConfiguration.java | 26 + .../config/LoggingAspectConfiguration.java | 17 + .../ccw/config/LoggingConfiguration.java | 51 ++ .../ccw/config/SecurityConfiguration.java | 99 +++ .../StaticResourcesWebConfiguration.java | 50 ++ .../com/finca/ccw/config/WebConfigurer.java | 130 +++ .../ccw/config/audit/AuditEventConverter.java | 88 ++ .../finca/ccw/config/audit/package-info.java | 4 + .../com/finca/ccw/config/package-info.java | 4 + .../ccw/domain/AbstractAuditingEntity.java | 75 ++ .../java/com/finca/ccw/domain/Authority.java | 60 ++ .../finca/ccw/domain/CollectionTracking.java | 382 +++++++++ .../java/com/finca/ccw/domain/Employee.java | 194 +++++ .../ccw/domain/PersistentAuditEvent.java | 106 +++ src/main/java/com/finca/ccw/domain/User.java | 229 ++++++ .../com/finca/ccw/domain/package-info.java | 4 + .../ccw/repository/AuthorityRepository.java | 9 + .../CollectionTrackingRepository.java | 12 + .../CustomAuditEventRepository.java | 92 +++ .../ccw/repository/EmployeeRepository.java | 12 + .../PersistenceAuditEventRepository.java | 21 + .../finca/ccw/repository/UserRepository.java | 42 + .../finca/ccw/repository/package-info.java | 4 + .../ccw/security/AuthoritiesConstants.java | 14 + .../security/DomainUserDetailsService.java | 61 ++ .../com/finca/ccw/security/SecurityUtils.java | 79 ++ .../security/SpringSecurityAuditorAware.java | 18 + .../security/UserNotActivatedException.java | 18 + .../finca/ccw/security/jwt/JWTConfigurer.java | 20 + .../com/finca/ccw/security/jwt/JWTFilter.java | 46 ++ .../finca/ccw/security/jwt/TokenProvider.java | 103 +++ .../com/finca/ccw/security/package-info.java | 4 + .../finca/ccw/service/AuditEventService.java | 77 ++ .../service/EmailAlreadyUsedException.java | 9 + .../ccw/service/InvalidPasswordException.java | 9 + .../com/finca/ccw/service/MailService.java | 111 +++ .../com/finca/ccw/service/UserService.java | 338 ++++++++ .../service/UsernameAlreadyUsedException.java | 9 + .../ccw/service/dto/PasswordChangeDTO.java | 34 + .../com/finca/ccw/service/dto/UserDTO.java | 192 +++++ .../finca/ccw/service/dto/package-info.java | 4 + .../finca/ccw/service/mapper/UserMapper.java | 78 ++ .../ccw/service/mapper/package-info.java | 4 + .../com/finca/ccw/service/package-info.java | 4 + .../finca/ccw/web/rest/AccountResource.java | 194 +++++ .../com/finca/ccw/web/rest/AuditResource.java | 76 ++ .../ccw/web/rest/ClientForwardController.java | 17 + .../web/rest/CollectionTrackingResource.java | 123 +++ .../finca/ccw/web/rest/EmployeeResource.java | 121 +++ .../finca/ccw/web/rest/UserJWTController.java | 67 ++ .../com/finca/ccw/web/rest/UserResource.java | 200 +++++ .../rest/errors/BadRequestAlertException.java | 40 + .../errors/EmailAlreadyUsedException.java | 9 + .../ccw/web/rest/errors/ErrorConstants.java | 16 + .../web/rest/errors/ExceptionTranslator.java | 212 +++++ .../ccw/web/rest/errors/FieldErrorVM.java | 31 + .../rest/errors/InvalidPasswordException.java | 12 + .../errors/LoginAlreadyUsedException.java | 9 + .../ccw/web/rest/errors/package-info.java | 6 + .../com/finca/ccw/web/rest/package-info.java | 4 + .../ccw/web/rest/vm/KeyAndPasswordVM.java | 26 + .../com/finca/ccw/web/rest/vm/LoginVM.java | 52 ++ .../finca/ccw/web/rest/vm/ManagedUserVM.java | 34 + .../finca/ccw/web/rest/vm/package-info.java | 4 + src/main/jib/entrypoint.sh | 2 +- src/main/resources/.h2.server.properties | 2 +- src/main/resources/config/application-dev.yml | 6 +- .../resources/config/application-prod.yml | 6 +- src/main/resources/config/application.yml | 14 +- .../20201230155237_added_entity_Employee.xml | 70 ++ ...155337_added_entity_CollectionTracking.xml | 117 +++ ..._entity_constraints_CollectionTracking.xml | 18 + .../fake-data/collection_tracking.csv | 11 + .../config/liquibase/fake-data/employee.csv | 11 + .../resources/config/liquibase/master.xml | 3 + src/main/resources/i18n/messages.properties | 12 +- .../resources/i18n/messages_en.properties | 12 +- .../resources/i18n/messages_fr.properties | 21 + src/main/webapp/app/account/account.module.ts | 4 +- .../webapp/app/admin/audits/audits.module.ts | 4 +- .../configuration/configuration.module.ts | 4 +- src/main/webapp/app/admin/docs/docs.module.ts | 4 +- .../webapp/app/admin/health/health.module.ts | 4 +- src/main/webapp/app/admin/logs/logs.module.ts | 4 +- .../app/admin/metrics/metrics.module.ts | 4 +- .../user-management/user-management.module.ts | 4 +- src/main/webapp/app/app-routing.module.ts | 2 +- src/main/webapp/app/app.main.ts | 4 +- src/main/webapp/app/app.module.ts | 22 +- .../interceptor/errorhandler.interceptor.ts | 2 +- src/main/webapp/app/core/core.module.ts | 2 +- .../app/core/language/language.constants.ts | 1 + ...tion-tracking-delete-dialog.component.html | 24 + ...ection-tracking-delete-dialog.component.ts | 30 + .../collection-tracking-detail.component.html | 100 +++ .../collection-tracking-detail.component.ts | 22 + .../collection-tracking-update.component.html | 254 ++++++ .../collection-tracking-update.component.ts | 141 ++++ .../collection-tracking.component.html | 99 +++ .../collection-tracking.component.ts | 55 ++ .../collection-tracking.module.ts | 21 + .../collection-tracking.route.ts | 83 ++ .../collection-tracking.service.ts | 75 ++ .../employee-delete-dialog.component.html | 24 + .../employee-delete-dialog.component.ts | 26 + .../employee/employee-detail.component.html | 50 ++ .../employee/employee-detail.component.ts | 22 + .../employee/employee-update.component.html | 92 +++ .../employee/employee-update.component.ts | 90 +++ .../entities/employee/employee.component.html | 71 ++ .../entities/employee/employee.component.ts | 49 ++ .../app/entities/employee/employee.module.ts | 16 + .../app/entities/employee/employee.route.ts | 83 ++ .../app/entities/employee/employee.service.ts | 38 + src/main/webapp/app/entities/entity.module.ts | 10 +- src/main/webapp/app/home/home.module.ts | 6 +- src/main/webapp/app/home/home.scss | 4 +- .../app/layouts/navbar/navbar.component.html | 14 +- .../app/shared/alert/alert-error.component.ts | 99 ++- .../language/find-language-from-key.pipe.ts | 1 + .../shared/model/collection-tracking.model.ts | 48 ++ .../webapp/app/shared/model/employee.model.ts | 25 + .../webapp/app/shared/shared-libs.module.ts | 2 +- src/main/webapp/app/shared/shared.module.ts | 8 +- .../webapp/i18n/en/collectionTracking.json | 39 + src/main/webapp/i18n/en/employee.json | 28 + src/main/webapp/i18n/en/global.json | 4 +- src/main/webapp/i18n/fr/activate.json | 9 + src/main/webapp/i18n/fr/audits.json | 28 + .../webapp/i18n/fr/collectionTracking.json | 39 + src/main/webapp/i18n/fr/configuration.json | 10 + src/main/webapp/i18n/fr/employee.json | 28 + src/main/webapp/i18n/fr/error.json | 14 + src/main/webapp/i18n/fr/global.json | 142 ++++ src/main/webapp/i18n/fr/health.json | 29 + src/main/webapp/i18n/fr/home.json | 19 + src/main/webapp/i18n/fr/login.json | 19 + src/main/webapp/i18n/fr/logs.json | 11 + src/main/webapp/i18n/fr/metrics.json | 102 +++ src/main/webapp/i18n/fr/password.json | 12 + src/main/webapp/i18n/fr/register.json | 24 + src/main/webapp/i18n/fr/reset.json | 26 + src/main/webapp/i18n/fr/sessions.json | 15 + src/main/webapp/i18n/fr/settings.json | 32 + src/main/webapp/i18n/fr/user-management.json | 30 + src/main/webapp/index.html | 4 +- src/main/webapp/manifest.webapp | 12 +- src/main/webapp/swagger-ui/index.html | 2 +- src/test/java/com/finca/ccw/ArchTest.java | 29 + .../ccw/config/NoOpMailConfiguration.java | 24 + .../StaticResourcesWebConfigurerTest.java | 75 ++ .../finca/ccw/config/WebConfigurerTest.java | 149 ++++ .../config/WebConfigurerTestController.java | 14 + .../config/timezone/HibernateTimeZoneIT.java | 162 ++++ .../ccw/domain/CollectionTrackingTest.java | 23 + .../com/finca/ccw/domain/EmployeeTest.java | 23 + .../CustomAuditEventRepositoryIT.java | 154 ++++ .../repository/timezone/DateTimeWrapper.java | 131 +++ .../timezone/DateTimeWrapperRepository.java | 10 + .../security/DomainUserDetailsServiceIT.java | 111 +++ .../ccw/security/SecurityUtilsUnitTest.java | 69 ++ .../finca/ccw/security/jwt/JWTFilterTest.java | 115 +++ .../ccw/security/jwt/TokenProviderTest.java | 106 +++ .../ccw/service/AuditEventServiceIT.java | 75 ++ .../com/finca/ccw/service/MailServiceIT.java | 245 ++++++ .../com/finca/ccw/service/UserServiceIT.java | 197 +++++ .../ccw/service/mapper/UserMapperTest.java | 134 +++ .../finca/ccw/web/rest/AccountResourceIT.java | 763 ++++++++++++++++++ .../finca/ccw/web/rest/AuditResourceIT.java | 135 ++++ .../web/rest/ClientForwardControllerTest.java | 57 ++ .../rest/CollectionTrackingResourceIT.java | 738 +++++++++++++++++ .../ccw/web/rest/EmployeeResourceIT.java | 339 ++++++++ .../java/com/finca/ccw/web/rest/TestUtil.java | 151 ++++ .../ccw/web/rest/UserJWTControllerIT.java | 99 +++ .../finca/ccw/web/rest/UserResourceIT.java | 592 ++++++++++++++ .../web/rest/WithUnauthenticatedMockUser.java | 23 + .../rest/errors/ExceptionTranslatorIT.java | 117 +++ .../ExceptionTranslatorTestController.java | 65 ++ .../activate/activate.component.spec.ts | 4 +- .../password-reset-finish.component.spec.ts | 4 +- .../password-reset-init.component.spec.ts | 4 +- .../password/password.component.spec.ts | 4 +- .../register/register.component.spec.ts | 4 +- .../settings/settings.component.spec.ts | 4 +- .../app/admin/audits/audits.component.spec.ts | 4 +- .../configuration.component.spec.ts | 4 +- .../app/admin/health/health.component.spec.ts | 4 +- .../app/admin/logs/logs.component.spec.ts | 4 +- .../admin/metrics/metrics.component.spec.ts | 4 +- ...management-delete-dialog.component.spec.ts | 4 +- .../user-management-detail.component.spec.ts | 4 +- .../user-management-update.component.spec.ts | 4 +- .../user-management.component.spec.ts | 4 +- ...n-tracking-delete-dialog.component.spec.ts | 65 ++ ...llection-tracking-detail.component.spec.ts | 37 + ...llection-tracking-update.component.spec.ts | 61 ++ .../collection-tracking.component.spec.ts | 49 ++ .../collection-tracking.service.spec.ts | 178 ++++ .../employee-delete-dialog.component.spec.ts | 65 ++ .../employee-detail.component.spec.ts | 37 + .../employee-update.component.spec.ts | 61 ++ .../employee/employee.component.spec.ts | 49 ++ .../employee/employee.service.spec.ts | 112 +++ .../spec/app/home/home.component.spec.ts | 4 +- .../app/layouts/main/main.component.spec.ts | 4 +- .../layouts/navbar/navbar.component.spec.ts | 4 +- .../alert/alert-error.component.spec.ts | 20 +- .../app/shared/alert/alert.component.spec.ts | 4 +- .../app/shared/login/login.component.spec.ts | 4 +- src/test/javascript/spec/test.module.ts | 2 +- .../config/application-testcontainers.yml | 2 +- src/test/resources/config/application.yml | 10 +- .../resources/i18n/messages_en.properties | 2 +- .../resources/i18n/messages_fr.properties | 1 + src/test/resources/logback.xml | 2 +- webpack/webpack.common.js | 3 +- webpack/webpack.prod.js | 3 +- 241 files changed, 13306 insertions(+), 247 deletions(-) create mode 100644 .jhipster/CollectionTracking.json create mode 100644 .jhipster/Employee.json create mode 100644 src/main/java/com/finca/ccw/ApplicationWebXml.java create mode 100644 src/main/java/com/finca/ccw/CcwApplicationApp.java create mode 100644 src/main/java/com/finca/ccw/aop/logging/LoggingAspect.java create mode 100644 src/main/java/com/finca/ccw/config/ApplicationProperties.java create mode 100644 src/main/java/com/finca/ccw/config/AsyncConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/CacheConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/CloudDatabaseConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/Constants.java create mode 100644 src/main/java/com/finca/ccw/config/DatabaseConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/DateTimeFormatConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/JacksonConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/LiquibaseConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/LocaleConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/LoggingAspectConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/LoggingConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/SecurityConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/StaticResourcesWebConfiguration.java create mode 100644 src/main/java/com/finca/ccw/config/WebConfigurer.java create mode 100644 src/main/java/com/finca/ccw/config/audit/AuditEventConverter.java create mode 100644 src/main/java/com/finca/ccw/config/audit/package-info.java create mode 100644 src/main/java/com/finca/ccw/config/package-info.java create mode 100644 src/main/java/com/finca/ccw/domain/AbstractAuditingEntity.java create mode 100644 src/main/java/com/finca/ccw/domain/Authority.java create mode 100644 src/main/java/com/finca/ccw/domain/CollectionTracking.java create mode 100644 src/main/java/com/finca/ccw/domain/Employee.java create mode 100644 src/main/java/com/finca/ccw/domain/PersistentAuditEvent.java create mode 100644 src/main/java/com/finca/ccw/domain/User.java create mode 100644 src/main/java/com/finca/ccw/domain/package-info.java create mode 100644 src/main/java/com/finca/ccw/repository/AuthorityRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/CollectionTrackingRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/CustomAuditEventRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/EmployeeRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/PersistenceAuditEventRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/UserRepository.java create mode 100644 src/main/java/com/finca/ccw/repository/package-info.java create mode 100644 src/main/java/com/finca/ccw/security/AuthoritiesConstants.java create mode 100644 src/main/java/com/finca/ccw/security/DomainUserDetailsService.java create mode 100644 src/main/java/com/finca/ccw/security/SecurityUtils.java create mode 100644 src/main/java/com/finca/ccw/security/SpringSecurityAuditorAware.java create mode 100644 src/main/java/com/finca/ccw/security/UserNotActivatedException.java create mode 100644 src/main/java/com/finca/ccw/security/jwt/JWTConfigurer.java create mode 100644 src/main/java/com/finca/ccw/security/jwt/JWTFilter.java create mode 100644 src/main/java/com/finca/ccw/security/jwt/TokenProvider.java create mode 100644 src/main/java/com/finca/ccw/security/package-info.java create mode 100644 src/main/java/com/finca/ccw/service/AuditEventService.java create mode 100644 src/main/java/com/finca/ccw/service/EmailAlreadyUsedException.java create mode 100644 src/main/java/com/finca/ccw/service/InvalidPasswordException.java create mode 100644 src/main/java/com/finca/ccw/service/MailService.java create mode 100644 src/main/java/com/finca/ccw/service/UserService.java create mode 100644 src/main/java/com/finca/ccw/service/UsernameAlreadyUsedException.java create mode 100644 src/main/java/com/finca/ccw/service/dto/PasswordChangeDTO.java create mode 100644 src/main/java/com/finca/ccw/service/dto/UserDTO.java create mode 100644 src/main/java/com/finca/ccw/service/dto/package-info.java create mode 100644 src/main/java/com/finca/ccw/service/mapper/UserMapper.java create mode 100644 src/main/java/com/finca/ccw/service/mapper/package-info.java create mode 100644 src/main/java/com/finca/ccw/service/package-info.java create mode 100644 src/main/java/com/finca/ccw/web/rest/AccountResource.java create mode 100644 src/main/java/com/finca/ccw/web/rest/AuditResource.java create mode 100644 src/main/java/com/finca/ccw/web/rest/ClientForwardController.java create mode 100644 src/main/java/com/finca/ccw/web/rest/CollectionTrackingResource.java create mode 100644 src/main/java/com/finca/ccw/web/rest/EmployeeResource.java create mode 100644 src/main/java/com/finca/ccw/web/rest/UserJWTController.java create mode 100644 src/main/java/com/finca/ccw/web/rest/UserResource.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/BadRequestAlertException.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/EmailAlreadyUsedException.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/ErrorConstants.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/ExceptionTranslator.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/FieldErrorVM.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/InvalidPasswordException.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/LoginAlreadyUsedException.java create mode 100644 src/main/java/com/finca/ccw/web/rest/errors/package-info.java create mode 100644 src/main/java/com/finca/ccw/web/rest/package-info.java create mode 100644 src/main/java/com/finca/ccw/web/rest/vm/KeyAndPasswordVM.java create mode 100644 src/main/java/com/finca/ccw/web/rest/vm/LoginVM.java create mode 100644 src/main/java/com/finca/ccw/web/rest/vm/ManagedUserVM.java create mode 100644 src/main/java/com/finca/ccw/web/rest/vm/package-info.java create mode 100644 src/main/resources/config/liquibase/changelog/20201230155237_added_entity_Employee.xml create mode 100644 src/main/resources/config/liquibase/changelog/20201230155337_added_entity_CollectionTracking.xml create mode 100644 src/main/resources/config/liquibase/changelog/20201230155337_added_entity_constraints_CollectionTracking.xml create mode 100644 src/main/resources/config/liquibase/fake-data/collection_tracking.csv create mode 100644 src/main/resources/config/liquibase/fake-data/employee.csv create mode 100644 src/main/resources/i18n/messages_fr.properties create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.html create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.html create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.html create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking.component.html create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking.component.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking.module.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking.route.ts create mode 100644 src/main/webapp/app/entities/collection-tracking/collection-tracking.service.ts create mode 100644 src/main/webapp/app/entities/employee/employee-delete-dialog.component.html create mode 100644 src/main/webapp/app/entities/employee/employee-delete-dialog.component.ts create mode 100644 src/main/webapp/app/entities/employee/employee-detail.component.html create mode 100644 src/main/webapp/app/entities/employee/employee-detail.component.ts create mode 100644 src/main/webapp/app/entities/employee/employee-update.component.html create mode 100644 src/main/webapp/app/entities/employee/employee-update.component.ts create mode 100644 src/main/webapp/app/entities/employee/employee.component.html create mode 100644 src/main/webapp/app/entities/employee/employee.component.ts create mode 100644 src/main/webapp/app/entities/employee/employee.module.ts create mode 100644 src/main/webapp/app/entities/employee/employee.route.ts create mode 100644 src/main/webapp/app/entities/employee/employee.service.ts create mode 100644 src/main/webapp/app/shared/model/collection-tracking.model.ts create mode 100644 src/main/webapp/app/shared/model/employee.model.ts create mode 100644 src/main/webapp/i18n/en/collectionTracking.json create mode 100644 src/main/webapp/i18n/en/employee.json create mode 100644 src/main/webapp/i18n/fr/activate.json create mode 100644 src/main/webapp/i18n/fr/audits.json create mode 100644 src/main/webapp/i18n/fr/collectionTracking.json create mode 100644 src/main/webapp/i18n/fr/configuration.json create mode 100644 src/main/webapp/i18n/fr/employee.json create mode 100644 src/main/webapp/i18n/fr/error.json create mode 100644 src/main/webapp/i18n/fr/global.json create mode 100644 src/main/webapp/i18n/fr/health.json create mode 100644 src/main/webapp/i18n/fr/home.json create mode 100644 src/main/webapp/i18n/fr/login.json create mode 100644 src/main/webapp/i18n/fr/logs.json create mode 100644 src/main/webapp/i18n/fr/metrics.json create mode 100644 src/main/webapp/i18n/fr/password.json create mode 100644 src/main/webapp/i18n/fr/register.json create mode 100644 src/main/webapp/i18n/fr/reset.json create mode 100644 src/main/webapp/i18n/fr/sessions.json create mode 100644 src/main/webapp/i18n/fr/settings.json create mode 100644 src/main/webapp/i18n/fr/user-management.json create mode 100644 src/test/java/com/finca/ccw/ArchTest.java create mode 100644 src/test/java/com/finca/ccw/config/NoOpMailConfiguration.java create mode 100644 src/test/java/com/finca/ccw/config/StaticResourcesWebConfigurerTest.java create mode 100644 src/test/java/com/finca/ccw/config/WebConfigurerTest.java create mode 100644 src/test/java/com/finca/ccw/config/WebConfigurerTestController.java create mode 100644 src/test/java/com/finca/ccw/config/timezone/HibernateTimeZoneIT.java create mode 100644 src/test/java/com/finca/ccw/domain/CollectionTrackingTest.java create mode 100644 src/test/java/com/finca/ccw/domain/EmployeeTest.java create mode 100644 src/test/java/com/finca/ccw/repository/CustomAuditEventRepositoryIT.java create mode 100644 src/test/java/com/finca/ccw/repository/timezone/DateTimeWrapper.java create mode 100644 src/test/java/com/finca/ccw/repository/timezone/DateTimeWrapperRepository.java create mode 100644 src/test/java/com/finca/ccw/security/DomainUserDetailsServiceIT.java create mode 100644 src/test/java/com/finca/ccw/security/SecurityUtilsUnitTest.java create mode 100644 src/test/java/com/finca/ccw/security/jwt/JWTFilterTest.java create mode 100644 src/test/java/com/finca/ccw/security/jwt/TokenProviderTest.java create mode 100644 src/test/java/com/finca/ccw/service/AuditEventServiceIT.java create mode 100644 src/test/java/com/finca/ccw/service/MailServiceIT.java create mode 100644 src/test/java/com/finca/ccw/service/UserServiceIT.java create mode 100644 src/test/java/com/finca/ccw/service/mapper/UserMapperTest.java create mode 100644 src/test/java/com/finca/ccw/web/rest/AccountResourceIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/AuditResourceIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/ClientForwardControllerTest.java create mode 100644 src/test/java/com/finca/ccw/web/rest/CollectionTrackingResourceIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/EmployeeResourceIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/TestUtil.java create mode 100644 src/test/java/com/finca/ccw/web/rest/UserJWTControllerIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/UserResourceIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/WithUnauthenticatedMockUser.java create mode 100644 src/test/java/com/finca/ccw/web/rest/errors/ExceptionTranslatorIT.java create mode 100644 src/test/java/com/finca/ccw/web/rest/errors/ExceptionTranslatorTestController.java create mode 100644 src/test/javascript/spec/app/entities/collection-tracking/collection-tracking-delete-dialog.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/collection-tracking/collection-tracking-detail.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/collection-tracking/collection-tracking-update.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/collection-tracking/collection-tracking.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/collection-tracking/collection-tracking.service.spec.ts create mode 100644 src/test/javascript/spec/app/entities/employee/employee-delete-dialog.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/employee/employee-detail.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/employee/employee-update.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/employee/employee.component.spec.ts create mode 100644 src/test/javascript/spec/app/entities/employee/employee.service.spec.ts create mode 100644 src/test/resources/i18n/messages_fr.properties diff --git a/.jhipster/CollectionTracking.json b/.jhipster/CollectionTracking.json new file mode 100644 index 0000000..bed443a --- /dev/null +++ b/.jhipster/CollectionTracking.json @@ -0,0 +1,110 @@ +{ + "name": "CollectionTracking", + "fields": [ + { + "fieldName": "employeeID", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "unitID", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "employeeName", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "businessProposal", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "subProposal", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "mobileNo", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "relationId", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "accountNo", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "accountTitle", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "noOfVisits", + "fieldType": "Integer", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "osAmount", + "fieldType": "BigDecimal", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "osProfit", + "fieldType": "BigDecimal", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "odDays", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "loanOfficer", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "visitedBy", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "pptDate", + "fieldType": "LocalDate", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "remakrs", + "fieldType": "String", + "fieldValidateRules": ["required"] + } + ], + "relationships": [ + { + "relationshipType": "many-to-one", + "otherEntityName": "employee", + "otherEntityRelationshipName": "collectionTracking", + "relationshipName": "employee", + "otherEntityField": "id" + } + ], + "changelogDate": "20201230155337", + "entityTableName": "collection_tracking", + "dto": "no", + "pagination": "no", + "service": "no", + "jpaMetamodelFiltering": false, + "fluentMethods": true, + "readOnly": false, + "embedded": false, + "clientRootFolder": "", + "applications": ["CCWApplication"] +} diff --git a/.jhipster/Employee.json b/.jhipster/Employee.json new file mode 100644 index 0000000..2cd3f92 --- /dev/null +++ b/.jhipster/Employee.json @@ -0,0 +1,53 @@ +{ + "name": "Employee", + "fields": [ + { + "fieldName": "employeeID", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "unitID", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "firstName", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "lastName", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "emailAddress", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "login", + "fieldType": "String" + } + ], + "relationships": [ + { + "relationshipType": "one-to-many", + "otherEntityName": "collectionTracking", + "otherEntityRelationshipName": "employee", + "relationshipName": "collectionTracking" + } + ], + "changelogDate": "20201230155237", + "entityTableName": "employee", + "dto": "no", + "pagination": "no", + "service": "no", + "jpaMetamodelFiltering": false, + "fluentMethods": true, + "readOnly": false, + "embedded": false, + "clientRootFolder": "", + "applications": ["CCWApplication"] +} diff --git a/.yo-rc.json b/.yo-rc.json index 4ab7ef4..e4f617f 100644 --- a/.yo-rc.json +++ b/.yo-rc.json @@ -2,10 +2,10 @@ "generator-jhipster": { "applicationType": "monolith", "gitCompany": "", - "baseName": "CollectionTrackingApplication", - "packageName": "com.finca.ccwapp", - "packageFolder": "com/finca/ccwapp", - "serverPort": 8080, + "baseName": "CCWApplication", + "packageName": "com.finca.ccw", + "packageFolder": "com/finca/ccw", + "serverPort": "8080", "serviceDiscoveryType": false, "authenticationType": "jwt", "uaaBaseName": "../uaa", @@ -21,10 +21,10 @@ "buildTool": "maven", "useSass": true, "clientPackageManager": "npm", - "testFrameworks": ["cucumber"], + "testFrameworks": [], "enableTranslation": true, "nativeLanguage": "en", - "languages": ["en"], + "languages": ["en", "fr"], "clientFramework": "angularX", "jhiPrefix": "jhi", "jhipsterVersion": "6.10.5", @@ -36,9 +36,14 @@ "dtoSuffix": "DTO", "otherModules": [], "blueprints": [], - "prettierJava": true + "prettierJava": true, + "skipUserManagement": false, + "skipClient": false, + "skipServer": false, + "clientThemeVariant": "" }, "git-provider": "GitHub", "git-company": "ask4abid", - "repository-name": "Collection-application" + "repository-name": "Collection-application", + "entities": ["Employee", "CollectionTracking"] } diff --git a/README.md b/README.md index 392802a..213f11d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CollectionTrackingApplication +# CCWApplication This application was generated using JHipster 6.10.5, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v6.10.5](https://www.jhipster.tech/documentation-archive/v6.10.5). @@ -106,7 +106,7 @@ update src/main/webapp/app/app.module.ts ### Packaging as jar -To build the final jar and optimize the CollectionTrackingApplication application for production, run: +To build the final jar and optimize the CCWApplication application for production, run: ``` diff --git a/angular.json b/angular.json index c47b449..1173567 100644 --- a/angular.json +++ b/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "collection-tracking-application": { + "ccw-application": { "root": "", "sourceRoot": "src/main/webapp", "projectType": "application", @@ -29,7 +29,7 @@ "architect": {} } }, - "defaultProject": "collection-tracking-application", + "defaultProject": "ccw-application", "cli": { "packageManager": "npm" } diff --git a/package.json b/package.json index 19bd7ab..e1b5e43 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "collection-tracking-application", + "name": "ccw-application", "version": "0.0.1-SNAPSHOT", - "description": "Description for CollectionTrackingApplication", + "description": "Description for CCWApplication", "private": true, "license": "UNLICENSED", "cacheDirectories": [ diff --git a/pom.xml b/pom.xml index d4167f2..1968205 100644 --- a/pom.xml +++ b/pom.xml @@ -3,11 +3,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.finca.ccwapp - collection-tracking-application + com.finca.ccw + ccw-application 0.0.1-SNAPSHOT jar - Collection Tracking Application + CCW Application @@ -247,6 +247,12 @@ org.springframework.boot spring-boot-starter-test test + + + org.junit.vintage + junit-vintage-engine + + org.springframework.boot @@ -307,30 +313,11 @@ io.dropwizard.metrics metrics-core - - - io.cucumber - cucumber-junit - test - - - io.cucumber - cucumber-spring - test - spring-boot:run - - - src/test/resources/ - - - src/test/features - - org.apache.maven.plugins @@ -600,7 +587,7 @@ adoptopenjdk:11-jre-hotspot - collectiontrackingapplication:latest + ccwapplication:latest @@ -636,11 +623,11 @@ ${project.basedir}/src/main/resources/config/liquibase/master.xml ${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml org.h2.Driver - jdbc:h2:file:${project.build.directory}/h2db/db/collectiontrackingapplication + jdbc:h2:file:${project.build.directory}/h2db/db/ccwapplication - CollectionTrackingApplication + CCWApplication - hibernate:spring:com.finca.ccwapp.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + hibernate:spring:com.finca.ccw.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy true debug !test diff --git a/sonar-project.properties b/sonar-project.properties index e8ce67d..44be08b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,5 @@ -sonar.projectKey=CollectionTrackingApplication -sonar.projectName=CollectionTrackingApplication generated by jhipster +sonar.projectKey=CCWApplication +sonar.projectName=CCWApplication generated by jhipster sonar.projectVersion=1.0 sonar.sources=src/main/ diff --git a/src/main/docker/app.yml b/src/main/docker/app.yml index e0a0e96..93c85a4 100644 --- a/src/main/docker/app.yml +++ b/src/main/docker/app.yml @@ -1,16 +1,16 @@ version: '2' services: - collectiontrackingapplication-app: - image: collectiontrackingapplication + ccwapplication-app: + image: ccwapplication environment: - _JAVA_OPTIONS=-Xmx512m -Xms256m - SPRING_PROFILES_ACTIVE=prod,swagger - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true - - SPRING_DATASOURCE_URL=jdbc:mysql://collectiontrackingapplication-mysql:3306/collectiontrackingapplication?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true + - SPRING_DATASOURCE_URL=jdbc:mysql://ccwapplication-mysql:3306/ccwapplication?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application ports: - 8080:8080 - collectiontrackingapplication-mysql: + ccwapplication-mysql: extends: file: mysql.yml - service: collectiontrackingapplication-mysql + service: ccwapplication-mysql diff --git a/src/main/docker/monitoring.yml b/src/main/docker/monitoring.yml index 7ce2238..2277ef7 100644 --- a/src/main/docker/monitoring.yml +++ b/src/main/docker/monitoring.yml @@ -1,6 +1,6 @@ version: '2' services: - collectiontrackingapplication-prometheus: + ccwapplication-prometheus: image: prom/prometheus:v2.18.1 volumes: - ./prometheus/:/etc/prometheus/ @@ -11,7 +11,7 @@ services: # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and # grafana/provisioning/datasources/datasource.yml network_mode: 'host' # to test locally running service - collectiontrackingapplication-grafana: + ccwapplication-grafana: image: grafana/grafana:7.0.1 volumes: - ./grafana/provisioning/:/etc/grafana/provisioning/ diff --git a/src/main/docker/mysql.yml b/src/main/docker/mysql.yml index a7e1f47..0d457e7 100644 --- a/src/main/docker/mysql.yml +++ b/src/main/docker/mysql.yml @@ -1,13 +1,13 @@ version: '2' services: - collectiontrackingapplication-mysql: + ccwapplication-mysql: image: mysql:8.0.20 # volumes: - # - ~/volumes/jhipster/CollectionTrackingApplication/mysql/:/var/lib/mysql/ + # - ~/volumes/jhipster/CCWApplication/mysql/:/var/lib/mysql/ environment: - MYSQL_USER=root - MYSQL_ALLOW_EMPTY_PASSWORD=yes - - MYSQL_DATABASE=collectiontrackingapplication + - MYSQL_DATABASE=ccwapplication ports: - 3306:3306 command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp diff --git a/src/main/docker/sonar.yml b/src/main/docker/sonar.yml index b6c0edd..f854e17 100644 --- a/src/main/docker/sonar.yml +++ b/src/main/docker/sonar.yml @@ -1,6 +1,6 @@ version: '2' services: - collectiontrackingapplication-sonar: + ccwapplication-sonar: image: sonarqube:8.3.1-community ports: - 9001:9000 diff --git a/src/main/java/com/finca/ccw/ApplicationWebXml.java b/src/main/java/com/finca/ccw/ApplicationWebXml.java new file mode 100644 index 0000000..8c821d1 --- /dev/null +++ b/src/main/java/com/finca/ccw/ApplicationWebXml.java @@ -0,0 +1,19 @@ +package com.finca.ccw; + +import io.github.jhipster.config.DefaultProfileUtil; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * This is a helper Java class that provides an alternative to creating a {@code web.xml}. + * This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc. + */ +public class ApplicationWebXml extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + // set a default to use when no profile is configured. + DefaultProfileUtil.addDefaultProfile(application.application()); + return application.sources(CcwApplicationApp.class); + } +} diff --git a/src/main/java/com/finca/ccw/CcwApplicationApp.java b/src/main/java/com/finca/ccw/CcwApplicationApp.java new file mode 100644 index 0000000..8963bb9 --- /dev/null +++ b/src/main/java/com/finca/ccw/CcwApplicationApp.java @@ -0,0 +1,104 @@ +package com.finca.ccw; + +import com.finca.ccw.config.ApplicationProperties; +import io.github.jhipster.config.DefaultProfileUtil; +import io.github.jhipster.config.JHipsterConstants; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; +import javax.annotation.PostConstruct; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.env.Environment; + +@SpringBootApplication +@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class }) +public class CcwApplicationApp { + private static final Logger log = LoggerFactory.getLogger(CcwApplicationApp.class); + + private final Environment env; + + public CcwApplicationApp(Environment env) { + this.env = env; + } + + /** + * Initializes CCWApplication. + *

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

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

+ * Properties are configured in the {@code application.yml} file. + * See {@link io.github.jhipster.config.JHipsterProperties} for a good example. + */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties {} diff --git a/src/main/java/com/finca/ccw/config/AsyncConfiguration.java b/src/main/java/com/finca/ccw/config/AsyncConfiguration.java new file mode 100644 index 0000000..0d61bc1 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/AsyncConfiguration.java @@ -0,0 +1,45 @@ +package com.finca.ccw.config; + +import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor; +import java.util.concurrent.Executor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; +import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +@EnableScheduling +public class AsyncConfiguration implements AsyncConfigurer { + private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class); + + private final TaskExecutionProperties taskExecutionProperties; + + public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) { + this.taskExecutionProperties = taskExecutionProperties; + } + + @Override + @Bean(name = "taskExecutor") + public Executor getAsyncExecutor() { + log.debug("Creating Async Task Executor"); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); + executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); + executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); + executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); + return new ExceptionHandlingAsyncTaskExecutor(executor); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new SimpleAsyncUncaughtExceptionHandler(); + } +} diff --git a/src/main/java/com/finca/ccw/config/CacheConfiguration.java b/src/main/java/com/finca/ccw/config/CacheConfiguration.java new file mode 100644 index 0000000..9cc094f --- /dev/null +++ b/src/main/java/com/finca/ccw/config/CacheConfiguration.java @@ -0,0 +1,78 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.config.cache.PrefixedKeyGenerator; +import java.time.Duration; +import org.ehcache.config.builders.*; +import org.ehcache.jsr107.Eh107Configuration; +import org.hibernate.cache.jcache.ConfigSettings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; +import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; +import org.springframework.boot.info.BuildProperties; +import org.springframework.boot.info.GitProperties; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.*; + +@Configuration +@EnableCaching +public class CacheConfiguration { + private GitProperties gitProperties; + private BuildProperties buildProperties; + private final javax.cache.configuration.Configuration jcacheConfiguration; + + public CacheConfiguration(JHipsterProperties jHipsterProperties) { + JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache(); + + jcacheConfiguration = + Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder + .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(ehcache.getMaxEntries())) + .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds()))) + .build() + ); + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) { + return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager); + } + + @Bean + public JCacheManagerCustomizer cacheManagerCustomizer() { + return cm -> { + createCache(cm, com.finca.ccw.repository.UserRepository.USERS_BY_LOGIN_CACHE); + createCache(cm, com.finca.ccw.repository.UserRepository.USERS_BY_EMAIL_CACHE); + createCache(cm, com.finca.ccw.domain.User.class.getName()); + createCache(cm, com.finca.ccw.domain.Authority.class.getName()); + createCache(cm, com.finca.ccw.domain.User.class.getName() + ".authorities"); + createCache(cm, com.finca.ccw.domain.Employee.class.getName()); + createCache(cm, com.finca.ccw.domain.Employee.class.getName() + ".collectionTrackings"); + createCache(cm, com.finca.ccw.domain.CollectionTracking.class.getName()); + // jhipster-needle-ehcache-add-entry + }; + } + + private void createCache(javax.cache.CacheManager cm, String cacheName) { + javax.cache.Cache cache = cm.getCache(cacheName); + if (cache == null) { + cm.createCache(cacheName, jcacheConfiguration); + } + } + + @Autowired(required = false) + public void setGitProperties(GitProperties gitProperties) { + this.gitProperties = gitProperties; + } + + @Autowired(required = false) + public void setBuildProperties(BuildProperties buildProperties) { + this.buildProperties = buildProperties; + } + + @Bean + public KeyGenerator keyGenerator() { + return new PrefixedKeyGenerator(this.gitProperties, this.buildProperties); + } +} diff --git a/src/main/java/com/finca/ccw/config/CloudDatabaseConfiguration.java b/src/main/java/com/finca/ccw/config/CloudDatabaseConfiguration.java new file mode 100644 index 0000000..b992460 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/CloudDatabaseConfiguration.java @@ -0,0 +1,24 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.JHipsterConstants; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.config.java.AbstractCloudConfig; +import org.springframework.context.annotation.*; + +@Configuration +@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD) +public class CloudDatabaseConfiguration extends AbstractCloudConfig { + private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class); + + private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari"; + + @Bean + @ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX) + public DataSource dataSource() { + log.info("Configuring JDBC datasource from a cloud provider"); + return connectionFactory().dataSource(); + } +} diff --git a/src/main/java/com/finca/ccw/config/Constants.java b/src/main/java/com/finca/ccw/config/Constants.java new file mode 100644 index 0000000..71b1f92 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/Constants.java @@ -0,0 +1,15 @@ +package com.finca.ccw.config; + +/** + * Application constants. + */ +public final class Constants { + // Regex for acceptable logins + public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; + + public static final String SYSTEM_ACCOUNT = "system"; + public static final String DEFAULT_LANGUAGE = "en"; + public static final String ANONYMOUS_USER = "anonymoususer"; + + private Constants() {} +} diff --git a/src/main/java/com/finca/ccw/config/DatabaseConfiguration.java b/src/main/java/com/finca/ccw/config/DatabaseConfiguration.java new file mode 100644 index 0000000..8848fec --- /dev/null +++ b/src/main/java/com/finca/ccw/config/DatabaseConfiguration.java @@ -0,0 +1,56 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.h2.H2ConfigurationHelper; +import java.sql.SQLException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableJpaRepositories("com.finca.ccw.repository") +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration { + private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); + + private final Environment env; + + public DatabaseConfiguration(Environment env) { + this.env = env; + } + + /** + * Open the TCP port for the H2 database, so it is available remotely. + * + * @return the H2 database TCP server. + * @throws SQLException if the server failed to start. + */ + @Bean(initMethod = "start", destroyMethod = "stop") + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public Object h2TCPServer() throws SQLException { + String port = getValidPortForH2(); + log.debug("H2 database is available on port {}", port); + return H2ConfigurationHelper.createServer(port); + } + + private String getValidPortForH2() { + int port = Integer.parseInt(env.getProperty("server.port")); + if (port < 10000) { + port = 10000 + port; + } else { + if (port < 63536) { + port = port + 2000; + } else { + port = port - 2000; + } + } + return String.valueOf(port); + } +} diff --git a/src/main/java/com/finca/ccw/config/DateTimeFormatConfiguration.java b/src/main/java/com/finca/ccw/config/DateTimeFormatConfiguration.java new file mode 100644 index 0000000..5311f05 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/DateTimeFormatConfiguration.java @@ -0,0 +1,20 @@ +package com.finca.ccw.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configure the converters to use the ISO format for dates by default. + */ +@Configuration +public class DateTimeFormatConfiguration implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(registry); + } +} diff --git a/src/main/java/com/finca/ccw/config/JacksonConfiguration.java b/src/main/java/com/finca/ccw/config/JacksonConfiguration.java new file mode 100644 index 0000000..c9ec90a --- /dev/null +++ b/src/main/java/com/finca/ccw/config/JacksonConfiguration.java @@ -0,0 +1,51 @@ +package com.finca.ccw.config; + +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.zalando.problem.ProblemModule; +import org.zalando.problem.violations.ConstraintViolationProblemModule; + +@Configuration +public class JacksonConfiguration { + + /** + * Support for Java date and time API. + * @return the corresponding Jackson module. + */ + @Bean + public JavaTimeModule javaTimeModule() { + return new JavaTimeModule(); + } + + @Bean + public Jdk8Module jdk8TimeModule() { + return new Jdk8Module(); + } + + /* + * Support for Hibernate types in Jackson. + */ + @Bean + public Hibernate5Module hibernate5Module() { + return new Hibernate5Module(); + } + + /* + * Module for serialization/deserialization of RFC7807 Problem. + */ + @Bean + public ProblemModule problemModule() { + return new ProblemModule(); + } + + /* + * Module for serialization/deserialization of ConstraintViolationProblem. + */ + @Bean + public ConstraintViolationProblemModule constraintViolationProblemModule() { + return new ConstraintViolationProblemModule(); + } +} diff --git a/src/main/java/com/finca/ccw/config/LiquibaseConfiguration.java b/src/main/java/com/finca/ccw/config/LiquibaseConfiguration.java new file mode 100644 index 0000000..355c664 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/LiquibaseConfiguration.java @@ -0,0 +1,68 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.liquibase.SpringLiquibaseUtil; +import java.util.concurrent.Executor; +import javax.sql.DataSource; +import liquibase.integration.spring.SpringLiquibase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; + +@Configuration +public class LiquibaseConfiguration { + private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class); + + private final Environment env; + + public LiquibaseConfiguration(Environment env) { + this.env = env; + } + + @Bean + public SpringLiquibase liquibase( + @Qualifier("taskExecutor") Executor executor, + @LiquibaseDataSource ObjectProvider liquibaseDataSource, + LiquibaseProperties liquibaseProperties, + ObjectProvider dataSource, + DataSourceProperties dataSourceProperties + ) { + // If you don't want Liquibase to start asynchronously, substitute by this: + // SpringLiquibase liquibase = SpringLiquibaseUtil.createSpringLiquibase(liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); + SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase( + this.env, + executor, + liquibaseDataSource.getIfAvailable(), + liquibaseProperties, + dataSource.getIfUnique(), + dataSourceProperties + ); + liquibase.setChangeLog("classpath:config/liquibase/master.xml"); + liquibase.setContexts(liquibaseProperties.getContexts()); + liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); + liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema()); + liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace()); + liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable()); + liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable()); + liquibase.setDropFirst(liquibaseProperties.isDropFirst()); + liquibase.setLabels(liquibaseProperties.getLabels()); + liquibase.setChangeLogParameters(liquibaseProperties.getParameters()); + liquibase.setRollbackFile(liquibaseProperties.getRollbackFile()); + liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate()); + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE))) { + liquibase.setShouldRun(false); + } else { + liquibase.setShouldRun(liquibaseProperties.isEnabled()); + log.debug("Configuring Liquibase"); + } + return liquibase; + } +} diff --git a/src/main/java/com/finca/ccw/config/LocaleConfiguration.java b/src/main/java/com/finca/ccw/config/LocaleConfiguration.java new file mode 100644 index 0000000..1919b3c --- /dev/null +++ b/src/main/java/com/finca/ccw/config/LocaleConfiguration.java @@ -0,0 +1,26 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.locale.AngularCookieLocaleResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.*; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; + +@Configuration +public class LocaleConfiguration implements WebMvcConfigurer { + + @Bean + public LocaleResolver localeResolver() { + AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver(); + cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY"); + return cookieLocaleResolver; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + localeChangeInterceptor.setParamName("language"); + registry.addInterceptor(localeChangeInterceptor); + } +} diff --git a/src/main/java/com/finca/ccw/config/LoggingAspectConfiguration.java b/src/main/java/com/finca/ccw/config/LoggingAspectConfiguration.java new file mode 100644 index 0000000..1e2f936 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/LoggingAspectConfiguration.java @@ -0,0 +1,17 @@ +package com.finca.ccw.config; + +import com.finca.ccw.aop.logging.LoggingAspect; +import io.github.jhipster.config.JHipsterConstants; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingAspectConfiguration { + + @Bean + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public LoggingAspect loggingAspect(Environment env) { + return new LoggingAspect(env); + } +} diff --git a/src/main/java/com/finca/ccw/config/LoggingConfiguration.java b/src/main/java/com/finca/ccw/config/LoggingConfiguration.java new file mode 100644 index 0000000..41baf9f --- /dev/null +++ b/src/main/java/com/finca/ccw/config/LoggingConfiguration.java @@ -0,0 +1,51 @@ +package com.finca.ccw.config; + +import static io.github.jhipster.config.logging.LoggingUtils.*; + +import ch.qos.logback.classic.LoggerContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.jhipster.config.JHipsterProperties; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +/* + * Configures the console and Logstash log appenders from the app properties + */ +@Configuration +public class LoggingConfiguration { + + public LoggingConfiguration( + @Value("${spring.application.name}") String appName, + @Value("${server.port}") String serverPort, + JHipsterProperties jHipsterProperties, + ObjectMapper mapper + ) + throws JsonProcessingException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + Map map = new HashMap<>(); + map.put("app_name", appName); + map.put("app_port", serverPort); + String customFields = mapper.writeValueAsString(map); + + JHipsterProperties.Logging loggingProperties = jHipsterProperties.getLogging(); + JHipsterProperties.Logging.Logstash logstashProperties = loggingProperties.getLogstash(); + + if (loggingProperties.isUseJsonFormat()) { + addJsonConsoleAppender(context, customFields); + } + if (logstashProperties.isEnabled()) { + addLogstashTcpSocketAppender(context, customFields, logstashProperties); + } + if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) { + addContextListener(context, customFields, loggingProperties); + } + if (jHipsterProperties.getMetrics().getLogs().isEnabled()) { + setMetricsMarkerLogbackFilter(context, loggingProperties.isUseJsonFormat()); + } + } +} diff --git a/src/main/java/com/finca/ccw/config/SecurityConfiguration.java b/src/main/java/com/finca/ccw/config/SecurityConfiguration.java new file mode 100644 index 0000000..27cd3b3 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/SecurityConfiguration.java @@ -0,0 +1,99 @@ +package com.finca.ccw.config; + +import com.finca.ccw.security.*; +import com.finca.ccw.security.jwt.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.web.filter.CorsFilter; +import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; + +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Import(SecurityProblemSupport.class) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + private final TokenProvider tokenProvider; + + private final CorsFilter corsFilter; + private final SecurityProblemSupport problemSupport; + + public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) { + this.tokenProvider = tokenProvider; + this.corsFilter = corsFilter; + this.problemSupport = problemSupport; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + public void configure(WebSecurity web) { + web + .ignoring() + .antMatchers(HttpMethod.OPTIONS, "/**") + .antMatchers("/app/**/*.{js,html}") + .antMatchers("/i18n/**") + .antMatchers("/content/**") + .antMatchers("/h2-console/**") + .antMatchers("/swagger-ui/index.html") + .antMatchers("/test/**"); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling() + .authenticationEntryPoint(problemSupport) + .accessDeniedHandler(problemSupport) + .and() + .headers() + .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:") + .and() + .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) + .and() + .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'") + .and() + .frameOptions() + .deny() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/authenticate").permitAll() + .antMatchers("/api/register").permitAll() + .antMatchers("/api/activate").permitAll() + .antMatchers("/api/account/reset-password/init").permitAll() + .antMatchers("/api/account/reset-password/finish").permitAll() + .antMatchers("/api/**").authenticated() + .antMatchers("/management/health").permitAll() + .antMatchers("/management/info").permitAll() + .antMatchers("/management/prometheus").permitAll() + .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) + .and() + .httpBasic() + .and() + .apply(securityConfigurerAdapter()); + // @formatter:on + } + + private JWTConfigurer securityConfigurerAdapter() { + return new JWTConfigurer(tokenProvider); + } +} diff --git a/src/main/java/com/finca/ccw/config/StaticResourcesWebConfiguration.java b/src/main/java/com/finca/ccw/config/StaticResourcesWebConfiguration.java new file mode 100644 index 0000000..1f0ac1a --- /dev/null +++ b/src/main/java/com/finca/ccw/config/StaticResourcesWebConfiguration.java @@ -0,0 +1,50 @@ +package com.finca.ccw.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.JHipsterProperties; +import java.util.concurrent.TimeUnit; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.CacheControl; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION }) +public class StaticResourcesWebConfiguration implements WebMvcConfigurer { + protected static final String[] RESOURCE_LOCATIONS = new String[] { + "classpath:/static/app/", + "classpath:/static/content/", + "classpath:/static/i18n/", + }; + protected static final String[] RESOURCE_PATHS = new String[] { "/app/*", "/content/*", "/i18n/*" }; + + private final JHipsterProperties jhipsterProperties; + + public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) { + this.jhipsterProperties = jHipsterProperties; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry); + initializeResourceHandler(resourceHandlerRegistration); + } + + protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) { + return registry.addResourceHandler(RESOURCE_PATHS); + } + + protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) { + resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl()); + } + + protected CacheControl getCacheControl() { + return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic(); + } + + private int getJHipsterHttpCacheProperty() { + return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays(); + } +} diff --git a/src/main/java/com/finca/ccw/config/WebConfigurer.java b/src/main/java/com/finca/ccw/config/WebConfigurer.java new file mode 100644 index 0000000..d23cb61 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/WebConfigurer.java @@ -0,0 +1,130 @@ +package com.finca.ccw.config; + +import static java.net.URLDecoder.decode; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.config.h2.H2ConfigurationHelper; +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.*; +import javax.servlet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.server.*; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.http.MediaType; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * Configuration of web application with Servlet 3.0 APIs. + */ +@Configuration +public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer { + private final Logger log = LoggerFactory.getLogger(WebConfigurer.class); + + private final Environment env; + + private final JHipsterProperties jHipsterProperties; + + public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) { + this.env = env; + this.jHipsterProperties = jHipsterProperties; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (env.getActiveProfiles().length != 0) { + log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); + } + + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { + initH2Console(servletContext); + } + log.info("Web application fully configured"); + } + + /** + * Customize the Servlet engine: Mime types, the document root, the cache. + */ + @Override + public void customize(WebServerFactory server) { + setMimeMappings(server); + // When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets. + setLocationForStaticAssets(server); + } + + private void setMimeMappings(WebServerFactory server) { + if (server instanceof ConfigurableServletWebServerFactory) { + MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); + // IE issue, see https://github.com/jhipster/generator-jhipster/pull/711 + mappings.add("html", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase()); + // CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64 + mappings.add("json", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase()); + ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server; + servletWebServer.setMimeMappings(mappings); + } + } + + private void setLocationForStaticAssets(WebServerFactory server) { + if (server instanceof ConfigurableServletWebServerFactory) { + ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server; + File root; + String prefixPath = resolvePathPrefix(); + root = new File(prefixPath + "target/classes/static/"); + if (root.exists() && root.isDirectory()) { + servletWebServer.setDocumentRoot(root); + } + } + } + + /** + * Resolve path prefix to static resources. + */ + private String resolvePathPrefix() { + String fullExecutablePath; + try { + fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + /* try without decoding if this ever happens */ + fullExecutablePath = this.getClass().getResource("").getPath(); + } + String rootPath = Paths.get(".").toUri().normalize().getPath(); + String extractedPath = fullExecutablePath.replace(rootPath, ""); + int extractionEndIndex = extractedPath.indexOf("target/"); + if (extractionEndIndex <= 0) { + return ""; + } + return extractedPath.substring(0, extractionEndIndex); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = jHipsterProperties.getCors(); + if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { + log.debug("Registering CORS filter"); + source.registerCorsConfiguration("/api/**", config); + source.registerCorsConfiguration("/management/**", config); + source.registerCorsConfiguration("/v2/api-docs", config); + } + return new CorsFilter(source); + } + + /** + * Initializes H2 console. + */ + private void initH2Console(ServletContext servletContext) { + log.debug("Initialize H2 console"); + H2ConfigurationHelper.initH2Console(servletContext); + } +} diff --git a/src/main/java/com/finca/ccw/config/audit/AuditEventConverter.java b/src/main/java/com/finca/ccw/config/audit/AuditEventConverter.java new file mode 100644 index 0000000..ef85a24 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/audit/AuditEventConverter.java @@ -0,0 +1,88 @@ +package com.finca.ccw.config.audit; + +import com.finca.ccw.domain.PersistentAuditEvent; +import java.util.*; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +@Component +public class AuditEventConverter { + + /** + * Convert a list of {@link PersistentAuditEvent}s to a list of {@link AuditEvent}s. + * + * @param persistentAuditEvents the list to convert. + * @return the converted list. + */ + public List convertToAuditEvent(Iterable persistentAuditEvents) { + if (persistentAuditEvents == null) { + return Collections.emptyList(); + } + List auditEvents = new ArrayList<>(); + for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) { + auditEvents.add(convertToAuditEvent(persistentAuditEvent)); + } + return auditEvents; + } + + /** + * Convert a {@link PersistentAuditEvent} to an {@link AuditEvent}. + * + * @param persistentAuditEvent the event to convert. + * @return the converted list. + */ + public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) { + if (persistentAuditEvent == null) { + return null; + } + return new AuditEvent( + persistentAuditEvent.getAuditEventDate(), + persistentAuditEvent.getPrincipal(), + persistentAuditEvent.getAuditEventType(), + convertDataToObjects(persistentAuditEvent.getData()) + ); + } + + /** + * Internal conversion. This is needed to support the current SpringBoot actuator {@code AuditEventRepository} interface. + * + * @param data the data to convert. + * @return a map of {@link String}, {@link Object}. + */ + public Map convertDataToObjects(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + results.put(entry.getKey(), entry.getValue()); + } + } + return results; + } + + /** + * Internal conversion. This method will allow to save additional data. + * By default, it will save the object as string. + * + * @param data the data to convert. + * @return a map of {@link String}, {@link String}. + */ + public Map convertDataToStrings(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + // Extract the data that will be saved. + if (entry.getValue() instanceof WebAuthenticationDetails) { + WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue(); + results.put("remoteAddress", authenticationDetails.getRemoteAddress()); + results.put("sessionId", authenticationDetails.getSessionId()); + } else { + results.put(entry.getKey(), Objects.toString(entry.getValue())); + } + } + } + return results; + } +} diff --git a/src/main/java/com/finca/ccw/config/audit/package-info.java b/src/main/java/com/finca/ccw/config/audit/package-info.java new file mode 100644 index 0000000..5372f5d --- /dev/null +++ b/src/main/java/com/finca/ccw/config/audit/package-info.java @@ -0,0 +1,4 @@ +/** + * Audit specific code. + */ +package com.finca.ccw.config.audit; diff --git a/src/main/java/com/finca/ccw/config/package-info.java b/src/main/java/com/finca/ccw/config/package-info.java new file mode 100644 index 0000000..d698ce6 --- /dev/null +++ b/src/main/java/com/finca/ccw/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Framework configuration files. + */ +package com.finca.ccw.config; diff --git a/src/main/java/com/finca/ccw/domain/AbstractAuditingEntity.java b/src/main/java/com/finca/ccw/domain/AbstractAuditingEntity.java new file mode 100644 index 0000000..924cfad --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/AbstractAuditingEntity.java @@ -0,0 +1,75 @@ +package com.finca.ccw.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.time.Instant; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +/** + * Base abstract class for entities which will hold definitions for created, last modified, created by, + * last modified by attributes. + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractAuditingEntity implements Serializable { + private static final long serialVersionUID = 1L; + + @CreatedBy + @Column(name = "created_by", nullable = false, length = 50, updatable = false) + @JsonIgnore + private String createdBy; + + @CreatedDate + @Column(name = "created_date", updatable = false) + @JsonIgnore + private Instant createdDate = Instant.now(); + + @LastModifiedBy + @Column(name = "last_modified_by", length = 50) + @JsonIgnore + private String lastModifiedBy; + + @LastModifiedDate + @Column(name = "last_modified_date") + @JsonIgnore + private Instant lastModifiedDate = Instant.now(); + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } +} diff --git a/src/main/java/com/finca/ccw/domain/Authority.java b/src/main/java/com/finca/ccw/domain/Authority.java new file mode 100644 index 0000000..718e32a --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/Authority.java @@ -0,0 +1,60 @@ +package com.finca.ccw.domain; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * An authority (a security role) used by Spring Security. + */ +@Entity +@Table(name = "jhi_authority") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class Authority implements Serializable { + private static final long serialVersionUID = 1L; + + @NotNull + @Size(max = 50) + @Id + @Column(length = 50) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Authority)) { + return false; + } + return Objects.equals(name, ((Authority) o).name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + // prettier-ignore + @Override + public String toString() { + return "Authority{" + + "name='" + name + '\'' + + "}"; + } +} diff --git a/src/main/java/com/finca/ccw/domain/CollectionTracking.java b/src/main/java/com/finca/ccw/domain/CollectionTracking.java new file mode 100644 index 0000000..5d88dd7 --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/CollectionTracking.java @@ -0,0 +1,382 @@ +package com.finca.ccw.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import javax.persistence.*; +import javax.validation.constraints.*; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * A CollectionTracking. + */ +@Entity +@Table(name = "collection_tracking") +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class CollectionTracking implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Column(name = "employee_id", nullable = false) + private String employeeID; + + @NotNull + @Column(name = "unit_id", nullable = false) + private String unitID; + + @NotNull + @Column(name = "employee_name", nullable = false) + private String employeeName; + + @NotNull + @Column(name = "business_proposal", nullable = false) + private String businessProposal; + + @NotNull + @Column(name = "sub_proposal", nullable = false) + private String subProposal; + + @NotNull + @Column(name = "mobile_no", nullable = false) + private String mobileNo; + + @NotNull + @Column(name = "relation_id", nullable = false) + private String relationId; + + @NotNull + @Column(name = "account_no", nullable = false) + private String accountNo; + + @NotNull + @Column(name = "account_title", nullable = false) + private String accountTitle; + + @NotNull + @Column(name = "no_of_visits", nullable = false) + private Integer noOfVisits; + + @NotNull + @Column(name = "os_amount", precision = 21, scale = 2, nullable = false) + private BigDecimal osAmount; + + @NotNull + @Column(name = "os_profit", precision = 21, scale = 2, nullable = false) + private BigDecimal osProfit; + + @NotNull + @Column(name = "od_days", nullable = false) + private String odDays; + + @NotNull + @Column(name = "loan_officer", nullable = false) + private String loanOfficer; + + @NotNull + @Column(name = "visited_by", nullable = false) + private String visitedBy; + + @NotNull + @Column(name = "ppt_date", nullable = false) + private LocalDate pptDate; + + @NotNull + @Column(name = "remakrs", nullable = false) + private String remakrs; + + @ManyToOne + @JsonIgnoreProperties(value = "collectionTrackings", allowSetters = true) + private Employee employee; + + // jhipster-needle-entity-add-field - JHipster will add fields here + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmployeeID() { + return employeeID; + } + + public CollectionTracking employeeID(String employeeID) { + this.employeeID = employeeID; + return this; + } + + public void setEmployeeID(String employeeID) { + this.employeeID = employeeID; + } + + public String getUnitID() { + return unitID; + } + + public CollectionTracking unitID(String unitID) { + this.unitID = unitID; + return this; + } + + public void setUnitID(String unitID) { + this.unitID = unitID; + } + + public String getEmployeeName() { + return employeeName; + } + + public CollectionTracking employeeName(String employeeName) { + this.employeeName = employeeName; + return this; + } + + public void setEmployeeName(String employeeName) { + this.employeeName = employeeName; + } + + public String getBusinessProposal() { + return businessProposal; + } + + public CollectionTracking businessProposal(String businessProposal) { + this.businessProposal = businessProposal; + return this; + } + + public void setBusinessProposal(String businessProposal) { + this.businessProposal = businessProposal; + } + + public String getSubProposal() { + return subProposal; + } + + public CollectionTracking subProposal(String subProposal) { + this.subProposal = subProposal; + return this; + } + + public void setSubProposal(String subProposal) { + this.subProposal = subProposal; + } + + public String getMobileNo() { + return mobileNo; + } + + public CollectionTracking mobileNo(String mobileNo) { + this.mobileNo = mobileNo; + return this; + } + + public void setMobileNo(String mobileNo) { + this.mobileNo = mobileNo; + } + + public String getRelationId() { + return relationId; + } + + public CollectionTracking relationId(String relationId) { + this.relationId = relationId; + return this; + } + + public void setRelationId(String relationId) { + this.relationId = relationId; + } + + public String getAccountNo() { + return accountNo; + } + + public CollectionTracking accountNo(String accountNo) { + this.accountNo = accountNo; + return this; + } + + public void setAccountNo(String accountNo) { + this.accountNo = accountNo; + } + + public String getAccountTitle() { + return accountTitle; + } + + public CollectionTracking accountTitle(String accountTitle) { + this.accountTitle = accountTitle; + return this; + } + + public void setAccountTitle(String accountTitle) { + this.accountTitle = accountTitle; + } + + public Integer getNoOfVisits() { + return noOfVisits; + } + + public CollectionTracking noOfVisits(Integer noOfVisits) { + this.noOfVisits = noOfVisits; + return this; + } + + public void setNoOfVisits(Integer noOfVisits) { + this.noOfVisits = noOfVisits; + } + + public BigDecimal getOsAmount() { + return osAmount; + } + + public CollectionTracking osAmount(BigDecimal osAmount) { + this.osAmount = osAmount; + return this; + } + + public void setOsAmount(BigDecimal osAmount) { + this.osAmount = osAmount; + } + + public BigDecimal getOsProfit() { + return osProfit; + } + + public CollectionTracking osProfit(BigDecimal osProfit) { + this.osProfit = osProfit; + return this; + } + + public void setOsProfit(BigDecimal osProfit) { + this.osProfit = osProfit; + } + + public String getOdDays() { + return odDays; + } + + public CollectionTracking odDays(String odDays) { + this.odDays = odDays; + return this; + } + + public void setOdDays(String odDays) { + this.odDays = odDays; + } + + public String getLoanOfficer() { + return loanOfficer; + } + + public CollectionTracking loanOfficer(String loanOfficer) { + this.loanOfficer = loanOfficer; + return this; + } + + public void setLoanOfficer(String loanOfficer) { + this.loanOfficer = loanOfficer; + } + + public String getVisitedBy() { + return visitedBy; + } + + public CollectionTracking visitedBy(String visitedBy) { + this.visitedBy = visitedBy; + return this; + } + + public void setVisitedBy(String visitedBy) { + this.visitedBy = visitedBy; + } + + public LocalDate getPptDate() { + return pptDate; + } + + public CollectionTracking pptDate(LocalDate pptDate) { + this.pptDate = pptDate; + return this; + } + + public void setPptDate(LocalDate pptDate) { + this.pptDate = pptDate; + } + + public String getRemakrs() { + return remakrs; + } + + public CollectionTracking remakrs(String remakrs) { + this.remakrs = remakrs; + return this; + } + + public void setRemakrs(String remakrs) { + this.remakrs = remakrs; + } + + public Employee getEmployee() { + return employee; + } + + public CollectionTracking employee(Employee employee) { + this.employee = employee; + return this; + } + + public void setEmployee(Employee employee) { + this.employee = employee; + } + + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CollectionTracking)) { + return false; + } + return id != null && id.equals(((CollectionTracking) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + // prettier-ignore + @Override + public String toString() { + return "CollectionTracking{" + + "id=" + getId() + + ", employeeID='" + getEmployeeID() + "'" + + ", unitID='" + getUnitID() + "'" + + ", employeeName='" + getEmployeeName() + "'" + + ", businessProposal='" + getBusinessProposal() + "'" + + ", subProposal='" + getSubProposal() + "'" + + ", mobileNo='" + getMobileNo() + "'" + + ", relationId='" + getRelationId() + "'" + + ", accountNo='" + getAccountNo() + "'" + + ", accountTitle='" + getAccountTitle() + "'" + + ", noOfVisits=" + getNoOfVisits() + + ", osAmount=" + getOsAmount() + + ", osProfit=" + getOsProfit() + + ", odDays='" + getOdDays() + "'" + + ", loanOfficer='" + getLoanOfficer() + "'" + + ", visitedBy='" + getVisitedBy() + "'" + + ", pptDate='" + getPptDate() + "'" + + ", remakrs='" + getRemakrs() + "'" + + "}"; + } +} diff --git a/src/main/java/com/finca/ccw/domain/Employee.java b/src/main/java/com/finca/ccw/domain/Employee.java new file mode 100644 index 0000000..77dfa67 --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/Employee.java @@ -0,0 +1,194 @@ +package com.finca.ccw.domain; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.*; +import javax.validation.constraints.*; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * A Employee. + */ +@Entity +@Table(name = "employee") +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +public class Employee implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Column(name = "employee_id", nullable = false) + private String employeeID; + + @NotNull + @Column(name = "unit_id", nullable = false) + private String unitID; + + @NotNull + @Column(name = "first_name", nullable = false) + private String firstName; + + @NotNull + @Column(name = "last_name", nullable = false) + private String lastName; + + @NotNull + @Column(name = "email_address", nullable = false) + private String emailAddress; + + @Column(name = "login") + private String login; + + @OneToMany(mappedBy = "employee") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private Set collectionTrackings = new HashSet<>(); + + // jhipster-needle-entity-add-field - JHipster will add fields here + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmployeeID() { + return employeeID; + } + + public Employee employeeID(String employeeID) { + this.employeeID = employeeID; + return this; + } + + public void setEmployeeID(String employeeID) { + this.employeeID = employeeID; + } + + public String getUnitID() { + return unitID; + } + + public Employee unitID(String unitID) { + this.unitID = unitID; + return this; + } + + public void setUnitID(String unitID) { + this.unitID = unitID; + } + + public String getFirstName() { + return firstName; + } + + public Employee firstName(String firstName) { + this.firstName = firstName; + return this; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public Employee lastName(String lastName) { + this.lastName = lastName; + return this; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmailAddress() { + return emailAddress; + } + + public Employee emailAddress(String emailAddress) { + this.emailAddress = emailAddress; + return this; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getLogin() { + return login; + } + + public Employee login(String login) { + this.login = login; + return this; + } + + public void setLogin(String login) { + this.login = login; + } + + public Set getCollectionTrackings() { + return collectionTrackings; + } + + public Employee collectionTrackings(Set collectionTrackings) { + this.collectionTrackings = collectionTrackings; + return this; + } + + public Employee addCollectionTracking(CollectionTracking collectionTracking) { + this.collectionTrackings.add(collectionTracking); + collectionTracking.setEmployee(this); + return this; + } + + public Employee removeCollectionTracking(CollectionTracking collectionTracking) { + this.collectionTrackings.remove(collectionTracking); + collectionTracking.setEmployee(null); + return this; + } + + public void setCollectionTrackings(Set collectionTrackings) { + this.collectionTrackings = collectionTrackings; + } + + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Employee)) { + return false; + } + return id != null && id.equals(((Employee) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + // prettier-ignore + @Override + public String toString() { + return "Employee{" + + "id=" + getId() + + ", employeeID='" + getEmployeeID() + "'" + + ", unitID='" + getUnitID() + "'" + + ", firstName='" + getFirstName() + "'" + + ", lastName='" + getLastName() + "'" + + ", emailAddress='" + getEmailAddress() + "'" + + ", login='" + getLogin() + "'" + + "}"; + } +} diff --git a/src/main/java/com/finca/ccw/domain/PersistentAuditEvent.java b/src/main/java/com/finca/ccw/domain/PersistentAuditEvent.java new file mode 100644 index 0000000..44354de --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/PersistentAuditEvent.java @@ -0,0 +1,106 @@ +package com.finca.ccw.domain; + +import java.io.Serializable; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.*; +import javax.validation.constraints.NotNull; + +/** + * Persist AuditEvent managed by the Spring Boot actuator. + * + * @see org.springframework.boot.actuate.audit.AuditEvent + */ +@Entity +@Table(name = "jhi_persistent_audit_event") +public class PersistentAuditEvent implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "event_id") + private Long id; + + @NotNull + @Column(nullable = false) + private String principal; + + @Column(name = "event_date") + private Instant auditEventDate; + + @Column(name = "event_type") + private String auditEventType; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value") + @CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns = @JoinColumn(name = "event_id")) + private Map data = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPrincipal() { + return principal; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public Instant getAuditEventDate() { + return auditEventDate; + } + + public void setAuditEventDate(Instant auditEventDate) { + this.auditEventDate = auditEventDate; + } + + public String getAuditEventType() { + return auditEventType; + } + + public void setAuditEventType(String auditEventType) { + this.auditEventType = auditEventType; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PersistentAuditEvent)) { + return false; + } + return id != null && id.equals(((PersistentAuditEvent) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + // prettier-ignore + @Override + public String toString() { + return "PersistentAuditEvent{" + + "principal='" + principal + '\'' + + ", auditEventDate=" + auditEventDate + + ", auditEventType='" + auditEventType + '\'' + + '}'; + } +} diff --git a/src/main/java/com/finca/ccw/domain/User.java b/src/main/java/com/finca/ccw/domain/User.java new file mode 100644 index 0000000..7c4827c --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/User.java @@ -0,0 +1,229 @@ +package com.finca.ccw.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.finca.ccw.config.Constants; +import java.io.Serializable; +import java.time.Instant; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * A user. + */ +@Entity +@Table(name = "jhi_user") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class User extends AbstractAuditingEntity implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @JsonIgnore + @NotNull + @Size(min = 60, max = 60) + @Column(name = "password_hash", length = 60, nullable = false) + private String password; + + @Size(max = 50) + @Column(name = "first_name", length = 50) + private String firstName; + + @Size(max = 50) + @Column(name = "last_name", length = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + @Column(length = 254, unique = true) + private String email; + + @NotNull + @Column(nullable = false) + private boolean activated = false; + + @Size(min = 2, max = 10) + @Column(name = "lang_key", length = 10) + private String langKey; + + @Size(max = 256) + @Column(name = "image_url", length = 256) + private String imageUrl; + + @Size(max = 20) + @Column(name = "activation_key", length = 20) + @JsonIgnore + private String activationKey; + + @Size(max = 20) + @Column(name = "reset_key", length = 20) + @JsonIgnore + private String resetKey; + + @Column(name = "reset_date") + private Instant resetDate = null; + + @JsonIgnore + @ManyToMany + @JoinTable( + name = "jhi_user_authority", + joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") } + ) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @BatchSize(size = 20) + private Set authorities = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + // Lowercase the login before saving it in database + public void setLogin(String login) { + this.login = StringUtils.lowerCase(login, Locale.ENGLISH); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean getActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getActivationKey() { + return activationKey; + } + + public void setActivationKey(String activationKey) { + this.activationKey = activationKey; + } + + public String getResetKey() { + return resetKey; + } + + public void setResetKey(String resetKey) { + this.resetKey = resetKey; + } + + public Instant getResetDate() { + return resetDate; + } + + public void setResetDate(Instant resetDate) { + this.resetDate = resetDate; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof User)) { + return false; + } + return id != null && id.equals(((User) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + // prettier-ignore + @Override + public String toString() { + return "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}"; + } +} diff --git a/src/main/java/com/finca/ccw/domain/package-info.java b/src/main/java/com/finca/ccw/domain/package-info.java new file mode 100644 index 0000000..a8c4f60 --- /dev/null +++ b/src/main/java/com/finca/ccw/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * JPA domain objects. + */ +package com.finca.ccw.domain; diff --git a/src/main/java/com/finca/ccw/repository/AuthorityRepository.java b/src/main/java/com/finca/ccw/repository/AuthorityRepository.java new file mode 100644 index 0000000..b244771 --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/AuthorityRepository.java @@ -0,0 +1,9 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.domain.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the {@link Authority} entity. + */ +public interface AuthorityRepository extends JpaRepository {} diff --git a/src/main/java/com/finca/ccw/repository/CollectionTrackingRepository.java b/src/main/java/com/finca/ccw/repository/CollectionTrackingRepository.java new file mode 100644 index 0000000..1338457 --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/CollectionTrackingRepository.java @@ -0,0 +1,12 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.domain.CollectionTracking; +import org.springframework.data.jpa.repository.*; +import org.springframework.stereotype.Repository; + +/** + * Spring Data repository for the CollectionTracking entity. + */ +@SuppressWarnings("unused") +@Repository +public interface CollectionTrackingRepository extends JpaRepository {} diff --git a/src/main/java/com/finca/ccw/repository/CustomAuditEventRepository.java b/src/main/java/com/finca/ccw/repository/CustomAuditEventRepository.java new file mode 100644 index 0000000..4dd3262 --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/CustomAuditEventRepository.java @@ -0,0 +1,92 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.config.Constants; +import com.finca.ccw.config.audit.AuditEventConverter; +import com.finca.ccw.domain.PersistentAuditEvent; +import java.time.Instant; +import java.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * An implementation of Spring Boot's {@link AuditEventRepository}. + */ +@Repository +public class CustomAuditEventRepository implements AuditEventRepository { + private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE"; + + /** + * Should be the same as in Liquibase migration. + */ + protected static final int EVENT_DATA_COLUMN_MAX_LENGTH = 255; + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + public CustomAuditEventRepository( + PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter + ) { + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + } + + @Override + public List find(String principal, Instant after, String type) { + Iterable persistentAuditEvents = persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType( + principal, + after, + type + ); + return auditEventConverter.convertToAuditEvent(persistentAuditEvents); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void add(AuditEvent event) { + if (!AUTHORIZATION_FAILURE.equals(event.getType()) && !Constants.ANONYMOUS_USER.equals(event.getPrincipal())) { + PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent(); + persistentAuditEvent.setPrincipal(event.getPrincipal()); + persistentAuditEvent.setAuditEventType(event.getType()); + persistentAuditEvent.setAuditEventDate(event.getTimestamp()); + Map eventData = auditEventConverter.convertDataToStrings(event.getData()); + persistentAuditEvent.setData(truncate(eventData)); + persistenceAuditEventRepository.save(persistentAuditEvent); + } + } + + /** + * Truncate event data that might exceed column length. + */ + private Map truncate(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + String value = entry.getValue(); + if (value != null) { + int length = value.length(); + if (length > EVENT_DATA_COLUMN_MAX_LENGTH) { + value = value.substring(0, EVENT_DATA_COLUMN_MAX_LENGTH); + log.warn( + "Event data for {} too long ({}) has been truncated to {}. Consider increasing column width.", + entry.getKey(), + length, + EVENT_DATA_COLUMN_MAX_LENGTH + ); + } + } + results.put(entry.getKey(), value); + } + } + return results; + } +} diff --git a/src/main/java/com/finca/ccw/repository/EmployeeRepository.java b/src/main/java/com/finca/ccw/repository/EmployeeRepository.java new file mode 100644 index 0000000..2ef2141 --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/EmployeeRepository.java @@ -0,0 +1,12 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.domain.Employee; +import org.springframework.data.jpa.repository.*; +import org.springframework.stereotype.Repository; + +/** + * Spring Data repository for the Employee entity. + */ +@SuppressWarnings("unused") +@Repository +public interface EmployeeRepository extends JpaRepository {} diff --git a/src/main/java/com/finca/ccw/repository/PersistenceAuditEventRepository.java b/src/main/java/com/finca/ccw/repository/PersistenceAuditEventRepository.java new file mode 100644 index 0000000..8a09986 --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/PersistenceAuditEventRepository.java @@ -0,0 +1,21 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.domain.PersistentAuditEvent; +import java.time.Instant; +import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the {@link PersistentAuditEvent} entity. + */ +public interface PersistenceAuditEventRepository extends JpaRepository { + List findByPrincipal(String principal); + + List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type); + + Page findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable); + + List findByAuditEventDateBefore(Instant before); +} diff --git a/src/main/java/com/finca/ccw/repository/UserRepository.java b/src/main/java/com/finca/ccw/repository/UserRepository.java new file mode 100644 index 0000000..2d8337e --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/UserRepository.java @@ -0,0 +1,42 @@ +package com.finca.ccw.repository; + +import com.finca.ccw.domain.User; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Spring Data JPA repository for the {@link User} entity. + */ +@Repository +public interface UserRepository extends JpaRepository { + String USERS_BY_LOGIN_CACHE = "usersByLogin"; + + String USERS_BY_EMAIL_CACHE = "usersByEmail"; + + Optional findOneByActivationKey(String activationKey); + + List findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime); + + Optional findOneByResetKey(String resetKey); + + Optional findOneByEmailIgnoreCase(String email); + + Optional findOneByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_LOGIN_CACHE) + Optional findOneWithAuthoritiesByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_EMAIL_CACHE) + Optional findOneWithAuthoritiesByEmailIgnoreCase(String email); + + Page findAllByLoginNot(Pageable pageable, String login); +} diff --git a/src/main/java/com/finca/ccw/repository/package-info.java b/src/main/java/com/finca/ccw/repository/package-info.java new file mode 100644 index 0000000..eaab41a --- /dev/null +++ b/src/main/java/com/finca/ccw/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Data JPA repositories. + */ +package com.finca.ccw.repository; diff --git a/src/main/java/com/finca/ccw/security/AuthoritiesConstants.java b/src/main/java/com/finca/ccw/security/AuthoritiesConstants.java new file mode 100644 index 0000000..0667649 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/AuthoritiesConstants.java @@ -0,0 +1,14 @@ +package com.finca.ccw.security; + +/** + * Constants for Spring Security authorities. + */ +public final class AuthoritiesConstants { + public static final String ADMIN = "ROLE_ADMIN"; + + public static final String USER = "ROLE_USER"; + + public static final String ANONYMOUS = "ROLE_ANONYMOUS"; + + private AuthoritiesConstants() {} +} diff --git a/src/main/java/com/finca/ccw/security/DomainUserDetailsService.java b/src/main/java/com/finca/ccw/security/DomainUserDetailsService.java new file mode 100644 index 0000000..14dd537 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/DomainUserDetailsService.java @@ -0,0 +1,61 @@ +package com.finca.ccw.security; + +import com.finca.ccw.domain.User; +import com.finca.ccw.repository.UserRepository; +import java.util.*; +import java.util.stream.Collectors; +import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Authenticate a user from the database. + */ +@Component("userDetailsService") +public class DomainUserDetailsService implements UserDetailsService { + private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class); + + private final UserRepository userRepository; + + public DomainUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional + public UserDetails loadUserByUsername(final String login) { + log.debug("Authenticating {}", login); + + if (new EmailValidator().isValid(login, null)) { + return userRepository + .findOneWithAuthoritiesByEmailIgnoreCase(login) + .map(user -> createSpringSecurityUser(login, user)) + .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); + } + + String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); + return userRepository + .findOneWithAuthoritiesByLogin(lowercaseLogin) + .map(user -> createSpringSecurityUser(lowercaseLogin, user)) + .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")); + } + + private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) { + if (!user.getActivated()) { + throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); + } + List grantedAuthorities = user + .getAuthorities() + .stream() + .map(authority -> new SimpleGrantedAuthority(authority.getName())) + .collect(Collectors.toList()); + return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities); + } +} diff --git a/src/main/java/com/finca/ccw/security/SecurityUtils.java b/src/main/java/com/finca/ccw/security/SecurityUtils.java new file mode 100644 index 0000000..78e32fd --- /dev/null +++ b/src/main/java/com/finca/ccw/security/SecurityUtils.java @@ -0,0 +1,79 @@ +package com.finca.ccw.security; + +import java.util.Optional; +import java.util.stream.Stream; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * Utility class for Spring Security. + */ +public final class SecurityUtils { + + private SecurityUtils() {} + + /** + * Get the login of the current user. + * + * @return the login of the current user. + */ + public static Optional getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); + } + + private static String extractPrincipal(Authentication authentication) { + if (authentication == null) { + return null; + } else if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } else if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + } + + /** + * Get the JWT of the current user. + * + * @return the JWT of the current user. + */ + public static Optional getCurrentUserJWT() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional + .ofNullable(securityContext.getAuthentication()) + .filter(authentication -> authentication.getCredentials() instanceof String) + .map(authentication -> (String) authentication.getCredentials()); + } + + /** + * Check if a user is authenticated. + * + * @return true if the user is authenticated, false otherwise. + */ + public static boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); + } + + /** + * If the current user has a specific authority (security role). + *

+ * The name of this method comes from the {@code isUserInRole()} method in the Servlet API. + * + * @param authority the authority to check. + * @return true if the current user has the authority, false otherwise. + */ + public static boolean isCurrentUserInRole(String authority) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && getAuthorities(authentication).anyMatch(authority::equals); + } + + private static Stream getAuthorities(Authentication authentication) { + return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority); + } +} diff --git a/src/main/java/com/finca/ccw/security/SpringSecurityAuditorAware.java b/src/main/java/com/finca/ccw/security/SpringSecurityAuditorAware.java new file mode 100644 index 0000000..5dc899c --- /dev/null +++ b/src/main/java/com/finca/ccw/security/SpringSecurityAuditorAware.java @@ -0,0 +1,18 @@ +package com.finca.ccw.security; + +import com.finca.ccw.config.Constants; +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link AuditorAware} based on Spring Security. + */ +@Component +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT)); + } +} diff --git a/src/main/java/com/finca/ccw/security/UserNotActivatedException.java b/src/main/java/com/finca/ccw/security/UserNotActivatedException.java new file mode 100644 index 0000000..9f9b111 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/UserNotActivatedException.java @@ -0,0 +1,18 @@ +package com.finca.ccw.security; + +import org.springframework.security.core.AuthenticationException; + +/** + * This exception is thrown in case of a not activated user trying to authenticate. + */ +public class UserNotActivatedException extends AuthenticationException { + private static final long serialVersionUID = 1L; + + public UserNotActivatedException(String message) { + super(message); + } + + public UserNotActivatedException(String message, Throwable t) { + super(message, t); + } +} diff --git a/src/main/java/com/finca/ccw/security/jwt/JWTConfigurer.java b/src/main/java/com/finca/ccw/security/jwt/JWTConfigurer.java new file mode 100644 index 0000000..929f899 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/jwt/JWTConfigurer.java @@ -0,0 +1,20 @@ +package com.finca.ccw.security.jwt; + +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class JWTConfigurer extends SecurityConfigurerAdapter { + private final TokenProvider tokenProvider; + + public JWTConfigurer(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void configure(HttpSecurity http) { + JWTFilter customFilter = new JWTFilter(tokenProvider); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/src/main/java/com/finca/ccw/security/jwt/JWTFilter.java b/src/main/java/com/finca/ccw/security/jwt/JWTFilter.java new file mode 100644 index 0000000..6533e65 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/jwt/JWTFilter.java @@ -0,0 +1,46 @@ +package com.finca.ccw.security.jwt; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +/** + * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is + * found. + */ +public class JWTFilter extends GenericFilterBean { + public static final String AUTHORIZATION_HEADER = "Authorization"; + + private final TokenProvider tokenProvider; + + public JWTFilter(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String jwt = resolveToken(httpServletRequest); + if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { + Authentication authentication = this.tokenProvider.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(servletRequest, servletResponse); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/finca/ccw/security/jwt/TokenProvider.java b/src/main/java/com/finca/ccw/security/jwt/TokenProvider.java new file mode 100644 index 0000000..a041964 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/jwt/TokenProvider.java @@ -0,0 +1,103 @@ +package com.finca.ccw.security.jwt; + +import io.github.jhipster.config.JHipsterProperties; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.*; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class TokenProvider { + private final Logger log = LoggerFactory.getLogger(TokenProvider.class); + + private static final String AUTHORITIES_KEY = "auth"; + + private Key key; + + private long tokenValidityInMilliseconds; + + private long tokenValidityInMillisecondsForRememberMe; + + private final JHipsterProperties jHipsterProperties; + + public TokenProvider(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @PostConstruct + public void init() { + byte[] keyBytes; + String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret(); + if (!StringUtils.isEmpty(secret)) { + log.warn( + "Warning: the JWT key used is not Base64-encoded. " + + "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security." + ); + keyBytes = secret.getBytes(StandardCharsets.UTF_8); + } else { + log.debug("Using a Base64-encoded JWT secret key"); + keyBytes = Decoders.BASE64.decode(jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret()); + } + this.key = Keys.hmacShaKeyFor(keyBytes); + this.tokenValidityInMilliseconds = 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds(); + this.tokenValidityInMillisecondsForRememberMe = + 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe(); + } + + public String createToken(Authentication authentication, boolean rememberMe) { + String authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")); + + long now = (new Date()).getTime(); + Date validity; + if (rememberMe) { + validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); + } else { + validity = new Date(now + this.tokenValidityInMilliseconds); + } + + return Jwts + .builder() + .setSubject(authentication.getName()) + .claim(AUTHORITIES_KEY, authorities) + .signWith(key, SignatureAlgorithm.HS512) + .setExpiration(validity) + .compact(); + } + + public Authentication getAuthentication(String token) { + Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + + Collection authorities = Arrays + .stream(claims.get(AUTHORITIES_KEY).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + User principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, token, authorities); + } + + public boolean validateToken(String authToken) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken); + return true; + } catch (JwtException | IllegalArgumentException e) { + log.info("Invalid JWT token."); + log.trace("Invalid JWT token trace.", e); + } + return false; + } +} diff --git a/src/main/java/com/finca/ccw/security/package-info.java b/src/main/java/com/finca/ccw/security/package-info.java new file mode 100644 index 0000000..5850503 --- /dev/null +++ b/src/main/java/com/finca/ccw/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Security configuration. + */ +package com.finca.ccw.security; diff --git a/src/main/java/com/finca/ccw/service/AuditEventService.java b/src/main/java/com/finca/ccw/service/AuditEventService.java new file mode 100644 index 0000000..cf1a0ed --- /dev/null +++ b/src/main/java/com/finca/ccw/service/AuditEventService.java @@ -0,0 +1,77 @@ +package com.finca.ccw.service; + +import com.finca.ccw.config.audit.AuditEventConverter; +import com.finca.ccw.repository.PersistenceAuditEventRepository; +import io.github.jhipster.config.JHipsterProperties; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Service for managing audit events. + *

+ * This is the default implementation to support SpringBoot Actuator {@code AuditEventRepository}. + */ +@Service +@Transactional +public class AuditEventService { + private final Logger log = LoggerFactory.getLogger(AuditEventService.class); + + private final JHipsterProperties jHipsterProperties; + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + public AuditEventService( + PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter, + JHipsterProperties jhipsterProperties + ) { + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + this.jHipsterProperties = jhipsterProperties; + } + + /** + * Old audit events should be automatically deleted after 30 days. + * + * This is scheduled to get fired at 12:00 (am). + */ + @Scheduled(cron = "0 0 12 * * ?") + public void removeOldAuditEvents() { + persistenceAuditEventRepository + .findByAuditEventDateBefore(Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS)) + .forEach( + auditEvent -> { + log.debug("Deleting audit data {}", auditEvent); + persistenceAuditEventRepository.delete(auditEvent); + } + ); + } + + @Transactional(readOnly = true) + public Page findAll(Pageable pageable) { + return persistenceAuditEventRepository.findAll(pageable).map(auditEventConverter::convertToAuditEvent); + } + + @Transactional(readOnly = true) + public Page findByDates(Instant fromDate, Instant toDate, Pageable pageable) { + return persistenceAuditEventRepository + .findAllByAuditEventDateBetween(fromDate, toDate, pageable) + .map(auditEventConverter::convertToAuditEvent); + } + + @Transactional(readOnly = true) + public Optional find(Long id) { + return persistenceAuditEventRepository.findById(id).map(auditEventConverter::convertToAuditEvent); + } +} diff --git a/src/main/java/com/finca/ccw/service/EmailAlreadyUsedException.java b/src/main/java/com/finca/ccw/service/EmailAlreadyUsedException.java new file mode 100644 index 0000000..12edd29 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/EmailAlreadyUsedException.java @@ -0,0 +1,9 @@ +package com.finca.ccw.service; + +public class EmailAlreadyUsedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super("Email is already in use!"); + } +} diff --git a/src/main/java/com/finca/ccw/service/InvalidPasswordException.java b/src/main/java/com/finca/ccw/service/InvalidPasswordException.java new file mode 100644 index 0000000..190ca70 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/InvalidPasswordException.java @@ -0,0 +1,9 @@ +package com.finca.ccw.service; + +public class InvalidPasswordException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super("Incorrect password"); + } +} diff --git a/src/main/java/com/finca/ccw/service/MailService.java b/src/main/java/com/finca/ccw/service/MailService.java new file mode 100644 index 0000000..b3c8bb2 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/MailService.java @@ -0,0 +1,111 @@ +package com.finca.ccw.service; + +import com.finca.ccw.domain.User; +import io.github.jhipster.config.JHipsterProperties; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +/** + * Service for sending emails. + *

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

+ * This is scheduled to get fired everyday, at 01:00 (am). + */ + @Scheduled(cron = "0 0 1 * * ?") + public void removeNotActivatedUsers() { + userRepository + .findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS)) + .forEach( + user -> { + log.debug("Deleting not activated user {}", user.getLogin()); + userRepository.delete(user); + this.clearUserCaches(user); + } + ); + } + + /** + * Gets a list of all the authorities. + * @return a list of all the authorities. + */ + @Transactional(readOnly = true) + public List getAuthorities() { + return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList()); + } + + private void clearUserCaches(User user) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin()); + if (user.getEmail() != null) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail()); + } + } +} diff --git a/src/main/java/com/finca/ccw/service/UsernameAlreadyUsedException.java b/src/main/java/com/finca/ccw/service/UsernameAlreadyUsedException.java new file mode 100644 index 0000000..24a7ef4 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/UsernameAlreadyUsedException.java @@ -0,0 +1,9 @@ +package com.finca.ccw.service; + +public class UsernameAlreadyUsedException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public UsernameAlreadyUsedException() { + super("Login name already used!"); + } +} diff --git a/src/main/java/com/finca/ccw/service/dto/PasswordChangeDTO.java b/src/main/java/com/finca/ccw/service/dto/PasswordChangeDTO.java new file mode 100644 index 0000000..9c11870 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/dto/PasswordChangeDTO.java @@ -0,0 +1,34 @@ +package com.finca.ccw.service.dto; + +/** + * A DTO representing a password change required data - current and new password. + */ +public class PasswordChangeDTO { + private String currentPassword; + private String newPassword; + + public PasswordChangeDTO() { + // Empty constructor needed for Jackson. + } + + public PasswordChangeDTO(String currentPassword, String newPassword) { + this.currentPassword = currentPassword; + this.newPassword = newPassword; + } + + public String getCurrentPassword() { + return currentPassword; + } + + public void setCurrentPassword(String currentPassword) { + this.currentPassword = currentPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/src/main/java/com/finca/ccw/service/dto/UserDTO.java b/src/main/java/com/finca/ccw/service/dto/UserDTO.java new file mode 100644 index 0000000..1c51569 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/dto/UserDTO.java @@ -0,0 +1,192 @@ +package com.finca.ccw.service.dto; + +import com.finca.ccw.config.Constants; +import com.finca.ccw.domain.Authority; +import com.finca.ccw.domain.User; +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; +import javax.validation.constraints.*; + +/** + * A DTO representing a user, with his authorities. + */ +public class UserDTO { + private Long id; + + @NotBlank + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + private String login; + + @Size(max = 50) + private String firstName; + + @Size(max = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + private String email; + + @Size(max = 256) + private String imageUrl; + + private boolean activated = false; + + @Size(min = 2, max = 10) + private String langKey; + + private String createdBy; + + private Instant createdDate; + + private String lastModifiedBy; + + private Instant lastModifiedDate; + + private Set authorities; + + public UserDTO() { + // Empty constructor needed for Jackson. + } + + public UserDTO(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.email = user.getEmail(); + this.activated = user.getActivated(); + this.imageUrl = user.getImageUrl(); + this.langKey = user.getLangKey(); + this.createdBy = user.getCreatedBy(); + this.createdDate = user.getCreatedDate(); + this.lastModifiedBy = user.getLastModifiedBy(); + this.lastModifiedDate = user.getLastModifiedDate(); + this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet()); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + // prettier-ignore + @Override + public String toString() { + return "UserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + } +} diff --git a/src/main/java/com/finca/ccw/service/dto/package-info.java b/src/main/java/com/finca/ccw/service/dto/package-info.java new file mode 100644 index 0000000..6ea3494 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data Transfer Objects. + */ +package com.finca.ccw.service.dto; diff --git a/src/main/java/com/finca/ccw/service/mapper/UserMapper.java b/src/main/java/com/finca/ccw/service/mapper/UserMapper.java new file mode 100644 index 0000000..512224e --- /dev/null +++ b/src/main/java/com/finca/ccw/service/mapper/UserMapper.java @@ -0,0 +1,78 @@ +package com.finca.ccw.service.mapper; + +import com.finca.ccw.domain.Authority; +import com.finca.ccw.domain.User; +import com.finca.ccw.service.dto.UserDTO; +import java.util.*; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; + +/** + * Mapper for the entity {@link User} and its DTO called {@link UserDTO}. + * + * Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct + * support is still in beta, and requires a manual step with an IDE. + */ +@Service +public class UserMapper { + + public List usersToUserDTOs(List users) { + return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).collect(Collectors.toList()); + } + + public UserDTO userToUserDTO(User user) { + return new UserDTO(user); + } + + public List userDTOsToUsers(List userDTOs) { + return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).collect(Collectors.toList()); + } + + public User userDTOToUser(UserDTO userDTO) { + if (userDTO == null) { + return null; + } else { + User user = new User(); + user.setId(userDTO.getId()); + user.setLogin(userDTO.getLogin()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set authorities = this.authoritiesFromStrings(userDTO.getAuthorities()); + user.setAuthorities(authorities); + return user; + } + } + + private Set authoritiesFromStrings(Set authoritiesAsString) { + Set authorities = new HashSet<>(); + + if (authoritiesAsString != null) { + authorities = + authoritiesAsString + .stream() + .map( + string -> { + Authority auth = new Authority(); + auth.setName(string); + return auth; + } + ) + .collect(Collectors.toSet()); + } + + return authorities; + } + + public User userFromId(Long id) { + if (id == null) { + return null; + } + User user = new User(); + user.setId(id); + return user; + } +} diff --git a/src/main/java/com/finca/ccw/service/mapper/package-info.java b/src/main/java/com/finca/ccw/service/mapper/package-info.java new file mode 100644 index 0000000..e0bd53c --- /dev/null +++ b/src/main/java/com/finca/ccw/service/mapper/package-info.java @@ -0,0 +1,4 @@ +/** + * MapStruct mappers for mapping domain objects and Data Transfer Objects. + */ +package com.finca.ccw.service.mapper; diff --git a/src/main/java/com/finca/ccw/service/package-info.java b/src/main/java/com/finca/ccw/service/package-info.java new file mode 100644 index 0000000..1c69ad8 --- /dev/null +++ b/src/main/java/com/finca/ccw/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service layer beans. + */ +package com.finca.ccw.service; diff --git a/src/main/java/com/finca/ccw/web/rest/AccountResource.java b/src/main/java/com/finca/ccw/web/rest/AccountResource.java new file mode 100644 index 0000000..50e66f5 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/AccountResource.java @@ -0,0 +1,194 @@ +package com.finca.ccw.web.rest; + +import com.finca.ccw.domain.User; +import com.finca.ccw.repository.UserRepository; +import com.finca.ccw.security.SecurityUtils; +import com.finca.ccw.service.MailService; +import com.finca.ccw.service.UserService; +import com.finca.ccw.service.dto.PasswordChangeDTO; +import com.finca.ccw.service.dto.UserDTO; +import com.finca.ccw.web.rest.errors.*; +import com.finca.ccw.web.rest.vm.KeyAndPasswordVM; +import com.finca.ccw.web.rest.vm.ManagedUserVM; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +public class AccountResource { + + private static class AccountResourceException extends RuntimeException { + + private AccountResourceException(String message) { + super(message); + } + } + + private final Logger log = LoggerFactory.getLogger(AccountResource.class); + + private final UserRepository userRepository; + + private final UserService userService; + + private final MailService mailService; + + public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) { + this.userRepository = userRepository; + this.userService = userService; + this.mailService = mailService; + } + + /** + * {@code POST /register} : register the user. + * + * @param managedUserVM the managed user View Model. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. + */ + @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) + public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { + if (!checkPasswordLength(managedUserVM.getPassword())) { + throw new InvalidPasswordException(); + } + User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); + mailService.sendActivationEmail(user); + } + + /** + * {@code GET /activate} : activate the registered user. + * + * @param key the activation key. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be activated. + */ + @GetMapping("/activate") + public void activateAccount(@RequestParam(value = "key") String key) { + Optional user = userService.activateRegistration(key); + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this activation key"); + } + } + + /** + * {@code GET /authenticate} : check if the user is authenticated, and return its login. + * + * @param request the HTTP request. + * @return the login if the user is authenticated. + */ + @GetMapping("/authenticate") + public String isAuthenticated(HttpServletRequest request) { + log.debug("REST request to check if the current user is authenticated"); + return request.getRemoteUser(); + } + + /** + * {@code GET /account} : get the current user. + * + * @return the current user. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be returned. + */ + @GetMapping("/account") + public UserDTO getAccount() { + return userService + .getUserWithAuthorities() + .map(UserDTO::new) + .orElseThrow(() -> new AccountResourceException("User could not be found")); + } + + /** + * {@code POST /account} : update the current user information. + * + * @param userDTO the current user information. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user login wasn't found. + */ + @PostMapping("/account") + public void saveAccount(@Valid @RequestBody UserDTO userDTO) { + String userLogin = SecurityUtils + .getCurrentUserLogin() + .orElseThrow(() -> new AccountResourceException("Current user login not found")); + Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userLogin))) { + throw new EmailAlreadyUsedException(); + } + Optional user = userRepository.findOneByLogin(userLogin); + if (!user.isPresent()) { + throw new AccountResourceException("User could not be found"); + } + userService.updateUser( + userDTO.getFirstName(), + userDTO.getLastName(), + userDTO.getEmail(), + userDTO.getLangKey(), + userDTO.getImageUrl() + ); + } + + /** + * {@code POST /account/change-password} : changes the current user's password. + * + * @param passwordChangeDto current and new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the new password is incorrect. + */ + @PostMapping(path = "/account/change-password") + public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) { + if (!checkPasswordLength(passwordChangeDto.getNewPassword())) { + throw new InvalidPasswordException(); + } + userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword()); + } + + /** + * {@code POST /account/reset-password/init} : Send an email to reset the password of the user. + * + * @param mail the mail of the user. + */ + @PostMapping(path = "/account/reset-password/init") + public void requestPasswordReset(@RequestBody String mail) { + Optional user = userService.requestPasswordReset(mail); + if (user.isPresent()) { + mailService.sendPasswordResetMail(user.get()); + } else { + // Pretend the request has been successful to prevent checking which emails really exist + // but log that an invalid attempt has been made + log.warn("Password reset requested for non existing mail"); + } + } + + /** + * {@code POST /account/reset-password/finish} : Finish to reset the password of the user. + * + * @param keyAndPassword the generated key and the new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the password could not be reset. + */ + @PostMapping(path = "/account/reset-password/finish") + public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { + if (!checkPasswordLength(keyAndPassword.getNewPassword())) { + throw new InvalidPasswordException(); + } + Optional user = userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); + + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this reset key"); + } + } + + private static boolean checkPasswordLength(String password) { + return ( + !StringUtils.isEmpty(password) && + password.length() >= ManagedUserVM.PASSWORD_MIN_LENGTH && + password.length() <= ManagedUserVM.PASSWORD_MAX_LENGTH + ); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/AuditResource.java b/src/main/java/com/finca/ccw/web/rest/AuditResource.java new file mode 100644 index 0000000..33834e2 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/AuditResource.java @@ -0,0 +1,76 @@ +package com.finca.ccw.web.rest; + +import com.finca.ccw.service.AuditEventService; +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * REST controller for getting the {@link AuditEvent}s. + */ +@RestController +@RequestMapping("/management/audits") +public class AuditResource { + private final AuditEventService auditEventService; + + public AuditResource(AuditEventService auditEventService) { + this.auditEventService = auditEventService; + } + + /** + * {@code GET /audits} : get a page of {@link AuditEvent}s. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent}s in body. + */ + @GetMapping + public ResponseEntity> getAll(Pageable pageable) { + Page page = auditEventService.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * {@code GET /audits} : get a page of {@link AuditEvent} between the {@code fromDate} and {@code toDate}. + * + * @param fromDate the start of the time period of {@link AuditEvent} to get. + * @param toDate the end of the time period of {@link AuditEvent} to get. + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent} in body. + */ + @GetMapping(params = { "fromDate", "toDate" }) + public ResponseEntity> getByDates( + @RequestParam(value = "fromDate") LocalDate fromDate, + @RequestParam(value = "toDate") LocalDate toDate, + Pageable pageable + ) { + Instant from = fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); + Instant to = toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(); + + Page page = auditEventService.findByDates(from, to, pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * {@code GET /audits/:id} : get an {@link AuditEvent} by id. + * + * @param id the id of the entity to get. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the {@link AuditEvent} in body, or status {@code 404 (Not Found)}. + */ + @GetMapping("/{id:.+}") + public ResponseEntity get(@PathVariable Long id) { + return ResponseUtil.wrapOrNotFound(auditEventService.find(id)); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/ClientForwardController.java b/src/main/java/com/finca/ccw/web/rest/ClientForwardController.java new file mode 100644 index 0000000..d4af3b8 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/ClientForwardController.java @@ -0,0 +1,17 @@ +package com.finca.ccw.web.rest; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class ClientForwardController { + + /** + * Forwards any unmapped paths (except those containing a period) to the client {@code index.html}. + * @return forward to client {@code index.html}. + */ + @GetMapping(value = "/**/{path:[^\\.]*}") + public String forward() { + return "forward:/"; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/CollectionTrackingResource.java b/src/main/java/com/finca/ccw/web/rest/CollectionTrackingResource.java new file mode 100644 index 0000000..2150970 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/CollectionTrackingResource.java @@ -0,0 +1,123 @@ +package com.finca.ccw.web.rest; + +import com.finca.ccw.domain.CollectionTracking; +import com.finca.ccw.repository.CollectionTrackingRepository; +import com.finca.ccw.web.rest.errors.BadRequestAlertException; +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.ResponseUtil; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; +import javax.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +/** + * REST controller for managing {@link com.finca.ccw.domain.CollectionTracking}. + */ +@RestController +@RequestMapping("/api") +@Transactional +public class CollectionTrackingResource { + private final Logger log = LoggerFactory.getLogger(CollectionTrackingResource.class); + + private static final String ENTITY_NAME = "collectionTracking"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final CollectionTrackingRepository collectionTrackingRepository; + + public CollectionTrackingResource(CollectionTrackingRepository collectionTrackingRepository) { + this.collectionTrackingRepository = collectionTrackingRepository; + } + + /** + * {@code POST /collection-trackings} : Create a new collectionTracking. + * + * @param collectionTracking the collectionTracking to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new collectionTracking, or with status {@code 400 (Bad Request)} if the collectionTracking has already an ID. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PostMapping("/collection-trackings") + public ResponseEntity createCollectionTracking(@Valid @RequestBody CollectionTracking collectionTracking) + throws URISyntaxException { + log.debug("REST request to save CollectionTracking : {}", collectionTracking); + if (collectionTracking.getId() != null) { + throw new BadRequestAlertException("A new collectionTracking cannot already have an ID", ENTITY_NAME, "idexists"); + } + CollectionTracking result = collectionTrackingRepository.save(collectionTracking); + return ResponseEntity + .created(new URI("/api/collection-trackings/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * {@code PUT /collection-trackings} : Updates an existing collectionTracking. + * + * @param collectionTracking the collectionTracking to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated collectionTracking, + * or with status {@code 400 (Bad Request)} if the collectionTracking is not valid, + * or with status {@code 500 (Internal Server Error)} if the collectionTracking couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PutMapping("/collection-trackings") + public ResponseEntity updateCollectionTracking(@Valid @RequestBody CollectionTracking collectionTracking) + throws URISyntaxException { + log.debug("REST request to update CollectionTracking : {}", collectionTracking); + if (collectionTracking.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + CollectionTracking result = collectionTrackingRepository.save(collectionTracking); + return ResponseEntity + .ok() + .headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, collectionTracking.getId().toString())) + .body(result); + } + + /** + * {@code GET /collection-trackings} : get all the collectionTrackings. + * + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of collectionTrackings in body. + */ + @GetMapping("/collection-trackings") + public List getAllCollectionTrackings() { + log.debug("REST request to get all CollectionTrackings"); + return collectionTrackingRepository.findAll(); + } + + /** + * {@code GET /collection-trackings/:id} : get the "id" collectionTracking. + * + * @param id the id of the collectionTracking to retrieve. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the collectionTracking, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/collection-trackings/{id}") + public ResponseEntity getCollectionTracking(@PathVariable Long id) { + log.debug("REST request to get CollectionTracking : {}", id); + Optional collectionTracking = collectionTrackingRepository.findById(id); + return ResponseUtil.wrapOrNotFound(collectionTracking); + } + + /** + * {@code DELETE /collection-trackings/:id} : delete the "id" collectionTracking. + * + * @param id the id of the collectionTracking to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/collection-trackings/{id}") + public ResponseEntity deleteCollectionTracking(@PathVariable Long id) { + log.debug("REST request to delete CollectionTracking : {}", id); + collectionTrackingRepository.deleteById(id); + return ResponseEntity + .noContent() + .headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString())) + .build(); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/EmployeeResource.java b/src/main/java/com/finca/ccw/web/rest/EmployeeResource.java new file mode 100644 index 0000000..36b749e --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/EmployeeResource.java @@ -0,0 +1,121 @@ +package com.finca.ccw.web.rest; + +import com.finca.ccw.domain.Employee; +import com.finca.ccw.repository.EmployeeRepository; +import com.finca.ccw.web.rest.errors.BadRequestAlertException; +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.ResponseUtil; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; +import javax.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +/** + * REST controller for managing {@link com.finca.ccw.domain.Employee}. + */ +@RestController +@RequestMapping("/api") +@Transactional +public class EmployeeResource { + private final Logger log = LoggerFactory.getLogger(EmployeeResource.class); + + private static final String ENTITY_NAME = "employee"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final EmployeeRepository employeeRepository; + + public EmployeeResource(EmployeeRepository employeeRepository) { + this.employeeRepository = employeeRepository; + } + + /** + * {@code POST /employees} : Create a new employee. + * + * @param employee the employee to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new employee, or with status {@code 400 (Bad Request)} if the employee has already an ID. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PostMapping("/employees") + public ResponseEntity createEmployee(@Valid @RequestBody Employee employee) throws URISyntaxException { + log.debug("REST request to save Employee : {}", employee); + if (employee.getId() != null) { + throw new BadRequestAlertException("A new employee cannot already have an ID", ENTITY_NAME, "idexists"); + } + Employee result = employeeRepository.save(employee); + return ResponseEntity + .created(new URI("/api/employees/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * {@code PUT /employees} : Updates an existing employee. + * + * @param employee the employee to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated employee, + * or with status {@code 400 (Bad Request)} if the employee is not valid, + * or with status {@code 500 (Internal Server Error)} if the employee couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PutMapping("/employees") + public ResponseEntity updateEmployee(@Valid @RequestBody Employee employee) throws URISyntaxException { + log.debug("REST request to update Employee : {}", employee); + if (employee.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + Employee result = employeeRepository.save(employee); + return ResponseEntity + .ok() + .headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, employee.getId().toString())) + .body(result); + } + + /** + * {@code GET /employees} : get all the employees. + * + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of employees in body. + */ + @GetMapping("/employees") + public List getAllEmployees() { + log.debug("REST request to get all Employees"); + return employeeRepository.findAll(); + } + + /** + * {@code GET /employees/:id} : get the "id" employee. + * + * @param id the id of the employee to retrieve. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the employee, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/employees/{id}") + public ResponseEntity getEmployee(@PathVariable Long id) { + log.debug("REST request to get Employee : {}", id); + Optional employee = employeeRepository.findById(id); + return ResponseUtil.wrapOrNotFound(employee); + } + + /** + * {@code DELETE /employees/:id} : delete the "id" employee. + * + * @param id the id of the employee to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/employees/{id}") + public ResponseEntity deleteEmployee(@PathVariable Long id) { + log.debug("REST request to delete Employee : {}", id); + employeeRepository.deleteById(id); + return ResponseEntity + .noContent() + .headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString())) + .build(); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/UserJWTController.java b/src/main/java/com/finca/ccw/web/rest/UserJWTController.java new file mode 100644 index 0000000..6d0df7c --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/UserJWTController.java @@ -0,0 +1,67 @@ +package com.finca.ccw.web.rest; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.finca.ccw.security.jwt.JWTFilter; +import com.finca.ccw.security.jwt.TokenProvider; +import com.finca.ccw.web.rest.vm.LoginVM; +import javax.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +/** + * Controller to authenticate users. + */ +@RestController +@RequestMapping("/api") +public class UserJWTController { + private final TokenProvider tokenProvider; + + private final AuthenticationManagerBuilder authenticationManagerBuilder; + + public UserJWTController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) { + this.tokenProvider = tokenProvider; + this.authenticationManagerBuilder = authenticationManagerBuilder; + } + + @PostMapping("/authenticate") + public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginVM.getUsername(), + loginVM.getPassword() + ); + + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe(); + String jwt = tokenProvider.createToken(authentication, rememberMe); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); + return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); + } + + /** + * Object to return as body in JWT Authentication. + */ + static class JWTToken { + private String idToken; + + JWTToken(String idToken) { + this.idToken = idToken; + } + + @JsonProperty("id_token") + String getIdToken() { + return idToken; + } + + void setIdToken(String idToken) { + this.idToken = idToken; + } + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/UserResource.java b/src/main/java/com/finca/ccw/web/rest/UserResource.java new file mode 100644 index 0000000..d807868 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/UserResource.java @@ -0,0 +1,200 @@ +package com.finca.ccw.web.rest; + +import com.finca.ccw.config.Constants; +import com.finca.ccw.domain.User; +import com.finca.ccw.repository.UserRepository; +import com.finca.ccw.security.AuthoritiesConstants; +import com.finca.ccw.service.MailService; +import com.finca.ccw.service.UserService; +import com.finca.ccw.service.dto.UserDTO; +import com.finca.ccw.web.rest.errors.BadRequestAlertException; +import com.finca.ccw.web.rest.errors.EmailAlreadyUsedException; +import com.finca.ccw.web.rest.errors.LoginAlreadyUsedException; +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.Collections; +import javax.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * REST controller for managing users. + *

+ * This class accesses the {@link User} entity, and needs to fetch its collection of authorities. + *

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

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

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

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

+ * Creates a new user if the login and email are not already used, and sends an + * mail with an activation link. + * The user needs to be activated on creation. + * + * @param userDTO the user to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use. + * @throws URISyntaxException if the Location URI syntax is incorrect. + * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use. + */ + @PostMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity createUser(@Valid @RequestBody UserDTO userDTO) throws URISyntaxException { + log.debug("REST request to save User : {}", userDTO); + + if (userDTO.getId() != null) { + throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists"); + // Lowercase the user login before comparing with database + } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) { + throw new LoginAlreadyUsedException(); + } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) { + throw new EmailAlreadyUsedException(); + } else { + User newUser = userService.createUser(userDTO); + mailService.sendCreationEmail(newUser); + return ResponseEntity + .created(new URI("/api/users/" + newUser.getLogin())) + .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.getLogin())) + .body(newUser); + } + } + + /** + * {@code PUT /users} : Updates an existing User. + * + * @param userDTO the user to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use. + */ + @PutMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity updateUser(@Valid @RequestBody UserDTO userDTO) { + log.debug("REST request to update User : {}", userDTO); + Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { + throw new EmailAlreadyUsedException(); + } + existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { + throw new LoginAlreadyUsedException(); + } + Optional updatedUser = userService.updateUser(userDTO); + + return ResponseUtil.wrapOrNotFound( + updatedUser, + HeaderUtil.createAlert(applicationName, "userManagement.updated", userDTO.getLogin()) + ); + } + + /** + * {@code GET /users} : get all users. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + public ResponseEntity> getAllUsers(Pageable pageable) { + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build(); + } + + final Page page = userService.getAllManagedUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + private boolean onlyContainsAllowedProperties(Pageable pageable) { + return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); + } + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/users/authorities") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public List getAuthorities() { + return userService.getAuthorities(); + } + + /** + * {@code GET /users/:login} : get the "login" user. + * + * @param login the login of the user to find. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") + public ResponseEntity getUser(@PathVariable String login) { + log.debug("REST request to get User : {}", login); + return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(UserDTO::new)); + } + + /** + * {@code DELETE /users/:login} : delete the "login" User. + * + * @param login the login of the user to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity deleteUser(@PathVariable String login) { + log.debug("REST request to delete User: {}", login); + userService.deleteUser(login); + return ResponseEntity.noContent().headers(HeaderUtil.createAlert(applicationName, "userManagement.deleted", login)).build(); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/BadRequestAlertException.java b/src/main/java/com/finca/ccw/web/rest/errors/BadRequestAlertException.java new file mode 100644 index 0000000..5d0e6fc --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/BadRequestAlertException.java @@ -0,0 +1,40 @@ +package com.finca.ccw.web.rest.errors; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Status; + +public class BadRequestAlertException extends AbstractThrowableProblem { + private static final long serialVersionUID = 1L; + + private final String entityName; + + private final String errorKey; + + public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) { + this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey); + } + + public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) { + super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey)); + this.entityName = entityName; + this.errorKey = errorKey; + } + + public String getEntityName() { + return entityName; + } + + public String getErrorKey() { + return errorKey; + } + + private static Map getAlertParameters(String entityName, String errorKey) { + Map parameters = new HashMap<>(); + parameters.put("message", "error." + errorKey); + parameters.put("params", entityName); + return parameters; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/EmailAlreadyUsedException.java b/src/main/java/com/finca/ccw/web/rest/errors/EmailAlreadyUsedException.java new file mode 100644 index 0000000..050b65c --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/EmailAlreadyUsedException.java @@ -0,0 +1,9 @@ +package com.finca.ccw.web.rest.errors; + +public class EmailAlreadyUsedException extends BadRequestAlertException { + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/ErrorConstants.java b/src/main/java/com/finca/ccw/web/rest/errors/ErrorConstants.java new file mode 100644 index 0000000..39d23fc --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/ErrorConstants.java @@ -0,0 +1,16 @@ +package com.finca.ccw.web.rest.errors; + +import java.net.URI; + +public final class ErrorConstants { + public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; + public static final String ERR_VALIDATION = "error.validation"; + public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; + public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); + public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); + public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); + public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); + public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); + + private ErrorConstants() {} +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/ExceptionTranslator.java b/src/main/java/com/finca/ccw/web/rest/errors/ExceptionTranslator.java new file mode 100644 index 0000000..f43bbb4 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/ExceptionTranslator.java @@ -0,0 +1,212 @@ +package com.finca.ccw.web.rest.errors; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.web.util.HeaderUtil; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.DataAccessException; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.NativeWebRequest; +import org.zalando.problem.DefaultProblem; +import org.zalando.problem.Problem; +import org.zalando.problem.ProblemBuilder; +import org.zalando.problem.Status; +import org.zalando.problem.StatusType; +import org.zalando.problem.spring.web.advice.ProblemHandling; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; +import org.zalando.problem.violations.ConstraintViolationProblem; + +/** + * Controller advice to translate the server side exceptions to client-friendly json structures. + * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). + */ +@ControllerAdvice +public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { + private static final String FIELD_ERRORS_KEY = "fieldErrors"; + private static final String MESSAGE_KEY = "message"; + private static final String PATH_KEY = "path"; + private static final String VIOLATIONS_KEY = "violations"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final Environment env; + + public ExceptionTranslator(Environment env) { + this.env = env; + } + + /** + * Post-process the Problem payload to add the message key for the front-end if needed. + */ + @Override + public ResponseEntity process(@Nullable ResponseEntity entity, NativeWebRequest request) { + if (entity == null) { + return entity; + } + Problem problem = entity.getBody(); + if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { + return entity; + } + ProblemBuilder builder = Problem + .builder() + .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()) + .withStatus(problem.getStatus()) + .withTitle(problem.getTitle()) + .with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI()); + + if (problem instanceof ConstraintViolationProblem) { + builder + .with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()) + .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION); + } else { + builder.withCause(((DefaultProblem) problem).getCause()).withDetail(problem.getDetail()).withInstance(problem.getInstance()); + problem.getParameters().forEach(builder::with); + if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) { + builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode()); + } + } + return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode()); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { + BindingResult result = ex.getBindingResult(); + List fieldErrors = result + .getFieldErrors() + .stream() + .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$", ""), f.getField(), f.getCode())) + .collect(Collectors.toList()); + + Problem problem = Problem + .builder() + .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE) + .withTitle("Method argument not valid") + .withStatus(defaultConstraintViolationStatus()) + .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION) + .with(FIELD_ERRORS_KEY, fieldErrors) + .build(); + return create(ex, problem, request); + } + + @ExceptionHandler + public ResponseEntity handleEmailAlreadyUsedException( + com.finca.ccw.service.EmailAlreadyUsedException ex, + NativeWebRequest request + ) { + EmailAlreadyUsedException problem = new EmailAlreadyUsedException(); + return create( + problem, + request, + HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity handleUsernameAlreadyUsedException( + com.finca.ccw.service.UsernameAlreadyUsedException ex, + NativeWebRequest request + ) { + LoginAlreadyUsedException problem = new LoginAlreadyUsedException(); + return create( + problem, + request, + HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity handleInvalidPasswordException( + com.finca.ccw.service.InvalidPasswordException ex, + NativeWebRequest request + ) { + return create(new InvalidPasswordException(), request); + } + + @ExceptionHandler + public ResponseEntity handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { + return create( + ex, + request, + HeaderUtil.createFailureAlert(applicationName, true, ex.getEntityName(), ex.getErrorKey(), ex.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) { + Problem problem = Problem.builder().withStatus(Status.CONFLICT).with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE).build(); + return create(ex, problem, request); + } + + @Override + public ProblemBuilder prepare(final Throwable throwable, final StatusType status, final URI type) { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + if (throwable instanceof HttpMessageConversionException) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Unable to convert http message") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + if (throwable instanceof DataAccessException) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Failure during data access") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + if (containsPackageName(throwable.getMessage())) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Unexpected runtime exception") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + } + + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail(throwable.getMessage()) + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + + private boolean containsPackageName(String message) { + // This list is for sure not complete + return StringUtils.containsAny(message, "org.", "java.", "net.", "javax.", "com.", "io.", "de.", "com.finca.ccw"); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/FieldErrorVM.java b/src/main/java/com/finca/ccw/web/rest/errors/FieldErrorVM.java new file mode 100644 index 0000000..f0ecf56 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/FieldErrorVM.java @@ -0,0 +1,31 @@ +package com.finca.ccw.web.rest.errors; + +import java.io.Serializable; + +public class FieldErrorVM implements Serializable { + private static final long serialVersionUID = 1L; + + private final String objectName; + + private final String field; + + private final String message; + + public FieldErrorVM(String dto, String field, String message) { + this.objectName = dto; + this.field = field; + this.message = message; + } + + public String getObjectName() { + return objectName; + } + + public String getField() { + return field; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/InvalidPasswordException.java b/src/main/java/com/finca/ccw/web/rest/errors/InvalidPasswordException.java new file mode 100644 index 0000000..17d891f --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/InvalidPasswordException.java @@ -0,0 +1,12 @@ +package com.finca.ccw.web.rest.errors; + +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Status; + +public class InvalidPasswordException extends AbstractThrowableProblem { + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/LoginAlreadyUsedException.java b/src/main/java/com/finca/ccw/web/rest/errors/LoginAlreadyUsedException.java new file mode 100644 index 0000000..0260e98 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/LoginAlreadyUsedException.java @@ -0,0 +1,9 @@ +package com.finca.ccw.web.rest.errors; + +public class LoginAlreadyUsedException extends BadRequestAlertException { + private static final long serialVersionUID = 1L; + + public LoginAlreadyUsedException() { + super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/errors/package-info.java b/src/main/java/com/finca/ccw/web/rest/errors/package-info.java new file mode 100644 index 0000000..3b1873f --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/errors/package-info.java @@ -0,0 +1,6 @@ +/** + * Specific errors used with Zalando's "problem-spring-web" library. + * + * More information on https://github.com/zalando/problem-spring-web + */ +package com.finca.ccw.web.rest.errors; diff --git a/src/main/java/com/finca/ccw/web/rest/package-info.java b/src/main/java/com/finca/ccw/web/rest/package-info.java new file mode 100644 index 0000000..f2c0963 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring MVC REST controllers. + */ +package com.finca.ccw.web.rest; diff --git a/src/main/java/com/finca/ccw/web/rest/vm/KeyAndPasswordVM.java b/src/main/java/com/finca/ccw/web/rest/vm/KeyAndPasswordVM.java new file mode 100644 index 0000000..dc907f8 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/vm/KeyAndPasswordVM.java @@ -0,0 +1,26 @@ +package com.finca.ccw.web.rest.vm; + +/** + * View Model object for storing the user's key and password. + */ +public class KeyAndPasswordVM { + private String key; + + private String newPassword; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/vm/LoginVM.java b/src/main/java/com/finca/ccw/web/rest/vm/LoginVM.java new file mode 100644 index 0000000..e2e7abe --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/vm/LoginVM.java @@ -0,0 +1,52 @@ +package com.finca.ccw.web.rest.vm; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * View Model object for storing a user's credentials. + */ +public class LoginVM { + @NotNull + @Size(min = 1, max = 50) + private String username; + + @NotNull + @Size(min = 4, max = 100) + private String password; + + private Boolean rememberMe; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(Boolean rememberMe) { + this.rememberMe = rememberMe; + } + + // prettier-ignore + @Override + public String toString() { + return "LoginVM{" + + "username='" + username + '\'' + + ", rememberMe=" + rememberMe + + '}'; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/vm/ManagedUserVM.java b/src/main/java/com/finca/ccw/web/rest/vm/ManagedUserVM.java new file mode 100644 index 0000000..75b5f85 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/vm/ManagedUserVM.java @@ -0,0 +1,34 @@ +package com.finca.ccw.web.rest.vm; + +import com.finca.ccw.service.dto.UserDTO; +import javax.validation.constraints.Size; + +/** + * View Model extending the UserDTO, which is meant to be used in the user management UI. + */ +public class ManagedUserVM extends UserDTO { + public static final int PASSWORD_MIN_LENGTH = 4; + + public static final int PASSWORD_MAX_LENGTH = 100; + + @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) + private String password; + + public ManagedUserVM() { + // Empty constructor needed for Jackson. + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + // prettier-ignore + @Override + public String toString() { + return "ManagedUserVM{" + super.toString() + "} "; + } +} diff --git a/src/main/java/com/finca/ccw/web/rest/vm/package-info.java b/src/main/java/com/finca/ccw/web/rest/vm/package-info.java new file mode 100644 index 0000000..8430952 --- /dev/null +++ b/src/main/java/com/finca/ccw/web/rest/vm/package-info.java @@ -0,0 +1,4 @@ +/** + * View Models used by Spring MVC REST controllers. + */ +package com.finca.ccw.web.rest.vm; diff --git a/src/main/jib/entrypoint.sh b/src/main/jib/entrypoint.sh index 244747c..67e7e89 100644 --- a/src/main/jib/entrypoint.sh +++ b/src/main/jib/entrypoint.sh @@ -1,4 +1,4 @@ #!/bin/sh echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP} -exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "com.finca.ccwapp.CollectionTrackingApplicationApp" "$@" +exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "com.finca.ccw.CcwApplicationApp" "$@" diff --git a/src/main/resources/.h2.server.properties b/src/main/resources/.h2.server.properties index b4fd91c..7b4db1b 100644 --- a/src/main/resources/.h2.server.properties +++ b/src/main/resources/.h2.server.properties @@ -1,5 +1,5 @@ #H2 Server Properties -0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/collectiontrackingapplication|CollectionTrackingApplication +0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/ccwapplication|CCWApplication webAllowOthers=true webPort=8082 webSSL=false diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 317d1b6..811b7cf 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -17,7 +17,7 @@ logging: level: ROOT: DEBUG io.github.jhipster: DEBUG - com.finca.ccwapp: DEBUG + com.finca.ccw: DEBUG spring: profiles: @@ -37,8 +37,8 @@ spring: indent-output: true datasource: type: com.zaxxer.hikari.HikariDataSource - url: jdbc:h2:file:./target/h2db/db/collectiontrackingapplication;DB_CLOSE_DELAY=-1 - username: CollectionTrackingApplication + url: jdbc:h2:file:./target/h2db/db/ccwapplication;DB_CLOSE_DELAY=-1 + username: CCWApplication password: hikari: poolName: Hikari diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index d27553b..f0605af 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -17,7 +17,7 @@ logging: level: ROOT: INFO io.github.jhipster: INFO - com.finca.ccwapp: INFO + com.finca.ccw: INFO management: metrics: @@ -33,7 +33,7 @@ spring: enabled: false datasource: type: com.zaxxer.hikari.HikariDataSource - url: jdbc:mysql://localhost:3306/CollectionTrackingApplication?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true + url: jdbc:mysql://localhost:3306/CCWApplication?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&createDatabaseIfNotExist=true username: root password: hikari: @@ -59,7 +59,7 @@ spring: # =================================================================== # To enable TLS in production, generate a certificate using: -# keytool -genkey -alias collectiontrackingapplication -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 +# keytool -genkey -alias ccwapplication -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 # # You can also use Let's Encrypt: # https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 61a8916..5e83991 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -59,7 +59,7 @@ management: spring: application: - name: CollectionTrackingApplication + name: CCWApplication profiles: # The commented value for `active` can be replaced with valid Spring profiles to load. # Otherwise, it will be filled in by maven when building the JAR file @@ -97,13 +97,13 @@ spring: allow-bean-definition-overriding: true task: execution: - thread-name-prefix: collection-tracking-application-task- + thread-name-prefix: ccw-application-task- pool: core-size: 2 max-size: 50 queue-capacity: 10000 scheduling: - thread-name-prefix: collection-tracking-application-scheduling- + thread-name-prefix: ccw-application-scheduling- pool: size: 2 thymeleaf: @@ -131,7 +131,7 @@ info: jhipster: clientApp: - name: 'collectionTrackingApplicationApp' + name: 'ccwApplicationApp' # By default CORS is disabled. Uncomment to enable. # cors: # allowed-origins: "*" @@ -141,11 +141,11 @@ jhipster: # allow-credentials: true # max-age: 1800 mail: - from: CollectionTrackingApplication@localhost + from: CCWApplication@localhost swagger: default-include-pattern: /api/.* - title: CollectionTrackingApplication API - description: CollectionTrackingApplication API documentation + title: CCWApplication API + description: CCWApplication API documentation version: 0.0.1 terms-of-service-url: contact-name: diff --git a/src/main/resources/config/liquibase/changelog/20201230155237_added_entity_Employee.xml b/src/main/resources/config/liquibase/changelog/20201230155237_added_entity_Employee.xml new file mode 100644 index 0000000..9d88ee2 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201230155237_added_entity_Employee.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_CollectionTracking.xml b/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_CollectionTracking.xml new file mode 100644 index 0000000..cff0765 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_CollectionTracking.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_constraints_CollectionTracking.xml b/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_constraints_CollectionTracking.xml new file mode 100644 index 0000000..63fe5bd --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201230155337_added_entity_constraints_CollectionTracking.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/main/resources/config/liquibase/fake-data/collection_tracking.csv b/src/main/resources/config/liquibase/fake-data/collection_tracking.csv new file mode 100644 index 0000000..1a1c6f7 --- /dev/null +++ b/src/main/resources/config/liquibase/fake-data/collection_tracking.csv @@ -0,0 +1,11 @@ +id;employee_id;unit_id;employee_name;business_proposal;sub_proposal;mobile_no;relation_id;account_no;account_title;no_of_visits;os_amount;os_profit;od_days;loan_officer;visited_by;ppt_date;remakrs +1;Identity;Summit;Handmade;Product;Ecuador Technician;bluetooth exploit;solutions compressing;Investment Account;withdrawal;31768;54559;7719;overriding frictionless;olive;Rustic Director;2020-12-29;North Dakota +2;input pixel;Points Dynamic homogeneous;SMS bandwidth;Garden Associate Concrete;lime;deposit Suriname Tasty Rubber Bacon;bandwidth;success Cuban Peso Peso Convertible strategy;copying;63793;33747;52066;Chicken;Sausages Automotive Wooden;Associate cross-platform Home Loan Account;2020-12-29;Officer +3;morph logistical JSON;Tasty;synthesizing;Ohio China virtual;connecting;plum European Monetary Unit (E.M.U.-6);solutions Granite Persistent;Diverse;channels;23013;95305;72004;Shoes;cyan Comoro Franc Shoes;Metrics tan;2020-12-29;Marketing +4;Falls Mission;Tasty integrated XSS;Handcrafted Metal Car cross-platform;override;Integration;backing up online;e-tailers;overriding Sports Fish;Louisiana;31725;48342;82482;middleware aggregate sky blue;Montana solid state application;e-enable Steel;2020-12-30;Nevada +5;Sausages Unbranded Fiji;withdrawal;moderator protocol;Gambia Central generate;Creative fault-tolerant innovate;Sports Gorgeous Rubber Sausages California;Unbranded Gambia unleash;Steel synergies;Credit Card Account;11615;26612;64204;connecting bypass Uganda Shilling;Decentralized Surinam Dollar;Auto Loan Account;2020-12-30;parallelism Small Concrete Table +6;Soft Croatia Cheese;Movies Licensed Cotton Keyboard;Handmade Steel Bacon Crescent;Rustic;Niger;protocol Group;Sleek invoice;Configurable Baby;Factors withdrawal;18541;14656;37568;Cross-platform Data Fantastic Steel Sausages;mobile Central e-markets;contextually-based Dynamic;2020-12-30;Berkshire +7;Sports encompassing;Officer Pants Jewelery;Dynamic;withdrawal orchid repurpose;Metrics;Bacon modular web services;haptic Metal Jewelery;Uzbekistan Sum Assistant Comoro Franc;Soft red payment;22404;38045;6949;Avon;Sleek Soft Chips;Dynamic;2020-12-30;protocol Metal +8;interface card Intelligent;Outdoors Handmade Rubber Sausages;models Agent;withdrawal Adaptive Frozen;Cedi;deposit connect;Azerbaijanian Manat harness;Turnpike;Progressive Costa Rican Colon;24911;63731;2476;copy Oklahoma;grey Rand Namibia Dollar indexing;black Bedfordshire invoice;2020-12-30;Phased 24/7 +9;Shirt solid state Central;HTTP Orchestrator Rhode Island;Ergonomic Fresh Car Illinois New Hampshire;rich;Shoals Plastic Internal;uniform;dynamic;optical;protocol Investor;8193;75891;68896;Berkshire drive Islands;Nevada Baby Books;CFP Franc Automotive;2020-12-30;Cambridgeshire Personal Loan Account Games +10;solutions;North Carolina bandwidth;JBOD maximize Vanuatu;Fort Sleek copying;Credit Card Account neural;Strategist haptic;drive Center;Personal Loan Account copying;engage Computers;19421;87579;15990;Plastic Assurance invoice;global Home Loan Account;wireless Cheese;2020-12-29;back-end withdrawal Checking Account diff --git a/src/main/resources/config/liquibase/fake-data/employee.csv b/src/main/resources/config/liquibase/fake-data/employee.csv new file mode 100644 index 0000000..745851e --- /dev/null +++ b/src/main/resources/config/liquibase/fake-data/employee.csv @@ -0,0 +1,11 @@ +id;employee_id;unit_id;first_name;last_name;email_address;login +1;calculate Investment Account;Soft Investment Account port;Lora;Hessel;Realigned;Tuna Granite +2;leading-edge;withdrawal;Kevon;Botsford;silver bypassing Home;bandwidth-monitored Sleek +3;monitor payment Switchable;dot-com;Jenifer;Krajcik;indigo orchestrate;Global +4;Germany National Albania;Singapore;Unique;MacGyver;Berkshire;Plastic +5;Concrete;Codes specifically reserved for testing purposes lime;Madaline;Lesch;frictionless Factors;deposit Decentralized Home Loan Account +6;Fish ROI Sausages;Designer payment;Jadyn;Dicki;infrastructures 24 hour Sleek;Cambridgeshire Illinois +7;Small Concrete Chicken;Won Bacon invoice;Cedrick;Erdman;Table;auxiliary microchip Concrete +8;1080p;Bedfordshire;Hipolito;Russel;Kids Checking Account;National +9;Pants;withdrawal;Christop;Abernathy;Rustic Personal Loan Account;Ball Investor +10;Soft Crest Practical Wooden Towels;monetize Pataca;Kadin;Fadel;Group De-engineered;communities incentivize payment diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 6b937fe..800c35c 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -14,7 +14,10 @@ + + + diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index ead8416..29e4014 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -5,17 +5,17 @@ error.status=Status: error.message=Message: # Activation email -email.activation.title=CollectionTrackingApplication account activation is required +email.activation.title=CCWApplication account activation is required email.activation.greeting=Dear {0} -email.activation.text1=Your CollectionTrackingApplication account has been created, please click on the URL below to activate it: +email.activation.text1=Your CCWApplication account has been created, please click on the URL below to activate it: email.activation.text2=Regards, -email.signature=CollectionTrackingApplication Team. +email.signature=CCWApplication Team. # Creation email -email.creation.text1=Your CollectionTrackingApplication account has been created, please click on the URL below to access it: +email.creation.text1=Your CCWApplication account has been created, please click on the URL below to access it: # Reset email -email.reset.title=CollectionTrackingApplication password reset +email.reset.title=CCWApplication password reset email.reset.greeting=Dear {0} -email.reset.text1=For your CollectionTrackingApplication account a password reset was requested, please click on the URL below to reset it: +email.reset.text1=For your CCWApplication account a password reset was requested, please click on the URL below to reset it: email.reset.text2=Regards, diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties index f4937b5..a96bfea 100644 --- a/src/main/resources/i18n/messages_en.properties +++ b/src/main/resources/i18n/messages_en.properties @@ -5,17 +5,17 @@ error.status=Status: error.message=Message: # Activation email -email.activation.title=CollectionTrackingApplication account activation +email.activation.title=CCWApplication account activation email.activation.greeting=Dear {0} -email.activation.text1=Your CollectionTrackingApplication account has been created, please click on the URL below to activate it: +email.activation.text1=Your CCWApplication account has been created, please click on the URL below to activate it: email.activation.text2=Regards, -email.signature=CollectionTrackingApplication Team. +email.signature=CCWApplication Team. # Creation email -email.creation.text1=Your CollectionTrackingApplication account has been created, please click on the URL below to access it: +email.creation.text1=Your CCWApplication account has been created, please click on the URL below to access it: # Reset email -email.reset.title=CollectionTrackingApplication password reset +email.reset.title=CCWApplication password reset email.reset.greeting=Dear {0} -email.reset.text1=For your CollectionTrackingApplication account a password reset was requested, please click on the URL below to reset it: +email.reset.text1=For your CCWApplication account a password reset was requested, please click on the URL below to reset it: email.reset.text2=Regards, diff --git a/src/main/resources/i18n/messages_fr.properties b/src/main/resources/i18n/messages_fr.properties new file mode 100644 index 0000000..198d6ee --- /dev/null +++ b/src/main/resources/i18n/messages_fr.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Votre demande ne peut être traitée +error.subtitle=Désolé, une erreur s'est produite. +error.status=Statut : +error.message=Message : + +# Activation email +email.activation.title=Activation de votre compte CCWApplication +email.activation.greeting=Cher {0} +email.activation.text1=Votre compte CCWApplication a été créé, pour l'activer merci de cliquer sur le lien ci-dessous : +email.activation.text2=Cordialement, +email.signature=CCWApplication. + +# Creation email +email.creation.text1=Votre compte CCWApplication a été créé, merci de cliquer sur le lien ci-dessous pour y accéder : + +# Reset email +email.reset.title=CCWApplication Réinitialisation de mot de passe +email.reset.greeting=Cher {0} +email.reset.text1=Un nouveau mot de passe pour votre compte CCWApplication a été demandé, veuillez cliquer sur le lien ci-dessous pour le réinitialiser : +email.reset.text2=Cordialement, diff --git a/src/main/webapp/app/account/account.module.ts b/src/main/webapp/app/account/account.module.ts index 63e94ca..d2d2e42 100644 --- a/src/main/webapp/app/account/account.module.ts +++ b/src/main/webapp/app/account/account.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { PasswordStrengthBarComponent } from './password/password-strength-bar.component'; import { RegisterComponent } from './register/register.component'; @@ -13,7 +13,7 @@ import { SettingsComponent } from './settings/settings.component'; import { accountState } from './account.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild(accountState)], + imports: [CcwApplicationSharedModule, RouterModule.forChild(accountState)], declarations: [ ActivateComponent, RegisterComponent, diff --git a/src/main/webapp/app/admin/audits/audits.module.ts b/src/main/webapp/app/admin/audits/audits.module.ts index 00c3a44..d3d6908 100644 --- a/src/main/webapp/app/admin/audits/audits.module.ts +++ b/src/main/webapp/app/admin/audits/audits.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { AuditsComponent } from './audits.component'; import { auditsRoute } from './audits.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([auditsRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([auditsRoute])], declarations: [AuditsComponent], }) export class AuditsModule {} diff --git a/src/main/webapp/app/admin/configuration/configuration.module.ts b/src/main/webapp/app/admin/configuration/configuration.module.ts index eca4e04..459b045 100644 --- a/src/main/webapp/app/admin/configuration/configuration.module.ts +++ b/src/main/webapp/app/admin/configuration/configuration.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { ConfigurationComponent } from './configuration.component'; import { configurationRoute } from './configuration.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([configurationRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([configurationRoute])], declarations: [ConfigurationComponent], }) export class ConfigurationModule {} diff --git a/src/main/webapp/app/admin/docs/docs.module.ts b/src/main/webapp/app/admin/docs/docs.module.ts index c964d5f..918feda 100644 --- a/src/main/webapp/app/admin/docs/docs.module.ts +++ b/src/main/webapp/app/admin/docs/docs.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { DocsComponent } from './docs.component'; import { docsRoute } from './docs.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([docsRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([docsRoute])], declarations: [DocsComponent], }) export class DocsModule {} diff --git a/src/main/webapp/app/admin/health/health.module.ts b/src/main/webapp/app/admin/health/health.module.ts index df7a91a..a98966e 100644 --- a/src/main/webapp/app/admin/health/health.module.ts +++ b/src/main/webapp/app/admin/health/health.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { HealthComponent } from './health.component'; import { HealthModalComponent } from './health-modal.component'; @@ -8,7 +8,7 @@ import { HealthModalComponent } from './health-modal.component'; import { healthRoute } from './health.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([healthRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([healthRoute])], declarations: [HealthComponent, HealthModalComponent], entryComponents: [HealthModalComponent], }) diff --git a/src/main/webapp/app/admin/logs/logs.module.ts b/src/main/webapp/app/admin/logs/logs.module.ts index 227f30d..37a5e5b 100644 --- a/src/main/webapp/app/admin/logs/logs.module.ts +++ b/src/main/webapp/app/admin/logs/logs.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { LogsComponent } from './logs.component'; import { logsRoute } from './logs.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([logsRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([logsRoute])], declarations: [LogsComponent], }) export class LogsModule {} diff --git a/src/main/webapp/app/admin/metrics/metrics.module.ts b/src/main/webapp/app/admin/metrics/metrics.module.ts index f50cabe..0d323c1 100644 --- a/src/main/webapp/app/admin/metrics/metrics.module.ts +++ b/src/main/webapp/app/admin/metrics/metrics.module.ts @@ -1,13 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { MetricsComponent } from './metrics.component'; import { metricsRoute } from './metrics.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([metricsRoute])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([metricsRoute])], declarations: [MetricsComponent], }) export class MetricsModule {} diff --git a/src/main/webapp/app/admin/user-management/user-management.module.ts b/src/main/webapp/app/admin/user-management/user-management.module.ts index b16e15f..e29895e 100644 --- a/src/main/webapp/app/admin/user-management/user-management.module.ts +++ b/src/main/webapp/app/admin/user-management/user-management.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { UserManagementComponent } from './user-management.component'; import { UserManagementDetailComponent } from './user-management-detail.component'; import { UserManagementUpdateComponent } from './user-management-update.component'; @@ -9,7 +9,7 @@ import { UserManagementDeleteDialogComponent } from './user-management-delete-di import { userManagementRoute } from './user-management.route'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild(userManagementRoute)], + imports: [CcwApplicationSharedModule, RouterModule.forChild(userManagementRoute)], declarations: [ UserManagementComponent, UserManagementDetailComponent, diff --git a/src/main/webapp/app/app-routing.module.ts b/src/main/webapp/app/app-routing.module.ts index f345f05..b6d8ffd 100644 --- a/src/main/webapp/app/app-routing.module.ts +++ b/src/main/webapp/app/app-routing.module.ts @@ -32,4 +32,4 @@ const LAYOUT_ROUTES = [navbarRoute, ...errorRoute]; ], exports: [RouterModule], }) -export class CollectionTrackingApplicationAppRoutingModule {} +export class CcwApplicationAppRoutingModule {} diff --git a/src/main/webapp/app/app.main.ts b/src/main/webapp/app/app.main.ts index f193d61..3978788 100644 --- a/src/main/webapp/app/app.main.ts +++ b/src/main/webapp/app/app.main.ts @@ -1,7 +1,7 @@ import './polyfills'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { ProdConfig } from './blocks/config/prod.config'; -import { CollectionTrackingApplicationAppModule } from './app.module'; +import { CcwApplicationAppModule } from './app.module'; ProdConfig(); @@ -10,7 +10,7 @@ if (module['hot']) { } platformBrowserDynamic() - .bootstrapModule(CollectionTrackingApplicationAppModule, { preserveWhitespaces: true }) + .bootstrapModule(CcwApplicationAppModule, { preserveWhitespaces: true }) // eslint-disable-next-line no-console .then(() => console.log('Application started')) .catch(err => console.error(err)); diff --git a/src/main/webapp/app/app.module.ts b/src/main/webapp/app/app.module.ts index c6eaacf..954619b 100644 --- a/src/main/webapp/app/app.module.ts +++ b/src/main/webapp/app/app.module.ts @@ -2,11 +2,11 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import './vendor'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; -import { CollectionTrackingApplicationCoreModule } from 'app/core/core.module'; -import { CollectionTrackingApplicationAppRoutingModule } from './app-routing.module'; -import { CollectionTrackingApplicationHomeModule } from './home/home.module'; -import { CollectionTrackingApplicationEntityModule } from './entities/entity.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationCoreModule } from 'app/core/core.module'; +import { CcwApplicationAppRoutingModule } from './app-routing.module'; +import { CcwApplicationHomeModule } from './home/home.module'; +import { CcwApplicationEntityModule } from './entities/entity.module'; // jhipster-needle-angular-add-module-import JHipster will add new module here import { MainComponent } from './layouts/main/main.component'; import { NavbarComponent } from './layouts/navbar/navbar.component'; @@ -18,14 +18,14 @@ import { ErrorComponent } from './layouts/error/error.component'; @NgModule({ imports: [ BrowserModule, - CollectionTrackingApplicationSharedModule, - CollectionTrackingApplicationCoreModule, - CollectionTrackingApplicationHomeModule, + CcwApplicationSharedModule, + CcwApplicationCoreModule, + CcwApplicationHomeModule, // jhipster-needle-angular-add-module JHipster will add new module here - CollectionTrackingApplicationEntityModule, - CollectionTrackingApplicationAppRoutingModule, + CcwApplicationEntityModule, + CcwApplicationAppRoutingModule, ], declarations: [MainComponent, NavbarComponent, ErrorComponent, PageRibbonComponent, ActiveMenuDirective, FooterComponent], bootstrap: [MainComponent], }) -export class CollectionTrackingApplicationAppModule {} +export class CcwApplicationAppModule {} diff --git a/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts b/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts index 84c891d..1dca392 100644 --- a/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts +++ b/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts @@ -12,7 +12,7 @@ export class ErrorHandlerInterceptor implements HttpInterceptor { return next.handle(request).pipe( tap(null, (err: HttpErrorResponse) => { if (!(err.status === 401 && (err.message === '' || (err.url && err.url.includes('api/account'))))) { - this.eventManager.broadcast(new JhiEventWithContent('collectionTrackingApplicationApp.httpError', err)); + this.eventManager.broadcast(new JhiEventWithContent('ccwApplicationApp.httpError', err)); } }) ); diff --git a/src/main/webapp/app/core/core.module.ts b/src/main/webapp/app/core/core.module.ts index c2ee3ba..c703f0d 100644 --- a/src/main/webapp/app/core/core.module.ts +++ b/src/main/webapp/app/core/core.module.ts @@ -75,7 +75,7 @@ import { fontAwesomeIcons } from './icons/font-awesome-icons'; }, ], }) -export class CollectionTrackingApplicationCoreModule { +export class CcwApplicationCoreModule { constructor(iconLibrary: FaIconLibrary, dpConfig: NgbDatepickerConfig, languageService: JhiLanguageService) { registerLocaleData(locale); iconLibrary.addIcons(...fontAwesomeIcons); diff --git a/src/main/webapp/app/core/language/language.constants.ts b/src/main/webapp/app/core/language/language.constants.ts index aa07208..0679ebb 100644 --- a/src/main/webapp/app/core/language/language.constants.ts +++ b/src/main/webapp/app/core/language/language.constants.ts @@ -4,5 +4,6 @@ */ export const LANGUAGES: string[] = [ 'en', + 'fr', // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array ]; diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.html b/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.html new file mode 100644 index 0000000..39860c5 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.html @@ -0,0 +1,24 @@ +

+ + + + + +
diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.ts new file mode 100644 index 0000000..3c154f2 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-delete-dialog.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { JhiEventManager } from 'ng-jhipster'; + +import { ICollectionTracking } from 'app/shared/model/collection-tracking.model'; +import { CollectionTrackingService } from './collection-tracking.service'; + +@Component({ + templateUrl: './collection-tracking-delete-dialog.component.html', +}) +export class CollectionTrackingDeleteDialogComponent { + collectionTracking?: ICollectionTracking; + + constructor( + protected collectionTrackingService: CollectionTrackingService, + public activeModal: NgbActiveModal, + protected eventManager: JhiEventManager + ) {} + + cancel(): void { + this.activeModal.dismiss(); + } + + confirmDelete(id: number): void { + this.collectionTrackingService.delete(id).subscribe(() => { + this.eventManager.broadcast('collectionTrackingListModification'); + this.activeModal.close(); + }); + } +} diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.html b/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.html new file mode 100644 index 0000000..409af04 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.html @@ -0,0 +1,100 @@ +
+
+
+

Collection Tracking {{ collectionTracking.id }}

+ +
+ + + +
+
Employee ID
+
+ {{ collectionTracking.employeeID }} +
+
Unit ID
+
+ {{ collectionTracking.unitID }} +
+
Employee Name
+
+ {{ collectionTracking.employeeName }} +
+
Business Proposal
+
+ {{ collectionTracking.businessProposal }} +
+
Sub Proposal
+
+ {{ collectionTracking.subProposal }} +
+
Mobile No
+
+ {{ collectionTracking.mobileNo }} +
+
Relation Id
+
+ {{ collectionTracking.relationId }} +
+
Account No
+
+ {{ collectionTracking.accountNo }} +
+
Account Title
+
+ {{ collectionTracking.accountTitle }} +
+
No Of Visits
+
+ {{ collectionTracking.noOfVisits }} +
+
Os Amount
+
+ {{ collectionTracking.osAmount }} +
+
Os Profit
+
+ {{ collectionTracking.osProfit }} +
+
Od Days
+
+ {{ collectionTracking.odDays }} +
+
Loan Officer
+
+ {{ collectionTracking.loanOfficer }} +
+
Visited By
+
+ {{ collectionTracking.visitedBy }} +
+
Ppt Date
+
+ {{ collectionTracking.pptDate }} +
+
Remakrs
+
+ {{ collectionTracking.remakrs }} +
+
Employee
+
+ +
+
+ + + + +
+
+
diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.ts new file mode 100644 index 0000000..acc253b --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-detail.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { ICollectionTracking } from 'app/shared/model/collection-tracking.model'; + +@Component({ + selector: 'jhi-collection-tracking-detail', + templateUrl: './collection-tracking-detail.component.html', +}) +export class CollectionTrackingDetailComponent implements OnInit { + collectionTracking: ICollectionTracking | null = null; + + constructor(protected activatedRoute: ActivatedRoute) {} + + ngOnInit(): void { + this.activatedRoute.data.subscribe(({ collectionTracking }) => (this.collectionTracking = collectionTracking)); + } + + previousState(): void { + window.history.back(); + } +} diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.html b/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.html new file mode 100644 index 0000000..3831033 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.html @@ -0,0 +1,254 @@ +
+
+
+

Create or edit a Collection Tracking

+ +
+ + +
+ + +
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + + + This field should be a number. + +
+
+ +
+ + +
+ + This field is required. + + + This field should be a number. + +
+
+ +
+ + +
+ + This field is required. + + + This field should be a number. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ +
+ + + + +
+
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+
+ +
+ + + +
+
+
+
diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.ts new file mode 100644 index 0000000..2c7387a --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking-update.component.ts @@ -0,0 +1,141 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { FormBuilder, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { ICollectionTracking, CollectionTracking } from 'app/shared/model/collection-tracking.model'; +import { CollectionTrackingService } from './collection-tracking.service'; +import { IEmployee } from 'app/shared/model/employee.model'; +import { EmployeeService } from 'app/entities/employee/employee.service'; + +@Component({ + selector: 'jhi-collection-tracking-update', + templateUrl: './collection-tracking-update.component.html', +}) +export class CollectionTrackingUpdateComponent implements OnInit { + isSaving = false; + employees: IEmployee[] = []; + pptDateDp: any; + + editForm = this.fb.group({ + id: [], + employeeID: [null, [Validators.required]], + unitID: [null, [Validators.required]], + employeeName: [null, [Validators.required]], + businessProposal: [null, [Validators.required]], + subProposal: [null, [Validators.required]], + mobileNo: [null, [Validators.required]], + relationId: [null, [Validators.required]], + accountNo: [null, [Validators.required]], + accountTitle: [null, [Validators.required]], + noOfVisits: [null, [Validators.required]], + osAmount: [null, [Validators.required]], + osProfit: [null, [Validators.required]], + odDays: [null, [Validators.required]], + loanOfficer: [null, [Validators.required]], + visitedBy: [null, [Validators.required]], + pptDate: [null, [Validators.required]], + remakrs: [null, [Validators.required]], + employee: [], + }); + + constructor( + protected collectionTrackingService: CollectionTrackingService, + protected employeeService: EmployeeService, + protected activatedRoute: ActivatedRoute, + private fb: FormBuilder + ) {} + + ngOnInit(): void { + this.activatedRoute.data.subscribe(({ collectionTracking }) => { + this.updateForm(collectionTracking); + + this.employeeService.query().subscribe((res: HttpResponse) => (this.employees = res.body || [])); + }); + } + + updateForm(collectionTracking: ICollectionTracking): void { + this.editForm.patchValue({ + id: collectionTracking.id, + employeeID: collectionTracking.employeeID, + unitID: collectionTracking.unitID, + employeeName: collectionTracking.employeeName, + businessProposal: collectionTracking.businessProposal, + subProposal: collectionTracking.subProposal, + mobileNo: collectionTracking.mobileNo, + relationId: collectionTracking.relationId, + accountNo: collectionTracking.accountNo, + accountTitle: collectionTracking.accountTitle, + noOfVisits: collectionTracking.noOfVisits, + osAmount: collectionTracking.osAmount, + osProfit: collectionTracking.osProfit, + odDays: collectionTracking.odDays, + loanOfficer: collectionTracking.loanOfficer, + visitedBy: collectionTracking.visitedBy, + pptDate: collectionTracking.pptDate, + remakrs: collectionTracking.remakrs, + employee: collectionTracking.employee, + }); + } + + previousState(): void { + window.history.back(); + } + + save(): void { + this.isSaving = true; + const collectionTracking = this.createFromForm(); + if (collectionTracking.id !== undefined) { + this.subscribeToSaveResponse(this.collectionTrackingService.update(collectionTracking)); + } else { + this.subscribeToSaveResponse(this.collectionTrackingService.create(collectionTracking)); + } + } + + private createFromForm(): ICollectionTracking { + return { + ...new CollectionTracking(), + id: this.editForm.get(['id'])!.value, + employeeID: this.editForm.get(['employeeID'])!.value, + unitID: this.editForm.get(['unitID'])!.value, + employeeName: this.editForm.get(['employeeName'])!.value, + businessProposal: this.editForm.get(['businessProposal'])!.value, + subProposal: this.editForm.get(['subProposal'])!.value, + mobileNo: this.editForm.get(['mobileNo'])!.value, + relationId: this.editForm.get(['relationId'])!.value, + accountNo: this.editForm.get(['accountNo'])!.value, + accountTitle: this.editForm.get(['accountTitle'])!.value, + noOfVisits: this.editForm.get(['noOfVisits'])!.value, + osAmount: this.editForm.get(['osAmount'])!.value, + osProfit: this.editForm.get(['osProfit'])!.value, + odDays: this.editForm.get(['odDays'])!.value, + loanOfficer: this.editForm.get(['loanOfficer'])!.value, + visitedBy: this.editForm.get(['visitedBy'])!.value, + pptDate: this.editForm.get(['pptDate'])!.value, + remakrs: this.editForm.get(['remakrs'])!.value, + employee: this.editForm.get(['employee'])!.value, + }; + } + + protected subscribeToSaveResponse(result: Observable>): void { + result.subscribe( + () => this.onSaveSuccess(), + () => this.onSaveError() + ); + } + + protected onSaveSuccess(): void { + this.isSaving = false; + this.previousState(); + } + + protected onSaveError(): void { + this.isSaving = false; + } + + trackById(index: number, item: IEmployee): any { + return item.id; + } +} diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.html b/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.html new file mode 100644 index 0000000..97d0e38 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.html @@ -0,0 +1,99 @@ +
+

+ Collection Trackings + + +

+ + + + + +
+ No collectionTrackings found +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDEmployee IDUnit IDEmployee NameBusiness ProposalSub ProposalMobile NoRelation IdAccount NoAccount TitleNo Of VisitsOs AmountOs ProfitOd DaysLoan OfficerVisited ByPpt DateRemakrsEmployee
{{ collectionTracking.id }}{{ collectionTracking.employeeID }}{{ collectionTracking.unitID }}{{ collectionTracking.employeeName }}{{ collectionTracking.businessProposal }}{{ collectionTracking.subProposal }}{{ collectionTracking.mobileNo }}{{ collectionTracking.relationId }}{{ collectionTracking.accountNo }}{{ collectionTracking.accountTitle }}{{ collectionTracking.noOfVisits }}{{ collectionTracking.osAmount }}{{ collectionTracking.osProfit }}{{ collectionTracking.odDays }}{{ collectionTracking.loanOfficer }}{{ collectionTracking.visitedBy }}{{ collectionTracking.pptDate | date:'mediumDate' }}{{ collectionTracking.remakrs }} + + +
+ + + + + +
+
+
+
diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.ts new file mode 100644 index 0000000..a8bb0fd --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking.component.ts @@ -0,0 +1,55 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +import { Subscription } from 'rxjs'; +import { JhiEventManager } from 'ng-jhipster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { ICollectionTracking } from 'app/shared/model/collection-tracking.model'; +import { CollectionTrackingService } from './collection-tracking.service'; +import { CollectionTrackingDeleteDialogComponent } from './collection-tracking-delete-dialog.component'; + +@Component({ + selector: 'jhi-collection-tracking', + templateUrl: './collection-tracking.component.html', +}) +export class CollectionTrackingComponent implements OnInit, OnDestroy { + collectionTrackings?: ICollectionTracking[]; + eventSubscriber?: Subscription; + + constructor( + protected collectionTrackingService: CollectionTrackingService, + protected eventManager: JhiEventManager, + protected modalService: NgbModal + ) {} + + loadAll(): void { + this.collectionTrackingService + .query() + .subscribe((res: HttpResponse) => (this.collectionTrackings = res.body || [])); + } + + ngOnInit(): void { + this.loadAll(); + this.registerChangeInCollectionTrackings(); + } + + ngOnDestroy(): void { + if (this.eventSubscriber) { + this.eventManager.destroy(this.eventSubscriber); + } + } + + trackId(index: number, item: ICollectionTracking): number { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return item.id!; + } + + registerChangeInCollectionTrackings(): void { + this.eventSubscriber = this.eventManager.subscribe('collectionTrackingListModification', () => this.loadAll()); + } + + delete(collectionTracking: ICollectionTracking): void { + const modalRef = this.modalService.open(CollectionTrackingDeleteDialogComponent, { size: 'lg', backdrop: 'static' }); + modalRef.componentInstance.collectionTracking = collectionTracking; + } +} diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking.module.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking.module.ts new file mode 100644 index 0000000..f1fdd2b --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; +import { CollectionTrackingComponent } from './collection-tracking.component'; +import { CollectionTrackingDetailComponent } from './collection-tracking-detail.component'; +import { CollectionTrackingUpdateComponent } from './collection-tracking-update.component'; +import { CollectionTrackingDeleteDialogComponent } from './collection-tracking-delete-dialog.component'; +import { collectionTrackingRoute } from './collection-tracking.route'; + +@NgModule({ + imports: [CcwApplicationSharedModule, RouterModule.forChild(collectionTrackingRoute)], + declarations: [ + CollectionTrackingComponent, + CollectionTrackingDetailComponent, + CollectionTrackingUpdateComponent, + CollectionTrackingDeleteDialogComponent, + ], + entryComponents: [CollectionTrackingDeleteDialogComponent], +}) +export class CcwApplicationCollectionTrackingModule {} diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking.route.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking.route.ts new file mode 100644 index 0000000..460a959 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking.route.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +import { Resolve, ActivatedRouteSnapshot, Routes, Router } from '@angular/router'; +import { Observable, of, EMPTY } from 'rxjs'; +import { flatMap } from 'rxjs/operators'; + +import { Authority } from 'app/shared/constants/authority.constants'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; +import { ICollectionTracking, CollectionTracking } from 'app/shared/model/collection-tracking.model'; +import { CollectionTrackingService } from './collection-tracking.service'; +import { CollectionTrackingComponent } from './collection-tracking.component'; +import { CollectionTrackingDetailComponent } from './collection-tracking-detail.component'; +import { CollectionTrackingUpdateComponent } from './collection-tracking-update.component'; + +@Injectable({ providedIn: 'root' }) +export class CollectionTrackingResolve implements Resolve { + constructor(private service: CollectionTrackingService, private router: Router) {} + + resolve(route: ActivatedRouteSnapshot): Observable | Observable { + const id = route.params['id']; + if (id) { + return this.service.find(id).pipe( + flatMap((collectionTracking: HttpResponse) => { + if (collectionTracking.body) { + return of(collectionTracking.body); + } else { + this.router.navigate(['404']); + return EMPTY; + } + }) + ); + } + return of(new CollectionTracking()); + } +} + +export const collectionTrackingRoute: Routes = [ + { + path: '', + component: CollectionTrackingComponent, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.collectionTracking.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: ':id/view', + component: CollectionTrackingDetailComponent, + resolve: { + collectionTracking: CollectionTrackingResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.collectionTracking.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: 'new', + component: CollectionTrackingUpdateComponent, + resolve: { + collectionTracking: CollectionTrackingResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.collectionTracking.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: ':id/edit', + component: CollectionTrackingUpdateComponent, + resolve: { + collectionTracking: CollectionTrackingResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.collectionTracking.home.title', + }, + canActivate: [UserRouteAccessService], + }, +]; diff --git a/src/main/webapp/app/entities/collection-tracking/collection-tracking.service.ts b/src/main/webapp/app/entities/collection-tracking/collection-tracking.service.ts new file mode 100644 index 0000000..1e40bf3 --- /dev/null +++ b/src/main/webapp/app/entities/collection-tracking/collection-tracking.service.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import * as moment from 'moment'; + +import { DATE_FORMAT } from 'app/shared/constants/input.constants'; +import { SERVER_API_URL } from 'app/app.constants'; +import { createRequestOption } from 'app/shared/util/request-util'; +import { ICollectionTracking } from 'app/shared/model/collection-tracking.model'; + +type EntityResponseType = HttpResponse; +type EntityArrayResponseType = HttpResponse; + +@Injectable({ providedIn: 'root' }) +export class CollectionTrackingService { + public resourceUrl = SERVER_API_URL + 'api/collection-trackings'; + + constructor(protected http: HttpClient) {} + + create(collectionTracking: ICollectionTracking): Observable { + const copy = this.convertDateFromClient(collectionTracking); + return this.http + .post(this.resourceUrl, copy, { observe: 'response' }) + .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); + } + + update(collectionTracking: ICollectionTracking): Observable { + const copy = this.convertDateFromClient(collectionTracking); + return this.http + .put(this.resourceUrl, copy, { observe: 'response' }) + .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); + } + + find(id: number): Observable { + return this.http + .get(`${this.resourceUrl}/${id}`, { observe: 'response' }) + .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); + } + + query(req?: any): Observable { + const options = createRequestOption(req); + return this.http + .get(this.resourceUrl, { params: options, observe: 'response' }) + .pipe(map((res: EntityArrayResponseType) => this.convertDateArrayFromServer(res))); + } + + delete(id: number): Observable> { + return this.http.delete(`${this.resourceUrl}/${id}`, { observe: 'response' }); + } + + protected convertDateFromClient(collectionTracking: ICollectionTracking): ICollectionTracking { + const copy: ICollectionTracking = Object.assign({}, collectionTracking, { + pptDate: + collectionTracking.pptDate && collectionTracking.pptDate.isValid() ? collectionTracking.pptDate.format(DATE_FORMAT) : undefined, + }); + return copy; + } + + protected convertDateFromServer(res: EntityResponseType): EntityResponseType { + if (res.body) { + res.body.pptDate = res.body.pptDate ? moment(res.body.pptDate) : undefined; + } + return res; + } + + protected convertDateArrayFromServer(res: EntityArrayResponseType): EntityArrayResponseType { + if (res.body) { + res.body.forEach((collectionTracking: ICollectionTracking) => { + collectionTracking.pptDate = collectionTracking.pptDate ? moment(collectionTracking.pptDate) : undefined; + }); + } + return res; + } +} diff --git a/src/main/webapp/app/entities/employee/employee-delete-dialog.component.html b/src/main/webapp/app/entities/employee/employee-delete-dialog.component.html new file mode 100644 index 0000000..eeb8831 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-delete-dialog.component.html @@ -0,0 +1,24 @@ +
+ + + + + +
diff --git a/src/main/webapp/app/entities/employee/employee-delete-dialog.component.ts b/src/main/webapp/app/entities/employee/employee-delete-dialog.component.ts new file mode 100644 index 0000000..32e3bdb --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-delete-dialog.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { JhiEventManager } from 'ng-jhipster'; + +import { IEmployee } from 'app/shared/model/employee.model'; +import { EmployeeService } from './employee.service'; + +@Component({ + templateUrl: './employee-delete-dialog.component.html', +}) +export class EmployeeDeleteDialogComponent { + employee?: IEmployee; + + constructor(protected employeeService: EmployeeService, public activeModal: NgbActiveModal, protected eventManager: JhiEventManager) {} + + cancel(): void { + this.activeModal.dismiss(); + } + + confirmDelete(id: number): void { + this.employeeService.delete(id).subscribe(() => { + this.eventManager.broadcast('employeeListModification'); + this.activeModal.close(); + }); + } +} diff --git a/src/main/webapp/app/entities/employee/employee-detail.component.html b/src/main/webapp/app/entities/employee/employee-detail.component.html new file mode 100644 index 0000000..753f9b9 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-detail.component.html @@ -0,0 +1,50 @@ +
+
+
+

Employee {{ employee.id }}

+ +
+ + + +
+
Employee ID
+
+ {{ employee.employeeID }} +
+
Unit ID
+
+ {{ employee.unitID }} +
+
First Name
+
+ {{ employee.firstName }} +
+
Last Name
+
+ {{ employee.lastName }} +
+
Email Address
+
+ {{ employee.emailAddress }} +
+
Login
+
+ {{ employee.login }} +
+
+ + + + +
+
+
diff --git a/src/main/webapp/app/entities/employee/employee-detail.component.ts b/src/main/webapp/app/entities/employee/employee-detail.component.ts new file mode 100644 index 0000000..a5756f6 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-detail.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { IEmployee } from 'app/shared/model/employee.model'; + +@Component({ + selector: 'jhi-employee-detail', + templateUrl: './employee-detail.component.html', +}) +export class EmployeeDetailComponent implements OnInit { + employee: IEmployee | null = null; + + constructor(protected activatedRoute: ActivatedRoute) {} + + ngOnInit(): void { + this.activatedRoute.data.subscribe(({ employee }) => (this.employee = employee)); + } + + previousState(): void { + window.history.back(); + } +} diff --git a/src/main/webapp/app/entities/employee/employee-update.component.html b/src/main/webapp/app/entities/employee/employee-update.component.html new file mode 100644 index 0000000..bb64ac1 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-update.component.html @@ -0,0 +1,92 @@ +
+
+
+

Create or edit a Employee

+ +
+ + +
+ + +
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+ + This field is required. + +
+
+ +
+ + +
+
+ +
+ + + +
+
+
+
diff --git a/src/main/webapp/app/entities/employee/employee-update.component.ts b/src/main/webapp/app/entities/employee/employee-update.component.ts new file mode 100644 index 0000000..ad7a615 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee-update.component.ts @@ -0,0 +1,90 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { FormBuilder, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { IEmployee, Employee } from 'app/shared/model/employee.model'; +import { EmployeeService } from './employee.service'; + +@Component({ + selector: 'jhi-employee-update', + templateUrl: './employee-update.component.html', +}) +export class EmployeeUpdateComponent implements OnInit { + isSaving = false; + + editForm = this.fb.group({ + id: [], + employeeID: [null, [Validators.required]], + unitID: [null, [Validators.required]], + firstName: [null, [Validators.required]], + lastName: [null, [Validators.required]], + emailAddress: [null, [Validators.required]], + login: [], + }); + + constructor(protected employeeService: EmployeeService, protected activatedRoute: ActivatedRoute, private fb: FormBuilder) {} + + ngOnInit(): void { + this.activatedRoute.data.subscribe(({ employee }) => { + this.updateForm(employee); + }); + } + + updateForm(employee: IEmployee): void { + this.editForm.patchValue({ + id: employee.id, + employeeID: employee.employeeID, + unitID: employee.unitID, + firstName: employee.firstName, + lastName: employee.lastName, + emailAddress: employee.emailAddress, + login: employee.login, + }); + } + + previousState(): void { + window.history.back(); + } + + save(): void { + this.isSaving = true; + const employee = this.createFromForm(); + if (employee.id !== undefined) { + this.subscribeToSaveResponse(this.employeeService.update(employee)); + } else { + this.subscribeToSaveResponse(this.employeeService.create(employee)); + } + } + + private createFromForm(): IEmployee { + return { + ...new Employee(), + id: this.editForm.get(['id'])!.value, + employeeID: this.editForm.get(['employeeID'])!.value, + unitID: this.editForm.get(['unitID'])!.value, + firstName: this.editForm.get(['firstName'])!.value, + lastName: this.editForm.get(['lastName'])!.value, + emailAddress: this.editForm.get(['emailAddress'])!.value, + login: this.editForm.get(['login'])!.value, + }; + } + + protected subscribeToSaveResponse(result: Observable>): void { + result.subscribe( + () => this.onSaveSuccess(), + () => this.onSaveError() + ); + } + + protected onSaveSuccess(): void { + this.isSaving = false; + this.previousState(); + } + + protected onSaveError(): void { + this.isSaving = false; + } +} diff --git a/src/main/webapp/app/entities/employee/employee.component.html b/src/main/webapp/app/entities/employee/employee.component.html new file mode 100644 index 0000000..928b629 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee.component.html @@ -0,0 +1,71 @@ +
+

+ Employees + + +

+ + + + + +
+ No employees found +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IDEmployee IDUnit IDFirst NameLast NameEmail AddressLogin
{{ employee.id }}{{ employee.employeeID }}{{ employee.unitID }}{{ employee.firstName }}{{ employee.lastName }}{{ employee.emailAddress }}{{ employee.login }} +
+ + + + + +
+
+
+
diff --git a/src/main/webapp/app/entities/employee/employee.component.ts b/src/main/webapp/app/entities/employee/employee.component.ts new file mode 100644 index 0000000..ed92bc8 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +import { Subscription } from 'rxjs'; +import { JhiEventManager } from 'ng-jhipster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { IEmployee } from 'app/shared/model/employee.model'; +import { EmployeeService } from './employee.service'; +import { EmployeeDeleteDialogComponent } from './employee-delete-dialog.component'; + +@Component({ + selector: 'jhi-employee', + templateUrl: './employee.component.html', +}) +export class EmployeeComponent implements OnInit, OnDestroy { + employees?: IEmployee[]; + eventSubscriber?: Subscription; + + constructor(protected employeeService: EmployeeService, protected eventManager: JhiEventManager, protected modalService: NgbModal) {} + + loadAll(): void { + this.employeeService.query().subscribe((res: HttpResponse) => (this.employees = res.body || [])); + } + + ngOnInit(): void { + this.loadAll(); + this.registerChangeInEmployees(); + } + + ngOnDestroy(): void { + if (this.eventSubscriber) { + this.eventManager.destroy(this.eventSubscriber); + } + } + + trackId(index: number, item: IEmployee): number { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return item.id!; + } + + registerChangeInEmployees(): void { + this.eventSubscriber = this.eventManager.subscribe('employeeListModification', () => this.loadAll()); + } + + delete(employee: IEmployee): void { + const modalRef = this.modalService.open(EmployeeDeleteDialogComponent, { size: 'lg', backdrop: 'static' }); + modalRef.componentInstance.employee = employee; + } +} diff --git a/src/main/webapp/app/entities/employee/employee.module.ts b/src/main/webapp/app/entities/employee/employee.module.ts new file mode 100644 index 0000000..39065f9 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; +import { EmployeeComponent } from './employee.component'; +import { EmployeeDetailComponent } from './employee-detail.component'; +import { EmployeeUpdateComponent } from './employee-update.component'; +import { EmployeeDeleteDialogComponent } from './employee-delete-dialog.component'; +import { employeeRoute } from './employee.route'; + +@NgModule({ + imports: [CcwApplicationSharedModule, RouterModule.forChild(employeeRoute)], + declarations: [EmployeeComponent, EmployeeDetailComponent, EmployeeUpdateComponent, EmployeeDeleteDialogComponent], + entryComponents: [EmployeeDeleteDialogComponent], +}) +export class CcwApplicationEmployeeModule {} diff --git a/src/main/webapp/app/entities/employee/employee.route.ts b/src/main/webapp/app/entities/employee/employee.route.ts new file mode 100644 index 0000000..5c27f42 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee.route.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { HttpResponse } from '@angular/common/http'; +import { Resolve, ActivatedRouteSnapshot, Routes, Router } from '@angular/router'; +import { Observable, of, EMPTY } from 'rxjs'; +import { flatMap } from 'rxjs/operators'; + +import { Authority } from 'app/shared/constants/authority.constants'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; +import { IEmployee, Employee } from 'app/shared/model/employee.model'; +import { EmployeeService } from './employee.service'; +import { EmployeeComponent } from './employee.component'; +import { EmployeeDetailComponent } from './employee-detail.component'; +import { EmployeeUpdateComponent } from './employee-update.component'; + +@Injectable({ providedIn: 'root' }) +export class EmployeeResolve implements Resolve { + constructor(private service: EmployeeService, private router: Router) {} + + resolve(route: ActivatedRouteSnapshot): Observable | Observable { + const id = route.params['id']; + if (id) { + return this.service.find(id).pipe( + flatMap((employee: HttpResponse) => { + if (employee.body) { + return of(employee.body); + } else { + this.router.navigate(['404']); + return EMPTY; + } + }) + ); + } + return of(new Employee()); + } +} + +export const employeeRoute: Routes = [ + { + path: '', + component: EmployeeComponent, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.employee.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: ':id/view', + component: EmployeeDetailComponent, + resolve: { + employee: EmployeeResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.employee.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: 'new', + component: EmployeeUpdateComponent, + resolve: { + employee: EmployeeResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.employee.home.title', + }, + canActivate: [UserRouteAccessService], + }, + { + path: ':id/edit', + component: EmployeeUpdateComponent, + resolve: { + employee: EmployeeResolve, + }, + data: { + authorities: [Authority.USER], + pageTitle: 'ccwApplicationApp.employee.home.title', + }, + canActivate: [UserRouteAccessService], + }, +]; diff --git a/src/main/webapp/app/entities/employee/employee.service.ts b/src/main/webapp/app/entities/employee/employee.service.ts new file mode 100644 index 0000000..0b61838 --- /dev/null +++ b/src/main/webapp/app/entities/employee/employee.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { SERVER_API_URL } from 'app/app.constants'; +import { createRequestOption } from 'app/shared/util/request-util'; +import { IEmployee } from 'app/shared/model/employee.model'; + +type EntityResponseType = HttpResponse; +type EntityArrayResponseType = HttpResponse; + +@Injectable({ providedIn: 'root' }) +export class EmployeeService { + public resourceUrl = SERVER_API_URL + 'api/employees'; + + constructor(protected http: HttpClient) {} + + create(employee: IEmployee): Observable { + return this.http.post(this.resourceUrl, employee, { observe: 'response' }); + } + + update(employee: IEmployee): Observable { + return this.http.put(this.resourceUrl, employee, { observe: 'response' }); + } + + find(id: number): Observable { + return this.http.get(`${this.resourceUrl}/${id}`, { observe: 'response' }); + } + + query(req?: any): Observable { + const options = createRequestOption(req); + return this.http.get(this.resourceUrl, { params: options, observe: 'response' }); + } + + delete(id: number): Observable> { + return this.http.delete(`${this.resourceUrl}/${id}`, { observe: 'response' }); + } +} diff --git a/src/main/webapp/app/entities/entity.module.ts b/src/main/webapp/app/entities/entity.module.ts index 06ba1a7..8756fbd 100644 --- a/src/main/webapp/app/entities/entity.module.ts +++ b/src/main/webapp/app/entities/entity.module.ts @@ -4,8 +4,16 @@ import { RouterModule } from '@angular/router'; @NgModule({ imports: [ RouterModule.forChild([ + { + path: 'employee', + loadChildren: () => import('./employee/employee.module').then(m => m.CcwApplicationEmployeeModule), + }, + { + path: 'collection-tracking', + loadChildren: () => import('./collection-tracking/collection-tracking.module').then(m => m.CcwApplicationCollectionTrackingModule), + }, /* jhipster-needle-add-entity-route - JHipster will add entity modules routes here */ ]), ], }) -export class CollectionTrackingApplicationEntityModule {} +export class CcwApplicationEntityModule {} diff --git a/src/main/webapp/app/home/home.module.ts b/src/main/webapp/app/home/home.module.ts index 4305fdf..6ed8426 100644 --- a/src/main/webapp/app/home/home.module.ts +++ b/src/main/webapp/app/home/home.module.ts @@ -1,12 +1,12 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CollectionTrackingApplicationSharedModule } from 'app/shared/shared.module'; +import { CcwApplicationSharedModule } from 'app/shared/shared.module'; import { HOME_ROUTE } from './home.route'; import { HomeComponent } from './home.component'; @NgModule({ - imports: [CollectionTrackingApplicationSharedModule, RouterModule.forChild([HOME_ROUTE])], + imports: [CcwApplicationSharedModule, RouterModule.forChild([HOME_ROUTE])], declarations: [HomeComponent], }) -export class CollectionTrackingApplicationHomeModule {} +export class CcwApplicationHomeModule {} diff --git a/src/main/webapp/app/home/home.scss b/src/main/webapp/app/home/home.scss index a22e3e8..e61f621 100644 --- a/src/main/webapp/app/home/home.scss +++ b/src/main/webapp/app/home/home.scss @@ -6,7 +6,7 @@ Main page styles display: inline-block; width: 347px; height: 497px; - background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; + background: url('../../content/images/jhipster_family_member_3.svg') no-repeat center top; background-size: contain; } @@ -17,7 +17,7 @@ Main page styles only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { .hipster { - background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; + background: url('../../content/images/jhipster_family_member_3.svg') no-repeat center top; background-size: contain; } } diff --git a/src/main/webapp/app/layouts/navbar/navbar.component.html b/src/main/webapp/app/layouts/navbar/navbar.component.html index 65ba0e4..15ccd52 100644 --- a/src/main/webapp/app/layouts/navbar/navbar.component.html +++ b/src/main/webapp/app/layouts/navbar/navbar.component.html @@ -1,7 +1,7 @@