` to any timeseries scraped from this config.
+ - job_name: 'prometheus'
+
+ # Override the global default and scrape targets from this job every 5 seconds.
+ scrape_interval: 5s
+
+ # scheme defaults to 'http'.
+ metrics_path: /management/prometheus
+ static_configs:
+ - targets:
+ # On MacOS, replace localhost by host.docker.internal
+ - localhost:8080
diff --git a/jhipster-6/bookstore-monolith/src/main/docker/sonar.yml b/jhipster-6/bookstore-monolith/src/main/docker/sonar.yml
new file mode 100644
index 0000000000..756175b7d0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/docker/sonar.yml
@@ -0,0 +1,7 @@
+version: '2'
+services:
+ bookstore-sonar:
+ image: sonarqube:7.1
+ ports:
+ - 9001:9000
+ - 9092:9092
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/ApplicationWebXml.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/ApplicationWebXml.java
new file mode 100644
index 0000000000..32f28a8e9e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/ApplicationWebXml.java
@@ -0,0 +1,21 @@
+package com.baeldung.jhipster6;
+
+import com.baeldung.jhipster6.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 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(BookstoreApp.class);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/BookstoreApp.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/BookstoreApp.java
new file mode 100644
index 0000000000..278efb9270
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/BookstoreApp.java
@@ -0,0 +1,98 @@
+package com.baeldung.jhipster6;
+
+import com.baeldung.jhipster6.config.ApplicationProperties;
+import com.baeldung.jhipster6.config.DefaultProfileUtil;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.core.env.Environment;
+
+import javax.annotation.PostConstruct;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collection;
+
+@SpringBootApplication
+@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
+public class BookstoreApp {
+
+ private static final Logger log = LoggerFactory.getLogger(BookstoreApp.class);
+
+ private final Environment env;
+
+ public BookstoreApp(Environment env) {
+ this.env = env;
+ }
+
+ /**
+ * Initializes Bookstore.
+ *
+ * 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(BookstoreApp.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/aop/logging/LoggingAspect.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/aop/logging/LoggingAspect.java
new file mode 100644
index 0000000000..1e5a47c12a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/aop/logging/LoggingAspect.java
@@ -0,0 +1,98 @@
+package com.baeldung.jhipster6.aop.logging;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+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 java.util.Arrays;
+
+/**
+ * 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 Logger log = LoggerFactory.getLogger(this.getClass());
+
+ 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.baeldung.jhipster5.repository..*)"+
+ " || within(com.baeldung.jhipster5.service..*)"+
+ " || within(com.baeldung.jhipster5.web.rest..*)")
+ public void applicationPackagePointcut() {
+ // Method is empty as this is just a Pointcut, the implementations are in the advices.
+ }
+
+ /**
+ * 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(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
+ log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e);
+
+ } else {
+ log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ 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 IllegalArgumentException
+ */
+ @Around("applicationPackagePointcut() && springBeanPointcut()")
+ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ if (log.isDebugEnabled()) {
+ log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
+ }
+ try {
+ Object result = joinPoint.proceed();
+ if (log.isDebugEnabled()) {
+ log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), result);
+ }
+ return result;
+ } catch (IllegalArgumentException e) {
+ log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
+ joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+
+ throw e;
+ }
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/ApplicationProperties.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/ApplicationProperties.java
new file mode 100644
index 0000000000..e9bf8bd1a8
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/ApplicationProperties.java
@@ -0,0 +1,14 @@
+package com.baeldung.jhipster6.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Properties specific to Bookstore.
+ *
+ * Properties are configured in the 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/AsyncConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/AsyncConfiguration.java
new file mode 100644
index 0000000000..a245402175
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/AsyncConfiguration.java
@@ -0,0 +1,59 @@
+package com.baeldung.jhipster6.config;
+
+import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
+import io.github.jhipster.config.JHipsterProperties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.*;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@Configuration
+@EnableAsync
+@EnableScheduling
+public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
+
+ private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @Override
+ @Bean(name = "taskExecutor")
+ public Executor getAsyncExecutor() {
+ log.debug("Creating Async Task Executor");
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
+ executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
+ executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
+ executor.setThreadNamePrefix("bookstore-Executor-");
+ return new ExceptionHandlingAsyncTaskExecutor(executor);
+ }
+
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new SimpleAsyncUncaughtExceptionHandler();
+ }
+
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+ taskRegistrar.setScheduler(scheduledTaskExecutor());
+ }
+
+ @Bean
+ public Executor scheduledTaskExecutor() {
+ return Executors.newScheduledThreadPool(jHipsterProperties.getAsync().getCorePoolSize());
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/CloudDatabaseConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/CloudDatabaseConfiguration.java
new file mode 100644
index 0000000000..3d57db6559
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/CloudDatabaseConfiguration.java
@@ -0,0 +1,28 @@
+package com.baeldung.jhipster6.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.config.java.AbstractCloudConfig;
+import org.springframework.context.annotation.*;
+
+import javax.sql.DataSource;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+
+@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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/Constants.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/Constants.java
new file mode 100644
index 0000000000..b5d6eba051
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/Constants.java
@@ -0,0 +1,17 @@
+package com.baeldung.jhipster6.config;
+
+/**
+ * Application constants.
+ */
+public final class Constants {
+
+ // Regex for acceptable logins
+ public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$";
+
+ public static final String SYSTEM_ACCOUNT = "system";
+ public static final String ANONYMOUS_USER = "anonymoususer";
+ public static final String DEFAULT_LANGUAGE = "en";
+
+ private Constants() {
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DatabaseConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DatabaseConfiguration.java
new file mode 100644
index 0000000000..2aa8ae2e82
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DatabaseConfiguration.java
@@ -0,0 +1,59 @@
+package com.baeldung.jhipster6.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+import io.github.jhipster.config.h2.H2ConfigurationHelper;
+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;
+
+import java.sql.SQLException;
+
+@Configuration
+@EnableJpaRepositories("com.baeldung.jhipster5.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DateTimeFormatConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DateTimeFormatConfiguration.java
new file mode 100644
index 0000000000..eeb4b55491
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DateTimeFormatConfiguration.java
@@ -0,0 +1,20 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DefaultProfileUtil.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DefaultProfileUtil.java
new file mode 100644
index 0000000000..02b3ce9db0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/DefaultProfileUtil.java
@@ -0,0 +1,51 @@
+package com.baeldung.jhipster6.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.core.env.Environment;
+
+import java.util.*;
+
+/**
+ * Utility class to load a Spring profile to be used as default
+ * when there is no spring.profiles.active set in the environment or as command line argument.
+ * If the value is not available in application.yml then dev profile will be used as default.
+ */
+public final class DefaultProfileUtil {
+
+ private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
+
+ private DefaultProfileUtil() {
+ }
+
+ /**
+ * Set a default to use when no profile is configured.
+ *
+ * @param app the Spring application
+ */
+ public static void addDefaultProfile(SpringApplication app) {
+ Map defProperties = new HashMap<>();
+ /*
+ * The default profile to use when no other profiles are defined
+ * This cannot be set in the application.yml file.
+ * See https://github.com/spring-projects/spring-boot/issues/1219
+ */
+ defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT);
+ app.setDefaultProperties(defProperties);
+ }
+
+ /**
+ * Get the profiles that are applied else get default profiles.
+ *
+ * @param env spring environment
+ * @return profiles
+ */
+ public static String[] getActiveProfiles(Environment env) {
+ String[] profiles = env.getActiveProfiles();
+ if (profiles.length == 0) {
+ return env.getDefaultProfiles();
+ }
+ return profiles;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/JacksonConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/JacksonConfiguration.java
new file mode 100644
index 0000000000..68fb92ef2e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/JacksonConfiguration.java
@@ -0,0 +1,63 @@
+package com.baeldung.jhipster6.config;
+
+import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+
+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();
+ }
+
+ /*
+ * Jackson Afterburner module to speed up serialization/deserialization.
+ */
+ @Bean
+ public AfterburnerModule afterburnerModule() {
+ return new AfterburnerModule();
+ }
+
+ /*
+ * Module for serialization/deserialization of RFC7807 Problem.
+ */
+ @Bean
+ ProblemModule problemModule() {
+ return new ProblemModule();
+ }
+
+ /*
+ * Module for serialization/deserialization of ConstraintViolationProblem.
+ */
+ @Bean
+ ConstraintViolationProblemModule constraintViolationProblemModule() {
+ return new ConstraintViolationProblemModule();
+ }
+
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LiquibaseConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LiquibaseConfiguration.java
new file mode 100644
index 0000000000..2f82a1f447
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LiquibaseConfiguration.java
@@ -0,0 +1,50 @@
+package com.baeldung.jhipster6.config;
+
+import javax.sql.DataSource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+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.task.TaskExecutor;
+
+import io.github.jhipster.config.JHipsterConstants;
+import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
+import liquibase.integration.spring.SpringLiquibase;
+
+@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") TaskExecutor taskExecutor,
+ DataSource dataSource, LiquibaseProperties liquibaseProperties) {
+
+ // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
+ SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
+ liquibase.setDataSource(dataSource);
+ liquibase.setChangeLog("classpath:config/liquibase/master.xml");
+ liquibase.setContexts(liquibaseProperties.getContexts());
+ liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
+ liquibase.setDropFirst(liquibaseProperties.isDropFirst());
+ liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
+ if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
+ liquibase.setShouldRun(false);
+ } else {
+ liquibase.setShouldRun(liquibaseProperties.isEnabled());
+ log.debug("Configuring Liquibase");
+ }
+ return liquibase;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LocaleConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LocaleConfiguration.java
new file mode 100644
index 0000000000..d73198e5bb
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LocaleConfiguration.java
@@ -0,0 +1,27 @@
+package com.baeldung.jhipster6.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(name = "localeResolver")
+ 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingAspectConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingAspectConfiguration.java
new file mode 100644
index 0000000000..1638234a89
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingAspectConfiguration.java
@@ -0,0 +1,19 @@
+package com.baeldung.jhipster6.config;
+
+import com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingConfiguration.java
new file mode 100644
index 0000000000..893a16876f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/LoggingConfiguration.java
@@ -0,0 +1,154 @@
+package com.baeldung.jhipster6.config;
+
+import java.net.InetSocketAddress;
+import java.util.Iterator;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.boolex.OnMarkerEvaluator;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggerContextListener;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.filter.EvaluatorFilter;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import ch.qos.logback.core.spi.FilterReply;
+import net.logstash.logback.appender.LogstashTcpSocketAppender;
+import net.logstash.logback.encoder.LogstashEncoder;
+import net.logstash.logback.stacktrace.ShortenedThrowableConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LoggingConfiguration {
+
+ private static final String LOGSTASH_APPENDER_NAME = "LOGSTASH";
+
+ private static final String ASYNC_LOGSTASH_APPENDER_NAME = "ASYNC_LOGSTASH";
+
+ private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);
+
+ private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ private final String appName;
+
+ private final String serverPort;
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public LoggingConfiguration(@Value("${spring.application.name}") String appName, @Value("${server.port}") String serverPort,
+ JHipsterProperties jHipsterProperties) {
+ this.appName = appName;
+ this.serverPort = serverPort;
+ this.jHipsterProperties = jHipsterProperties;
+ if (jHipsterProperties.getLogging().getLogstash().isEnabled()) {
+ addLogstashAppender(context);
+ addContextListener(context);
+ }
+ if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
+ setMetricsMarkerLogbackFilter(context);
+ }
+ }
+
+ private void addContextListener(LoggerContext context) {
+ LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener();
+ loggerContextListener.setContext(context);
+ context.addListener(loggerContextListener);
+ }
+
+ private void addLogstashAppender(LoggerContext context) {
+ log.info("Initializing Logstash logging");
+
+ LogstashTcpSocketAppender logstashAppender = new LogstashTcpSocketAppender();
+ logstashAppender.setName(LOGSTASH_APPENDER_NAME);
+ logstashAppender.setContext(context);
+ String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"}";
+
+ // More documentation is available at: https://github.com/logstash/logstash-logback-encoder
+ LogstashEncoder logstashEncoder = new LogstashEncoder();
+ // Set the Logstash appender config from JHipster properties
+ logstashAppender.addDestinations(new InetSocketAddress(jHipsterProperties.getLogging().getLogstash().getHost(), jHipsterProperties.getLogging().getLogstash().getPort()));
+
+ ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter();
+ throwableConverter.setRootCauseFirst(true);
+ logstashEncoder.setThrowableConverter(throwableConverter);
+ logstashEncoder.setCustomFields(customFields);
+
+ logstashAppender.setEncoder(logstashEncoder);
+ logstashAppender.start();
+
+ // Wrap the appender in an Async appender for performance
+ AsyncAppender asyncLogstashAppender = new AsyncAppender();
+ asyncLogstashAppender.setContext(context);
+ asyncLogstashAppender.setName(ASYNC_LOGSTASH_APPENDER_NAME);
+ asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize());
+ asyncLogstashAppender.addAppender(logstashAppender);
+ asyncLogstashAppender.start();
+
+ context.getLogger("ROOT").addAppender(asyncLogstashAppender);
+ }
+
+ // Configure a log filter to remove "metrics" logs from all appenders except the "LOGSTASH" appender
+ private void setMetricsMarkerLogbackFilter(LoggerContext context) {
+ log.info("Filtering metrics logs from all appenders except the {} appender", LOGSTASH_APPENDER_NAME);
+ OnMarkerEvaluator onMarkerMetricsEvaluator = new OnMarkerEvaluator();
+ onMarkerMetricsEvaluator.setContext(context);
+ onMarkerMetricsEvaluator.addMarker("metrics");
+ onMarkerMetricsEvaluator.start();
+ EvaluatorFilter metricsFilter = new EvaluatorFilter<>();
+ metricsFilter.setContext(context);
+ metricsFilter.setEvaluator(onMarkerMetricsEvaluator);
+ metricsFilter.setOnMatch(FilterReply.DENY);
+ metricsFilter.start();
+
+ for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
+ for (Iterator> it = logger.iteratorForAppenders(); it.hasNext();) {
+ Appender appender = it.next();
+ if (!appender.getName().equals(ASYNC_LOGSTASH_APPENDER_NAME)) {
+ log.debug("Filter metrics logs from the {} appender", appender.getName());
+ appender.setContext(context);
+ appender.addFilter(metricsFilter);
+ appender.start();
+ }
+ }
+ }
+ }
+
+ /**
+ * Logback configuration is achieved by configuration file and API.
+ * When configuration file change is detected, the configuration is reset.
+ * This listener ensures that the programmatic configuration is also re-applied after reset.
+ */
+ class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener {
+
+ @Override
+ public boolean isResetResistant() {
+ return true;
+ }
+
+ @Override
+ public void onStart(LoggerContext context) {
+ addLogstashAppender(context);
+ }
+
+ @Override
+ public void onReset(LoggerContext context) {
+ addLogstashAppender(context);
+ }
+
+ @Override
+ public void onStop(LoggerContext context) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {
+ // Nothing to do.
+ }
+ }
+
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/SecurityConfiguration.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/SecurityConfiguration.java
new file mode 100644
index 0000000000..f42ba55c20
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/SecurityConfiguration.java
@@ -0,0 +1,125 @@
+package com.baeldung.jhipster6.config;
+
+import com.baeldung.jhipster6.security.*;
+import com.baeldung.jhipster6.security.jwt.*;
+import com.baeldung.jhipster6.security.AuthoritiesConstants;
+import com.baeldung.jhipster6.security.jwt.JWTConfigurer;
+import com.baeldung.jhipster6.security.jwt.TokenProvider;
+
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+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.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.filter.CorsFilter;
+import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+@Import(SecurityProblemSupport.class)
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private final AuthenticationManagerBuilder authenticationManagerBuilder;
+
+ private final UserDetailsService userDetailsService;
+
+ private final TokenProvider tokenProvider;
+
+ private final CorsFilter corsFilter;
+
+ private final SecurityProblemSupport problemSupport;
+
+ public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
+ this.authenticationManagerBuilder = authenticationManagerBuilder;
+ this.userDetailsService = userDetailsService;
+ this.tokenProvider = tokenProvider;
+ this.corsFilter = corsFilter;
+ this.problemSupport = problemSupport;
+ }
+
+ @PostConstruct
+ public void init() {
+ try {
+ authenticationManagerBuilder
+ .userDetailsService(userDetailsService)
+ .passwordEncoder(passwordEncoder());
+ } catch (Exception e) {
+ throw new BeanInitializationException("Security configuration failed", e);
+ }
+ }
+
+ @Override
+ @Bean
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ 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 {
+ http
+ .csrf()
+ .disable()
+ .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
+ .exceptionHandling()
+ .authenticationEntryPoint(problemSupport)
+ .accessDeniedHandler(problemSupport)
+ .and()
+ .headers()
+ .frameOptions()
+ .disable()
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeRequests()
+ .antMatchers("/api/books/purchase/**").authenticated()
+ .antMatchers("/api/register").permitAll()
+ .antMatchers("/api/activate").permitAll()
+ .antMatchers("/api/authenticate").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/**").hasAuthority(AuthoritiesConstants.ADMIN)
+ .and()
+ .apply(securityConfigurerAdapter());
+
+ }
+
+ private JWTConfigurer securityConfigurerAdapter() {
+ return new JWTConfigurer(tokenProvider);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/WebConfigurer.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/WebConfigurer.java
new file mode 100644
index 0000000000..8bed192c9f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/WebConfigurer.java
@@ -0,0 +1,142 @@
+package com.baeldung.jhipster6.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+import io.github.jhipster.config.JHipsterProperties;
+import io.github.jhipster.web.filter.CachingHttpHeadersFilter;
+
+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;
+
+import javax.servlet.*;
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.*;
+
+import static java.net.URLDecoder.decode;
+
+/**
+ * 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());
+ }
+ EnumSet disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
+ if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_PRODUCTION))) {
+ initCachingHttpHeadersFilter(servletContext, disps);
+ }
+ 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);
+ }
+
+ /**
+ * Initializes the caching HTTP Headers Filter.
+ */
+ private void initCachingHttpHeadersFilter(ServletContext servletContext,
+ EnumSet disps) {
+ log.debug("Registering Caching HTTP Headers Filter");
+ FilterRegistration.Dynamic cachingHttpHeadersFilter =
+ servletContext.addFilter("cachingHttpHeadersFilter",
+ new CachingHttpHeadersFilter(jHipsterProperties));
+
+ cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/i18n/*");
+ cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*");
+ cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*");
+ cachingHttpHeadersFilter.setAsyncSupported(true);
+ }
+
+ @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);
+ }
+
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/AuditEventConverter.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/AuditEventConverter.java
new file mode 100644
index 0000000000..fee87ffb96
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/AuditEventConverter.java
@@ -0,0 +1,86 @@
+package com.baeldung.jhipster6.config.audit;
+
+import com.baeldung.jhipster6.domain.PersistentAuditEvent;
+
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+@Component
+public class AuditEventConverter {
+
+ /**
+ * Convert a list of PersistentAuditEvent to a list of AuditEvent
+ *
+ * @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 PersistentAuditEvent to an 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 AuditEventRepository interface
+ *
+ * @param data the data to convert
+ * @return a map of String, 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 String, 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/package-info.java
new file mode 100644
index 0000000000..ce7911db40
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/audit/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Audit specific code.
+ */
+package com.baeldung.jhipster6.config.audit;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/package-info.java
new file mode 100644
index 0000000000..2f6261950a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/config/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Framework configuration files.
+ */
+package com.baeldung.jhipster6.config;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/AbstractAuditingEntity.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/AbstractAuditingEntity.java
new file mode 100644
index 0000000000..b3881af03b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/AbstractAuditingEntity.java
@@ -0,0 +1,79 @@
+package com.baeldung.jhipster6.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.hibernate.envers.Audited;
+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;
+
+import java.io.Serializable;
+import java.time.Instant;
+import javax.persistence.Column;
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * Base abstract class for entities which will hold definitions for created, last modified by and created,
+ * last modified by date.
+ */
+@MappedSuperclass
+@Audited
+@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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Authority.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Authority.java
new file mode 100644
index 0000000000..81d2b930cb
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Authority.java
@@ -0,0 +1,59 @@
+package com.baeldung.jhipster6.domain;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Column;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+
+/**
+ * An authority (a security role) used by Spring Security.
+ */
+@Entity
+@Table(name = "jhi_authority")
+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 == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Authority authority = (Authority) o;
+
+ return !(name != null ? !name.equals(authority.name) : authority.name != null);
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Authority{" +
+ "name='" + name + '\'' +
+ "}";
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Book.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Book.java
new file mode 100644
index 0000000000..14c5ec3396
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/Book.java
@@ -0,0 +1,153 @@
+package com.baeldung.jhipster6.domain;
+
+
+
+import javax.persistence.*;
+import javax.validation.constraints.*;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Objects;
+
+/**
+ * A Book.
+ */
+@Entity
+@Table(name = "book")
+public class Book implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @NotNull
+ @Column(name = "author", nullable = false)
+ private String author;
+
+ @NotNull
+ @Column(name = "published", nullable = false)
+ private LocalDate published;
+
+ @NotNull
+ @Min(value = 0)
+ @Column(name = "quantity", nullable = false)
+ private Integer quantity;
+
+ @NotNull
+ @DecimalMin(value = "0")
+ @Column(name = "price", nullable = false)
+ private Double price;
+
+ // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Book title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public Book author(String author) {
+ this.author = author;
+ return this;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public LocalDate getPublished() {
+ return published;
+ }
+
+ public Book published(LocalDate published) {
+ this.published = published;
+ return this;
+ }
+
+ public void setPublished(LocalDate published) {
+ this.published = published;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ public Book quantity(Integer quantity) {
+ this.quantity = quantity;
+ return this;
+ }
+
+ public void setQuantity(Integer quantity) {
+ this.quantity = quantity;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public Book price(Double price) {
+ this.price = price;
+ return this;
+ }
+
+ public void setPrice(Double price) {
+ this.price = price;
+ }
+ // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Book book = (Book) o;
+ if (book.getId() == null || getId() == null) {
+ return false;
+ }
+ return Objects.equals(getId(), book.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getId());
+ }
+
+ @Override
+ public String toString() {
+ return "Book{" +
+ "id=" + getId() +
+ ", title='" + getTitle() + "'" +
+ ", author='" + getAuthor() + "'" +
+ ", published='" + getPublished() + "'" +
+ ", quantity=" + getQuantity() +
+ ", price=" + getPrice() +
+ "}";
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/PersistentAuditEvent.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/PersistentAuditEvent.java
new file mode 100644
index 0000000000..f953eeded9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/PersistentAuditEvent.java
@@ -0,0 +1,109 @@
+package com.baeldung.jhipster6.domain;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.Map;
+
+/**
+ * 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 == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PersistentAuditEvent persistentAuditEvent = (PersistentAuditEvent) o;
+ return !(persistentAuditEvent.getId() == null || getId() == null) && Objects.equals(getId(), persistentAuditEvent.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getId());
+ }
+
+ @Override
+ public String toString() {
+ return "PersistentAuditEvent{" +
+ "principal='" + principal + '\'' +
+ ", auditEventDate=" + auditEventDate +
+ ", auditEventType='" + auditEventType + '\'' +
+ '}';
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/User.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/User.java
new file mode 100644
index 0000000000..d1ff6c33fa
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/User.java
@@ -0,0 +1,231 @@
+package com.baeldung.jhipster6.domain;
+
+import com.baeldung.jhipster6.config.Constants;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.commons.lang3.StringUtils;
+import org.hibernate.annotations.BatchSize;
+import javax.validation.constraints.Email;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.time.Instant;
+
+/**
+ * A user.
+ */
+@Entity
+@Table(name = "jhi_user")
+
+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 = 6)
+ @Column(name = "lang_key", length = 6)
+ 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")})
+
+ @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 == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ User user = (User) o;
+ return !(user.getId() == null || getId() == null) && Objects.equals(getId(), user.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getId());
+ }
+
+ @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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/package-info.java
new file mode 100644
index 0000000000..9063a204d5
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/domain/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * JPA domain objects.
+ */
+package com.baeldung.jhipster6.domain;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/AuthorityRepository.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/AuthorityRepository.java
new file mode 100644
index 0000000000..335631195d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/AuthorityRepository.java
@@ -0,0 +1,11 @@
+package com.baeldung.jhipster6.repository;
+
+import com.baeldung.jhipster6.domain.Authority;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * Spring Data JPA repository for the Authority entity.
+ */
+public interface AuthorityRepository extends JpaRepository {
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/BookRepository.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/BookRepository.java
new file mode 100644
index 0000000000..0863d1b89b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/BookRepository.java
@@ -0,0 +1,15 @@
+package com.baeldung.jhipster6.repository;
+
+import com.baeldung.jhipster6.domain.Book;
+import org.springframework.data.jpa.repository.*;
+import org.springframework.stereotype.Repository;
+
+
+/**
+ * Spring Data repository for the Book entity.
+ */
+@SuppressWarnings("unused")
+@Repository
+public interface BookRepository extends JpaRepository {
+
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/CustomAuditEventRepository.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/CustomAuditEventRepository.java
new file mode 100644
index 0000000000..4e17cfff2b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/CustomAuditEventRepository.java
@@ -0,0 +1,89 @@
+package com.baeldung.jhipster6.repository;
+
+import com.baeldung.jhipster6.config.Constants;
+import com.baeldung.jhipster6.config.audit.AuditEventConverter;
+import com.baeldung.jhipster6.domain.PersistentAuditEvent;
+
+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;
+
+import java.time.Instant;
+import java.util.*;
+
+/**
+ * An implementation of Spring Boot's 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/PersistenceAuditEventRepository.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/PersistenceAuditEventRepository.java
new file mode 100644
index 0000000000..ca0025321e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/PersistenceAuditEventRepository.java
@@ -0,0 +1,25 @@
+package com.baeldung.jhipster6.repository;
+
+import com.baeldung.jhipster6.domain.PersistentAuditEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.time.Instant;
+import java.util.List;
+
+/**
+ * Spring Data JPA repository for the PersistentAuditEvent entity.
+ */
+public interface PersistenceAuditEventRepository extends JpaRepository {
+
+ List findByPrincipal(String principal);
+
+ List findByAuditEventDateAfter(Instant after);
+
+ List findByPrincipalAndAuditEventDateAfter(String principal, Instant after);
+
+ List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type);
+
+ Page findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable);
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/UserRepository.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/UserRepository.java
new file mode 100644
index 0000000000..27fd5fc359
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/UserRepository.java
@@ -0,0 +1,40 @@
+package com.baeldung.jhipster6.repository;
+
+import com.baeldung.jhipster6.domain.User;
+
+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;
+import java.util.List;
+import java.util.Optional;
+import java.time.Instant;
+
+/**
+ * Spring Data JPA repository for the User entity.
+ */
+@Repository
+public interface UserRepository extends JpaRepository {
+
+ Optional findOneByActivationKey(String activationKey);
+
+ List findAllByActivatedIsFalseAndCreatedDateBefore(Instant dateTime);
+
+ Optional findOneByResetKey(String resetKey);
+
+ Optional findOneByEmailIgnoreCase(String email);
+
+ Optional findOneByLogin(String login);
+
+ @EntityGraph(attributePaths = "authorities")
+ Optional findOneWithAuthoritiesById(Long id);
+
+ @EntityGraph(attributePaths = "authorities")
+ Optional findOneWithAuthoritiesByLogin(String login);
+
+ @EntityGraph(attributePaths = "authorities")
+ Optional findOneWithAuthoritiesByEmail(String email);
+
+ Page findAllByLoginNot(Pageable pageable, String login);
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/package-info.java
new file mode 100644
index 0000000000..949ee586b7
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/repository/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Data JPA repositories.
+ */
+package com.baeldung.jhipster6.repository;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/AuthoritiesConstants.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/AuthoritiesConstants.java
new file mode 100644
index 0000000000..3f1709a6d9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/AuthoritiesConstants.java
@@ -0,0 +1,16 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/CustomAuthenticationManager.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/CustomAuthenticationManager.java
new file mode 100644
index 0000000000..092adf1b06
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/CustomAuthenticationManager.java
@@ -0,0 +1,126 @@
+package com.baeldung.jhipster6.security;
+
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.security.dto.LoginRequest;
+import com.baeldung.jhipster6.security.dto.LoginResponse;
+import com.baeldung.jhipster6.service.UserService;
+import com.baeldung.jhipster6.service.dto.UserDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+@Component
+public class CustomAuthenticationManager implements AuthenticationManager {
+
+ private final static Logger LOG = LoggerFactory.getLogger(CustomAuthenticationManager.class);
+
+ private final String REMOTE_LOGIN_URL = "https://example.com/login";
+
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ @Autowired
+ private UserService userService;
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+ LoginRequest loginRequest = new LoginRequest();
+ loginRequest.setUsername(authentication.getPrincipal().toString());
+ loginRequest.setPassword(authentication.getCredentials().toString());
+
+ try
+ {
+ ResponseEntity response =
+ restTemplate.postForEntity(
+ REMOTE_LOGIN_URL,
+ loginRequest,
+ LoginResponse.class);
+
+ if(response.getStatusCode().is2xxSuccessful())
+ {
+ //
+ // Need to create a new local user if this is the first time logging in; this
+ // is required so they can be issued JWTs. We can use this flow to also keep
+ // our local use entry up to date with data from the remote service if needed
+ // (for example, if the first and last name might change, this is where we would
+ // update the local user entry)
+ //
+
+ User user = userService.getUserWithAuthoritiesByLogin(authentication.getPrincipal().toString())
+ .orElseGet(() -> userService.createUser(createUserDTO(response.getBody(), authentication)));
+ return createAuthentication(authentication, user);
+ }
+ else
+ {
+ throw new BadCredentialsException("Invalid username or password");
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Failed to authenticate", e);
+ throw new AuthenticationServiceException("Failed to login", e);
+ }
+ }
+
+ /**
+ * Creates a new authentication with basic roles
+ * @param auth Contains auth details that will be copied into the new one.
+ * @param user User object representing who is logging in
+ * @return Authentication
+ */
+ private Authentication createAuthentication(Authentication auth, User user) {
+
+ //
+ // Honor any roles the user already has set; default is just USER role
+ // but could be modified after account creation
+ //
+
+ Collection extends GrantedAuthority> authorities = user
+ .getAuthorities()
+ .stream()
+ .map(a -> new SimpleGrantedAuthority(a.getName()))
+ .collect(Collectors.toSet());
+
+ UsernamePasswordAuthenticationToken token
+ = new UsernamePasswordAuthenticationToken(
+ user.getId(),
+ auth.getCredentials().toString(),
+ authorities);
+
+ return token;
+ }
+
+ /**
+ * Creates a new UserDTO with basic info.
+ * @param loginResponse Response from peloton login API
+ * @param authentication Contains user login info (namely username and password)
+ * @return UserDTO
+ */
+ private UserDTO createUserDTO(LoginResponse loginResponse, Authentication authentication) {
+
+ UserDTO dto = new UserDTO();
+
+ dto.setActivated(true);
+ dto.setEmail(loginResponse.getEmail());
+ dto.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
+ dto.setFirstName(loginResponse.getFirstName());
+ dto.setLastName(loginResponse.getLastName());
+
+ return dto;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/DomainUserDetailsService.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/DomainUserDetailsService.java
new file mode 100644
index 0000000000..3102604ee4
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/DomainUserDetailsService.java
@@ -0,0 +1,63 @@
+package com.baeldung.jhipster6.security;
+
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.repository.UserRepository;
+
+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;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 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.findOneWithAuthoritiesByEmail(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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SecurityUtils.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SecurityUtils.java
new file mode 100644
index 0000000000..a4a1982b3c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SecurityUtils.java
@@ -0,0 +1,76 @@
+package com.baeldung.jhipster6.security;
+
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Optional;
+
+/**
+ * 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(securityContext.getAuthentication())
+ .map(authentication -> {
+ 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() {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ return Optional.ofNullable(securityContext.getAuthentication())
+ .map(authentication -> authentication.getAuthorities().stream()
+ .noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)))
+ .orElse(false);
+ }
+
+ /**
+ * If the current user has a specific authority (security role).
+ *
+ * The name of this method comes from the 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) {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ return Optional.ofNullable(securityContext.getAuthentication())
+ .map(authentication -> authentication.getAuthorities().stream()
+ .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
+ .orElse(false);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SpringSecurityAuditorAware.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SpringSecurityAuditorAware.java
new file mode 100644
index 0000000000..aff636c13c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/SpringSecurityAuditorAware.java
@@ -0,0 +1,20 @@
+package com.baeldung.jhipster6.security;
+
+import com.baeldung.jhipster6.config.Constants;
+
+import java.util.Optional;
+
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implementation of 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/UserNotActivatedException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/UserNotActivatedException.java
new file mode 100644
index 0000000000..a1f71f5408
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/UserNotActivatedException.java
@@ -0,0 +1,19 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginRequest.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginRequest.java
new file mode 100644
index 0000000000..1de2638577
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginRequest.java
@@ -0,0 +1,30 @@
+package com.baeldung.jhipster6.security.dto;
+
+/**
+ * Simple DTO representing a login request to a remote service.
+ */
+public class LoginRequest {
+
+ private String username;
+
+ private String password;
+
+ public LoginRequest() {
+ }
+
+ 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;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginResponse.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginResponse.java
new file mode 100644
index 0000000000..6063180569
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/dto/LoginResponse.java
@@ -0,0 +1,50 @@
+package com.baeldung.jhipster6.security.dto;
+
+/**
+ * Simple DTO representing the response of logging in using a remote service.
+ */
+public class LoginResponse {
+
+ private String username;
+
+ private String firstName;
+
+ private String lastName;
+
+ private String email;
+
+ public LoginResponse() {
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ 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;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTConfigurer.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTConfigurer.java
new file mode 100644
index 0000000000..570f6c8d9d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTConfigurer.java
@@ -0,0 +1,21 @@
+package com.baeldung.jhipster6.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 TokenProvider tokenProvider;
+
+ public JWTConfigurer(TokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public void configure(HttpSecurity http) throws Exception {
+ JWTFilter customFilter = new JWTFilter(tokenProvider);
+ http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTFilter.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTFilter.java
new file mode 100644
index 0000000000..4e921a2f6e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/JWTFilter.java
@@ -0,0 +1,48 @@
+package com.baeldung.jhipster6.security.jwt;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * 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 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/TokenProvider.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/TokenProvider.java
new file mode 100644
index 0000000000..20f46c06e9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/jwt/TokenProvider.java
@@ -0,0 +1,119 @@
+package com.baeldung.jhipster6.security.jwt;
+
+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;
+
+import io.github.jhipster.config.JHipsterProperties;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+
+@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.parser()
+ .setSigningKey(key)
+ .parseClaimsJws(token)
+ .getBody();
+
+ Collection extends GrantedAuthority> 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.parser().setSigningKey(key).parseClaimsJws(authToken);
+ return true;
+ } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
+ log.info("Invalid JWT signature.");
+ log.trace("Invalid JWT signature trace: {}", e);
+ } catch (ExpiredJwtException e) {
+ log.info("Expired JWT token.");
+ log.trace("Expired JWT token trace: {}", e);
+ } catch (UnsupportedJwtException e) {
+ log.info("Unsupported JWT token.");
+ log.trace("Unsupported JWT token trace: {}", e);
+ } catch (IllegalArgumentException e) {
+ log.info("JWT token compact of handler are invalid.");
+ log.trace("JWT token compact of handler are invalid trace: {}", e);
+ }
+ return false;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/package-info.java
new file mode 100644
index 0000000000..3fc05c27e7
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/security/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Security configuration.
+ */
+package com.baeldung.jhipster6.security;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/AuditEventService.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/AuditEventService.java
new file mode 100644
index 0000000000..4d008e096e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/AuditEventService.java
@@ -0,0 +1,52 @@
+package com.baeldung.jhipster6.service;
+
+import com.baeldung.jhipster6.config.audit.AuditEventConverter;
+import com.baeldung.jhipster6.repository.PersistenceAuditEventRepository;
+
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.util.Optional;
+
+/**
+ * Service for managing audit events.
+ *
+ * This is the default implementation to support SpringBoot Actuator AuditEventRepository
+ */
+@Service
+@Transactional
+public class AuditEventService {
+
+ private final PersistenceAuditEventRepository persistenceAuditEventRepository;
+
+ private final AuditEventConverter auditEventConverter;
+
+ public AuditEventService(
+ PersistenceAuditEventRepository persistenceAuditEventRepository,
+ AuditEventConverter auditEventConverter) {
+
+ this.persistenceAuditEventRepository = persistenceAuditEventRepository;
+ this.auditEventConverter = auditEventConverter;
+ }
+
+ public Page findAll(Pageable pageable) {
+ return persistenceAuditEventRepository.findAll(pageable)
+ .map(auditEventConverter::convertToAuditEvent);
+ }
+
+ public Page findByDates(Instant fromDate, Instant toDate, Pageable pageable) {
+ return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable)
+ .map(auditEventConverter::convertToAuditEvent);
+ }
+
+ public Optional find(Long id) {
+ return Optional.ofNullable(persistenceAuditEventRepository.findById(id))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(auditEventConverter::convertToAuditEvent);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/BookService.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/BookService.java
new file mode 100644
index 0000000000..00fe847527
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/BookService.java
@@ -0,0 +1,50 @@
+package com.baeldung.jhipster6.service;
+
+import com.baeldung.jhipster6.service.dto.BookDTO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service Interface for managing Book.
+ */
+public interface BookService {
+
+ /**
+ * Save a book.
+ *
+ * @param bookDTO the entity to save
+ * @return the persisted entity
+ */
+ BookDTO save(BookDTO bookDTO);
+
+ /**
+ * Get all the books.
+ *
+ * @return the list of entities
+ */
+ List findAll();
+
+
+ /**
+ * Get the "id" book.
+ *
+ * @param id the id of the entity
+ * @return the entity
+ */
+ Optional findOne(Long id);
+
+ /**
+ * Delete the "id" book.
+ *
+ * @param id the id of the entity
+ */
+ void delete(Long id);
+
+ /**
+ * Simulates purchasing a book by reducing the stock of a book by 1.
+ * @param id the id of the book
+ * @return Updated BookDTO, empty if not found, or throws exception if an error occurs.
+ */
+ Optional purchase(Long id);
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/MailService.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/MailService.java
new file mode 100644
index 0000000000..807cf8b5d6
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/MailService.java
@@ -0,0 +1,105 @@
+package com.baeldung.jhipster6.service;
+
+import com.baeldung.jhipster6.domain.User;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import javax.mail.internet.MimeMessage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.MessageSource;
+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 @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 (Exception e) {
+ if (log.isDebugEnabled()) {
+ log.warn("Email could not be sent to user '{}'", to, e);
+ } else {
+ log.warn("Email could not be sent to user '{}': {}", to, e.getMessage());
+ }
+ }
+ }
+
+ @Async
+ public void sendEmailFromTemplate(User user, String templateName, String titleKey) {
+ 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/UserService.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/UserService.java
new file mode 100644
index 0000000000..9c43332070
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/UserService.java
@@ -0,0 +1,276 @@
+package com.baeldung.jhipster6.service;
+
+import com.baeldung.jhipster6.config.Constants;
+import com.baeldung.jhipster6.domain.Authority;
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.repository.AuthorityRepository;
+import com.baeldung.jhipster6.repository.UserRepository;
+import com.baeldung.jhipster6.security.AuthoritiesConstants;
+import com.baeldung.jhipster6.security.SecurityUtils;
+import com.baeldung.jhipster6.service.dto.UserDTO;
+import com.baeldung.jhipster6.service.util.RandomUtil;
+import com.baeldung.jhipster6.web.rest.errors.*;
+import com.baeldung.jhipster6.web.rest.errors.EmailAlreadyUsedException;
+import com.baeldung.jhipster6.web.rest.errors.InvalidPasswordException;
+import com.baeldung.jhipster6.web.rest.errors.LoginAlreadyUsedException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 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;
+
+ public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) {
+ this.userRepository = userRepository;
+ this.passwordEncoder = passwordEncoder;
+ this.authorityRepository = authorityRepository;
+ }
+
+ 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);
+ 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);
+ return user;
+ });
+ }
+
+ public Optional requestPasswordReset(String mail) {
+ return userRepository.findOneByEmailIgnoreCase(mail)
+ .filter(User::getActivated)
+ .map(user -> {
+ user.setResetKey(RandomUtil.generateResetKey());
+ user.setResetDate(Instant.now());
+ return user;
+ });
+ }
+
+ public User registerUser(UserDTO userDTO, String password) {
+ userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).ifPresent(existingUser -> {
+ boolean removed = removeNonActivatedUser(existingUser);
+ if (!removed) {
+ throw new LoginAlreadyUsedException();
+ }
+ });
+ 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());
+ 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);
+ log.debug("Created Information for User: {}", newUser);
+ return newUser;
+ }
+
+ private boolean removeNonActivatedUser(User existingUser){
+ if (existingUser.getActivated()) {
+ return false;
+ }
+ userRepository.delete(existingUser);
+ userRepository.flush();
+ return true;
+ }
+
+ public User createUser(UserDTO userDTO) {
+ User user = new User();
+ user.setLogin(userDTO.getLogin().toLowerCase());
+ user.setFirstName(userDTO.getFirstName());
+ user.setLastName(userDTO.getLastName());
+ 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);
+ log.debug("Created Information for User: {}", user);
+ return 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);
+ user.setEmail(email.toLowerCase());
+ user.setLangKey(langKey);
+ user.setImageUrl(imageUrl);
+ log.debug("Changed Information for User: {}", 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 -> {
+ user.setLogin(userDTO.getLogin().toLowerCase());
+ user.setFirstName(userDTO.getFirstName());
+ user.setLastName(userDTO.getLastName());
+ 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);
+ 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);
+ log.debug("Deleted User: {}", user);
+ });
+ }
+
+ 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);
+ 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(Long id) {
+ return userRepository.findOneWithAuthoritiesById(id);
+ }
+
+ @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
+ .findAllByActivatedIsFalseAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS))
+ .forEach(user -> {
+ log.debug("Deleting not activated user {}", user.getLogin());
+ userRepository.delete(user);
+ });
+ }
+
+ /**
+ * @return a list of all the authorities
+ */
+ public List getAuthorities() {
+ return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList());
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/BookDTO.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/BookDTO.java
new file mode 100644
index 0000000000..6334af6c21
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/BookDTO.java
@@ -0,0 +1,112 @@
+package com.baeldung.jhipster6.service.dto;
+import java.time.LocalDate;
+import javax.validation.constraints.*;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * A DTO for the Book entity.
+ */
+public class BookDTO implements Serializable {
+
+ private Long id;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private String author;
+
+ @NotNull
+ private LocalDate published;
+
+ @NotNull
+ @Min(value = 0)
+ private Integer quantity;
+
+ @NotNull
+ @DecimalMin(value = "0")
+ private Double price;
+
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public LocalDate getPublished() {
+ return published;
+ }
+
+ public void setPublished(LocalDate published) {
+ this.published = published;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(Integer quantity) {
+ this.quantity = quantity;
+ }
+
+ public Double getPrice() {
+ return price;
+ }
+
+ public void setPrice(Double price) {
+ this.price = price;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BookDTO bookDTO = (BookDTO) o;
+ if (bookDTO.getId() == null || getId() == null) {
+ return false;
+ }
+ return Objects.equals(getId(), bookDTO.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getId());
+ }
+
+ @Override
+ public String toString() {
+ return "BookDTO{" +
+ "id=" + getId() +
+ ", title='" + getTitle() + "'" +
+ ", author='" + getAuthor() + "'" +
+ ", published='" + getPublished() + "'" +
+ ", quantity=" + getQuantity() +
+ ", price=" + getPrice() +
+ "}";
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/PasswordChangeDTO.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/PasswordChangeDTO.java
new file mode 100644
index 0000000000..1439b6e58d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/PasswordChangeDTO.java
@@ -0,0 +1,35 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/UserDTO.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/UserDTO.java
new file mode 100644
index 0000000000..2dd38f9c0f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/UserDTO.java
@@ -0,0 +1,199 @@
+package com.baeldung.jhipster6.service.dto;
+
+import com.baeldung.jhipster6.config.Constants;
+
+import com.baeldung.jhipster6.domain.Authority;
+import com.baeldung.jhipster6.domain.User;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+
+import javax.validation.constraints.*;
+import java.time.Instant;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 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 = 6)
+ 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;
+ }
+
+ @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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/package-info.java
new file mode 100644
index 0000000000..c536320584
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/dto/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Data Transfer Objects.
+ */
+package com.baeldung.jhipster6.service.dto;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/impl/BookServiceImpl.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/impl/BookServiceImpl.java
new file mode 100644
index 0000000000..0012489c5e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/impl/BookServiceImpl.java
@@ -0,0 +1,110 @@
+package com.baeldung.jhipster6.service.impl;
+
+import com.baeldung.jhipster6.service.BookService;
+import com.baeldung.jhipster6.domain.Book;
+import com.baeldung.jhipster6.repository.BookRepository;
+import com.baeldung.jhipster6.service.dto.BookDTO;
+import com.baeldung.jhipster6.service.mapper.BookMapper;
+import com.baeldung.jhipster6.web.rest.errors.BadRequestAlertException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Service Implementation for managing Book.
+ */
+@Service
+@Transactional
+public class BookServiceImpl implements BookService {
+
+ private final Logger log = LoggerFactory.getLogger(BookServiceImpl.class);
+
+ private final BookRepository bookRepository;
+
+ private final BookMapper bookMapper;
+
+ public BookServiceImpl(BookRepository bookRepository, BookMapper bookMapper) {
+ this.bookRepository = bookRepository;
+ this.bookMapper = bookMapper;
+ }
+
+ /**
+ * Save a book.
+ *
+ * @param bookDTO the entity to save
+ * @return the persisted entity
+ */
+ @Override
+ public BookDTO save(BookDTO bookDTO) {
+ log.debug("Request to save Book : {}", bookDTO);
+ Book book = bookMapper.toEntity(bookDTO);
+ book = bookRepository.save(book);
+ return bookMapper.toDto(book);
+ }
+
+ /**
+ * Get all the books.
+ *
+ * @return the list of entities
+ */
+ @Override
+ @Transactional(readOnly = true)
+ public List findAll() {
+ log.debug("Request to get all Books");
+ return bookRepository.findAll().stream()
+ .map(bookMapper::toDto)
+ .collect(Collectors.toCollection(LinkedList::new));
+ }
+
+
+ /**
+ * Get one book by id.
+ *
+ * @param id the id of the entity
+ * @return the entity
+ */
+ @Override
+ @Transactional(readOnly = true)
+ public Optional findOne(Long id) {
+ log.debug("Request to get Book : {}", id);
+ return bookRepository.findById(id)
+ .map(bookMapper::toDto);
+ }
+
+ /**
+ * Delete the book by id.
+ *
+ * @param id the id of the entity
+ */
+ @Override
+ public void delete(Long id) {
+ log.debug("Request to delete Book : {}", id);
+ bookRepository.deleteById(id);
+ }
+
+ @Override
+ public Optional purchase(Long id) {
+ Optional bookDTO = findOne(id);
+ if(bookDTO.isPresent()) {
+ int quantity = bookDTO.get().getQuantity();
+ if(quantity > 0) {
+ bookDTO.get().setQuantity(quantity - 1);
+ Book book = bookMapper.toEntity(bookDTO.get());
+ book = bookRepository.save(book);
+ return bookDTO;
+ }
+ else {
+ throw new BadRequestAlertException("Book is not in stock", "book", "notinstock");
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/BookMapper.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/BookMapper.java
new file mode 100644
index 0000000000..64350fb4ed
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/BookMapper.java
@@ -0,0 +1,25 @@
+package com.baeldung.jhipster6.service.mapper;
+
+import com.baeldung.jhipster6.domain.*;
+import com.baeldung.jhipster6.service.dto.BookDTO;
+import com.baeldung.jhipster6.domain.Book;
+
+import org.mapstruct.*;
+
+/**
+ * Mapper for the entity Book and its DTO BookDTO.
+ */
+@Mapper(componentModel = "spring", uses = {})
+public interface BookMapper extends EntityMapper {
+
+
+
+ default Book fromId(Long id) {
+ if (id == null) {
+ return null;
+ }
+ Book book = new Book();
+ book.setId(id);
+ return book;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/EntityMapper.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/EntityMapper.java
new file mode 100644
index 0000000000..6ca8d89480
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/EntityMapper.java
@@ -0,0 +1,21 @@
+package com.baeldung.jhipster6.service.mapper;
+
+import java.util.List;
+
+/**
+ * Contract for a generic dto to entity mapper.
+ *
+ * @param - DTO type parameter.
+ * @param - Entity type parameter.
+ */
+
+public interface EntityMapper {
+
+ E toEntity(D dto);
+
+ D toDto(E entity);
+
+ List toEntity(List dtoList);
+
+ List toDto(List entityList);
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/UserMapper.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/UserMapper.java
new file mode 100644
index 0000000000..8b2b4cfba0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/UserMapper.java
@@ -0,0 +1,81 @@
+package com.baeldung.jhipster6.service.mapper;
+
+import com.baeldung.jhipster6.domain.Authority;
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.service.dto.UserDTO;
+
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Mapper for the entity User and its DTO called 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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/package-info.java
new file mode 100644
index 0000000000..6ce6ec42fb
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/mapper/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * MapStruct mappers for mapping domain objects and Data Transfer Objects.
+ */
+package com.baeldung.jhipster6.service.mapper;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/package-info.java
new file mode 100644
index 0000000000..4f9584c45e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Service layer beans.
+ */
+package com.baeldung.jhipster6.service;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/util/RandomUtil.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/util/RandomUtil.java
new file mode 100644
index 0000000000..74b8b178c2
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/service/util/RandomUtil.java
@@ -0,0 +1,41 @@
+package com.baeldung.jhipster6.service.util;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+/**
+ * Utility class for generating random Strings.
+ */
+public final class RandomUtil {
+
+ private static final int DEF_COUNT = 20;
+
+ private RandomUtil() {
+ }
+
+ /**
+ * Generate a password.
+ *
+ * @return the generated password
+ */
+ public static String generatePassword() {
+ return RandomStringUtils.randomAlphanumeric(DEF_COUNT);
+ }
+
+ /**
+ * Generate an activation key.
+ *
+ * @return the generated activation key
+ */
+ public static String generateActivationKey() {
+ return RandomStringUtils.randomNumeric(DEF_COUNT);
+ }
+
+ /**
+ * Generate a reset key.
+ *
+ * @return the generated reset key
+ */
+ public static String generateResetKey() {
+ return RandomStringUtils.randomNumeric(DEF_COUNT);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AccountResource.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AccountResource.java
new file mode 100644
index 0000000000..3603df148b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AccountResource.java
@@ -0,0 +1,184 @@
+package com.baeldung.jhipster6.web.rest;
+
+
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.repository.UserRepository;
+import com.baeldung.jhipster6.security.SecurityUtils;
+import com.baeldung.jhipster6.service.MailService;
+import com.baeldung.jhipster6.service.UserService;
+import com.baeldung.jhipster6.service.dto.PasswordChangeDTO;
+import com.baeldung.jhipster6.service.dto.UserDTO;
+import com.baeldung.jhipster6.web.rest.errors.*;
+import com.baeldung.jhipster6.web.rest.errors.EmailAlreadyUsedException;
+import com.baeldung.jhipster6.web.rest.errors.EmailNotFoundException;
+import com.baeldung.jhipster6.web.rest.errors.InternalServerErrorException;
+import com.baeldung.jhipster6.web.rest.errors.InvalidPasswordException;
+import com.baeldung.jhipster6.web.rest.errors.LoginAlreadyUsedException;
+import com.baeldung.jhipster6.web.rest.vm.KeyAndPasswordVM;
+import com.baeldung.jhipster6.web.rest.vm.ManagedUserVM;
+
+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.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.*;
+
+/**
+ * REST controller for managing the current user's account.
+ */
+@RestController
+@RequestMapping("/api")
+public class AccountResource {
+
+ 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;
+ }
+
+ /**
+ * POST /register : register the user.
+ *
+ * @param managedUserVM the managed user View Model
+ * @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
+ * @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
+ * @throws LoginAlreadyUsedException 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);
+ }
+
+ /**
+ * GET /activate : activate the registered user.
+ *
+ * @param key the activation key
+ * @throws RuntimeException 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 InternalServerErrorException("No user was found for this activation key");
+ }
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * GET /account : get the current user.
+ *
+ * @return the current user
+ * @throws RuntimeException 500 (Internal Server Error) if the user couldn't be returned
+ */
+ @GetMapping("/account")
+ public UserDTO getAccount() {
+ return userService.getUserWithAuthorities()
+ .map(UserDTO::new)
+ .orElseThrow(() -> new InternalServerErrorException("User could not be found"));
+ }
+
+ /**
+ * POST /account : update the current user information.
+ *
+ * @param userDTO the current user information
+ * @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
+ * @throws RuntimeException 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 InternalServerErrorException("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 InternalServerErrorException("User could not be found");
+ }
+ userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(),
+ userDTO.getLangKey(), userDTO.getImageUrl());
+ }
+
+ /**
+ * POST /account/change-password : changes the current user's password
+ *
+ * @param passwordChangeDto current and new password
+ * @throws InvalidPasswordException 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());
+ }
+
+ /**
+ * POST /account/reset-password/init : Send an email to reset the password of the user
+ *
+ * @param mail the mail of the user
+ * @throws EmailNotFoundException 400 (Bad Request) if the email address is not registered
+ */
+ @PostMapping(path = "/account/reset-password/init")
+ public void requestPasswordReset(@RequestBody String mail) {
+ mailService.sendPasswordResetMail(
+ userService.requestPasswordReset(mail)
+ .orElseThrow(EmailNotFoundException::new)
+ );
+ }
+
+ /**
+ * POST /account/reset-password/finish : Finish to reset the password of the user
+ *
+ * @param keyAndPassword the generated key and the new password
+ * @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
+ * @throws RuntimeException 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 InternalServerErrorException("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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AuditResource.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AuditResource.java
new file mode 100644
index 0000000000..ebcdc42f64
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/AuditResource.java
@@ -0,0 +1,77 @@
+package com.baeldung.jhipster6.web.rest;
+
+import com.baeldung.jhipster6.service.AuditEventService;
+import com.baeldung.jhipster6.web.rest.util.PaginationUtil;
+
+import io.github.jhipster.web.util.ResponseUtil;
+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 java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.List;
+
+/**
+ * REST controller for getting the audit events.
+ */
+@RestController
+@RequestMapping("/management/audits")
+public class AuditResource {
+
+ private final AuditEventService auditEventService;
+
+ public AuditResource(AuditEventService auditEventService) {
+ this.auditEventService = auditEventService;
+ }
+
+ /**
+ * GET /audits : get a page of AuditEvents.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
+ */
+ @GetMapping
+ public ResponseEntity> getAll(Pageable pageable) {
+ Page page = auditEventService.findAll(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /audits : get a page of AuditEvents between the fromDate and toDate.
+ *
+ * @param fromDate the start of the time period of AuditEvents to get
+ * @param toDate the end of the time period of AuditEvents to get
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
+ */
+ @GetMapping(params = {"fromDate", "toDate"})
+ public ResponseEntity> getByDates(
+ @RequestParam(value = "fromDate") LocalDate fromDate,
+ @RequestParam(value = "toDate") LocalDate toDate,
+ Pageable pageable) {
+
+ Page page = auditEventService.findByDates(
+ fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(),
+ toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(),
+ pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /audits/:id : get an AuditEvent by id.
+ *
+ * @param id the id of the entity to get
+ * @return the ResponseEntity with status 200 (OK) and the AuditEvent in body, or status 404 (Not Found)
+ */
+ @GetMapping("/{id:.+}")
+ public ResponseEntity get(@PathVariable Long id) {
+ return ResponseUtil.wrapOrNotFound(auditEventService.find(id));
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/BookResource.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/BookResource.java
new file mode 100644
index 0000000000..584216b51d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/BookResource.java
@@ -0,0 +1,118 @@
+package com.baeldung.jhipster6.web.rest;
+import com.baeldung.jhipster6.service.BookService;
+import com.baeldung.jhipster6.web.rest.errors.BadRequestAlertException;
+import com.baeldung.jhipster6.web.rest.util.HeaderUtil;
+import com.baeldung.jhipster6.service.dto.BookDTO;
+import io.github.jhipster.web.util.ResponseUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * REST controller for managing Book.
+ */
+@RestController
+@RequestMapping("/api")
+public class BookResource {
+
+ private final Logger log = LoggerFactory.getLogger(BookResource.class);
+
+ private static final String ENTITY_NAME = "book";
+
+ private final BookService bookService;
+
+ public BookResource(BookService bookService) {
+ this.bookService = bookService;
+ }
+
+ /**
+ * POST /books : Create a new book.
+ *
+ * @param bookDTO the bookDTO to create
+ * @return the ResponseEntity with status 201 (Created) and with body the new bookDTO, or with status 400 (Bad Request) if the book has already an ID
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PostMapping("/books")
+ public ResponseEntity createBook(@Valid @RequestBody BookDTO bookDTO) throws URISyntaxException {
+ log.debug("REST request to save Book : {}", bookDTO);
+ if (bookDTO.getId() != null) {
+ throw new BadRequestAlertException("A new book cannot already have an ID", ENTITY_NAME, "idexists");
+ }
+ BookDTO result = bookService.save(bookDTO);
+ return ResponseEntity.created(new URI("/api/books/" + result.getId()))
+ .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * PUT /books : Updates an existing book.
+ *
+ * @param bookDTO the bookDTO to update
+ * @return the ResponseEntity with status 200 (OK) and with body the updated bookDTO,
+ * or with status 400 (Bad Request) if the bookDTO is not valid,
+ * or with status 500 (Internal Server Error) if the bookDTO couldn't be updated
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PutMapping("/books")
+ public ResponseEntity updateBook(@Valid @RequestBody BookDTO bookDTO) throws URISyntaxException {
+ log.debug("REST request to update Book : {}", bookDTO);
+ if (bookDTO.getId() == null) {
+ throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
+ }
+ BookDTO result = bookService.save(bookDTO);
+ return ResponseEntity.ok()
+ .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, bookDTO.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * GET /books : get all the books.
+ *
+ * @return the ResponseEntity with status 200 (OK) and the list of books in body
+ */
+ @GetMapping("/books")
+ public List getAllBooks() {
+ log.debug("REST request to get all Books");
+ return bookService.findAll();
+ }
+
+ /**
+ * GET /books/:id : get the "id" book.
+ *
+ * @param id the id of the bookDTO to retrieve
+ * @return the ResponseEntity with status 200 (OK) and with body the bookDTO, or with status 404 (Not Found)
+ */
+ @GetMapping("/books/{id}")
+ public ResponseEntity getBook(@PathVariable Long id) {
+ log.debug("REST request to get Book : {}", id);
+ Optional bookDTO = bookService.findOne(id);
+ return ResponseUtil.wrapOrNotFound(bookDTO);
+ }
+
+ /**
+ * DELETE /books/:id : delete the "id" book.
+ *
+ * @param id the id of the bookDTO to delete
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @DeleteMapping("/books/{id}")
+ public ResponseEntity deleteBook(@PathVariable Long id) {
+ log.debug("REST request to delete Book : {}", id);
+ bookService.delete(id);
+ return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
+ }
+
+ @GetMapping("/books/purchase/{id}")
+ public ResponseEntity purchase(@PathVariable Long id) {
+ Optional bookDTO = bookService.purchase(id);
+ return ResponseUtil.wrapOrNotFound(bookDTO);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/ClientForwardController.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/ClientForwardController.java
new file mode 100644
index 0000000000..a524d90fc0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/ClientForwardController.java
@@ -0,0 +1,17 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/LogsResource.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/LogsResource.java
new file mode 100644
index 0000000000..76d71d23d0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/LogsResource.java
@@ -0,0 +1,36 @@
+package com.baeldung.jhipster6.web.rest;
+
+import com.baeldung.jhipster6.web.rest.vm.LoggerVM;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Controller for view and managing Log Level at runtime.
+ */
+@RestController
+@RequestMapping("/management")
+public class LogsResource {
+
+ @GetMapping("/logs")
+ public List getList() {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ return context.getLoggerList()
+ .stream()
+ .map(LoggerVM::new)
+ .collect(Collectors.toList());
+ }
+
+ @PutMapping("/logs")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void changeLevel(@RequestBody LoggerVM jsonLogger) {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserJWTController.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserJWTController.java
new file mode 100644
index 0000000000..1bb647d2ef
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserJWTController.java
@@ -0,0 +1,71 @@
+package com.baeldung.jhipster6.web.rest;
+
+import com.baeldung.jhipster6.security.jwt.JWTFilter;
+import com.baeldung.jhipster6.security.jwt.TokenProvider;
+import com.baeldung.jhipster6.web.rest.vm.LoginVM;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+/**
+ * Controller to authenticate users.
+ */
+@RestController
+@RequestMapping("/api")
+public class UserJWTController {
+
+ private final TokenProvider tokenProvider;
+
+ private final AuthenticationManager authenticationManager;
+
+ public UserJWTController(TokenProvider tokenProvider, AuthenticationManager authenticationManager) {
+ this.tokenProvider = tokenProvider;
+ this.authenticationManager = authenticationManager;
+ }
+
+ @PostMapping("/authenticate")
+ public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM) {
+
+ UsernamePasswordAuthenticationToken authenticationToken =
+ new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
+
+ Authentication authentication = this.authenticationManager.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserResource.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserResource.java
new file mode 100644
index 0000000000..36fa8f319c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/UserResource.java
@@ -0,0 +1,183 @@
+package com.baeldung.jhipster6.web.rest;
+
+import com.baeldung.jhipster6.config.Constants;
+import com.baeldung.jhipster6.domain.User;
+import com.baeldung.jhipster6.repository.UserRepository;
+import com.baeldung.jhipster6.security.AuthoritiesConstants;
+import com.baeldung.jhipster6.service.MailService;
+import com.baeldung.jhipster6.service.UserService;
+import com.baeldung.jhipster6.service.dto.UserDTO;
+import com.baeldung.jhipster6.web.rest.errors.BadRequestAlertException;
+import com.baeldung.jhipster6.web.rest.errors.EmailAlreadyUsedException;
+import com.baeldung.jhipster6.web.rest.errors.LoginAlreadyUsedException;
+import com.baeldung.jhipster6.web.rest.util.HeaderUtil;
+import com.baeldung.jhipster6.web.rest.util.PaginationUtil;
+import io.github.jhipster.web.util.ResponseUtil;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+/**
+ * REST controller for managing users.
+ *
+ * This class accesses the 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 final Logger log = LoggerFactory.getLogger(UserResource.class);
+
+ 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;
+ }
+
+ /**
+ * 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 ResponseEntity with status 201 (Created) and with body the new user, or with status 400 (Bad Request) if the login or email is already in use
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ * @throws BadRequestAlertException 400 (Bad Request) if the login or email is already in use
+ */
+ @PostMapping("/users")
+ @PreAuthorize("hasRole(\"" + 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( "A user is created with identifier " + newUser.getLogin(), newUser.getLogin()))
+ .body(newUser);
+ }
+ }
+
+ /**
+ * PUT /users : Updates an existing User.
+ *
+ * @param userDTO the user to update
+ * @return the ResponseEntity with status 200 (OK) and with body the updated user
+ * @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already in use
+ * @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already in use
+ */
+ @PutMapping("/users")
+ @PreAuthorize("hasRole(\"" + 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("A user is updated with identifier " + userDTO.getLogin(), userDTO.getLogin()));
+ }
+
+ /**
+ * GET /users : get all users.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and with body all users
+ */
+ @GetMapping("/users")
+ public ResponseEntity> getAllUsers(Pageable pageable) {
+ final Page page = userService.getAllManagedUsers(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/users");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * @return a string list of the all of the roles
+ */
+ @GetMapping("/users/authorities")
+ @PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
+ public List getAuthorities() {
+ return userService.getAuthorities();
+ }
+
+ /**
+ * GET /users/:login : get the "login" user.
+ *
+ * @param login the login of the user to find
+ * @return the ResponseEntity with status 200 (OK) and with body the "login" user, or with status 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));
+ }
+
+ /**
+ * DELETE /users/:login : delete the "login" User.
+ *
+ * @param login the login of the user to delete
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
+ @PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
+ public ResponseEntity deleteUser(@PathVariable String login) {
+ log.debug("REST request to delete User: {}", login);
+ userService.deleteUser(login);
+ return ResponseEntity.ok().headers(HeaderUtil.createAlert( "A user is deleted with identifier " + login, login)).build();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/BadRequestAlertException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/BadRequestAlertException.java
new file mode 100644
index 0000000000..a2e71a28bf
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/BadRequestAlertException.java
@@ -0,0 +1,42 @@
+package com.baeldung.jhipster6.web.rest.errors;
+
+import org.zalando.problem.AbstractThrowableProblem;
+import org.zalando.problem.Status;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/CustomParameterizedException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/CustomParameterizedException.java
new file mode 100644
index 0000000000..31add5c588
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/CustomParameterizedException.java
@@ -0,0 +1,54 @@
+package com.baeldung.jhipster6.web.rest.errors;
+
+import org.zalando.problem.AbstractThrowableProblem;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.zalando.problem.Status.BAD_REQUEST;
+
+/**
+ * Custom, parameterized exception, which can be translated on the client side.
+ * For example:
+ *
+ *
+ * throw new CustomParameterizedException("myCustomError", "hello", "world");
+ *
+ *
+ * Can be translated with:
+ *
+ *
+ * "error.myCustomError" : "The server says {{param0}} to {{param1}}"
+ *
+ */
+public class CustomParameterizedException extends AbstractThrowableProblem {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String PARAM = "param";
+
+ public CustomParameterizedException(String message, String... params) {
+ this(message, toParamMap(params));
+ }
+
+ public CustomParameterizedException(String message, Map paramMap) {
+ super(ErrorConstants.PARAMETERIZED_TYPE, "Parameterized Exception", BAD_REQUEST, null, null, null, toProblemParameters(message, paramMap));
+ }
+
+ public static Map toParamMap(String... params) {
+ Map paramMap = new HashMap<>();
+ if (params != null && params.length > 0) {
+ for (int i = 0; i < params.length; i++) {
+ paramMap.put(PARAM + i, params[i]);
+ }
+ }
+ return paramMap;
+ }
+
+ public static Map toProblemParameters(String message, Map paramMap) {
+ Map parameters = new HashMap<>();
+ parameters.put("message", message);
+ parameters.put("params", paramMap);
+ return parameters;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailAlreadyUsedException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailAlreadyUsedException.java
new file mode 100644
index 0000000000..01e358b76c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailAlreadyUsedException.java
@@ -0,0 +1,10 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailNotFoundException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailNotFoundException.java
new file mode 100644
index 0000000000..51a7857f32
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/EmailNotFoundException.java
@@ -0,0 +1,13 @@
+package com.baeldung.jhipster6.web.rest.errors;
+
+import org.zalando.problem.AbstractThrowableProblem;
+import org.zalando.problem.Status;
+
+public class EmailNotFoundException extends AbstractThrowableProblem {
+
+ private static final long serialVersionUID = 1L;
+
+ public EmailNotFoundException() {
+ super(ErrorConstants.EMAIL_NOT_FOUND_TYPE, "Email address not registered", Status.BAD_REQUEST);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ErrorConstants.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ErrorConstants.java
new file mode 100644
index 0000000000..b6a256dda0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ErrorConstants.java
@@ -0,0 +1,21 @@
+package com.baeldung.jhipster6.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 PARAMETERIZED_TYPE = URI.create(PROBLEM_BASE_URL + "/parameterized");
+ public static final URI ENTITY_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/entity-not-found");
+ 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");
+ public static final URI EMAIL_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/email-not-found");
+
+ private ErrorConstants() {
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ExceptionTranslator.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ExceptionTranslator.java
new file mode 100644
index 0000000000..d9e5697c77
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/ExceptionTranslator.java
@@ -0,0 +1,112 @@
+package com.baeldung.jhipster6.web.rest.errors;
+
+import com.baeldung.jhipster6.web.rest.util.HeaderUtil;
+
+import org.springframework.dao.ConcurrencyFailureException;
+import org.springframework.http.ResponseEntity;
+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.spring.web.advice.ProblemHandling;
+import org.zalando.problem.violations.ConstraintViolationProblem;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
+
+/**
+ * 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 {
+
+ 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";
+
+ /**
+ * 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(), 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 handleNoSuchElementException(NoSuchElementException ex, NativeWebRequest request) {
+ Problem problem = Problem.builder()
+ .withStatus(Status.NOT_FOUND)
+ .with(MESSAGE_KEY, ErrorConstants.ENTITY_NOT_FOUND_TYPE)
+ .build();
+ return create(ex, problem, request);
+ }
+
+ @ExceptionHandler
+ public ResponseEntity handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
+ return create(ex, request, HeaderUtil.createFailureAlert(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);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/FieldErrorVM.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/FieldErrorVM.java
new file mode 100644
index 0000000000..7c2504a3fc
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/FieldErrorVM.java
@@ -0,0 +1,33 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InternalServerErrorException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InternalServerErrorException.java
new file mode 100644
index 0000000000..844f315bfd
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InternalServerErrorException.java
@@ -0,0 +1,16 @@
+package com.baeldung.jhipster6.web.rest.errors;
+
+import org.zalando.problem.AbstractThrowableProblem;
+import org.zalando.problem.Status;
+
+/**
+ * Simple exception with a message, that returns an Internal Server Error code.
+ */
+public class InternalServerErrorException extends AbstractThrowableProblem {
+
+ private static final long serialVersionUID = 1L;
+
+ public InternalServerErrorException(String message) {
+ super(ErrorConstants.DEFAULT_TYPE, message, Status.INTERNAL_SERVER_ERROR);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InvalidPasswordException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InvalidPasswordException.java
new file mode 100644
index 0000000000..02d4b4da01
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/InvalidPasswordException.java
@@ -0,0 +1,13 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/LoginAlreadyUsedException.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/LoginAlreadyUsedException.java
new file mode 100644
index 0000000000..fabd4d153d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/LoginAlreadyUsedException.java
@@ -0,0 +1,10 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/errors/package-info.java
new file mode 100644
index 0000000000..b791df1268
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/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.baeldung.jhipster6.web.rest.errors;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/package-info.java
new file mode 100644
index 0000000000..f5d106c277
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring MVC REST controllers.
+ */
+package com.baeldung.jhipster6.web.rest;
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/HeaderUtil.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/HeaderUtil.java
new file mode 100644
index 0000000000..8546b97956
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/HeaderUtil.java
@@ -0,0 +1,45 @@
+package com.baeldung.jhipster6.web.rest.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+
+/**
+ * Utility class for HTTP headers creation.
+ */
+public final class HeaderUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(HeaderUtil.class);
+
+ private static final String APPLICATION_NAME = "bookstoreApp";
+
+ private HeaderUtil() {
+ }
+
+ public static HttpHeaders createAlert(String message, String param) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-" + APPLICATION_NAME + "-alert", message);
+ headers.add("X-" + APPLICATION_NAME + "-params", param);
+ return headers;
+ }
+
+ public static HttpHeaders createEntityCreationAlert(String entityName, String param) {
+ return createAlert("A new " + entityName + " is created with identifier " + param, param);
+ }
+
+ public static HttpHeaders createEntityUpdateAlert(String entityName, String param) {
+ return createAlert("A " + entityName + " is updated with identifier " + param, param);
+ }
+
+ public static HttpHeaders createEntityDeletionAlert(String entityName, String param) {
+ return createAlert("A " + entityName + " is deleted with identifier " + param, param);
+ }
+
+ public static HttpHeaders createFailureAlert(String entityName, String errorKey, String defaultMessage) {
+ log.error("Entity processing failed, {}", defaultMessage);
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-" + APPLICATION_NAME + "-error", defaultMessage);
+ headers.add("X-" + APPLICATION_NAME + "-params", entityName);
+ return headers;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/PaginationUtil.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/PaginationUtil.java
new file mode 100644
index 0000000000..7c28d93486
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/util/PaginationUtil.java
@@ -0,0 +1,45 @@
+package com.baeldung.jhipster6.web.rest.util;
+
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Utility class for handling pagination.
+ *
+ *
+ * Pagination uses the same principles as the GitHub API,
+ * and follow RFC 5988 (Link header).
+ */
+public final class PaginationUtil {
+
+ private PaginationUtil() {
+ }
+
+ public static HttpHeaders generatePaginationHttpHeaders(Page page, String baseUrl) {
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-Total-Count", Long.toString(page.getTotalElements()));
+ String link = "";
+ if ((page.getNumber() + 1) < page.getTotalPages()) {
+ link = "<" + generateUri(baseUrl, page.getNumber() + 1, page.getSize()) + ">; rel=\"next\",";
+ }
+ // prev link
+ if ((page.getNumber()) > 0) {
+ link += "<" + generateUri(baseUrl, page.getNumber() - 1, page.getSize()) + ">; rel=\"prev\",";
+ }
+ // last and first link
+ int lastPage = 0;
+ if (page.getTotalPages() > 0) {
+ lastPage = page.getTotalPages() - 1;
+ }
+ link += "<" + generateUri(baseUrl, lastPage, page.getSize()) + ">; rel=\"last\",";
+ link += "<" + generateUri(baseUrl, 0, page.getSize()) + ">; rel=\"first\"";
+ headers.add(HttpHeaders.LINK, link);
+ return headers;
+ }
+
+ private static String generateUri(String baseUrl, int page, int size) {
+ return UriComponentsBuilder.fromUriString(baseUrl).queryParam("page", page).queryParam("size", size).toUriString();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/KeyAndPasswordVM.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/KeyAndPasswordVM.java
new file mode 100644
index 0000000000..b2b1f91b0a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/KeyAndPasswordVM.java
@@ -0,0 +1,27 @@
+package com.baeldung.jhipster6.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/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoggerVM.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoggerVM.java
new file mode 100644
index 0000000000..d7ca167edf
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoggerVM.java
@@ -0,0 +1,46 @@
+package com.baeldung.jhipster6.web.rest.vm;
+
+import ch.qos.logback.classic.Logger;
+
+/**
+ * View Model object for storing a Logback logger.
+ */
+public class LoggerVM {
+
+ private String name;
+
+ private String level;
+
+ public LoggerVM(Logger logger) {
+ this.name = logger.getName();
+ this.level = logger.getEffectiveLevel().toString();
+ }
+
+ public LoggerVM() {
+ // Empty public constructor used by Jackson.
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+
+ public void setLevel(String level) {
+ this.level = level;
+ }
+
+ @Override
+ public String toString() {
+ return "LoggerVM{" +
+ "name='" + name + '\'' +
+ ", level='" + level + '\'' +
+ '}';
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoginVM.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoginVM.java
new file mode 100644
index 0000000000..40ede22b3a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/LoginVM.java
@@ -0,0 +1,52 @@
+package com.baeldung.jhipster6.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 = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH)
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return "LoginVM{" +
+ "username='" + username + '\'' +
+ ", rememberMe=" + rememberMe +
+ '}';
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/ManagedUserVM.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/ManagedUserVM.java
new file mode 100644
index 0000000000..2746ca6de0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/ManagedUserVM.java
@@ -0,0 +1,35 @@
+package com.baeldung.jhipster6.web.rest.vm;
+
+import com.baeldung.jhipster6.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;
+ }
+
+ @Override
+ public String toString() {
+ return "ManagedUserVM{" +
+ "} " + super.toString();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/package-info.java b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/package-info.java
new file mode 100644
index 0000000000..af0defc392
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/java/com/baeldung/jhipster6/web/rest/vm/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * View Models used by Spring MVC REST controllers.
+ */
+package com.baeldung.jhipster6.web.rest.vm;
diff --git a/jhipster-6/bookstore-monolith/src/main/jib/entrypoint.sh b/jhipster-6/bookstore-monolith/src/main/jib/entrypoint.sh
new file mode 100644
index 0000000000..b3c4541011
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/jib/entrypoint.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
+exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "com.baeldung.jhipster5.BookstoreApp" "$@"
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/.h2.server.properties b/jhipster-6/bookstore-monolith/src/main/resources/.h2.server.properties
new file mode 100644
index 0000000000..99767b3a8a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/.h2.server.properties
@@ -0,0 +1,5 @@
+#H2 Server Properties
+0=JHipster H2 (Memory)|org.h2.Driver|jdbc\:h2\:mem\:bookstore|Bookstore
+webAllowOthers=true
+webPort=8082
+webSSL=false
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/banner.txt b/jhipster-6/bookstore-monolith/src/main/resources/banner.txt
new file mode 100644
index 0000000000..e0bc55aaff
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/banner.txt
@@ -0,0 +1,10 @@
+
+ ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗
+ ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗
+ ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╝ ╚█████╗ ██║ ██████╗ ███████╔╝
+ ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔═══██║ ██║ ██╔════╝ ╚═══██╗ ██║ ██╔═══╝ ██╔══██║
+ ${AnsiColor.GREEN}╚██████╔╝${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╝ ██║ ████████╗ ██║ ╚██╗
+ ${AnsiColor.GREEN} ╚═════╝ ${AnsiColor.RED} ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝
+
+${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} ::
+:: https://www.jhipster.tech ::${AnsiColor.DEFAULT}
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/application-dev.yml b/jhipster-6/bookstore-monolith/src/main/resources/config/application-dev.yml
new file mode 100644
index 0000000000..64742feb45
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/application-dev.yml
@@ -0,0 +1,122 @@
+# ===================================================================
+# Spring Boot configuration for the "dev" profile.
+#
+# This configuration overrides the application.yml file.
+#
+# More information on profiles: https://www.jhipster.tech/profiles/
+# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+logging:
+ level:
+ ROOT: DEBUG
+ io.github.jhipster: DEBUG
+ com.baeldung.jhipster5: DEBUG
+
+spring:
+ profiles:
+ active: dev
+ include:
+ - swagger
+ # Uncomment to activate TLS for the dev profile
+ #- tls
+ devtools:
+ restart:
+ enabled: true
+ additional-exclude: .h2.server.properties
+ livereload:
+ enabled: false # we use Webpack dev server + BrowserSync for livereload
+ jackson:
+ serialization:
+ indent-output: true
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ url: jdbc:h2:mem:bookstore;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+ username: Bookstore
+ password:
+ hikari:
+ poolName: Hikari
+ auto-commit: false
+ h2:
+ console:
+ enabled: false
+ jpa:
+ database-platform: io.github.jhipster.domain.util.FixedH2Dialect
+ database: H2
+ show-sql: true
+ properties:
+ hibernate.id.new_generator_mappings: true
+ hibernate.connection.provider_disables_autocommit: true
+ hibernate.cache.use_second_level_cache: false
+ hibernate.cache.use_query_cache: false
+ hibernate.generate_statistics: true
+ liquibase:
+ contexts: dev
+ mail:
+ host: localhost
+ port: 25
+ username:
+ password:
+ messages:
+ cache-duration: PT1S # 1 second, see the ISO 8601 standard
+ thymeleaf:
+ cache: false
+
+server:
+ port: 8080
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+jhipster:
+ http:
+ version: V_1_1 # To use HTTP/2 you will need to activate TLS (see application-tls.yml)
+ # CORS is only enabled by default with the "dev" profile, so BrowserSync can access the API
+ cors:
+ allowed-origins: "*"
+ allowed-methods: "*"
+ allowed-headers: "*"
+ exposed-headers: "Authorization,Link,X-Total-Count"
+ allow-credentials: true
+ max-age: 1800
+ security:
+ authentication:
+ jwt:
+ # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one)
+ base64-secret: NDJmOTVlZjI2NzhlZDRjNmVkNTM1NDE2NjkyNDljZDJiNzBlMjI5YmZjMjY3MzdjZmZlMjI3NjE4OTRkNzc5MWYzNDNlYWMzYmJjOWRmMjc5ZWQyZTZmOWZkOTMxZWZhNWE1MTVmM2U2NjFmYjhlNDc2Y2Q3NzliMGY0YzFkNmI=
+ # Token is valid 24 hours
+ token-validity-in-seconds: 86400
+ token-validity-in-seconds-for-remember-me: 2592000
+ mail: # specific JHipster mail property, for standard properties see MailProperties
+ from: Bookstore@localhost
+ base-url: http://127.0.0.1:8080
+ metrics:
+ logs: # Reports metrics in the logs
+ enabled: false
+ report-frequency: 60 # in seconds
+ logging:
+ logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
+ enabled: false
+ host: localhost
+ port: 5000
+ queue-size: 512
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# application:
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/application-prod.yml b/jhipster-6/bookstore-monolith/src/main/resources/config/application-prod.yml
new file mode 100644
index 0000000000..d698099fac
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/application-prod.yml
@@ -0,0 +1,133 @@
+# ===================================================================
+# Spring Boot configuration for the "prod" profile.
+#
+# This configuration overrides the application.yml file.
+#
+# More information on profiles: https://www.jhipster.tech/profiles/
+# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+logging:
+ level:
+ ROOT: INFO
+ com.baeldung.jhipster5: INFO
+ io.github.jhipster: INFO
+
+spring:
+ devtools:
+ restart:
+ enabled: false
+ livereload:
+ enabled: false
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ url: jdbc:mysql://localhost:3306/Bookstore?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
+ username: root
+ password:
+ hikari:
+ poolName: Hikari
+ auto-commit: false
+ data-source-properties:
+ cachePrepStmts: true
+ prepStmtCacheSize: 250
+ prepStmtCacheSqlLimit: 2048
+ useServerPrepStmts: true
+ jpa:
+ database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
+ database: MYSQL
+ show-sql: false
+ properties:
+ hibernate.id.new_generator_mappings: true
+ hibernate.connection.provider_disables_autocommit: true
+ hibernate.cache.use_second_level_cache: false
+ hibernate.cache.use_query_cache: false
+ hibernate.generate_statistics: true
+ liquibase:
+ contexts: prod
+ mail:
+ host: localhost
+ port: 25
+ username:
+ password:
+ thymeleaf:
+ cache: true
+
+# ===================================================================
+# To enable TLS in production, generate a certificate using:
+# keytool -genkey -alias bookstore -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
+#
+# Then, modify the server.ssl properties so your "server" configuration looks like:
+#
+# server:
+# port: 443
+# ssl:
+# key-store: classpath:config/tls/keystore.p12
+# key-store-password: password
+# key-store-type: PKCS12
+# key-alias: bookstore
+# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/)
+# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
+# ===================================================================
+server:
+ port: 8080
+ compression:
+ enabled: true
+ mime-types: text/html,text/xml,text/plain,text/css, application/javascript, application/json
+ min-response-size: 1024
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+jhipster:
+ http:
+ version: V_1_1 # To use HTTP/2 you will need SSL support (see above the "server.ssl" configuration)
+ cache: # Used by the CachingHttpHeadersFilter
+ timeToLiveInDays: 1461
+ security:
+ authentication:
+ jwt:
+ # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one)
+ # As this is the PRODUCTION configuration, you MUST change the default key, and store it securely:
+ # - In the JHipster Registry (which includes a Spring Cloud Config server)
+ # - In a separate `application-prod.yml` file, in the same folder as your executable WAR file
+ # - In the `JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET` environment variable
+ base64-secret: NDJmOTVlZjI2NzhlZDRjNmVkNTM1NDE2NjkyNDljZDJiNzBlMjI5YmZjMjY3MzdjZmZlMjI3NjE4OTRkNzc5MWYzNDNlYWMzYmJjOWRmMjc5ZWQyZTZmOWZkOTMxZWZhNWE1MTVmM2U2NjFmYjhlNDc2Y2Q3NzliMGY0YzFkNmI=
+ # Token is valid 24 hours
+ token-validity-in-seconds: 86400
+ token-validity-in-seconds-for-remember-me: 2592000
+ mail: # specific JHipster mail property, for standard properties see MailProperties
+ from: Bookstore@localhost
+ base-url: http://my-server-url-to-change # Modify according to your server's URL
+ metrics:
+ logs: # Reports metrics in the logs
+ enabled: false
+ report-frequency: 60 # in seconds
+ logging:
+ logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
+ enabled: false
+ host: localhost
+ port: 5000
+ queue-size: 512
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# application:
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/application-tls.yml b/jhipster-6/bookstore-monolith/src/main/resources/config/application-tls.yml
new file mode 100644
index 0000000000..c4e0565cc7
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/application-tls.yml
@@ -0,0 +1,20 @@
+# ===================================================================
+# Activate this profile to enable TLS and HTTP/2.
+#
+# JHipster has generated a self-signed certificate, which will be used to encrypt traffic.
+# As your browser will not understand this certificate, you will need to import it.
+#
+# Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag
+# at chrome://flags/#allow-insecure-localhost
+# ===================================================================
+server:
+ ssl:
+ key-store: classpath:config/tls/keystore.p12
+ key-store-password: password
+ key-store-type: PKCS12
+ key-alias: selfsigned
+ ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
+ enabled-protocols: TLSv1.2
+jhipster:
+ http:
+ version: V_2_0
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/application.yml b/jhipster-6/bookstore-monolith/src/main/resources/config/application.yml
new file mode 100644
index 0000000000..5b28b7f00d
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/application.yml
@@ -0,0 +1,140 @@
+# ===================================================================
+# Spring Boot configuration.
+#
+# This configuration will be overridden by the Spring profile you use,
+# for example application-dev.yml if you use the "dev" profile.
+#
+# More information on profiles: https://www.jhipster.tech/profiles/
+# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+management:
+ endpoints:
+ web:
+ base-path: /management
+ exposure:
+ include: ["configprops", "env", "health", "info", "threaddump", "logfile", "jhi-metrics", "prometheus" ]
+ endpoint:
+ health:
+ show-details: when-authorized
+ jhi-metrics:
+ enabled: true
+ info:
+ git:
+ mode: full
+ health:
+ mail:
+ enabled: false # When using the MailService, configure an SMTP server and set this to true
+ metrics:
+ export:
+ # Prometheus is the default metrics backend
+ prometheus:
+ enabled: true
+ step: 60
+ binders:
+ jvm:
+ enabled: true
+ processor:
+ enabled: true
+ uptime:
+ enabled: true
+ logback:
+ enabled: true
+ files:
+ enabled: true
+ integration:
+ enabled: true
+ distribution:
+ percentiles-histogram:
+ all: true
+ percentiles:
+ all: 0, 0.5, 0.75, 0.95, 0.99, 1.0
+ web:
+ server:
+ auto-time-requests: true
+
+spring:
+ application:
+ name: Bookstore
+ 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 WAR file
+ # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS`
+ active: #spring.profiles.active#
+ jpa:
+ open-in-view: false
+ properties:
+ hibernate.jdbc.time_zone: UTC
+ hibernate:
+ ddl-auto: none
+ naming:
+ physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
+ implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+ messages:
+ basename: i18n/messages
+ mvc:
+ favicon:
+ enabled: false
+ thymeleaf:
+ mode: HTML
+
+server:
+ servlet:
+ session:
+ cookie:
+ http-only: true
+
+# Properties to be exposed on the /info management endpoint
+info:
+ # Comma separated list of profiles that will trigger the ribbon to show
+ display-ribbon-on-profiles: "dev"
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+jhipster:
+ async:
+ core-pool-size: 2
+ max-pool-size: 50
+ queue-capacity: 10000
+ # By default CORS is disabled. Uncomment to enable.
+ #cors:
+ #allowed-origins: "*"
+ #allowed-methods: "*"
+ #allowed-headers: "*"
+ #exposed-headers: "Authorization,Link,X-Total-Count"
+ #allow-credentials: true
+ #max-age: 1800
+ mail:
+ from: Bookstore@localhost
+ swagger:
+ default-include-pattern: /api/.*
+ title: Bookstore API
+ description: Bookstore API documentation
+ version: 0.0.1
+ terms-of-service-url:
+ contact-name:
+ contact-url:
+ contact-email:
+ license:
+ license-url:
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# application:
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/authorities.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/authorities.csv
new file mode 100644
index 0000000000..af5c6dfa18
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/authorities.csv
@@ -0,0 +1,3 @@
+name
+ROLE_ADMIN
+ROLE_USER
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml
new file mode 100644
index 0000000000..dd4b01d487
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/20190319124041_added_entity_Book.xml b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/20190319124041_added_entity_Book.xml
new file mode 100644
index 0000000000..f040387cf1
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/changelog/20190319124041_added_entity_Book.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/authority.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/authority.csv
new file mode 100644
index 0000000000..af5c6dfa18
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/authority.csv
@@ -0,0 +1,3 @@
+name
+ROLE_ADMIN
+ROLE_USER
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user.csv
new file mode 100644
index 0000000000..b25922b699
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user.csv
@@ -0,0 +1,5 @@
+id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by
+1;system;$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG;System;System;system@localhost;;true;en;system;system
+2;anonymoususer;$2a$10$j8S5d7Sr7.8VTOYNviDPOeWX8KcYILUVJBsYV83Y5NtECayypx9lO;Anonymous;User;anonymous@localhost;;true;en;system;system
+3;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system
+4;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user_authority.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user_authority.csv
new file mode 100644
index 0000000000..06c5feeeea
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/data/user_authority.csv
@@ -0,0 +1,6 @@
+user_id;authority_name
+1;ROLE_ADMIN
+1;ROLE_USER
+3;ROLE_ADMIN
+3;ROLE_USER
+4;ROLE_USER
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/master.xml b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/master.xml
new file mode 100644
index 0000000000..e045ee0100
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/master.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users.csv
new file mode 100644
index 0000000000..b25922b699
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users.csv
@@ -0,0 +1,5 @@
+id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by
+1;system;$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG;System;System;system@localhost;;true;en;system;system
+2;anonymoususer;$2a$10$j8S5d7Sr7.8VTOYNviDPOeWX8KcYILUVJBsYV83Y5NtECayypx9lO;Anonymous;User;anonymous@localhost;;true;en;system;system
+3;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system
+4;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users_authorities.csv b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users_authorities.csv
new file mode 100644
index 0000000000..06c5feeeea
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/config/liquibase/users_authorities.csv
@@ -0,0 +1,6 @@
+user_id;authority_name
+1;ROLE_ADMIN
+1;ROLE_USER
+3;ROLE_ADMIN
+3;ROLE_USER
+4;ROLE_USER
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/config/tls/keystore.p12 b/jhipster-6/bookstore-monolith/src/main/resources/config/tls/keystore.p12
new file mode 100644
index 0000000000..364fad7435
Binary files /dev/null and b/jhipster-6/bookstore-monolith/src/main/resources/config/tls/keystore.p12 differ
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/i18n/messages.properties b/jhipster-6/bookstore-monolith/src/main/resources/i18n/messages.properties
new file mode 100644
index 0000000000..52a60093c5
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/i18n/messages.properties
@@ -0,0 +1,21 @@
+# Error page
+error.title=Your request cannot be processed
+error.subtitle=Sorry, an error has occurred.
+error.status=Status:
+error.message=Message:
+
+# Activation email
+email.activation.title=Bookstore account activation
+email.activation.greeting=Dear {0}
+email.activation.text1=Your Bookstore account has been created, please click on the URL below to activate it:
+email.activation.text2=Regards,
+email.signature=Bookstore Team.
+
+# Creation email
+email.creation.text1=Your Bookstore account has been created, please click on the URL below to access it:
+
+# Reset email
+email.reset.title=Bookstore password reset
+email.reset.greeting=Dear {0}
+email.reset.text1=For your Bookstore account a password reset was requested, please click on the URL below to reset it:
+email.reset.text2=Regards,
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/logback-spring.xml b/jhipster-6/bookstore-monolith/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000000..b0e2d9aa95
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/logback-spring.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/templates/error.html b/jhipster-6/bookstore-monolith/src/main/resources/templates/error.html
new file mode 100644
index 0000000000..08616bcf1e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/templates/error.html
@@ -0,0 +1,163 @@
+
+
+
+
+
+ Your request cannot be processed
+
+
+
+
+
Your request cannot be processed :(
+
+
Sorry, an error has occurred.
+
+
Status: (
)
+
+ Message:
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/activationEmail.html b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/activationEmail.html
new file mode 100644
index 0000000000..cb021d8e6a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/activationEmail.html
@@ -0,0 +1,25 @@
+
+
+
+ JHipster activation
+
+
+
+
+
+ Dear
+
+
+ Your JHipster account has been created, please click on the URL below to activate it:
+
+
+ Activation link
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/creationEmail.html b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/creationEmail.html
new file mode 100644
index 0000000000..dc0cff5883
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/creationEmail.html
@@ -0,0 +1,25 @@
+
+
+
+ JHipster creation
+
+
+
+
+
+ Dear
+
+
+ Your JHipster account has been created, please click on the URL below to access it:
+
+
+ Login link
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/passwordResetEmail.html b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/passwordResetEmail.html
new file mode 100644
index 0000000000..f44511265b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/resources/templates/mail/passwordResetEmail.html
@@ -0,0 +1,25 @@
+
+
+
+ JHipster password reset
+
+
+
+
+
+ Dear
+
+
+ For your JHipster account a password reset was requested, please click on the URL below to reset it:
+
+
+ Login link
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/404.html b/jhipster-6/bookstore-monolith/src/main/webapp/404.html
new file mode 100644
index 0000000000..3fdc0bee1a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/404.html
@@ -0,0 +1,61 @@
+
+
+
+
+ Page Not Found
+
+
+
+
+
+ Page Not Found
+ Sorry, but the page you were trying to view does not exist.
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/WEB-INF/web.xml b/jhipster-6/bookstore-monolith/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000..f1611b515a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ html
+ text/html;charset=utf-8
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.module.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.module.ts
new file mode 100644
index 0000000000..a167cab1c2
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.module.ts
@@ -0,0 +1,30 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { BookstoreSharedModule } from 'app/shared';
+
+import {
+ PasswordStrengthBarComponent,
+ RegisterComponent,
+ ActivateComponent,
+ PasswordComponent,
+ PasswordResetInitComponent,
+ PasswordResetFinishComponent,
+ SettingsComponent,
+ accountState
+} from './';
+
+@NgModule({
+ imports: [BookstoreSharedModule, RouterModule.forChild(accountState)],
+ declarations: [
+ ActivateComponent,
+ RegisterComponent,
+ PasswordComponent,
+ PasswordStrengthBarComponent,
+ PasswordResetInitComponent,
+ PasswordResetFinishComponent,
+ SettingsComponent
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BookstoreAccountModule {}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.route.ts
new file mode 100644
index 0000000000..cba5d40716
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/account.route.ts
@@ -0,0 +1,12 @@
+import { Routes } from '@angular/router';
+
+import { settingsRoute } from './';
+
+const ACCOUNT_ROUTES = [settingsRoute];
+
+export const accountState: Routes = [
+ {
+ path: '',
+ children: ACCOUNT_ROUTES
+ }
+];
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.html
new file mode 100644
index 0000000000..c7078ede86
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.html
@@ -0,0 +1,17 @@
+
+
+
+
Activation
+
+
+
Your user account has been activated. Please
+
sign in.
+
+
+
+ Your user could not be activated. Please use the registration form to sign up.
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.ts
new file mode 100644
index 0000000000..5c398073c3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.component.ts
@@ -0,0 +1,37 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute } from '@angular/router';
+
+import { LoginModalService } from 'app/core';
+import { ActivateService } from './activate.service';
+
+@Component({
+ selector: 'jhi-activate',
+ templateUrl: './activate.component.html'
+})
+export class ActivateComponent implements OnInit {
+ error: string;
+ success: string;
+ modalRef: NgbModalRef;
+
+ constructor(private activateService: ActivateService, private loginModalService: LoginModalService, private route: ActivatedRoute) {}
+
+ ngOnInit() {
+ this.route.queryParams.subscribe(params => {
+ this.activateService.get(params['key']).subscribe(
+ () => {
+ this.error = null;
+ this.success = 'OK';
+ },
+ () => {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ );
+ });
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.route.ts
new file mode 100644
index 0000000000..b415b17a18
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { ActivateComponent } from './activate.component';
+
+export const activateRoute: Route = {
+ path: 'activate',
+ component: ActivateComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'Activation'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.service.ts
new file mode 100644
index 0000000000..adade9efad
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/activate/activate.service.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class ActivateService {
+ constructor(private http: HttpClient) {}
+
+ get(key: string): Observable {
+ return this.http.get(SERVER_API_URL + 'api/activate', {
+ params: new HttpParams().set('key', key)
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/index.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/index.ts
new file mode 100644
index 0000000000..aeada0551c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/index.ts
@@ -0,0 +1,19 @@
+export * from './activate/activate.component';
+export * from './activate/activate.service';
+export * from './activate/activate.route';
+export * from './password/password.component';
+export * from './password/password-strength-bar.component';
+export * from './password/password.service';
+export * from './password/password.route';
+export * from './password-reset/finish/password-reset-finish.component';
+export * from './password-reset/finish/password-reset-finish.service';
+export * from './password-reset/finish/password-reset-finish.route';
+export * from './password-reset/init/password-reset-init.component';
+export * from './password-reset/init/password-reset-init.service';
+export * from './password-reset/init/password-reset-init.route';
+export * from './register/register.component';
+export * from './register/register.service';
+export * from './register/register.route';
+export * from './settings/settings.component';
+export * from './settings/settings.route';
+export * from './account.route';
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html
new file mode 100644
index 0000000000..6d6baea694
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html
@@ -0,0 +1,77 @@
+
+
+
+
Reset password
+
+
+ The password reset key is missing.
+
+
+
+
Choose a new password
+
+
+
+
Your password couldn't be reset. Remember a password request is only valid for 24 hours.
+
+
+
+ Your password has been reset. Please
+ sign in.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts
new file mode 100644
index 0000000000..72aac25c96
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts
@@ -0,0 +1,65 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute } from '@angular/router';
+
+import { LoginModalService } from 'app/core';
+import { PasswordResetFinishService } from './password-reset-finish.service';
+
+@Component({
+ selector: 'jhi-password-reset-finish',
+ templateUrl: './password-reset-finish.component.html'
+})
+export class PasswordResetFinishComponent implements OnInit, AfterViewInit {
+ confirmPassword: string;
+ doNotMatch: string;
+ error: string;
+ keyMissing: boolean;
+ resetAccount: any;
+ success: string;
+ modalRef: NgbModalRef;
+ key: string;
+
+ constructor(
+ private passwordResetFinishService: PasswordResetFinishService,
+ private loginModalService: LoginModalService,
+ private route: ActivatedRoute,
+ private elementRef: ElementRef,
+ private renderer: Renderer
+ ) {}
+
+ ngOnInit() {
+ this.route.queryParams.subscribe(params => {
+ this.key = params['key'];
+ });
+ this.resetAccount = {};
+ this.keyMissing = !this.key;
+ }
+
+ ngAfterViewInit() {
+ if (this.elementRef.nativeElement.querySelector('#password') != null) {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#password'), 'focus', []);
+ }
+ }
+
+ finishReset() {
+ this.doNotMatch = null;
+ this.error = null;
+ if (this.resetAccount.password !== this.confirmPassword) {
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.passwordResetFinishService.save({ key: this.key, newPassword: this.resetAccount.password }).subscribe(
+ () => {
+ this.success = 'OK';
+ },
+ () => {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ );
+ }
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts
new file mode 100644
index 0000000000..a09cba9377
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { PasswordResetFinishComponent } from './password-reset-finish.component';
+
+export const passwordResetFinishRoute: Route = {
+ path: 'reset/finish',
+ component: PasswordResetFinishComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'Password'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts
new file mode 100644
index 0000000000..706bdaa5b1
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class PasswordResetFinishService {
+ constructor(private http: HttpClient) {}
+
+ save(keyAndPassword: any): Observable {
+ return this.http.post(SERVER_API_URL + 'api/account/reset-password/finish', keyAndPassword);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html
new file mode 100644
index 0000000000..7fe7b0bdec
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html
@@ -0,0 +1,46 @@
+
+
+
+
Reset your password
+
+
+ Email address isn't registered! Please check and try again.
+
+
+
+
Enter the email address you used to register.
+
+
+
+
Check your emails for details on how to reset your password.
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts
new file mode 100644
index 0000000000..e32617341c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { EMAIL_NOT_FOUND_TYPE } from 'app/shared';
+import { PasswordResetInitService } from './password-reset-init.service';
+
+@Component({
+ selector: 'jhi-password-reset-init',
+ templateUrl: './password-reset-init.component.html'
+})
+export class PasswordResetInitComponent implements OnInit, AfterViewInit {
+ error: string;
+ errorEmailNotExists: string;
+ resetAccount: any;
+ success: string;
+
+ constructor(private passwordResetInitService: PasswordResetInitService, private elementRef: ElementRef, private renderer: Renderer) {}
+
+ ngOnInit() {
+ this.resetAccount = {};
+ }
+
+ ngAfterViewInit() {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#email'), 'focus', []);
+ }
+
+ requestReset() {
+ this.error = null;
+ this.errorEmailNotExists = null;
+
+ this.passwordResetInitService.save(this.resetAccount.email).subscribe(
+ () => {
+ this.success = 'OK';
+ },
+ response => {
+ this.success = null;
+ if (response.status === 400 && response.error.type === EMAIL_NOT_FOUND_TYPE) {
+ this.errorEmailNotExists = 'ERROR';
+ } else {
+ this.error = 'ERROR';
+ }
+ }
+ );
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts
new file mode 100644
index 0000000000..a1708c98b3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { PasswordResetInitComponent } from './password-reset-init.component';
+
+export const passwordResetInitRoute: Route = {
+ path: 'reset/request',
+ component: PasswordResetInitComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'Password'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts
new file mode 100644
index 0000000000..c24ccf94d2
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class PasswordResetInitService {
+ constructor(private http: HttpClient) {}
+
+ save(mail: string): Observable {
+ return this.http.post(SERVER_API_URL + 'api/account/reset-password/init', mail);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.component.ts
new file mode 100644
index 0000000000..4159fde882
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.component.ts
@@ -0,0 +1,85 @@
+import { Component, ElementRef, Input, Renderer } from '@angular/core';
+
+@Component({
+ selector: 'jhi-password-strength-bar',
+ template: `
+
+ `,
+ styleUrls: ['password-strength-bar.scss']
+})
+export class PasswordStrengthBarComponent {
+ colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0'];
+
+ constructor(private renderer: Renderer, private elementRef: ElementRef) {}
+
+ measureStrength(p: string): number {
+ let force = 0;
+ const regex = /[$-/:-?{-~!"^_`\[\]]/g; // "
+ const lowerLetters = /[a-z]+/.test(p);
+ const upperLetters = /[A-Z]+/.test(p);
+ const numbers = /[0-9]+/.test(p);
+ const symbols = regex.test(p);
+
+ const flags = [lowerLetters, upperLetters, numbers, symbols];
+ const passedMatches = flags.filter((isMatchedFlag: boolean) => {
+ return isMatchedFlag === true;
+ }).length;
+
+ force += 2 * p.length + (p.length >= 10 ? 1 : 0);
+ force += passedMatches * 10;
+
+ // penalty (short password)
+ force = p.length <= 6 ? Math.min(force, 10) : force;
+
+ // penalty (poor variety of characters)
+ force = passedMatches === 1 ? Math.min(force, 10) : force;
+ force = passedMatches === 2 ? Math.min(force, 20) : force;
+ force = passedMatches === 3 ? Math.min(force, 40) : force;
+
+ return force;
+ }
+
+ getColor(s: number): any {
+ let idx = 0;
+ if (s <= 10) {
+ idx = 0;
+ } else if (s <= 20) {
+ idx = 1;
+ } else if (s <= 30) {
+ idx = 2;
+ } else if (s <= 40) {
+ idx = 3;
+ } else {
+ idx = 4;
+ }
+ return { idx: idx + 1, col: this.colors[idx] };
+ }
+
+ @Input()
+ set passwordToCheck(password: string) {
+ if (password) {
+ const c = this.getColor(this.measureStrength(password));
+ const element = this.elementRef.nativeElement;
+ if (element.className) {
+ this.renderer.setElementClass(element, element.className, false);
+ }
+ const lis = element.getElementsByTagName('li');
+ for (let i = 0; i < lis.length; i++) {
+ if (i < c.idx) {
+ this.renderer.setElementStyle(lis[i], 'backgroundColor', c.col);
+ } else {
+ this.renderer.setElementStyle(lis[i], 'backgroundColor', '#DDD');
+ }
+ }
+ }
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.scss b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.scss
new file mode 100644
index 0000000000..9744b9b784
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password-strength-bar.scss
@@ -0,0 +1,23 @@
+/* ==========================================================================
+start Password strength bar style
+========================================================================== */
+ul#strength {
+ display: inline;
+ list-style: none;
+ margin: 0;
+ margin-left: 15px;
+ padding: 0;
+ vertical-align: 2px;
+}
+
+.point {
+ background: #ddd;
+ border-radius: 2px;
+ display: inline-block;
+ height: 5px;
+ margin-right: 1px;
+ width: 20px;
+ &:last-child {
+ margin: 0 !important;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.html
new file mode 100644
index 0000000000..79fb60c3bc
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.html
@@ -0,0 +1,77 @@
+
+
+
+
Password for [{{account.login}}]
+
+
+ Password changed!
+
+
+ An error has occurred! The password could not be changed.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.ts
new file mode 100644
index 0000000000..3004effa57
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit } from '@angular/core';
+
+import { AccountService } from 'app/core';
+import { PasswordService } from './password.service';
+
+@Component({
+ selector: 'jhi-password',
+ templateUrl: './password.component.html'
+})
+export class PasswordComponent implements OnInit {
+ doNotMatch: string;
+ error: string;
+ success: string;
+ account: any;
+ currentPassword: string;
+ newPassword: string;
+ confirmPassword: string;
+
+ constructor(private passwordService: PasswordService, private accountService: AccountService) {}
+
+ ngOnInit() {
+ this.accountService.identity().then(account => {
+ this.account = account;
+ });
+ }
+
+ changePassword() {
+ if (this.newPassword !== this.confirmPassword) {
+ this.error = null;
+ this.success = null;
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.doNotMatch = null;
+ this.passwordService.save(this.newPassword, this.currentPassword).subscribe(
+ () => {
+ this.error = null;
+ this.success = 'OK';
+ },
+ () => {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ );
+ }
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.route.ts
new file mode 100644
index 0000000000..4bb115fd44
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from 'app/core';
+import { PasswordComponent } from './password.component';
+
+export const passwordRoute: Route = {
+ path: 'password',
+ component: PasswordComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'Password'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.service.ts
new file mode 100644
index 0000000000..028df7b0e4
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/password/password.service.ts
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class PasswordService {
+ constructor(private http: HttpClient) {}
+
+ save(newPassword: string, currentPassword: string): Observable {
+ return this.http.post(SERVER_API_URL + 'api/account/change-password', { currentPassword, newPassword });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.html
new file mode 100644
index 0000000000..596f782828
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.html
@@ -0,0 +1,124 @@
+
+
+
+
Registration
+
+
+ Registration saved! Please check your email for confirmation.
+
+
+
+ Registration failed! Please try again later.
+
+
+
+ Login name already registered! Please choose another one.
+
+
+
+ Email is already in use! Please choose another one.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
+
+
+
If you want to
+
sign in, you can try the default accounts:
- Administrator (login="admin" and password="admin")
- User (login="user" and password="user").
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.ts
new file mode 100644
index 0000000000..85244d2970
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.component.ts
@@ -0,0 +1,71 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+
+import { EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/shared';
+import { LoginModalService } from 'app/core';
+import { Register } from './register.service';
+
+@Component({
+ selector: 'jhi-register',
+ templateUrl: './register.component.html'
+})
+export class RegisterComponent implements OnInit, AfterViewInit {
+ confirmPassword: string;
+ doNotMatch: string;
+ error: string;
+ errorEmailExists: string;
+ errorUserExists: string;
+ registerAccount: any;
+ success: boolean;
+ modalRef: NgbModalRef;
+
+ constructor(
+ private loginModalService: LoginModalService,
+ private registerService: Register,
+ private elementRef: ElementRef,
+ private renderer: Renderer
+ ) {}
+
+ ngOnInit() {
+ this.success = false;
+ this.registerAccount = {};
+ }
+
+ ngAfterViewInit() {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#login'), 'focus', []);
+ }
+
+ register() {
+ if (this.registerAccount.password !== this.confirmPassword) {
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.doNotMatch = null;
+ this.error = null;
+ this.errorUserExists = null;
+ this.errorEmailExists = null;
+ this.registerAccount.langKey = 'en';
+ this.registerService.save(this.registerAccount).subscribe(
+ () => {
+ this.success = true;
+ },
+ response => this.processError(response)
+ );
+ }
+ }
+
+ openLogin() {
+ this.modalRef = this.loginModalService.open();
+ }
+
+ private processError(response: HttpErrorResponse) {
+ this.success = null;
+ if (response.status === 400 && response.error.type === LOGIN_ALREADY_USED_TYPE) {
+ this.errorUserExists = 'ERROR';
+ } else if (response.status === 400 && response.error.type === EMAIL_ALREADY_USED_TYPE) {
+ this.errorEmailExists = 'ERROR';
+ } else {
+ this.error = 'ERROR';
+ }
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.route.ts
new file mode 100644
index 0000000000..626cd32ff9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { RegisterComponent } from './register.component';
+
+export const registerRoute: Route = {
+ path: 'register',
+ component: RegisterComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'Registration'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.service.ts
new file mode 100644
index 0000000000..dfe6f1da6a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/register/register.service.ts
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class Register {
+ constructor(private http: HttpClient) {}
+
+ save(account: any): Observable {
+ return this.http.post(SERVER_API_URL + 'api/register', account);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.html
new file mode 100644
index 0000000000..bae1bb67e6
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.html
@@ -0,0 +1,80 @@
+
+
+
+
User settings for [{{settingsAccount.login}}]
+
+
+ Settings saved!
+
+
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.ts
new file mode 100644
index 0000000000..92afaca793
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.component.ts
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+
+import { AccountService } from 'app/core';
+
+@Component({
+ selector: 'jhi-settings',
+ templateUrl: './settings.component.html'
+})
+export class SettingsComponent implements OnInit {
+ error: string;
+ success: string;
+ settingsAccount: any;
+ languages: any[];
+
+ constructor(private accountService: AccountService) {}
+
+ ngOnInit() {
+ this.accountService.identity().then(account => {
+ this.settingsAccount = this.copyAccount(account);
+ });
+ }
+
+ save() {
+ this.accountService.save(this.settingsAccount).subscribe(
+ () => {
+ this.error = null;
+ this.success = 'OK';
+ this.accountService.identity(true).then(account => {
+ this.settingsAccount = this.copyAccount(account);
+ });
+ },
+ () => {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ );
+ }
+
+ copyAccount(account) {
+ return {
+ activated: account.activated,
+ email: account.email,
+ firstName: account.firstName,
+ langKey: account.langKey,
+ lastName: account.lastName,
+ login: account.login,
+ imageUrl: account.imageUrl
+ };
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.route.ts
new file mode 100644
index 0000000000..3c9cf18e15
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/account/settings/settings.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from 'app/core';
+import { SettingsComponent } from './settings.component';
+
+export const settingsRoute: Route = {
+ path: 'settings',
+ component: SettingsComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'Settings'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.module.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.module.ts
new file mode 100644
index 0000000000..4e46e0fe13
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.module.ts
@@ -0,0 +1,43 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { BookstoreSharedModule } from 'app/shared';
+/* jhipster-needle-add-admin-module-import - JHipster will add admin modules imports here */
+
+import {
+ adminState,
+ AuditsComponent,
+ UserMgmtComponent,
+ UserMgmtDetailComponent,
+ UserMgmtUpdateComponent,
+ UserMgmtDeleteDialogComponent,
+ LogsComponent,
+ JhiMetricsMonitoringComponent,
+ JhiHealthModalComponent,
+ JhiHealthCheckComponent,
+ JhiConfigurationComponent,
+ JhiDocsComponent
+} from './';
+
+@NgModule({
+ imports: [
+ BookstoreSharedModule,
+ RouterModule.forChild(adminState)
+ /* jhipster-needle-add-admin-module - JHipster will add admin modules here */
+ ],
+ declarations: [
+ AuditsComponent,
+ UserMgmtComponent,
+ UserMgmtDetailComponent,
+ UserMgmtUpdateComponent,
+ UserMgmtDeleteDialogComponent,
+ LogsComponent,
+ JhiConfigurationComponent,
+ JhiHealthCheckComponent,
+ JhiHealthModalComponent,
+ JhiDocsComponent,
+ JhiMetricsMonitoringComponent
+ ],
+ entryComponents: [UserMgmtDeleteDialogComponent, JhiHealthModalComponent],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BookstoreAdminModule {}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.route.ts
new file mode 100644
index 0000000000..88c7e575f0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/admin.route.ts
@@ -0,0 +1,18 @@
+import { Routes } from '@angular/router';
+
+import { auditsRoute, configurationRoute, docsRoute, healthRoute, logsRoute, metricsRoute, userMgmtRoute } from './';
+
+import { UserRouteAccessService } from 'app/core';
+
+const ADMIN_ROUTES = [auditsRoute, configurationRoute, docsRoute, healthRoute, logsRoute, ...userMgmtRoute, metricsRoute];
+
+export const adminState: Routes = [
+ {
+ path: '',
+ data: {
+ authorities: ['ROLE_ADMIN']
+ },
+ canActivate: [UserRouteAccessService],
+ children: ADMIN_ROUTES
+ }
+];
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit-data.model.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit-data.model.ts
new file mode 100644
index 0000000000..a2506c4090
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit-data.model.ts
@@ -0,0 +1,3 @@
+export class AuditData {
+ constructor(public remoteAddress: string, public sessionId: string) {}
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit.model.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit.model.ts
new file mode 100644
index 0000000000..6497fb444e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audit.model.ts
@@ -0,0 +1,5 @@
+import { AuditData } from './audit-data.model';
+
+export class Audit {
+ constructor(public data: AuditData, public principal: string, public timestamp: string, public type: string) {}
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.html
new file mode 100644
index 0000000000..38af44044a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.html
@@ -0,0 +1,52 @@
+
+
Audits
+
+
+
+
+
+
+
+ | Date |
+ User |
+ State |
+ Extra data |
+
+
+
+
+ | {{audit.timestamp| date:'medium'}} |
+ {{audit.principal}} |
+ {{audit.type}} |
+
+ {{audit.data.message}}
+ Remote Address {{audit.data.remoteAddress}}
+ |
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.ts
new file mode 100644
index 0000000000..21739275f2
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.component.ts
@@ -0,0 +1,126 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { HttpResponse } from '@angular/common/http';
+import { DatePipe } from '@angular/common';
+import { ActivatedRoute, Router } from '@angular/router';
+import { JhiParseLinks, JhiAlertService } from 'ng-jhipster';
+
+import { ITEMS_PER_PAGE } from 'app/shared';
+import { Audit } from './audit.model';
+import { AuditsService } from './audits.service';
+
+@Component({
+ selector: 'jhi-audit',
+ templateUrl: './audits.component.html'
+})
+export class AuditsComponent implements OnInit, OnDestroy {
+ audits: Audit[];
+ fromDate: string;
+ itemsPerPage: any;
+ links: any;
+ page: number;
+ routeData: any;
+ predicate: any;
+ previousPage: any;
+ reverse: boolean;
+ toDate: string;
+ totalItems: number;
+
+ constructor(
+ private auditsService: AuditsService,
+ private alertService: JhiAlertService,
+ private parseLinks: JhiParseLinks,
+ private activatedRoute: ActivatedRoute,
+ private datePipe: DatePipe,
+ private router: Router
+ ) {
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.routeData = this.activatedRoute.data.subscribe(data => {
+ this.page = data['pagingParams'].page;
+ this.previousPage = data['pagingParams'].page;
+ this.reverse = data['pagingParams'].ascending;
+ this.predicate = data['pagingParams'].predicate;
+ });
+ }
+
+ ngOnInit() {
+ this.today();
+ this.previousMonth();
+ this.loadAll();
+ }
+
+ ngOnDestroy() {
+ this.routeData.unsubscribe();
+ }
+
+ previousMonth() {
+ const dateFormat = 'yyyy-MM-dd';
+ let fromDate: Date = new Date();
+
+ if (fromDate.getMonth() === 0) {
+ fromDate = new Date(fromDate.getFullYear() - 1, 11, fromDate.getDate());
+ } else {
+ fromDate = new Date(fromDate.getFullYear(), fromDate.getMonth() - 1, fromDate.getDate());
+ }
+
+ this.fromDate = this.datePipe.transform(fromDate, dateFormat);
+ }
+
+ today() {
+ const dateFormat = 'yyyy-MM-dd';
+ // Today + 1 day - needed if the current day must be included
+ const today: Date = new Date();
+ today.setDate(today.getDate() + 1);
+ const date = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+ this.toDate = this.datePipe.transform(date, dateFormat);
+ }
+
+ loadAll() {
+ this.auditsService
+ .query({
+ page: this.page - 1,
+ size: this.itemsPerPage,
+ sort: this.sort(),
+ fromDate: this.fromDate,
+ toDate: this.toDate
+ })
+ .subscribe(
+ (res: HttpResponse) => this.onSuccess(res.body, res.headers),
+ (res: HttpResponse) => this.onError(res.body)
+ );
+ }
+
+ sort() {
+ const result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
+ if (this.predicate !== 'id') {
+ result.push('id');
+ }
+ return result;
+ }
+
+ loadPage(page: number) {
+ if (page !== this.previousPage) {
+ this.previousPage = page;
+ this.transition();
+ }
+ }
+
+ transition() {
+ this.router.navigate(['/admin/audits'], {
+ queryParams: {
+ page: this.page,
+ sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
+ }
+ });
+ this.loadAll();
+ }
+
+ private onSuccess(data, headers) {
+ this.links = this.parseLinks.parse(headers.get('link'));
+ this.totalItems = headers.get('X-Total-Count');
+ this.audits = data;
+ }
+
+ private onError(error) {
+ this.alertService.error(error.error, error.message, null);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.route.ts
new file mode 100644
index 0000000000..87af5c6e8c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.route.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Route } from '@angular/router';
+import { JhiPaginationUtil, JhiResolvePagingParams } from 'ng-jhipster';
+
+import { AuditsComponent } from './audits.component';
+
+export const auditsRoute: Route = {
+ path: 'audits',
+ component: AuditsComponent,
+ resolve: {
+ pagingParams: JhiResolvePagingParams
+ },
+ data: {
+ pageTitle: 'Audits',
+ defaultSort: 'auditEventDate,desc'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.service.ts
new file mode 100644
index 0000000000..78e8cca7e2
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/audits/audits.service.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { createRequestOption } from 'app/shared';
+import { SERVER_API_URL } from 'app/app.constants';
+import { Audit } from './audit.model';
+
+@Injectable({ providedIn: 'root' })
+export class AuditsService {
+ constructor(private http: HttpClient) {}
+
+ query(req: any): Observable> {
+ const params: HttpParams = createRequestOption(req);
+ params.set('fromDate', req.fromDate);
+ params.set('toDate', req.toDate);
+
+ const requestURL = SERVER_API_URL + 'management/audits';
+
+ return this.http.get(requestURL, {
+ params,
+ observe: 'response'
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.html
new file mode 100644
index 0000000000..02a4a96433
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.html
@@ -0,0 +1,46 @@
+
+
Configuration
+
+
Filter (by prefix)
+
Spring configuration
+
+
+
+ | Prefix |
+ Properties |
+
+
+
+
+ | {{entry.prefix}} |
+
+
+ {{key}}
+
+ {{entry.properties[key] | json}}
+
+
+ |
+
+
+
+
+
{{key}}
+
+
+
+ | Property |
+ Value |
+
+
+
+
+ | {{item.key}} |
+
+ {{item.val}}
+ |
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.ts
new file mode 100644
index 0000000000..6867210c91
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit } from '@angular/core';
+
+import { JhiConfigurationService } from './configuration.service';
+
+@Component({
+ selector: 'jhi-configuration',
+ templateUrl: './configuration.component.html'
+})
+export class JhiConfigurationComponent implements OnInit {
+ allConfiguration: any = null;
+ configuration: any = null;
+ configKeys: any[];
+ filter: string;
+ orderProp: string;
+ reverse: boolean;
+
+ constructor(private configurationService: JhiConfigurationService) {
+ this.configKeys = [];
+ this.filter = '';
+ this.orderProp = 'prefix';
+ this.reverse = false;
+ }
+
+ keys(dict): Array {
+ return dict === undefined ? [] : Object.keys(dict);
+ }
+
+ ngOnInit() {
+ this.configurationService.get().subscribe(configuration => {
+ this.configuration = configuration;
+
+ for (const config of configuration) {
+ if (config.properties !== undefined) {
+ this.configKeys.push(Object.keys(config.properties));
+ }
+ }
+ });
+
+ this.configurationService.getEnv().subscribe(configuration => {
+ this.allConfiguration = configuration;
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.route.ts
new file mode 100644
index 0000000000..f4ad9c3688
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router';
+
+import { JhiConfigurationComponent } from './configuration.component';
+
+export const configurationRoute: Route = {
+ path: 'jhi-configuration',
+ component: JhiConfigurationComponent,
+ data: {
+ pageTitle: 'Configuration'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.service.ts
new file mode 100644
index 0000000000..5f9dfd491c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/configuration/configuration.service.ts
@@ -0,0 +1,67 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class JhiConfigurationService {
+ constructor(private http: HttpClient) {}
+
+ get(): Observable {
+ return this.http.get(SERVER_API_URL + 'management/configprops', { observe: 'response' }).pipe(
+ map((res: HttpResponse) => {
+ const properties: any[] = [];
+ const propertiesObject = this.getConfigPropertiesObjects(res.body);
+ for (const key in propertiesObject) {
+ if (propertiesObject.hasOwnProperty(key)) {
+ properties.push(propertiesObject[key]);
+ }
+ }
+
+ return properties.sort((propertyA, propertyB) => {
+ return propertyA.prefix === propertyB.prefix ? 0 : propertyA.prefix < propertyB.prefix ? -1 : 1;
+ });
+ })
+ );
+ }
+
+ getConfigPropertiesObjects(res: Object) {
+ // This code is for Spring Boot 2
+ if (res['contexts'] !== undefined) {
+ for (const key in res['contexts']) {
+ // If the key is not bootstrap, it will be the ApplicationContext Id
+ // For default app, it is baseName
+ // For microservice, it is baseName-1
+ if (!key.startsWith('bootstrap')) {
+ return res['contexts'][key]['beans'];
+ }
+ }
+ }
+ // by default, use the default ApplicationContext Id
+ return res['contexts']['Bookstore']['beans'];
+ }
+
+ getEnv(): Observable {
+ return this.http.get(SERVER_API_URL + 'management/env', { observe: 'response' }).pipe(
+ map((res: HttpResponse) => {
+ const properties: any = {};
+ const propertySources = res.body['propertySources'];
+
+ for (const propertyObject of propertySources) {
+ const name = propertyObject['name'];
+ const detailProperties = propertyObject['properties'];
+ const vals: any[] = [];
+ for (const keyDetail in detailProperties) {
+ if (detailProperties.hasOwnProperty(keyDetail)) {
+ vals.push({ key: keyDetail, val: detailProperties[keyDetail]['value'] });
+ }
+ }
+ properties[name] = vals;
+ }
+ return properties;
+ })
+ );
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.html
new file mode 100644
index 0000000000..30efbbb93e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.html
@@ -0,0 +1,2 @@
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.ts
new file mode 100644
index 0000000000..b338e7c3a6
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'jhi-docs',
+ templateUrl: './docs.component.html'
+})
+export class JhiDocsComponent {
+ constructor() {}
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.route.ts
new file mode 100644
index 0000000000..d7df51b935
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/docs/docs.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router';
+
+import { JhiDocsComponent } from './docs.component';
+
+export const docsRoute: Route = {
+ path: 'docs',
+ component: JhiDocsComponent,
+ data: {
+ pageTitle: 'API'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.html
new file mode 100644
index 0000000000..efc125e3a0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.html
@@ -0,0 +1,35 @@
+
+
+
+
Properties
+
+
+
+
+ | Name |
+ Value |
+
+
+
+
+ | {{entry.key}} |
+ {{readableValue(entry.value)}} |
+
+
+
+
+
+
+
Error
+
{{currentHealth.error}}
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.ts
new file mode 100644
index 0000000000..28128bf321
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health-modal.component.ts
@@ -0,0 +1,41 @@
+import { Component } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiHealthService } from './health.service';
+
+@Component({
+ selector: 'jhi-health-modal',
+ templateUrl: './health-modal.component.html'
+})
+export class JhiHealthModalComponent {
+ currentHealth: any;
+
+ constructor(private healthService: JhiHealthService, public activeModal: NgbActiveModal) {}
+
+ baseName(name) {
+ return this.healthService.getBaseName(name);
+ }
+
+ subSystemName(name) {
+ return this.healthService.getSubSystemName(name);
+ }
+
+ readableValue(value: number) {
+ if (this.currentHealth.name === 'diskSpace') {
+ // Should display storage space in an human readable unit
+ const val = value / 1073741824;
+ if (val > 1) {
+ // Value
+ return val.toFixed(2) + ' GB';
+ } else {
+ return (value / 1048576).toFixed(2) + ' MB';
+ }
+ }
+
+ if (typeof value === 'object') {
+ return JSON.stringify(value);
+ } else {
+ return value.toString();
+ }
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.html
new file mode 100644
index 0000000000..b314daa0ba
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.html
@@ -0,0 +1,34 @@
+
+
+ Health Checks
+
+
+
+
+
+
+ | Service Name |
+ Status |
+ Details |
+
+
+
+
+ | {{ baseName(health.name) }} {{subSystemName(health.name)}} |
+
+
+ {{health.status}}
+
+ |
+
+
+
+
+ |
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.ts
new file mode 100644
index 0000000000..ada3ef62f4
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.component.ts
@@ -0,0 +1,66 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiHealthService } from './health.service';
+import { JhiHealthModalComponent } from './health-modal.component';
+
+@Component({
+ selector: 'jhi-health',
+ templateUrl: './health.component.html'
+})
+export class JhiHealthCheckComponent implements OnInit {
+ healthData: any;
+ updatingHealth: boolean;
+
+ constructor(private modalService: NgbModal, private healthService: JhiHealthService) {}
+
+ ngOnInit() {
+ this.refresh();
+ }
+
+ baseName(name: string) {
+ return this.healthService.getBaseName(name);
+ }
+
+ getBadgeClass(statusState) {
+ if (statusState === 'UP') {
+ return 'badge-success';
+ } else {
+ return 'badge-danger';
+ }
+ }
+
+ refresh() {
+ this.updatingHealth = true;
+
+ this.healthService.checkHealth().subscribe(
+ health => {
+ this.healthData = this.healthService.transformHealthData(health);
+ this.updatingHealth = false;
+ },
+ error => {
+ if (error.status === 503) {
+ this.healthData = this.healthService.transformHealthData(error.error);
+ this.updatingHealth = false;
+ }
+ }
+ );
+ }
+
+ showHealth(health: any) {
+ const modalRef = this.modalService.open(JhiHealthModalComponent);
+ modalRef.componentInstance.currentHealth = health;
+ modalRef.result.then(
+ result => {
+ // Left blank intentionally, nothing to do here
+ },
+ reason => {
+ // Left blank intentionally, nothing to do here
+ }
+ );
+ }
+
+ subSystemName(name: string) {
+ return this.healthService.getSubSystemName(name);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.route.ts
new file mode 100644
index 0000000000..0b67775651
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router';
+
+import { JhiHealthCheckComponent } from './health.component';
+
+export const healthRoute: Route = {
+ path: 'jhi-health',
+ component: JhiHealthCheckComponent,
+ data: {
+ pageTitle: 'Health Checks'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.service.ts
new file mode 100644
index 0000000000..4c1b0e5ec8
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/health/health.service.ts
@@ -0,0 +1,133 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class JhiHealthService {
+ separator: string;
+
+ constructor(private http: HttpClient) {
+ this.separator = '.';
+ }
+
+ checkHealth(): Observable {
+ return this.http.get(SERVER_API_URL + 'management/health');
+ }
+
+ transformHealthData(data): any {
+ const response = [];
+ this.flattenHealthData(response, null, data.details);
+ return response;
+ }
+
+ getBaseName(name): string {
+ if (name) {
+ const split = name.split('.');
+ return split[0];
+ }
+ }
+
+ getSubSystemName(name): string {
+ if (name) {
+ const split = name.split('.');
+ split.splice(0, 1);
+ const remainder = split.join('.');
+ return remainder ? ' - ' + remainder : '';
+ }
+ }
+
+ /* private methods */
+ private addHealthObject(result, isLeaf, healthObject, name): any {
+ const healthData: any = {
+ name
+ };
+
+ const details = {};
+ let hasDetails = false;
+
+ for (const key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ const value = healthObject[key];
+ if (key === 'status' || key === 'error') {
+ healthData[key] = value;
+ } else {
+ if (!this.isHealthObject(value)) {
+ details[key] = value;
+ hasDetails = true;
+ }
+ }
+ }
+ }
+
+ // Add the details
+ if (hasDetails) {
+ healthData.details = details;
+ }
+
+ // Only add nodes if they provide additional information
+ if (isLeaf || hasDetails || healthData.error) {
+ result.push(healthData);
+ }
+ return healthData;
+ }
+
+ private flattenHealthData(result, path, data): any {
+ for (const key in data) {
+ if (data.hasOwnProperty(key)) {
+ const value = data[key];
+ if (this.isHealthObject(value)) {
+ if (this.hasSubSystem(value)) {
+ this.addHealthObject(result, false, value, this.getModuleName(path, key));
+ this.flattenHealthData(result, this.getModuleName(path, key), value);
+ } else {
+ this.addHealthObject(result, true, value, this.getModuleName(path, key));
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private getModuleName(path, name): string {
+ let result;
+ if (path && name) {
+ result = path + this.separator + name;
+ } else if (path) {
+ result = path;
+ } else if (name) {
+ result = name;
+ } else {
+ result = '';
+ }
+ return result;
+ }
+
+ private hasSubSystem(healthObject): boolean {
+ let result = false;
+
+ for (const key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ const value = healthObject[key];
+ if (value && value.status) {
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ private isHealthObject(healthObject): boolean {
+ let result = false;
+
+ for (const key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ if (key === 'status') {
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/index.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/index.ts
new file mode 100644
index 0000000000..7f631ffb9b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/index.ts
@@ -0,0 +1,27 @@
+export * from './audits/audits.component';
+export * from './audits/audits.service';
+export * from './audits/audits.route';
+export * from './audits/audit.model';
+export * from './audits/audit-data.model';
+export * from './configuration/configuration.component';
+export * from './configuration/configuration.service';
+export * from './configuration/configuration.route';
+export * from './docs/docs.component';
+export * from './docs/docs.route';
+export * from './health/health.component';
+export * from './health/health-modal.component';
+export * from './health/health.service';
+export * from './health/health.route';
+export * from './logs/logs.component';
+export * from './logs/logs.service';
+export * from './logs/logs.route';
+export * from './logs/log.model';
+export * from './metrics/metrics.component';
+export * from './metrics/metrics.service';
+export * from './metrics/metrics.route';
+export * from './user-management/user-management-update.component';
+export * from './user-management/user-management-delete-dialog.component';
+export * from './user-management/user-management-detail.component';
+export * from './user-management/user-management.component';
+export * from './user-management/user-management.route';
+export * from './admin.route';
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/log.model.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/log.model.ts
new file mode 100644
index 0000000000..3f27b6728c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/log.model.ts
@@ -0,0 +1,3 @@
+export class Log {
+ constructor(public name: string, public level: string) {}
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.html
new file mode 100644
index 0000000000..cf5d6a046f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.html
@@ -0,0 +1,28 @@
+
+
Logs
+
+
There are {{ loggers.length }} loggers.
+
+
Filter
+
+
+
+
+ | Name |
+ Level |
+
+
+
+
+ | {{logger.name | slice:0:140}} |
+
+
+
+
+
+
+
+ |
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.ts
new file mode 100644
index 0000000000..28547f9ae6
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.component.ts
@@ -0,0 +1,32 @@
+import { Component, OnInit } from '@angular/core';
+
+import { Log } from './log.model';
+import { LogsService } from './logs.service';
+
+@Component({
+ selector: 'jhi-logs',
+ templateUrl: './logs.component.html'
+})
+export class LogsComponent implements OnInit {
+ loggers: Log[];
+ filter: string;
+ orderProp: string;
+ reverse: boolean;
+
+ constructor(private logsService: LogsService) {
+ this.filter = '';
+ this.orderProp = 'name';
+ this.reverse = false;
+ }
+
+ ngOnInit() {
+ this.logsService.findAll().subscribe(response => (this.loggers = response.body));
+ }
+
+ changeLevel(name: string, level: string) {
+ const log = new Log(name, level);
+ this.logsService.changeLevel(log).subscribe(() => {
+ this.logsService.findAll().subscribe(response => (this.loggers = response.body));
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.route.ts
new file mode 100644
index 0000000000..cfa87715d8
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router';
+
+import { LogsComponent } from './logs.component';
+
+export const logsRoute: Route = {
+ path: 'logs',
+ component: LogsComponent,
+ data: {
+ pageTitle: 'Logs'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.service.ts
new file mode 100644
index 0000000000..71a596b0ab
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/logs/logs.service.ts
@@ -0,0 +1,19 @@
+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 { Log } from './log.model';
+
+@Injectable({ providedIn: 'root' })
+export class LogsService {
+ constructor(private http: HttpClient) {}
+
+ changeLevel(log: Log): Observable> {
+ return this.http.put(SERVER_API_URL + 'management/logs', log, { observe: 'response' });
+ }
+
+ findAll(): Observable> {
+ return this.http.get(SERVER_API_URL + 'management/logs', { observe: 'response' });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.html
new file mode 100644
index 0000000000..75902d8fb3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.html
@@ -0,0 +1,56 @@
+
+
+ Application Metrics
+
+
+
+
JVM Metrics
+
+
+
+
+
+
+
+
+
+
Garbage collector statistics
+
+
+
+
Updating...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.ts
new file mode 100644
index 0000000000..ed508c8187
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiMetricsService } from './metrics.service';
+
+@Component({
+ selector: 'jhi-metrics',
+ templateUrl: './metrics.component.html'
+})
+export class JhiMetricsMonitoringComponent implements OnInit {
+ metrics: any = {};
+ threadData: any = {};
+ updatingMetrics = true;
+ JCACHE_KEY: string;
+
+ constructor(private modalService: NgbModal, private metricsService: JhiMetricsService) {
+ this.JCACHE_KEY = 'jcache.statistics';
+ }
+
+ ngOnInit() {
+ this.refresh();
+ }
+
+ refresh() {
+ this.updatingMetrics = true;
+ this.metricsService.getMetrics().subscribe(metrics => {
+ this.metrics = metrics;
+ this.metricsService.threadDump().subscribe(data => {
+ this.threadData = data.threads;
+ this.updatingMetrics = false;
+ });
+ });
+ }
+
+ isObjectExisting(metrics: any, key: string) {
+ return metrics && metrics[key];
+ }
+
+ isObjectExistingAndNotEmpty(metrics: any, key: string) {
+ return this.isObjectExisting(metrics, key) && JSON.stringify(metrics[key]) !== '{}';
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.route.ts
new file mode 100644
index 0000000000..abc18b8254
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router';
+
+import { JhiMetricsMonitoringComponent } from './metrics.component';
+
+export const metricsRoute: Route = {
+ path: 'jhi-metrics',
+ component: JhiMetricsMonitoringComponent,
+ data: {
+ pageTitle: 'Application Metrics'
+ }
+};
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.service.ts
new file mode 100644
index 0000000000..15cfe3536c
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/metrics/metrics.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class JhiMetricsService {
+ constructor(private http: HttpClient) {}
+
+ getMetrics(): Observable
{
+ return this.http.get(SERVER_API_URL + 'management/jhi-metrics');
+ }
+
+ threadDump(): Observable {
+ return this.http.get(SERVER_API_URL + 'management/threaddump');
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html
new file mode 100644
index 0000000000..adb1a908da
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html
@@ -0,0 +1,19 @@
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts
new file mode 100644
index 0000000000..d7674f6cd9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts
@@ -0,0 +1,29 @@
+import { Component } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { JhiEventManager } from 'ng-jhipster';
+
+import { User, UserService } from 'app/core';
+
+@Component({
+ selector: 'jhi-user-mgmt-delete-dialog',
+ templateUrl: './user-management-delete-dialog.component.html'
+})
+export class UserMgmtDeleteDialogComponent {
+ user: User;
+
+ constructor(private userService: UserService, public activeModal: NgbActiveModal, private eventManager: JhiEventManager) {}
+
+ clear() {
+ this.activeModal.dismiss('cancel');
+ }
+
+ confirmDelete(login) {
+ this.userService.delete(login).subscribe(response => {
+ this.eventManager.broadcast({
+ name: 'userListModification',
+ content: 'Deleted a user'
+ });
+ this.activeModal.dismiss(true);
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.html
new file mode 100644
index 0000000000..051f335ded
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.html
@@ -0,0 +1,47 @@
+
+
+
+
+ User [{{user.login}}]
+
+
+ - Login
+ -
+ {{user.login}}
+
+
+
+ - First Name
+ - {{user.firstName}}
+ - Last Name
+ - {{user.lastName}}
+ - Email
+ - {{user.email}}
+ - Created By
+ - {{user.createdBy}}
+ - Created Date
+ - {{user.createdDate | date:'dd/MM/yy HH:mm' }}
+ - Last Modified By
+ - {{user.lastModifiedBy}}
+ - Last Modified Date
+ - {{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}
+ - Profiles
+ -
+
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.ts
new file mode 100644
index 0000000000..0b323d89a0
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-detail.component.ts
@@ -0,0 +1,20 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { User } from 'app/core';
+
+@Component({
+ selector: 'jhi-user-mgmt-detail',
+ templateUrl: './user-management-detail.component.html'
+})
+export class UserMgmtDetailComponent implements OnInit {
+ user: User;
+
+ constructor(private route: ActivatedRoute) {}
+
+ ngOnInit() {
+ this.route.data.subscribe(({ user }) => {
+ this.user = user.body ? user.body : user;
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.html
new file mode 100644
index 0000000000..b2d04b4227
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.html
@@ -0,0 +1,118 @@
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.ts
new file mode 100644
index 0000000000..e51e4f4a33
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management-update.component.ts
@@ -0,0 +1,51 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import { User, UserService } from 'app/core';
+
+@Component({
+ selector: 'jhi-user-mgmt-update',
+ templateUrl: './user-management-update.component.html'
+})
+export class UserMgmtUpdateComponent implements OnInit {
+ user: User;
+ languages: any[];
+ authorities: any[];
+ isSaving: boolean;
+
+ constructor(private userService: UserService, private route: ActivatedRoute, private router: Router) {}
+
+ ngOnInit() {
+ this.isSaving = false;
+ this.route.data.subscribe(({ user }) => {
+ this.user = user.body ? user.body : user;
+ });
+ this.authorities = [];
+ this.userService.authorities().subscribe(authorities => {
+ this.authorities = authorities;
+ });
+ }
+
+ previousState() {
+ window.history.back();
+ }
+
+ save() {
+ this.isSaving = true;
+ if (this.user.id !== null) {
+ this.userService.update(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
+ } else {
+ this.user.langKey = 'en';
+ this.userService.create(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
+ }
+ }
+
+ private onSaveSuccess(result) {
+ this.isSaving = false;
+ this.previousState();
+ }
+
+ private onSaveError() {
+ this.isSaving = false;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.html b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.html
new file mode 100644
index 0000000000..4592998c1f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.html
@@ -0,0 +1,78 @@
+
+
+ Users
+
+
+
+
+
+
+
+ | ID |
+ Login |
+ Email |
+ |
+ Profiles |
+ Created Date |
+ Last Modified By |
+ Last Modified Date |
+ |
+
+
+
+
+ | {{user.id}} |
+ {{user.login}} |
+ {{user.email}} |
+
+
+
+ |
+
+
+
+ {{ authority }}
+
+ |
+ {{user.createdDate | date:'dd/MM/yy HH:mm'}} |
+ {{user.lastModifiedBy}} |
+ {{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}} |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.ts
new file mode 100644
index 0000000000..439442e3b6
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.component.ts
@@ -0,0 +1,144 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { HttpResponse } from '@angular/common/http';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { ActivatedRoute, Router } from '@angular/router';
+import { JhiEventManager, JhiParseLinks, JhiAlertService } from 'ng-jhipster';
+
+import { ITEMS_PER_PAGE } from 'app/shared';
+import { AccountService, UserService, User } from 'app/core';
+import { UserMgmtDeleteDialogComponent } from 'app/admin';
+
+@Component({
+ selector: 'jhi-user-mgmt',
+ templateUrl: './user-management.component.html'
+})
+export class UserMgmtComponent implements OnInit, OnDestroy {
+ currentAccount: any;
+ users: User[];
+ error: any;
+ success: any;
+ routeData: any;
+ links: any;
+ totalItems: any;
+ itemsPerPage: any;
+ page: any;
+ predicate: any;
+ previousPage: any;
+ reverse: any;
+
+ constructor(
+ private userService: UserService,
+ private alertService: JhiAlertService,
+ private accountService: AccountService,
+ private parseLinks: JhiParseLinks,
+ private activatedRoute: ActivatedRoute,
+ private router: Router,
+ private eventManager: JhiEventManager,
+ private modalService: NgbModal
+ ) {
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.routeData = this.activatedRoute.data.subscribe(data => {
+ this.page = data['pagingParams'].page;
+ this.previousPage = data['pagingParams'].page;
+ this.reverse = data['pagingParams'].ascending;
+ this.predicate = data['pagingParams'].predicate;
+ });
+ }
+
+ ngOnInit() {
+ this.accountService.identity().then(account => {
+ this.currentAccount = account;
+ this.loadAll();
+ this.registerChangeInUsers();
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeData.unsubscribe();
+ }
+
+ registerChangeInUsers() {
+ this.eventManager.subscribe('userListModification', response => this.loadAll());
+ }
+
+ setActive(user, isActivated) {
+ user.activated = isActivated;
+
+ this.userService.update(user).subscribe(response => {
+ if (response.status === 200) {
+ this.error = null;
+ this.success = 'OK';
+ this.loadAll();
+ } else {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ });
+ }
+
+ loadAll() {
+ this.userService
+ .query({
+ page: this.page - 1,
+ size: this.itemsPerPage,
+ sort: this.sort()
+ })
+ .subscribe(
+ (res: HttpResponse) => this.onSuccess(res.body, res.headers),
+ (res: HttpResponse) => this.onError(res.body)
+ );
+ }
+
+ trackIdentity(index, item: User) {
+ return item.id;
+ }
+
+ sort() {
+ const result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
+ if (this.predicate !== 'id') {
+ result.push('id');
+ }
+ return result;
+ }
+
+ loadPage(page: number) {
+ if (page !== this.previousPage) {
+ this.previousPage = page;
+ this.transition();
+ }
+ }
+
+ transition() {
+ this.router.navigate(['/admin/user-management'], {
+ queryParams: {
+ page: this.page,
+ sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
+ }
+ });
+ this.loadAll();
+ }
+
+ deleteUser(user: User) {
+ const modalRef = this.modalService.open(UserMgmtDeleteDialogComponent, { size: 'lg', backdrop: 'static' });
+ modalRef.componentInstance.user = user;
+ modalRef.result.then(
+ result => {
+ // Left blank intentionally, nothing to do here
+ },
+ reason => {
+ // Left blank intentionally, nothing to do here
+ }
+ );
+ }
+
+ private onSuccess(data, headers) {
+ this.links = this.parseLinks.parse(headers.get('link'));
+ this.totalItems = headers.get('X-Total-Count');
+ this.users = data;
+ }
+
+ private onError(error) {
+ this.alertService.error(error.error, error.message, null);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.route.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.route.ts
new file mode 100644
index 0000000000..bf1115516f
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/admin/user-management/user-management.route.ts
@@ -0,0 +1,68 @@
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router';
+import { JhiPaginationUtil, JhiResolvePagingParams } from 'ng-jhipster';
+
+import { AccountService, User, UserService } from 'app/core';
+import { UserMgmtComponent } from './user-management.component';
+import { UserMgmtDetailComponent } from './user-management-detail.component';
+import { UserMgmtUpdateComponent } from './user-management-update.component';
+
+@Injectable({ providedIn: 'root' })
+export class UserResolve implements CanActivate {
+ constructor(private accountService: AccountService) {}
+
+ canActivate() {
+ return this.accountService.identity().then(account => this.accountService.hasAnyAuthority(['ROLE_ADMIN']));
+ }
+}
+
+@Injectable({ providedIn: 'root' })
+export class UserMgmtResolve implements Resolve {
+ constructor(private service: UserService) {}
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+ const id = route.params['login'] ? route.params['login'] : null;
+ if (id) {
+ return this.service.find(id);
+ }
+ return new User();
+ }
+}
+
+export const userMgmtRoute: Routes = [
+ {
+ path: 'user-management',
+ component: UserMgmtComponent,
+ resolve: {
+ pagingParams: JhiResolvePagingParams
+ },
+ data: {
+ pageTitle: 'Users',
+ defaultSort: 'id,asc'
+ }
+ },
+ {
+ path: 'user-management/:login/view',
+ component: UserMgmtDetailComponent,
+ resolve: {
+ user: UserMgmtResolve
+ },
+ data: {
+ pageTitle: 'Users'
+ }
+ },
+ {
+ path: 'user-management/new',
+ component: UserMgmtUpdateComponent,
+ resolve: {
+ user: UserMgmtResolve
+ }
+ },
+ {
+ path: 'user-management/:login/edit',
+ component: UserMgmtUpdateComponent,
+ resolve: {
+ user: UserMgmtResolve
+ }
+ }
+];
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/app-routing.module.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/app-routing.module.ts
new file mode 100644
index 0000000000..c40d4df774
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/app-routing.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { errorRoute, navbarRoute } from './layouts';
+import { DEBUG_INFO_ENABLED } from 'app/app.constants';
+
+const LAYOUT_ROUTES = [navbarRoute, ...errorRoute];
+
+@NgModule({
+ imports: [
+ RouterModule.forRoot(
+ [
+ {
+ path: 'admin',
+ loadChildren: './admin/admin.module#BookstoreAdminModule'
+ },
+ ...LAYOUT_ROUTES
+ ],
+ { useHash: true, enableTracing: DEBUG_INFO_ENABLED }
+ )
+ ],
+ exports: [RouterModule]
+})
+export class BookstoreAppRoutingModule {}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/app.constants.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.constants.ts
new file mode 100644
index 0000000000..9760a49a91
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.constants.ts
@@ -0,0 +1,8 @@
+// These constants are injected via webpack environment variables.
+// You can add more variables in webpack.common.js or in profile specific webpack..js files.
+// If you change the values in the webpack config files, you need to re run webpack to update the application
+
+export const VERSION = process.env.VERSION;
+export const DEBUG_INFO_ENABLED: boolean = !!process.env.DEBUG_INFO_ENABLED;
+export const SERVER_API_URL = process.env.SERVER_API_URL;
+export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP;
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/app.main.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.main.ts
new file mode 100644
index 0000000000..7695bb8571
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.main.ts
@@ -0,0 +1,14 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { ProdConfig } from './blocks/config/prod.config';
+import { BookstoreAppModule } from './app.module';
+
+ProdConfig();
+
+if (module['hot']) {
+ module['hot'].accept();
+}
+
+platformBrowserDynamic()
+ .bootstrapModule(BookstoreAppModule, { preserveWhitespaces: true })
+ .then(success => console.log(`Application started`))
+ .catch(err => console.error(err));
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/app.module.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.module.ts
new file mode 100644
index 0000000000..5fb96ed8c5
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/app.module.ts
@@ -0,0 +1,70 @@
+import './vendor.ts';
+
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { HTTP_INTERCEPTORS } from '@angular/common/http';
+import { NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap';
+import { Ng2Webstorage } from 'ngx-webstorage';
+import { NgJhipsterModule } from 'ng-jhipster';
+
+import { AuthInterceptor } from './blocks/interceptor/auth.interceptor';
+import { AuthExpiredInterceptor } from './blocks/interceptor/auth-expired.interceptor';
+import { ErrorHandlerInterceptor } from './blocks/interceptor/errorhandler.interceptor';
+import { NotificationInterceptor } from './blocks/interceptor/notification.interceptor';
+import { BookstoreSharedModule } from 'app/shared';
+import { BookstoreCoreModule } from 'app/core';
+import { BookstoreAppRoutingModule } from './app-routing.module';
+import { BookstoreHomeModule } from './home/home.module';
+import { BookstoreAccountModule } from './account/account.module';
+import { BookstoreEntityModule } from './entities/entity.module';
+import * as moment from 'moment';
+// jhipster-needle-angular-add-module-import JHipster will add new module here
+import { JhiMainComponent, NavbarComponent, FooterComponent, PageRibbonComponent, ErrorComponent } from './layouts';
+
+@NgModule({
+ imports: [
+ BrowserModule,
+ Ng2Webstorage.forRoot({ prefix: 'jhi', separator: '-' }),
+ NgJhipsterModule.forRoot({
+ // set below to true to make alerts look like toast
+ alertAsToast: false,
+ alertTimeout: 5000
+ }),
+ BookstoreSharedModule.forRoot(),
+ BookstoreCoreModule,
+ BookstoreHomeModule,
+ BookstoreAccountModule,
+ // jhipster-needle-angular-add-module JHipster will add new module here
+ BookstoreEntityModule,
+ BookstoreAppRoutingModule
+ ],
+ declarations: [JhiMainComponent, NavbarComponent, ErrorComponent, PageRibbonComponent, FooterComponent],
+ providers: [
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: AuthInterceptor,
+ multi: true
+ },
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: AuthExpiredInterceptor,
+ multi: true
+ },
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: ErrorHandlerInterceptor,
+ multi: true
+ },
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: NotificationInterceptor,
+ multi: true
+ }
+ ],
+ bootstrap: [JhiMainComponent]
+})
+export class BookstoreAppModule {
+ constructor(private dpConfig: NgbDatepickerConfig) {
+ this.dpConfig.minDate = { year: moment().year() - 100, month: 1, day: 1 };
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/prod.config.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/prod.config.ts
new file mode 100644
index 0000000000..c6221c1eaf
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/prod.config.ts
@@ -0,0 +1,9 @@
+import { enableProdMode } from '@angular/core';
+import { DEBUG_INFO_ENABLED } from 'app/app.constants';
+
+export function ProdConfig() {
+ // disable debug data on prod profile to improve performance
+ if (!DEBUG_INFO_ENABLED) {
+ enableProdMode();
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/uib-pagination.config.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/uib-pagination.config.ts
new file mode 100644
index 0000000000..0c2ea94808
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/config/uib-pagination.config.ts
@@ -0,0 +1,14 @@
+import { Injectable } from '@angular/core';
+import { NgbPaginationConfig } from '@ng-bootstrap/ng-bootstrap';
+import { ITEMS_PER_PAGE } from 'app/shared';
+
+@Injectable({ providedIn: 'root' })
+export class PaginationConfig {
+ // tslint:disable-next-line: no-unused-variable
+ constructor(private config: NgbPaginationConfig) {
+ config.boundaryLinks = true;
+ config.maxSize = 5;
+ config.pageSize = ITEMS_PER_PAGE;
+ config.size = 'sm';
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts
new file mode 100644
index 0000000000..bc1b70cfef
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { tap } from 'rxjs/operators';
+import { LoginService } from 'app/core/login/login.service';
+
+@Injectable()
+export class AuthExpiredInterceptor implements HttpInterceptor {
+ constructor(private loginService: LoginService) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(request).pipe(
+ tap(
+ (event: HttpEvent) => {},
+ (err: any) => {
+ if (err instanceof HttpErrorResponse) {
+ if (err.status === 401) {
+ this.loginService.logout();
+ }
+ }
+ }
+ )
+ );
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts
new file mode 100644
index 0000000000..23cdeaf66b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable()
+export class AuthInterceptor implements HttpInterceptor {
+ constructor(private localStorage: LocalStorageService, private sessionStorage: SessionStorageService) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ if (!request || !request.url || (/^http/.test(request.url) && !(SERVER_API_URL && request.url.startsWith(SERVER_API_URL)))) {
+ return next.handle(request);
+ }
+
+ const token = this.localStorage.retrieve('authenticationToken') || this.sessionStorage.retrieve('authenticationToken');
+ if (!!token) {
+ request = request.clone({
+ setHeaders: {
+ Authorization: 'Bearer ' + token
+ }
+ });
+ }
+ return next.handle(request);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts
new file mode 100644
index 0000000000..e464f66cd3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { JhiEventManager } from 'ng-jhipster';
+import { HttpInterceptor, HttpRequest, HttpErrorResponse, HttpHandler, HttpEvent } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { tap } from 'rxjs/operators';
+
+@Injectable()
+export class ErrorHandlerInterceptor implements HttpInterceptor {
+ constructor(private eventManager: JhiEventManager) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(request).pipe(
+ tap(
+ (event: HttpEvent) => {},
+ (err: any) => {
+ if (err instanceof HttpErrorResponse) {
+ if (!(err.status === 401 && (err.message === '' || (err.url && err.url.includes('/api/account'))))) {
+ this.eventManager.broadcast({ name: 'bookstoreApp.httpError', content: err });
+ }
+ }
+ }
+ )
+ );
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts
new file mode 100644
index 0000000000..34af81d482
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts
@@ -0,0 +1,34 @@
+import { JhiAlertService } from 'ng-jhipster';
+import { HttpInterceptor, HttpRequest, HttpResponse, HttpHandler, HttpEvent } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { tap } from 'rxjs/operators';
+
+@Injectable()
+export class NotificationInterceptor implements HttpInterceptor {
+ constructor(private alertService: JhiAlertService) {}
+
+ intercept(request: HttpRequest, next: HttpHandler): Observable> {
+ return next.handle(request).pipe(
+ tap(
+ (event: HttpEvent) => {
+ if (event instanceof HttpResponse) {
+ const arr = event.headers.keys();
+ let alert = null;
+ arr.forEach(entry => {
+ if (entry.toLowerCase().endsWith('app-alert')) {
+ alert = event.headers.get(entry);
+ }
+ });
+ if (alert) {
+ if (typeof alert === 'string') {
+ this.alertService.success(alert, null, null);
+ }
+ }
+ }
+ },
+ (err: any) => {}
+ )
+ );
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/account.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/account.service.ts
new file mode 100644
index 0000000000..a6548f6dd9
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/account.service.ts
@@ -0,0 +1,108 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable, Subject } from 'rxjs';
+
+import { SERVER_API_URL } from 'app/app.constants';
+import { Account } from 'app/core/user/account.model';
+
+@Injectable({ providedIn: 'root' })
+export class AccountService {
+ private userIdentity: any;
+ private authenticated = false;
+ private authenticationState = new Subject();
+
+ constructor(private http: HttpClient) {}
+
+ fetch(): Observable> {
+ return this.http.get(SERVER_API_URL + 'api/account', { observe: 'response' });
+ }
+
+ save(account: any): Observable> {
+ return this.http.post(SERVER_API_URL + 'api/account', account, { observe: 'response' });
+ }
+
+ authenticate(identity) {
+ this.userIdentity = identity;
+ this.authenticated = identity !== null;
+ this.authenticationState.next(this.userIdentity);
+ }
+
+ hasAnyAuthority(authorities: string[]): boolean {
+ if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) {
+ return false;
+ }
+
+ for (let i = 0; i < authorities.length; i++) {
+ if (this.userIdentity.authorities.includes(authorities[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ hasAuthority(authority: string): Promise {
+ if (!this.authenticated) {
+ return Promise.resolve(false);
+ }
+
+ return this.identity().then(
+ id => {
+ return Promise.resolve(id.authorities && id.authorities.includes(authority));
+ },
+ () => {
+ return Promise.resolve(false);
+ }
+ );
+ }
+
+ identity(force?: boolean): Promise {
+ if (force) {
+ this.userIdentity = undefined;
+ }
+
+ // check and see if we have retrieved the userIdentity data from the server.
+ // if we have, reuse it by immediately resolving
+ if (this.userIdentity) {
+ return Promise.resolve(this.userIdentity);
+ }
+
+ // retrieve the userIdentity data from the server, update the identity object, and then resolve.
+ return this.fetch()
+ .toPromise()
+ .then(response => {
+ const account = response.body;
+ if (account) {
+ this.userIdentity = account;
+ this.authenticated = true;
+ } else {
+ this.userIdentity = null;
+ this.authenticated = false;
+ }
+ this.authenticationState.next(this.userIdentity);
+ return this.userIdentity;
+ })
+ .catch(err => {
+ this.userIdentity = null;
+ this.authenticated = false;
+ this.authenticationState.next(this.userIdentity);
+ return null;
+ });
+ }
+
+ isAuthenticated(): boolean {
+ return this.authenticated;
+ }
+
+ isIdentityResolved(): boolean {
+ return this.userIdentity !== undefined;
+ }
+
+ getAuthenticationState(): Observable {
+ return this.authenticationState.asObservable();
+ }
+
+ getImageUrl(): string {
+ return this.isIdentityResolved() ? this.userIdentity.imageUrl : null;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/auth-jwt.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/auth-jwt.service.ts
new file mode 100644
index 0000000000..5ad53e3dfe
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/auth-jwt.service.ts
@@ -0,0 +1,59 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
+
+import { SERVER_API_URL } from 'app/app.constants';
+
+@Injectable({ providedIn: 'root' })
+export class AuthServerProvider {
+ constructor(private http: HttpClient, private $localStorage: LocalStorageService, private $sessionStorage: SessionStorageService) {}
+
+ getToken() {
+ return this.$localStorage.retrieve('authenticationToken') || this.$sessionStorage.retrieve('authenticationToken');
+ }
+
+ login(credentials): Observable {
+ const data = {
+ username: credentials.username,
+ password: credentials.password,
+ rememberMe: credentials.rememberMe
+ };
+ return this.http.post(SERVER_API_URL + 'api/authenticate', data, { observe: 'response' }).pipe(map(authenticateSuccess.bind(this)));
+
+ function authenticateSuccess(resp) {
+ const bearerToken = resp.headers.get('Authorization');
+ if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') {
+ const jwt = bearerToken.slice(7, bearerToken.length);
+ this.storeAuthenticationToken(jwt, credentials.rememberMe);
+ return jwt;
+ }
+ }
+ }
+
+ loginWithToken(jwt, rememberMe) {
+ if (jwt) {
+ this.storeAuthenticationToken(jwt, rememberMe);
+ return Promise.resolve(jwt);
+ } else {
+ return Promise.reject('auth-jwt-service Promise reject'); // Put appropriate error message here
+ }
+ }
+
+ storeAuthenticationToken(jwt, rememberMe) {
+ if (rememberMe) {
+ this.$localStorage.store('authenticationToken', jwt);
+ } else {
+ this.$sessionStorage.store('authenticationToken', jwt);
+ }
+ }
+
+ logout(): Observable {
+ return new Observable(observer => {
+ this.$localStorage.clear('authenticationToken');
+ this.$sessionStorage.clear('authenticationToken');
+ observer.complete();
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/csrf.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/csrf.service.ts
new file mode 100644
index 0000000000..01fdccb02a
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/csrf.service.ts
@@ -0,0 +1,11 @@
+import { Injectable } from '@angular/core';
+import { CookieService } from 'ngx-cookie';
+
+@Injectable({ providedIn: 'root' })
+export class CSRFService {
+ constructor(private cookieService: CookieService) {}
+
+ getCSRF(name = 'XSRF-TOKEN') {
+ return this.cookieService.get(name);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/state-storage.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/state-storage.service.ts
new file mode 100644
index 0000000000..0e5befbfc3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/state-storage.service.ts
@@ -0,0 +1,46 @@
+import { Injectable } from '@angular/core';
+import { SessionStorageService } from 'ngx-webstorage';
+
+@Injectable({ providedIn: 'root' })
+export class StateStorageService {
+ constructor(private $sessionStorage: SessionStorageService) {}
+
+ getPreviousState() {
+ return this.$sessionStorage.retrieve('previousState');
+ }
+
+ resetPreviousState() {
+ this.$sessionStorage.clear('previousState');
+ }
+
+ storePreviousState(previousStateName, previousStateParams) {
+ const previousState = { name: previousStateName, params: previousStateParams };
+ this.$sessionStorage.store('previousState', previousState);
+ }
+
+ getDestinationState() {
+ return this.$sessionStorage.retrieve('destinationState');
+ }
+
+ storeUrl(url: string) {
+ this.$sessionStorage.store('previousUrl', url);
+ }
+
+ getUrl() {
+ return this.$sessionStorage.retrieve('previousUrl');
+ }
+
+ storeDestinationState(destinationState, destinationStateParams, fromState) {
+ const destinationInfo = {
+ destination: {
+ name: destinationState.name,
+ data: destinationState.data
+ },
+ params: destinationStateParams,
+ from: {
+ name: fromState.name
+ }
+ };
+ this.$sessionStorage.store('destinationState', destinationInfo);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/user-route-access-service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/user-route-access-service.ts
new file mode 100644
index 0000000000..a55b0bc035
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/auth/user-route-access-service.ts
@@ -0,0 +1,52 @@
+import { Injectable, isDevMode } from '@angular/core';
+import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
+
+import { AccountService } from '../';
+import { LoginModalService } from '../login/login-modal.service';
+import { StateStorageService } from './state-storage.service';
+
+@Injectable({ providedIn: 'root' })
+export class UserRouteAccessService implements CanActivate {
+ constructor(
+ private router: Router,
+ private loginModalService: LoginModalService,
+ private accountService: AccountService,
+ private stateStorageService: StateStorageService
+ ) {}
+
+ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Promise {
+ const authorities = route.data['authorities'];
+ // We need to call the checkLogin / and so the accountService.identity() function, to ensure,
+ // that the client has a principal too, if they already logged in by the server.
+ // This could happen on a page refresh.
+ return this.checkLogin(authorities, state.url);
+ }
+
+ checkLogin(authorities: string[], url: string): Promise {
+ return this.accountService.identity().then(account => {
+ if (!authorities || authorities.length === 0) {
+ return true;
+ }
+
+ if (account) {
+ const hasAnyAuthority = this.accountService.hasAnyAuthority(authorities);
+ if (hasAnyAuthority) {
+ return true;
+ }
+ if (isDevMode()) {
+ console.error('User has not any of required authorities: ', authorities);
+ }
+ return false;
+ }
+
+ this.stateStorageService.storeUrl(url);
+ this.router.navigate(['accessdenied']).then(() => {
+ // only show the login dialog, if the user hasn't logged in yet
+ if (!account) {
+ this.loginModalService.open();
+ }
+ });
+ return false;
+ });
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/core.module.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/core.module.ts
new file mode 100644
index 0000000000..7569b8f59e
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/core.module.ts
@@ -0,0 +1,24 @@
+import { NgModule, LOCALE_ID } from '@angular/core';
+import { DatePipe, registerLocaleData } from '@angular/common';
+import { HttpClientModule } from '@angular/common/http';
+import { Title } from '@angular/platform-browser';
+import locale from '@angular/common/locales/en';
+
+@NgModule({
+ imports: [HttpClientModule],
+ exports: [],
+ declarations: [],
+ providers: [
+ Title,
+ {
+ provide: LOCALE_ID,
+ useValue: 'en'
+ },
+ DatePipe
+ ]
+})
+export class BookstoreCoreModule {
+ constructor() {
+ registerLocaleData(locale);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/index.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/index.ts
new file mode 100644
index 0000000000..38827443a5
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/index.ts
@@ -0,0 +1,11 @@
+export * from './auth/csrf.service';
+export * from './auth/state-storage.service';
+export * from './auth/account.service';
+export * from './auth/auth-jwt.service';
+export * from './user/account.model';
+export * from './user/user.model';
+export * from './auth/user-route-access-service';
+export * from './login/login-modal.service';
+export * from './login/login.service';
+export * from './user/user.service';
+export * from './core.module';
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login-modal.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login-modal.service.ts
new file mode 100644
index 0000000000..a0002aa56b
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login-modal.service.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiLoginModalComponent } from 'app/shared/login/login.component';
+
+@Injectable({ providedIn: 'root' })
+export class LoginModalService {
+ private isOpen = false;
+ constructor(private modalService: NgbModal) {}
+
+ open(): NgbModalRef {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+ const modalRef = this.modalService.open(JhiLoginModalComponent);
+ modalRef.result.then(
+ result => {
+ this.isOpen = false;
+ },
+ reason => {
+ this.isOpen = false;
+ }
+ );
+ return modalRef;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login.service.ts
new file mode 100644
index 0000000000..e91508ff44
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/login/login.service.ts
@@ -0,0 +1,38 @@
+import { Injectable } from '@angular/core';
+
+import { AccountService } from 'app/core/auth/account.service';
+import { AuthServerProvider } from 'app/core/auth/auth-jwt.service';
+
+@Injectable({ providedIn: 'root' })
+export class LoginService {
+ constructor(private accountService: AccountService, private authServerProvider: AuthServerProvider) {}
+
+ login(credentials, callback?) {
+ const cb = callback || function() {};
+
+ return new Promise((resolve, reject) => {
+ this.authServerProvider.login(credentials).subscribe(
+ data => {
+ this.accountService.identity(true).then(account => {
+ resolve(data);
+ });
+ return cb();
+ },
+ err => {
+ this.logout();
+ reject(err);
+ return cb(err);
+ }
+ );
+ });
+ }
+
+ loginWithToken(jwt, rememberMe) {
+ return this.authServerProvider.loginWithToken(jwt, rememberMe);
+ }
+
+ logout() {
+ this.authServerProvider.logout().subscribe();
+ this.accountService.authenticate(null);
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/account.model.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/account.model.ts
new file mode 100644
index 0000000000..35679657e3
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/account.model.ts
@@ -0,0 +1,12 @@
+export class Account {
+ constructor(
+ public activated: boolean,
+ public authorities: string[],
+ public email: string,
+ public firstName: string,
+ public langKey: string,
+ public lastName: string,
+ public login: string,
+ public imageUrl: string
+ ) {}
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.model.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.model.ts
new file mode 100644
index 0000000000..e82da11ac5
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.model.ts
@@ -0,0 +1,47 @@
+export interface IUser {
+ id?: any;
+ login?: string;
+ firstName?: string;
+ lastName?: string;
+ email?: string;
+ activated?: boolean;
+ langKey?: string;
+ authorities?: any[];
+ createdBy?: string;
+ createdDate?: Date;
+ lastModifiedBy?: string;
+ lastModifiedDate?: Date;
+ password?: string;
+}
+
+export class User implements IUser {
+ constructor(
+ public id?: any,
+ public login?: string,
+ public firstName?: string,
+ public lastName?: string,
+ public email?: string,
+ public activated?: boolean,
+ public langKey?: string,
+ public authorities?: any[],
+ public createdBy?: string,
+ public createdDate?: Date,
+ public lastModifiedBy?: string,
+ public lastModifiedDate?: Date,
+ public password?: string
+ ) {
+ this.id = id ? id : null;
+ this.login = login ? login : null;
+ this.firstName = firstName ? firstName : null;
+ this.lastName = lastName ? lastName : null;
+ this.email = email ? email : null;
+ this.activated = activated ? activated : false;
+ this.langKey = langKey ? langKey : null;
+ this.authorities = authorities ? authorities : null;
+ this.createdBy = createdBy ? createdBy : null;
+ this.createdDate = createdDate ? createdDate : null;
+ this.lastModifiedBy = lastModifiedBy ? lastModifiedBy : null;
+ this.lastModifiedDate = lastModifiedDate ? lastModifiedDate : null;
+ this.password = password ? password : null;
+ }
+}
diff --git a/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.service.ts b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.service.ts
new file mode 100644
index 0000000000..5c8065bedd
--- /dev/null
+++ b/jhipster-6/bookstore-monolith/src/main/webapp/app/core/user/user.service.ts
@@ -0,0 +1,39 @@
+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 { IUser } from './user.model';
+
+@Injectable({ providedIn: 'root' })
+export class UserService {
+ public resourceUrl = SERVER_API_URL + 'api/users';
+
+ constructor(private http: HttpClient) {}
+
+ create(user: IUser): Observable> {
+ return this.http.post(this.resourceUrl, user, { observe: 'response' });
+ }
+
+ update(user: IUser): Observable> {
+ return this.http.put(this.resourceUrl, user, { observe: 'response' });
+ }
+
+ find(login: string): Observable> {
+ return this.http.get