From 99fca612f68e22d468c4ef66702f9d8d0621d0cc Mon Sep 17 00:00:00 2001 From: Piper Han Date: Thu, 1 Dec 2022 21:56:05 +0800 Subject: [PATCH 1/4] update prompts, add coroutine option;add debug log message --- generators/app/util.js | 1 + generators/server/index.js | 16 +- generators/server/prompts.js | 336 +++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 generators/server/prompts.js diff --git a/generators/app/util.js b/generators/app/util.js index d9f1462c5..e4909dbab 100644 --- a/generators/app/util.js +++ b/generators/app/util.js @@ -50,6 +50,7 @@ function displayLogo() { this.log( chalk.blue(' _______________________________________________________________________________________________________________\n') ); + this.log(chalk.red(' Just for Debug: coroutine. \n')); } module.exports = { diff --git a/generators/server/index.js b/generators/server/index.js index 8c3e804e4..b775e1be9 100644 --- a/generators/server/index.js +++ b/generators/server/index.js @@ -19,10 +19,13 @@ /* eslint-disable consistent-return */ const os = require('os'); +const constants = require('generator-jhipster/generators/generator-constants'); +const prompts = require('generator-jhipster/generators/server/prompts'); const shelljs = require('shelljs'); const ServerGenerator = require('generator-jhipster/generators/server'); const writeFiles = require('./files').writeFiles; const kotlinConstants = require('../generator-kotlin-constants'); +const { askForKotlinServerSideOpts } = require('./prompts'); module.exports = class extends ServerGenerator { constructor(args, opts, features) { @@ -48,7 +51,18 @@ module.exports = class extends ServerGenerator { get prompting() { // Here we are not overriding this phase and hence its being handled by JHipster - return super._prompting(); + return { + askForModuleName: prompts.askForModuleName, + askForServerSideOpts: askForKotlinServerSideOpts, + askForOptionalItems: prompts.askForOptionalItems, + + setSharedConfigOptions() { + // Make dist dir available in templates + this.BUILD_DIR = this.getBuildDirectoryForBuildTool(this.jhipsterConfig.buildTool); + this.CLIENT_DIST_DIR = + this.getResourceBuildDirectoryForBuildTool(this.jhipsterConfig.buildTool) + constants.CLIENT_DIST_DIR; + }, + }; } get configuring() { diff --git a/generators/server/prompts.js b/generators/server/prompts.js new file mode 100644 index 000000000..dc492fc11 --- /dev/null +++ b/generators/server/prompts.js @@ -0,0 +1,336 @@ +/** + * Copyright 2013-2022 the original author or authors from the JHipster project. + * + * This file is part of the JHipster project, see https://www.jhipster.tech/ + * for more information. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const chalk = require('chalk'); + +const constants = require('generator-jhipster/generators/generator-constants'); +const { serverDefaultConfig } = require('generator-jhipster/generators/generator-defaults'); +const { GATEWAY, MICROSERVICE, MONOLITH } = require('generator-jhipster/jdl/jhipster/application-types'); +const { CAFFEINE, EHCACHE, HAZELCAST, INFINISPAN, MEMCACHED, REDIS } = require('generator-jhipster/jdl/jhipster/cache-types'); +const cacheProviderTypes = require('generator-jhipster/jdl/jhipster/cache-types'); +const { JWT, OAUTH2, SESSION } = require('generator-jhipster/jdl/jhipster/authentication-types'); +const { GRADLE, MAVEN } = require('generator-jhipster/jdl/jhipster/build-tool-types'); +const { CASSANDRA, H2_DISK, H2_MEMORY, MONGODB, NEO4J, SQL } = require('generator-jhipster/jdl/jhipster/database-types'); +const databaseTypes = require('generator-jhipster/jdl/jhipster/database-types'); +const { CONSUL, EUREKA } = require('generator-jhipster/jdl/jhipster/service-discovery-types'); +const serviceDiscoveryTypes = require('generator-jhipster/jdl/jhipster/service-discovery-types'); +const { OptionNames } = require('generator-jhipster/jdl/jhipster/application-options'); + +const { + AUTHENTICATION_TYPE, + BUILD_TOOL, + CACHE_PROVIDER, + DATABASE_TYPE, + PACKAGE_NAME, + DEV_DATABASE_TYPE, + PROD_DATABASE_TYPE, + REACTIVE, + SERVER_PORT, + SERVICE_DISCOVERY_TYPE, +} = OptionNames; +const NO_SERVICE_DISCOVERY = serviceDiscoveryTypes.NO; +const NO_DATABASE = databaseTypes.NO; +const NO_CACHE_PROVIDER = cacheProviderTypes.NO; + +module.exports = { + askForKotlinServerSideOpts, +}; + +function askForKotlinServerSideOpts() { + if (this.existingProject) return undefined; + + const applicationType = this.jhipsterConfig.applicationType; + const defaultPort = applicationType === GATEWAY ? '8080' : '8081'; + const prompts = [ + { + when: () => [MONOLITH, MICROSERVICE].includes(applicationType), + type: 'confirm', + name: REACTIVE, + message: 'Do you want to make it reactive with Spring WebFlux?', + default: serverDefaultConfig.reactive, + }, + { + when: answers => answers.reactive, + type: 'confirm', + name: 'coroutine', + message: 'Using coroutines for reactive application?', + default: false, + }, + { + when: () => applicationType === GATEWAY || applicationType === MICROSERVICE, + type: 'input', + name: SERVER_PORT, + validate: input => (/^([0-9]*)$/.test(input) ? true : 'This is not a valid port number.'), + message: + 'As you are running in a microservice architecture, on which port would like your server to run? It should be unique to avoid port conflicts.', + default: defaultPort, + }, + { + type: 'input', + name: PACKAGE_NAME, + validate: input => + /^([a-z_]{1}[a-z0-9_]*(\.[a-z_]{1}[a-z0-9_]*)*)$/.test(input) + ? true + : 'The package name you have provided is not a valid Java package name.', + message: 'What is your default Java package name?', + default: serverDefaultConfig.packageName, + store: true, + }, + { + when: () => applicationType === 'gateway' || applicationType === 'microservice', + type: 'list', + name: SERVICE_DISCOVERY_TYPE, + message: 'Which service discovery server do you want to use?', + choices: [ + { + value: EUREKA, + name: 'JHipster Registry (uses Eureka, provides Spring Cloud Config support and monitoring dashboards)', + }, + { + value: CONSUL, + name: 'Consul', + }, + { + value: NO_SERVICE_DISCOVERY, + name: 'No service discovery', + }, + ], + default: EUREKA, + }, + { + when: answers => + (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) || + [GATEWAY, MICROSERVICE].includes(applicationType), + type: 'list', + name: AUTHENTICATION_TYPE, + message: `Which ${chalk.yellow('*type*')} of authentication would you like to use?`, + choices: answers => { + const opts = [ + { + value: JWT, + name: 'JWT authentication (stateless, with a token)', + }, + ]; + opts.push({ + value: OAUTH2, + name: 'OAuth 2.0 / OIDC Authentication (stateful, works with Keycloak and Okta)', + }); + if (applicationType === MONOLITH && answers.serviceDiscoveryType !== EUREKA) { + opts.push({ + value: SESSION, + name: 'HTTP Session Authentication (stateful, default Spring Security mechanism)', + }); + } + return opts; + }, + default: serverDefaultConfig.authenticationType, + }, + { + type: 'list', + name: DATABASE_TYPE, + message: `Which ${chalk.yellow('*type*')} of database would you like to use?`, + choices: answers => { + const opts = []; + if (!answers.reactive) { + opts.push({ + value: SQL, + name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, Oracle, MSSQL)', + }); + } else { + opts.push({ + value: SQL, + name: 'SQL (H2, PostgreSQL, MySQL, MariaDB, MSSQL)', + }); + } + opts.push({ + value: MONGODB, + name: 'MongoDB', + }); + if (answers.authenticationType !== OAUTH2) { + opts.push({ + value: CASSANDRA, + name: 'Cassandra', + }); + } + opts.push({ + value: 'couchbase', + name: '[BETA] Couchbase', + }); + opts.push({ + value: NEO4J, + name: '[BETA] Neo4j', + }); + opts.push({ + value: NO_DATABASE, + name: 'No database', + }); + return opts; + }, + default: serverDefaultConfig.databaseType, + }, + { + when: response => response.databaseType === SQL, + type: 'list', + name: PROD_DATABASE_TYPE, + message: `Which ${chalk.yellow('*production*')} database would you like to use?`, + choices: answers => (answers.reactive ? constants.R2DBC_DB_OPTIONS : constants.SQL_DB_OPTIONS), + default: serverDefaultConfig.prodDatabaseType, + }, + { + when: response => response.databaseType === SQL, + type: 'list', + name: DEV_DATABASE_TYPE, + message: `Which ${chalk.yellow('*development*')} database would you like to use?`, + choices: response => + [ + { + value: H2_DISK, + name: 'H2 with disk-based persistence', + }, + { + value: H2_MEMORY, + name: 'H2 with in-memory persistence', + }, + ].concat(constants.SQL_DB_OPTIONS.find(it => it.value === response.prodDatabaseType)), + default: serverDefaultConfig.devDatabaseType, + }, + { + when: answers => !answers.reactive, + type: 'list', + name: CACHE_PROVIDER, + message: 'Which cache do you want to use? (Spring cache abstraction)', + choices: [ + { + value: EHCACHE, + name: 'Ehcache (local cache, for a single node)', + }, + { + value: CAFFEINE, + name: 'Caffeine (local cache, for a single node)', + }, + { + value: HAZELCAST, + name: 'Hazelcast (distributed cache, for multiple nodes, supports rate-limiting for gateway applications)', + }, + { + value: INFINISPAN, + name: 'Infinispan (hybrid cache, for multiple nodes)', + }, + { + value: MEMCACHED, + name: 'Memcached (distributed cache) - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', + }, + { + value: REDIS, + name: 'Redis (distributed cache)', + }, + { + value: NO_CACHE_PROVIDER, + name: 'No cache - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!', + }, + ], + default: applicationType === MICROSERVICE ? 2 : serverDefaultConfig.cacheProvider, + }, + { + when: answers => + ((answers.cacheProvider !== NO_CACHE_PROVIDER && answers.cacheProvider !== MEMCACHED) || applicationType === GATEWAY) && + answers.databaseType === SQL && + !answers.reactive, + type: 'confirm', + name: 'enableHibernateCache', + message: 'Do you want to use Hibernate 2nd level cache?', + default: serverDefaultConfig.enableHibernateCache, + }, + { + type: 'list', + name: BUILD_TOOL, + message: 'Would you like to use Maven or Gradle for building the backend?', + choices: [ + { + value: MAVEN, + name: 'Maven', + }, + { + value: GRADLE, + name: 'Gradle', + }, + ], + default: serverDefaultConfig.buildTool, + }, + { + when: answers => answers.buildTool === GRADLE && this.options.experimental, + type: 'confirm', + name: 'enableGradleEnterprise', + message: 'Do you want to enable Gradle Enterprise integration?', + default: serverDefaultConfig.enableGradleEnterprise, + }, + { + when: answers => answers.enableGradleEnterprise, + type: 'input', + name: 'gradleEnterpriseHost', + message: 'Enter your Gradle Enterprise host', + validate: input => (input.length === 0 ? 'Please enter your Gradle Enterprise host' : true), + }, + { + when: applicationType === MONOLITH, + type: 'list', + name: SERVICE_DISCOVERY_TYPE, + message: 'Do you want to use the JHipster Registry to configure, monitor and scale your application?', + choices: [ + { + value: NO_SERVICE_DISCOVERY, + name: 'No', + }, + { + value: EUREKA, + name: 'Yes', + }, + ], + default: serverDefaultConfig.serviceDiscoveryType, + }, + ]; + + return this.prompt(prompts).then(answers => { + this.serviceDiscoveryType = this.jhipsterConfig.serviceDiscoveryType = answers.serviceDiscoveryType; + if (this.jhipsterConfig.applicationType === GATEWAY) { + this.reactive = this.jhipsterConfig.reactive = answers.reactive = true; + } else { + this.reactive = this.jhipsterConfig.reactive = answers.reactive; + } + // config coroutine + if (this.reactive) { + this.coroutine = this.jhipsterConfig.coroutine = answers.coroutine; + } + this.authenticationType = this.jhipsterConfig.authenticationType = answers.authenticationType; + + this.packageName = this.jhipsterConfig.packageName = answers.packageName; + this.serverPort = this.jhipsterConfig.serverPort = answers.serverPort || '8080'; + this.cacheProvider = this.jhipsterConfig.cacheProvider = !answers.reactive ? answers.cacheProvider : NO_CACHE_PROVIDER; + this.enableHibernateCache = this.jhipsterConfig.enableHibernateCache = !!answers.enableHibernateCache; + + const { databaseType } = answers; + this.databaseType = this.jhipsterConfig.databaseType = databaseType; + this.devDatabaseType = this.jhipsterConfig.devDatabaseType = answers.devDatabaseType || databaseType; + this.prodDatabaseType = this.jhipsterConfig.prodDatabaseType = answers.prodDatabaseType || databaseType; + this.searchEngine = this.jhipsterConfig.searchEngine = answers.searchEngine; + this.buildTool = this.jhipsterConfig.buildTool = answers.buildTool; + this.enableGradleEnterprise = this.jhipsterConfig.enableGradleEnterprise = answers.enableGradleEnterprise; + this.gradleEnterpriseHost = this.jhipsterConfig.gradleEnterpriseHost = answers.gradleEnterpriseHost; + }); +} From 7dc8c8e2237d50708ac72c86c6c618c86d918963 Mon Sep 17 00:00:00 2001 From: Piper Han Date: Thu, 1 Dec 2022 22:22:13 +0800 Subject: [PATCH 2/4] finish UserController UserService UserRepository --- generators/server/files-coroutine-list.js | 1844 +++++++++++++++++ generators/server/files.js | 23 +- generators/server/prompts.js | 2 +- .../repository/AuthorityRepository.kt.ejs | 50 + .../repository/UserRepository.kt.ejs | 586 ++++++ .../search/UserSearchRepository.kt.ejs | 74 + .../security/DomainUserDetailsService.kt.ejs | 86 + .../coroutine/service/UserService.kt.ejs | 672 ++++++ .../coroutine/web/rest/AccountResource.kt.ejs | 327 +++ .../web/rest/PublicUserResource.kt.ejs | 141 ++ .../web/rest/UserJWTController.kt.ejs | 66 + .../coroutine/web/rest/UserResource.kt.ejs | 227 ++ 12 files changed, 4094 insertions(+), 4 deletions(-) create mode 100644 generators/server/files-coroutine-list.js create mode 100644 generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs create mode 100644 generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs diff --git a/generators/server/files-coroutine-list.js b/generators/server/files-coroutine-list.js new file mode 100644 index 000000000..d1ee9efc1 --- /dev/null +++ b/generators/server/files-coroutine-list.js @@ -0,0 +1,1844 @@ +const { mergeSections, addSectionsCondition } = require('generator-jhipster/generators/utils'); +const constants = require('generator-jhipster/generators/generator-constants'); + +/* Constants use throughout */ +const INTERPOLATE_REGEX = constants.INTERPOLATE_REGEX; +const DOCKER_DIR = constants.DOCKER_DIR; +const TEST_DIR = constants.TEST_DIR; +const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR; +const SERVER_MAIN_RES_DIR = constants.SERVER_MAIN_RES_DIR; +const SERVER_TEST_SRC_DIR = constants.SERVER_TEST_SRC_DIR; + +const SERVER_TEST_RES_DIR = constants.SERVER_TEST_RES_DIR; + +const shouldSkipUserManagement = generator => + generator.skipUserManagement && (!generator.applicationTypeMonolith || !generator.authenticationTypeOauth2); + +const liquibaseFiles = { + serverResource: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + { + override: generator => + !generator.jhipsterConfig.incrementalChangelog || generator.configOptions.recreateInitialChangelog, + file: 'config/liquibase/changelog/initial_schema.xml', + renameTo: () => 'config/liquibase/changelog/00000000000000_initial_schema.xml', + options: { interpolate: INTERPOLATE_REGEX }, + }, + { + override: generator => + !generator.jhipsterConfig.incrementalChangelog || generator.configOptions.recreateInitialChangelog, + file: 'config/liquibase/master.xml', + }, + ], + }, + ], +}; + +const mongoDbFiles = { + docker: [ + { + path: DOCKER_DIR, + templates: ['mongodb.yml', 'mongodb-cluster.yml', 'mongodb/MongoDB.Dockerfile', 'mongodb/scripts/init_replicaset.js'], + }, + ], + serverResource: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/dbmigrations/package-info.java', + renameTo: generator => `${generator.javaDir}config/dbmigrations/package-info.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement || (generator.skipUserManagement && generator.authenticationTypeOauth2), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/dbmigrations/InitialSetupMigration.java', + renameTo: generator => `${generator.javaDir}config/dbmigrations/InitialSetupMigration.java`, + }, + ], + }, + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/MongoDbTestContainer.java', + renameTo: generator => `${generator.testDir}config/MongoDbTestContainer.java`, + }, + { + file: 'package/config/EmbeddedMongo.java', + renameTo: generator => `${generator.testDir}config/EmbeddedMongo.java`, + }, + ], + }, + ], +}; + +const neo4jFiles = { + docker: [ + { + path: DOCKER_DIR, + templates: ['neo4j.yml'], + }, + ], + serverResource: [ + { + condition: generator => !generator.skipUserManagement || generator.authenticationTypeOauth2, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/neo4j/Neo4jMigrations.java', + renameTo: generator => `${generator.javaDir}config/neo4j/Neo4jMigrations.java`, + }, + { + file: 'package/config/neo4j/package-info.java', + renameTo: generator => `${generator.javaDir}config/neo4j/package-info.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement || generator.authenticationTypeOauth2, + path: SERVER_MAIN_RES_DIR, + templates: ['config/neo4j/migrations/user__admin.json', 'config/neo4j/migrations/user__user.json'], + }, + ], + serverTestFw: [ + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/Neo4jTestContainer.java', + renameTo: generator => `${generator.testDir}config/Neo4jTestContainer.java`, + }, + { + file: 'package/config/EmbeddedNeo4j.java', + renameTo: generator => `${generator.testDir}config/EmbeddedNeo4j.java`, + }, + ], + }, + ], +}; + +const cassandraFiles = { + docker: [ + { + path: DOCKER_DIR, + templates: [ + // docker-compose files + 'cassandra.yml', + 'cassandra-cluster.yml', + 'cassandra-migration.yml', + // dockerfiles + 'cassandra/Cassandra-Migration.Dockerfile', + // scripts + 'cassandra/scripts/autoMigrate.sh', + 'cassandra/scripts/execute-cql.sh', + ], + }, + ], + serverResource: [ + { + path: SERVER_MAIN_RES_DIR, + templates: [ + 'config/cql/create-keyspace-prod.cql', + 'config/cql/create-keyspace.cql', + 'config/cql/drop-keyspace.cql', + { file: 'config/cql/changelog/README.md', method: 'copy' }, + ], + }, + { + condition: generator => + !generator.applicationTypeMicroservice && (!generator.skipUserManagement || generator.authenticationTypeOauth2), + path: SERVER_MAIN_RES_DIR, + templates: [ + { file: 'config/cql/changelog/create-tables.cql', renameTo: () => 'config/cql/changelog/00000000000000_create-tables.cql' }, + { + file: 'config/cql/changelog/insert_default_users.cql', + renameTo: () => 'config/cql/changelog/00000000000001_insert_default_users.cql', + }, + ], + }, + ], + serverTestFw: [ + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/CassandraKeyspaceIT.java', + renameTo: generator => `${generator.testDir}CassandraKeyspaceIT.java`, + }, + { + file: 'package/config/CassandraTestContainer.java', + renameTo: generator => `${generator.testDir}config/CassandraTestContainer.java`, + }, + { + file: 'package/config/EmbeddedCassandra.java', + renameTo: generator => `${generator.testDir}config/EmbeddedCassandra.java`, + }, + ], + }, + ], +}; + +const baseServerFiles = { + jib: [ + { + path: 'src/main/docker/jib/', + templates: ['entrypoint.sh'], + }, + ], + packageJson: [ + { + condition: generator => generator.skipClient, + templates: ['package.json'], + }, + ], + docker: [ + { + path: DOCKER_DIR, + templates: [ + 'app.yml', + 'jhipster-control-center.yml', + 'sonar.yml', + 'monitoring.yml', + 'prometheus/prometheus.yml', + 'grafana/provisioning/dashboards/dashboard.yml', + 'grafana/provisioning/dashboards/JVM.json', + 'grafana/provisioning/datasources/datasource.yml', + ], + }, + { + condition: generator => generator.cacheProviderHazelcast, + path: DOCKER_DIR, + templates: ['hazelcast-management-center.yml'], + }, + { + condition: generator => generator.cacheProviderMemcached, + path: DOCKER_DIR, + templates: ['memcached.yml'], + }, + { + condition: generator => generator.cacheProviderRedis, + path: DOCKER_DIR, + templates: ['redis.yml', 'redis-cluster.yml', 'redis/Redis-Cluster.Dockerfile', 'redis/connectRedisCluster.sh'], + }, + { + condition: generator => generator.searchEngineElasticsearch, + path: DOCKER_DIR, + templates: ['elasticsearch.yml'], + }, + { + condition: generator => generator.messageBrokerKafka, + path: DOCKER_DIR, + templates: ['kafka.yml'], + }, + { + condition: generator => !!generator.serviceDiscoveryType, + path: DOCKER_DIR, + templates: [{ file: 'config/README.md', renameTo: () => 'central-server-config/README.md' }], + }, + { + condition: generator => generator.serviceDiscoveryType && generator.serviceDiscoveryConsul, + path: DOCKER_DIR, + templates: [ + 'consul.yml', + { file: 'config/git2consul.json', method: 'copy' }, + { file: 'config/consul-config/application.yml', renameTo: () => 'central-server-config/application.yml' }, + ], + }, + { + condition: generator => generator.serviceDiscoveryType && generator.serviceDiscoveryEureka, + path: DOCKER_DIR, + templates: [ + 'jhipster-registry.yml', + { + file: 'config/docker-config/application.yml', + renameTo: () => 'central-server-config/docker-config/application.yml', + }, + { + file: 'config/localhost-config/application.yml', + renameTo: () => 'central-server-config/localhost-config/application.yml', + }, + ], + }, + { + condition: generator => !!generator.enableSwaggerCodegen, + path: DOCKER_DIR, + templates: ['swagger-editor.yml'], + }, + { + condition: generator => generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice, + path: DOCKER_DIR, + templates: [ + 'keycloak.yml', + { file: 'config/realm-config/jhipster-realm.json', renameTo: () => 'realm-config/jhipster-realm.json' }, + ], + }, + { + condition: generator => + generator.serviceDiscoveryType || generator.applicationTypeGateway || generator.applicationTypeMicroservice, + path: DOCKER_DIR, + templates: ['zipkin.yml'], + }, + ], + serverBuild: [ + { + templates: [ + { file: 'checkstyle.xml', options: { interpolate: INTERPOLATE_REGEX } }, + { file: 'devcontainer/devcontainer.json', renameTo: () => '.devcontainer/devcontainer.json' }, + { file: 'devcontainer/Dockerfile', renameTo: () => '.devcontainer/Dockerfile' }, + ], + }, + { + condition: generator => generator.buildToolGradle, + templates: [ + 'build.gradle', + 'settings.gradle', + 'gradle.properties', + 'gradle/sonar.gradle', + 'gradle/docker.gradle', + { file: 'gradle/profile_dev.gradle', options: { interpolate: INTERPOLATE_REGEX } }, + { file: 'gradle/profile_prod.gradle', options: { interpolate: INTERPOLATE_REGEX } }, + 'gradle/war.gradle', + 'gradle/zipkin.gradle', + { file: 'gradlew', method: 'copy', noEjs: true }, + { file: 'gradlew.bat', method: 'copy', noEjs: true }, + { file: 'gradle/wrapper/gradle-wrapper.jar', method: 'copy', noEjs: true }, + 'gradle/wrapper/gradle-wrapper.properties', + ], + }, + { + condition: generator => generator.buildToolGradle && !!generator.enableSwaggerCodegen, + templates: ['gradle/swagger.gradle'], + }, + { + condition: generator => generator.buildToolMaven, + templates: [ + { file: 'mvnw', method: 'copy', noEjs: true }, + { file: 'mvnw.cmd', method: 'copy', noEjs: true }, + { file: '.mvn/jvm.config', method: 'copy', noEjs: true }, + { file: '.mvn/wrapper/maven-wrapper.jar', method: 'copy', noEjs: true }, + { file: '.mvn/wrapper/maven-wrapper.properties', method: 'copy', noEjs: true }, + { file: 'pom.xml', options: { interpolate: INTERPOLATE_REGEX } }, + ], + }, + { + condition: generator => !generator.skipClient, + templates: [ + { file: 'npmw', method: 'copy', noEjs: true }, + { file: 'npmw.cmd', method: 'copy', noEjs: true }, + ], + }, + ], + serverResource: [ + { + condition: generator => generator.clientFrameworkReact, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'banner-react.txt', + method: 'copy', + noEjs: true, + renameTo: () => 'banner.txt', + }, + ], + }, + { + condition: generator => generator.clientFrameworkVue, + path: SERVER_MAIN_RES_DIR, + templates: [ + { + file: 'banner-vue.txt', + method: 'copy', + noEjs: true, + renameTo: () => 'banner.txt', + }, + ], + }, + { + condition: generator => !generator.clientFrameworkReact && !generator.clientFrameworkVue, + path: SERVER_MAIN_RES_DIR, + templates: [{ file: 'banner.txt', method: 'copy', noEjs: true }], + }, + { + condition: generator => !!generator.enableSwaggerCodegen, + path: SERVER_MAIN_RES_DIR, + templates: ['swagger/api.yml'], + }, + { + path: SERVER_MAIN_RES_DIR, + templates: [ + // Thymeleaf templates + { file: 'templates/error.html', method: 'copy' }, + 'logback-spring.xml', + 'config/application.yml', + 'config/application-dev.yml', + 'config/application-tls.yml', + 'config/application-prod.yml', + 'i18n/messages.properties', + ], + }, + ], + serverJavaAuthConfig: [ + { + condition: generator => + !generator.reactive && (generator.databaseTypeSql || generator.databaseTypeMongodb || generator.databaseTypeCouchbase), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/SpringSecurityAuditorAware.java', + renameTo: generator => `${generator.javaDir}security/SpringSecurityAuditorAware.java`, + }, + ], + }, + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/SecurityUtils.java', + renameTo: generator => `${generator.javaDir}security/SecurityUtils.java`, + }, + { + file: 'package/security/AuthoritiesConstants.java', + renameTo: generator => `${generator.javaDir}security/AuthoritiesConstants.java`, + }, + { + file: 'package/security/package-info.java', + renameTo: generator => `${generator.javaDir}security/package-info.java`, + }, + ], + }, + { + condition: generator => !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/security/SecurityUtilsUnitTest.java', + renameTo: generator => `${generator.testDir}security/SecurityUtilsUnitTest.java`, + }, + ], + }, + { + condition: generator => generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/security/SecurityUtilsUnitTest_reactive.java', + renameTo: generator => `${generator.testDir}security/SecurityUtilsUnitTest.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeJwt, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/jwt/TokenProvider.java', + renameTo: generator => `${generator.javaDir}security/jwt/TokenProvider.java`, + }, + { + file: 'package/security/jwt/JWTFilter.java', + renameTo: generator => `${generator.javaDir}security/jwt/JWTFilter.java`, + }, + { + file: 'package/management/SecurityMetersService.java', + renameTo: generator => `${generator.javaDir}management/SecurityMetersService.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeJwt && !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/jwt/JWTConfigurer.java', + renameTo: generator => `${generator.javaDir}security/jwt/JWTConfigurer.java`, + }, + ], + }, + { + condition: generator => generator.reactive && generator.applicationTypeGateway && generator.authenticationTypeJwt, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/jwt/JWTRelayGatewayFilterFactory.java', + renameTo: generator => `${generator.testDir}security/jwt/JWTRelayGatewayFilterFactory.java`, + }, + ], + }, + { + condition: generator => !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/SecurityConfiguration.java', + renameTo: generator => `${generator.javaDir}config/SecurityConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/SecurityConfiguration_reactive.java', + renameTo: generator => `${generator.javaDir}config/SecurityConfiguration.java`, + }, + ], + }, + { + condition: generator => !shouldSkipUserManagement(generator) && generator.authenticationTypeSession && !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/PersistentTokenRememberMeServices.java', + renameTo: generator => `${generator.javaDir}security/PersistentTokenRememberMeServices.java`, + }, + { + file: 'package/domain/PersistentToken.java', + renameTo: generator => `${generator.javaDir}domain/PersistentToken.java`, + }, + ], + }, + { + condition: generator => + !shouldSkipUserManagement(generator) && + generator.authenticationTypeSession && + !generator.reactive && + !generator.databaseTypeCouchbase, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/repository/PersistentTokenRepository.java', + renameTo: generator => `${generator.javaDir}repository/PersistentTokenRepository.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/AudienceValidator.java', + renameTo: generator => `${generator.javaDir}security/oauth2/AudienceValidator.java`, + }, + { + file: 'package/security/oauth2/JwtGrantedAuthorityConverter.java', + renameTo: generator => `${generator.javaDir}security/oauth2/JwtGrantedAuthorityConverter.java`, + }, + { + file: 'package/security/oauth2/OAuthIdpTokenResponseDTO.java', + renameTo: generator => `${generator.javaDir}security/oauth2/OAuthIdpTokenResponseDTO.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/AudienceValidatorTest.java', + renameTo: generator => `${generator.javaDir}security/oauth2/AudienceValidatorTest.java`, + }, + { + file: 'package/config/TestSecurityConfiguration.java', + renameTo: generator => `${generator.testDir}config/TestSecurityConfiguration.java`, + }, + ], + }, + { + condition: generator => + !generator.reactive && + generator.authenticationTypeOauth2 && + (generator.applicationTypeMicroservice || generator.applicationTypeGateway), + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/AuthorizationHeaderUtilTest.java', + renameTo: generator => `${generator.javaDir}security/oauth2/AuthorizationHeaderUtilTest.java`, + }, + ], + }, + { + condition: generator => !shouldSkipUserManagement(generator) && !generator.authenticationTypeOauth2, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'coroutine/security/DomainUserDetailsService.java', + renameTo: generator => `${generator.javaDir}security/DomainUserDetailsService.java`, + }, + { + file: 'package/security/UserNotActivatedException.java', + renameTo: generator => `${generator.javaDir}security/UserNotActivatedException.java`, + }, + ], + }, + { + condition: generator => !generator.applicationTypeMicroservice && generator.authenticationTypeJwt, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/vm/LoginVM.java', + renameTo: generator => `${generator.javaDir}web/rest/vm/LoginVM.java`, + }, + { + file: 'coroutine/web/rest/UserJWTController.java', + renameTo: generator => `${generator.javaDir}web/rest/UserJWTController.java`, + }, + ], + }, + { + condition: generator => !!generator.enableSwaggerCodegen, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/OpenApiConfiguration.java', + renameTo: generator => `${generator.javaDir}config/OpenApiConfiguration.java`, + }, + ], + }, + { + condition: generator => !generator.reactive && generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/CustomClaimConverter.java', + renameTo: generator => `${generator.javaDir}security/oauth2/CustomClaimConverter.java`, + }, + ], + }, + { + condition: generator => !generator.reactive && generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/CustomClaimConverterIT.java', + renameTo: generator => `${generator.javaDir}security/oauth2/CustomClaimConverterIT.java`, + }, + ], + }, + ], + serverJavaGateway: [ + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryType, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/web/rest/vm/RouteVM.java', renameTo: generator => `${generator.javaDir}web/rest/vm/RouteVM.java` }, + { + file: 'package/web/rest/GatewayResource.java', + renameTo: generator => `${generator.javaDir}web/rest/GatewayResource.java`, + }, + ], + }, + { + condition: generator => + generator.authenticationTypeOauth2 && (generator.applicationTypeMonolith || generator.applicationTypeGateway), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/AuthInfoResource.java', + renameTo: generator => `${generator.javaDir}web/rest/AuthInfoResource.java`, + }, + ], + }, + { + condition: generator => + generator.authenticationTypeOauth2 && + !generator.reactive && + (generator.applicationTypeMonolith || generator.applicationTypeGateway), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/LogoutResource.java', + renameTo: generator => `${generator.javaDir}web/rest/LogoutResource.java`, + }, + ], + }, + { + condition: generator => + generator.authenticationTypeOauth2 && + generator.reactive && + (generator.applicationTypeMonolith || generator.applicationTypeGateway), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/LogoutResource_reactive.java', + renameTo: generator => `${generator.javaDir}web/rest/LogoutResource.java`, + }, + ], + }, + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryType && generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/filter/ModifyServersOpenApiFilter.java', + renameTo: generator => `${generator.javaDir}web/filter/ModifyServersOpenApiFilter.java`, + }, + ], + }, + { + condition: generator => generator.applicationTypeGateway && generator.serviceDiscoveryType && generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/filter/ModifyServersOpenApiFilterTest.java', + renameTo: generator => `${generator.testDir}web/filter/ModifyServersOpenApiFilterTest.java`, + }, + ], + }, + ], + serverMicroservice: [ + { + condition: generator => + !generator.reactive && + (generator.applicationTypeMicroservice || generator.applicationTypeGateway) && + generator.authenticationTypeJwt, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/FeignConfiguration.java', + renameTo: generator => `${generator.javaDir}config/FeignConfiguration.java`, + }, + { + file: 'package/client/JWT_UserFeignClientInterceptor.java', + renameTo: generator => `${generator.javaDir}client/UserFeignClientInterceptor.java`, + }, + ], + }, + { + condition: generator => + !generator.reactive && + generator.authenticationTypeOauth2 && + (generator.applicationTypeMicroservice || generator.applicationTypeGateway), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/security/oauth2/AuthorizationHeaderUtil.java', + renameTo: generator => `${generator.javaDir}security/oauth2/AuthorizationHeaderUtil.java`, + }, + { + file: 'package/config/FeignConfiguration.java', + renameTo: generator => `${generator.javaDir}config/FeignConfiguration.java`, + }, + { + file: 'package/client/AuthorizedFeignClient.java', + renameTo: generator => `${generator.javaDir}client/AuthorizedFeignClient.java`, + }, + { + file: 'package/client/OAuth2InterceptedFeignConfiguration.java', + renameTo: generator => `${generator.javaDir}client/OAuth2InterceptedFeignConfiguration.java`, + }, + { + file: 'package/client/TokenRelayRequestInterceptor.java', + renameTo: generator => `${generator.javaDir}client/TokenRelayRequestInterceptor.java`, + }, + ], + }, + { + condition: generator => !generator.reactive && generator.applicationTypeGateway && !generator.serviceDiscoveryType, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/RestTemplateConfiguration.java', + renameTo: generator => `${generator.javaDir}config/RestTemplateConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.applicationTypeMicroservice, + path: SERVER_MAIN_RES_DIR, + templates: [{ file: 'static/microservices_index.html', renameTo: () => 'static/index.html' }], + }, + ], + serverMicroserviceAndGateway: [ + { + condition: generator => generator.serviceDiscoveryType, + path: SERVER_MAIN_RES_DIR, + templates: ['config/bootstrap.yml', 'config/bootstrap-prod.yml'], + }, + ], + serverJavaApp: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [{ file: 'package/Application.java', renameTo: generator => `${generator.javaDir}${generator.mainClass}.java` }], + }, + { + condition: generator => generator.serviceDiscoveryType && generator.serviceDiscoveryEureka, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/EurekaWorkaroundConfiguration.java', + renameTo: generator => `${generator.javaDir}/config/EurekaWorkaroundConfiguration.java`, + }, + ], + }, + { + condition: generator => !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [{ file: 'package/ApplicationWebXml.java', renameTo: generator => `${generator.javaDir}ApplicationWebXml.java` }], + }, + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/TechnicalStructureTest.java', + renameTo: generator => `${generator.testDir}TechnicalStructureTest.java`, + }, + ], + }, + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/AsyncSyncConfiguration.java', + renameTo: generator => `${generator.testDir}config/AsyncSyncConfiguration.java`, + }, + { + file: 'package/IntegrationTest.java', + renameTo: generator => `${generator.testDir}/IntegrationTest.java`, + }, + { + file: 'package/config/SpringBootTestClassOrderer.java', + renameTo: generator => `${generator.testDir}config/SpringBootTestClassOrderer.java`, + }, + ], + }, + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/GeneratedByJHipster.java', + renameTo: generator => `${generator.javaDir}GeneratedByJHipster.java`, + }, + ], + }, + ], + serverJavaConfig: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/aop/logging/LoggingAspect.java', + renameTo: generator => `${generator.javaDir}aop/logging/LoggingAspect.java`, + }, + { file: 'package/config/package-info.java', renameTo: generator => `${generator.javaDir}config/package-info.java` }, + { + file: 'package/config/AsyncConfiguration.java', + renameTo: generator => `${generator.javaDir}config/AsyncConfiguration.java`, + }, + { + file: 'package/config/CRLFLogConverter.java', + renameTo: generator => `${generator.javaDir}config/CRLFLogConverter.java`, + }, + { + file: 'package/config/DateTimeFormatConfiguration.java', + renameTo: generator => `${generator.javaDir}config/DateTimeFormatConfiguration.java`, + }, + { + file: 'package/config/LoggingConfiguration.java', + renameTo: generator => `${generator.javaDir}config/LoggingConfiguration.java`, + }, + { + file: 'package/config/ApplicationProperties.java', + renameTo: generator => `${generator.javaDir}config/ApplicationProperties.java`, + }, + { + file: 'package/config/JacksonConfiguration.java', + renameTo: generator => `${generator.javaDir}config/JacksonConfiguration.java`, + }, + { + file: 'package/config/LoggingAspectConfiguration.java', + renameTo: generator => `${generator.javaDir}config/LoggingAspectConfiguration.java`, + }, + { file: 'package/config/WebConfigurer.java', renameTo: generator => `${generator.javaDir}config/WebConfigurer.java` }, + ], + }, + { + condition: generator => !generator.skipClient && !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/StaticResourcesWebConfiguration.java', + renameTo: generator => `${generator.javaDir}config/StaticResourcesWebConfiguration.java`, + }, + ], + }, + { + condition: generator => + !generator.skipUserManagement || + generator.databaseTypeSql || + generator.databaseTypeMongodb || + generator.databaseTypeCouchbase || + generator.databaseTypeNeo4j, + path: SERVER_MAIN_SRC_DIR, + templates: [{ file: 'package/config/Constants.java', renameTo: generator => `${generator.javaDir}config/Constants.java` }], + }, + { + condition: generator => !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/LocaleConfiguration.java', + renameTo: generator => `${generator.javaDir}config/LocaleConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/ReactorConfiguration.java', + renameTo: generator => `${generator.javaDir}config/ReactorConfiguration.java`, + }, + { + file: 'package/config/LocaleConfiguration_reactive.java', + renameTo: generator => `${generator.javaDir}config/LocaleConfiguration.java`, + }, + ], + }, + { + condition: generator => + generator.cacheProviderEhCache || + generator.cacheProviderCaffeine || + generator.cacheProviderHazelcast || + generator.cacheProviderInfinispan || + generator.cacheProviderMemcached || + generator.cacheProviderRedis || + generator.applicationTypeGateway, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/CacheConfiguration.java', + renameTo: generator => `${generator.javaDir}config/CacheConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.cacheProviderInfinispan, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/CacheFactoryConfiguration.java', + renameTo: generator => `${generator.javaDir}config/CacheFactoryConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.cacheProviderRedis, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/EmbeddedRedis.java', + renameTo: generator => `${generator.testDir}config/EmbeddedRedis.java`, + }, + { + file: 'package/config/RedisTestContainer.java', + renameTo: generator => `${generator.testDir}config/RedisTestContainer.java`, + }, + ], + }, + { + condition: generator => !generator.databaseTypeNo, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: generator => `package/config/DatabaseConfiguration_${generator.databaseType}.java`, + renameTo: generator => `${generator.javaDir}config/DatabaseConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.communicationSpringWebsocket, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/WebsocketConfiguration.java', + renameTo: generator => `${generator.javaDir}config/WebsocketConfiguration.java`, + }, + { + file: 'package/config/WebsocketSecurityConfiguration.java', + renameTo: generator => `${generator.javaDir}config/WebsocketSecurityConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.searchEngineElasticsearch, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/ElasticsearchConfiguration.java', + renameTo: generator => `${generator.javaDir}config/ElasticsearchConfiguration.java`, + }, + ], + }, + ], + serverJavaDomain: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/domain/package-info.java', renameTo: generator => `${generator.javaDir}domain/package-info.java` }, + ], + }, + { + condition: generator => + generator.databaseTypeSql || + generator.databaseTypeMongodb || + generator.databaseTypeNeo4j || + generator.databaseTypeCouchbase, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/domain/AbstractAuditingEntity.java', + renameTo: generator => `${generator.javaDir}domain/AbstractAuditingEntity.java`, + }, + ], + }, + ], + serverJavaPackageInfo: [ + { + condition: generator => generator.searchEngineElasticsearch, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/repository/search/package-info.java', + renameTo: generator => `${generator.javaDir}repository/search/package-info.java`, + }, + ], + }, + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/repository/package-info.java', renameTo: generator => `${generator.javaDir}repository/package-info.java` }, + ], + }, + ], + serverJavaServiceError: [ + { + condition: generator => !generator.skipUserManagement, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/service/EmailAlreadyUsedException.java', + renameTo: generator => `${generator.javaDir}service/EmailAlreadyUsedException.java`, + }, + { + file: 'package/service/InvalidPasswordException.java', + renameTo: generator => `${generator.javaDir}service/InvalidPasswordException.java`, + }, + { + file: 'package/service/UsernameAlreadyUsedException.java', + renameTo: generator => `${generator.javaDir}service/UsernameAlreadyUsedException.java`, + }, + ], + }, + ], + serverJavaService: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/service/package-info.java', renameTo: generator => `${generator.javaDir}service/package-info.java` }, + ], + }, + { + condition: generator => generator.messageBrokerKafka, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/KafkaSseConsumer.java', + renameTo: generator => `${generator.javaDir}config/KafkaSseConsumer.java`, + }, + { + file: 'package/config/KafkaSseProducer.java', + renameTo: generator => `${generator.javaDir}config/KafkaSseProducer.java`, + }, + ], + }, + ], + serverJavaWebError: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/errors/package-info.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/package-info.java`, + }, + { + file: 'package/web/rest/errors/BadRequestAlertException.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/BadRequestAlertException.java`, + }, + { + file: 'package/web/rest/errors/ErrorConstants.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/ErrorConstants.java`, + }, + { + file: 'package/web/rest/errors/ExceptionTranslator.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/ExceptionTranslator.java`, + }, + { + file: 'package/web/rest/errors/FieldErrorVM.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/FieldErrorVM.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/errors/EmailAlreadyUsedException.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/EmailAlreadyUsedException.java`, + }, + { + file: 'package/web/rest/errors/InvalidPasswordException.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/InvalidPasswordException.java`, + }, + { + file: 'package/web/rest/errors/LoginAlreadyUsedException.java', + renameTo: generator => `${generator.javaDir}web/rest/errors/LoginAlreadyUsedException.java`, + }, + ], + }, + ], + serverJavaWeb: [ + { + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/vm/package-info.java', + renameTo: generator => `${generator.javaDir}web/rest/vm/package-info.java`, + }, + { + file: 'package/web/rest/package-info.java', + renameTo: generator => `${generator.javaDir}web/rest/package-info.java`, + }, + ], + }, + { + condition: generator => !generator.skipClient && !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/ClientForwardController.java', + renameTo: generator => `${generator.javaDir}web/rest/ClientForwardController.java`, + }, + ], + }, + { + condition: generator => !generator.skipClient && generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/filter/SpaWebFilter.java', + renameTo: generator => `${generator.javaDir}web/filter/SpaWebFilter.java`, + }, + ], + }, + { + condition: generator => generator.messageBrokerKafka && generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/KafkaResource_reactive.java', + renameTo: generator => + `${generator.javaDir}web/rest/${generator.upperFirstCamelCase(generator.baseName)}KafkaResource.java`, + }, + ], + }, + { + condition: generator => generator.messageBrokerKafka && !generator.reactive, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/rest/KafkaResource.java', + renameTo: generator => + `${generator.javaDir}web/rest/${generator.upperFirstCamelCase(generator.baseName)}KafkaResource.java`, + }, + ], + }, + ], + serverJavaWebsocket: [ + { + condition: generator => generator.communicationSpringWebsocket, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/web/websocket/package-info.java', + renameTo: generator => `${generator.javaDir}web/websocket/package-info.java`, + }, + { + file: 'package/web/websocket/ActivityService.java', + renameTo: generator => `${generator.javaDir}web/websocket/ActivityService.java`, + }, + { + file: 'package/web/websocket/dto/package-info.java', + renameTo: generator => `${generator.javaDir}web/websocket/dto/package-info.java`, + }, + { + file: 'package/web/websocket/dto/ActivityDTO.java', + renameTo: generator => `${generator.javaDir}web/websocket/dto/ActivityDTO.java`, + }, + ], + }, + ], + serverTestReactive: [ + { + condition: generator => generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/JHipsterBlockHoundIntegration.java', + renameTo: generator => `${generator.testDir}config/JHipsterBlockHoundIntegration.java`, + }, + ], + }, + { + condition: generator => generator.reactive, + path: SERVER_TEST_RES_DIR, + templates: ['META-INF/services/reactor.blockhound.integration.BlockHoundIntegration'], + }, + ], + springBootOauth2: [ + { + condition: generator => generator.authenticationTypeOauth2 && generator.applicationTypeMonolith, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/config/OAuth2Configuration.java', + renameTo: generator => `${generator.javaDir}config/OAuth2Configuration.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: generator => `package/web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, + renameTo: generator => + `${generator.javaDir}web/filter/OAuth2${generator.reactive ? 'Reactive' : ''}RefreshTokensWebFilter.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/test/util/OAuth2TestUtil.java', + renameTo: generator => `${generator.testDir}test/util/OAuth2TestUtil.java`, + }, + ], + }, + ], + serverTestFw: [ + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { file: 'package/web/rest/TestUtil.java', renameTo: generator => `${generator.testDir}web/rest/TestUtil.java` }, + { + file: 'package/web/rest/errors/ExceptionTranslatorTestController.java', + renameTo: generator => `${generator.testDir}web/rest/errors/ExceptionTranslatorTestController.java`, + }, + ], + }, + { + condition: generator => !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/errors/ExceptionTranslatorIT.java', + renameTo: generator => `${generator.testDir}web/rest/errors/ExceptionTranslatorIT.java`, + }, + ], + }, + { + condition: generator => generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/errors/ExceptionTranslatorIT_reactive.java', + renameTo: generator => `${generator.testDir}web/rest/errors/ExceptionTranslatorIT.java`, + }, + ], + }, + { + condition: generator => !generator.skipClient && !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/ClientForwardControllerTest.java', + renameTo: generator => `${generator.testDir}web/rest/ClientForwardControllerTest.java`, + }, + ], + }, + { + path: SERVER_TEST_RES_DIR, + templates: ['config/application.yml', 'logback.xml', 'junit-platform.properties'], + }, + { + // TODO : add these tests to reactive + condition: generator => !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/WebConfigurerTest.java', + renameTo: generator => `${generator.testDir}config/WebConfigurerTest.java`, + }, + { + file: 'package/config/WebConfigurerTestController.java', + renameTo: generator => `${generator.testDir}config/WebConfigurerTestController.java`, + }, + ], + }, + { + // TODO : add these tests to reactive + condition: generator => !generator.skipClient && !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/StaticResourcesWebConfigurerTest.java', + renameTo: generator => `${generator.testDir}config/StaticResourcesWebConfigurerTest.java`, + }, + ], + }, + { + condition: generator => generator.serviceDiscoveryType, + path: SERVER_TEST_RES_DIR, + templates: ['config/bootstrap.yml'], + }, + { + condition: generator => + generator.authenticationTypeOauth2 && (generator.applicationTypeMonolith || generator.applicationTypeGateway), + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/LogoutResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/LogoutResourceIT.java`, + }, + ], + }, + { + condition: generator => generator.gatlingTests, + path: TEST_DIR, + templates: [ + // Create Gatling test files + 'gatling/conf/gatling.conf', + 'gatling/conf/logback.xml', + ], + }, + { + condition: generator => generator.cucumberTests, + path: SERVER_TEST_SRC_DIR, + templates: [ + // Create Cucumber test files + { file: 'package/cucumber/CucumberIT.java', renameTo: generator => `${generator.testDir}cucumber/CucumberIT.java` }, + { + file: 'package/cucumber/stepdefs/StepDefs.java', + renameTo: generator => `${generator.testDir}cucumber/stepdefs/StepDefs.java`, + }, + { + file: 'package/cucumber/CucumberTestContextConfiguration.java', + renameTo: generator => `${generator.testDir}cucumber/CucumberTestContextConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.cucumberTests, + path: SERVER_TEST_RES_DIR, + templates: [{ file: 'package/features/gitkeep', renameTo: generator => `${generator.testDir}cucumber/gitkeep`, noEjs: true }], + }, + { + condition: generator => !shouldSkipUserManagement(generator) && !generator.authenticationTypeOauth2, + path: SERVER_TEST_SRC_DIR, + templates: [ + // Create auth config test files + { + file: 'package/security/DomainUserDetailsServiceIT.java', + renameTo: generator => `${generator.testDir}security/DomainUserDetailsServiceIT.java`, + }, + ], + }, + { + condition: generator => generator.messageBrokerKafka, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/KafkaTestContainer.java', + renameTo: generator => `${generator.testDir}config/KafkaTestContainer.java`, + }, + { + file: 'package/config/EmbeddedKafka.java', + renameTo: generator => `${generator.testDir}config/EmbeddedKafka.java`, + }, + ], + }, + { + condition: generator => generator.messageBrokerKafka && !generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/KafkaResourceIT.java', + renameTo: generator => + `${generator.testDir}web/rest/${generator.upperFirstCamelCase(generator.baseName)}KafkaResourceIT.java`, + }, + ], + }, + { + condition: generator => generator.messageBrokerKafka && generator.reactive, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/KafkaResourceIT_reactive.java', + renameTo: generator => + `${generator.testDir}web/rest/${generator.upperFirstCamelCase(generator.baseName)}KafkaResourceIT.java`, + }, + ], + }, + ], + serverJavaUserManagement: [ + { + condition: generator => generator.isUsingBuiltInUser(), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/domain/User.java', + renameTo: generator => `${generator.javaDir}domain/${generator.asEntity('User')}.java`, + }, + ], + }, + { + condition: generator => generator.isUsingBuiltInAuthority(), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/domain/Authority.java', renameTo: generator => `${generator.javaDir}domain/Authority.java` }, + { + file: 'coroutine/repository/AuthorityRepository.java', + renameTo: generator => `${generator.javaDir}repository/AuthorityRepository.java`, + }, + ], + }, + { + condition: generator => + (generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice) || + (!generator.skipUserManagement && generator.databaseTypeSql), + path: SERVER_MAIN_RES_DIR, + templates: ['config/liquibase/data/user.csv'], + }, + { + condition: generator => + (generator.authenticationTypeOauth2 && !generator.applicationTypeMicroservice && generator.databaseTypeSql) || + (!generator.skipUserManagement && generator.databaseTypeSql), + path: SERVER_MAIN_RES_DIR, + templates: ['config/liquibase/data/authority.csv', 'config/liquibase/data/user_authority.csv'], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { file: 'package/config/Constants.java', renameTo: generator => `${generator.javaDir}config/Constants.java` }, + { file: 'coroutine/service/UserService.java', renameTo: generator => `${generator.javaDir}service/UserService.java` }, + { + file: 'package/service/dto/package-info.java', + renameTo: generator => `${generator.javaDir}service/dto/package-info.java`, + }, + { + file: 'package/service/dto/AdminUserDTO.java', + renameTo: generator => `${generator.javaDir}service/dto/${generator.asDto('AdminUser')}.java`, + }, + { + file: 'package/service/dto/UserDTO.java', + renameTo: generator => `${generator.javaDir}service/dto/${generator.asDto('User')}.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2 && !generator.databaseTypeNo, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/service/mapper/package-info.java', + renameTo: generator => `${generator.javaDir}service/mapper/package-info.java`, + }, + { + file: 'package/service/mapper/UserMapper.java', + renameTo: generator => `${generator.javaDir}service/mapper/UserMapper.java`, + }, + { + file: 'coroutine/repository/UserRepository.java', + renameTo: generator => `${generator.javaDir}repository/UserRepository.java`, + }, + { + file: 'coroutine/web/rest/PublicUserResource.java', + renameTo: generator => `${generator.javaDir}web/rest/PublicUserResource.java`, + }, + { + file: 'package/web/rest/vm/ManagedUserVM.java', + renameTo: generator => `${generator.javaDir}web/rest/vm/ManagedUserVM.java`, + }, + ], + }, + { + condition: generator => generator.skipUserManagement && (generator.applicationTypeGateway || generator.applicationTypeMonolith), + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'coroutine/web/rest/AccountResource.java', + renameTo: generator => `${generator.javaDir}web/rest/AccountResource.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/service/UserServiceIT.java', + renameTo: generator => `${generator.testDir}service/UserServiceIT.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2 && !generator.databaseTypeNo, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/service/mapper/UserMapperTest.java', + renameTo: generator => `${generator.testDir}service/mapper/UserMapperTest.java`, + }, + { + file: 'package/web/rest/PublicUserResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/PublicUserResourceIT.java`, + }, + { + file: 'package/web/rest/UserResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/UserResourceIT.java`, + }, + ], + }, + { + condition: generator => + generator.skipUserManagement && + !generator.authenticationTypeOauth2 && + (generator.applicationTypeGateway || generator.applicationTypeMonolith), + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/AccountResourceIT_skipUserManagement.java', + renameTo: generator => `${generator.testDir}web/rest/AccountResourceIT.java`, + }, + ], + }, + { + condition: generator => + generator.skipUserManagement && + generator.authenticationTypeOauth2 && + (generator.applicationTypeGateway || generator.applicationTypeMonolith), + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/AccountResourceIT_oauth2.java', + renameTo: generator => `${generator.testDir}web/rest/AccountResourceIT.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeOauth2 && generator.searchEngineElasticsearch, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/repository/search/UserSearchRepository.java', + renameTo: generator => `${generator.javaDir}repository/search/UserSearchRepository.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_MAIN_RES_DIR, + templates: [ + 'templates/mail/activationEmail.html', + 'templates/mail/creationEmail.html', + 'templates/mail/passwordResetEmail.html', + ], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_TEST_RES_DIR, + templates: [ + 'templates/mail/activationEmail.html', + 'templates/mail/creationEmail.html', + 'templates/mail/passwordResetEmail.html', + ], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'coroutine/repository/UserRepository.java', + renameTo: generator => `${generator.javaDir}repository/UserRepository.java`, + }, + + /* User management java service files */ + { file: 'coroutine/service/UserService.java', renameTo: generator => `${generator.javaDir}service/UserService.java` }, + { file: 'package/service/MailService.java', renameTo: generator => `${generator.javaDir}service/MailService.java` }, + + /* User management java web files */ + { + file: 'package/service/dto/package-info.java', + renameTo: generator => `${generator.javaDir}service/dto/package-info.java`, + }, + { + file: 'package/service/dto/AdminUserDTO.java', + renameTo: generator => `${generator.javaDir}service/dto/${generator.asDto('AdminUser')}.java`, + }, + { + file: 'package/service/dto/UserDTO.java', + renameTo: generator => `${generator.javaDir}service/dto/${generator.asDto('User')}.java`, + }, + { + file: 'package/service/dto/PasswordChangeDTO.java', + renameTo: generator => `${generator.javaDir}service/dto/PasswordChangeDTO.java`, + }, + { + file: 'package/web/rest/vm/ManagedUserVM.java', + renameTo: generator => `${generator.javaDir}web/rest/vm/ManagedUserVM.java`, + }, + { + file: 'coroutine/web/rest/AccountResource.java', + renameTo: generator => `${generator.javaDir}web/rest/AccountResource.java`, + }, + { file: 'coroutine/web/rest/UserResource.java', renameTo: generator => `${generator.javaDir}web/rest/UserResource.java` }, + { + file: 'coroutine/web/rest/PublicUserResource.java', + renameTo: generator => `${generator.javaDir}web/rest/PublicUserResource.java`, + }, + { + file: 'package/web/rest/vm/KeyAndPasswordVM.java', + renameTo: generator => `${generator.javaDir}web/rest/vm/KeyAndPasswordVM.java`, + }, + { + file: 'package/service/mapper/package-info.java', + renameTo: generator => `${generator.javaDir}service/mapper/package-info.java`, + }, + { + file: 'package/service/mapper/UserMapper.java', + renameTo: generator => `${generator.javaDir}service/mapper/UserMapper.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement && generator.searchEngineElasticsearch, + path: SERVER_MAIN_SRC_DIR, + templates: [ + { + file: 'package/repository/search/UserSearchRepository.java', + renameTo: generator => `${generator.javaDir}repository/search/UserSearchRepository.java`, + }, + ], + }, + { + condition: generator => + generator.databaseTypeSql || + generator.messageBrokerKafka || + generator.cacheProviderRedis || + generator.databaseTypeMongodb || + generator.databaseTypeCassandra || + generator.searchEngineElasticsearch || + generator.databaseTypeCouchbase || + generator.searchEngineCouchbase || + generator.databaseTypeNeo4j, + path: SERVER_TEST_RES_DIR, + templates: ['testcontainers.properties', 'META-INF/spring.factories'], + }, + { + condition: generator => + generator.databaseTypeSql || + generator.messageBrokerKafka || + generator.cacheProviderRedis || + generator.databaseTypeMongodb || + generator.databaseTypeCassandra || + generator.searchEngineElasticsearch || + generator.databaseTypeCouchbase || + generator.searchEngineCouchbase || + generator.databaseTypeNeo4j, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/TestContainersSpringContextCustomizerFactory.java', + renameTo: generator => `${generator.testDir}config/TestContainersSpringContextCustomizerFactory.java`, + }, + ], + }, + { + condition: generator => generator.searchEngineElasticsearch, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/EmbeddedElasticsearch.java', + renameTo: generator => `${generator.testDir}config/EmbeddedElasticsearch.java`, + }, + { + file: 'package/config/ElasticsearchTestContainer.java', + renameTo: generator => `${generator.testDir}config/ElasticsearchTestContainer.java`, + }, + ], + }, + { + condition: ({ searchEngineElasticsearch }) => searchEngineElasticsearch, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/config/ElasticsearchTestConfiguration.java', + renameTo: generator => `${generator.testDir}config/ElasticsearchTestConfiguration.java`, + }, + ], + }, + { + condition: generator => generator.authenticationTypeJwt, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/management/SecurityMetersServiceTests.java', + renameTo: generator => `${generator.testDir}management/SecurityMetersServiceTests.java`, + }, + { + file: 'package/security/jwt/TokenProviderTest.java', + renameTo: generator => `${generator.testDir}security/jwt/TokenProviderTest.java`, + }, + { + file: 'package/security/jwt/TokenProviderSecurityMetersTests.java', + renameTo: generator => `${generator.testDir}security/jwt/TokenProviderSecurityMetersTests.java`, + }, + { + file: 'package/security/jwt/JWTFilterTest.java', + renameTo: generator => `${generator.testDir}security/jwt/JWTFilterTest.java`, + }, + ], + }, + { + condition: generator => !generator.applicationTypeMicroservice && generator.authenticationTypeJwt, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/UserJWTControllerIT.java', + renameTo: generator => `${generator.testDir}web/rest/UserJWTControllerIT.java`, + }, + ], + }, + { + condition: generator => + !generator.skipUserManagement && + generator.cucumberTests && + !generator.databaseTypeMongodb && + !generator.databaseTypeCassandra, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/cucumber/stepdefs/UserStepDefs.java', + renameTo: generator => `${generator.testDir}cucumber/stepdefs/UserStepDefs.java`, + }, + ], + }, + { + condition: generator => + !generator.skipUserManagement && + generator.cucumberTests && + !generator.databaseTypeMongodb && + !generator.databaseTypeCassandra, + path: SERVER_TEST_RES_DIR, + templates: [ + { + file: 'package/features/user/user.feature', + renameTo: generator => `${generator.testDir}cucumber/user.feature`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_TEST_RES_DIR, + templates: [ + /* User management java test files */ + 'templates/mail/testEmail.html', + ], + }, + { + condition: generator => !generator.skipUserManagement && !generator.enableTranslation, + path: SERVER_TEST_RES_DIR, + templates: ['i18n/messages_en.properties'], + }, + { + condition: generator => !generator.skipUserManagement, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/service/MailServiceIT.java', + renameTo: generator => `${generator.testDir}service/MailServiceIT.java`, + }, + { + file: 'package/service/UserServiceIT.java', + renameTo: generator => `${generator.testDir}service/UserServiceIT.java`, + }, + { + file: 'package/service/mapper/UserMapperTest.java', + renameTo: generator => `${generator.testDir}service/mapper/UserMapperTest.java`, + }, + { + file: 'package/web/rest/PublicUserResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/PublicUserResourceIT.java`, + }, + { + file: 'package/web/rest/UserResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/UserResourceIT.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement && !generator.authenticationTypeOauth2, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/AccountResourceIT.java', + renameTo: generator => `${generator.testDir}web/rest/AccountResourceIT.java`, + }, + ], + }, + { + condition: generator => !generator.skipUserManagement && generator.authenticationTypeOauth2, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/AccountResourceIT_oauth2.java', + renameTo: generator => `${generator.testDir}web/rest/AccountResourceIT.java`, + }, + ], + }, + { + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: 'package/web/rest/WithUnauthenticatedMockUser.java', + renameTo: generator => `${generator.testDir}web/rest/WithUnauthenticatedMockUser.java`, + }, + ], + }, + ], +}; + +const serverFiles = mergeSections( + baseServerFiles, + addSectionsCondition(liquibaseFiles, context => context.databaseTypeSql), + addSectionsCondition(mongoDbFiles, context => context.databaseTypeMongodb), + addSectionsCondition(neo4jFiles, context => context.databaseTypeNeo4j), + addSectionsCondition(cassandraFiles, context => context.databaseTypeCassandra) +); + +module.exports = { + serverFiles, +}; diff --git a/generators/server/files.js b/generators/server/files.js index b8f8fb2df..908bf784a 100644 --- a/generators/server/files.js +++ b/generators/server/files.js @@ -21,9 +21,10 @@ const path = require('path'); const serverCleanup = require('generator-jhipster/generators/cleanup'); const baseServerFiles = require('generator-jhipster/generators/server/files').serverFiles; const { MAIN_DIR, TEST_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_RES_DIR } = require('generator-jhipster/generators/generator-constants'); -const cheerio = require('cheerio'); +const cheerio = require('cheerio'); const { GRADLE, MAVEN } = require('generator-jhipster/jdl/jhipster/build-tool-types'); +const baseCoroutineServerFiles = require('./files-coroutine-list').serverFiles; const kotlinConstants = require('../generator-kotlin-constants'); const { writeCouchbaseFiles } = require('./files-couchbase'); const { writeSqlFiles } = require('./files-sql'); @@ -33,6 +34,7 @@ const { makeKotlinServerFiles } = require('../util'); const SERVER_MAIN_KOTLIN_SRC_DIR = `${MAIN_DIR}kotlin/`; const SERVER_TEST_SRC_KOTLIN_DIR = `${TEST_DIR}kotlin/`; const files = makeKotlinServerFiles(baseServerFiles); +const coroutineFiles = makeKotlinServerFiles(baseCoroutineServerFiles); const serverFiles = { ...files, @@ -48,6 +50,20 @@ const serverFiles = { ], }; +const coroutineServerFiles = { + ...coroutineFiles, + serverBuild: [ + ...coroutineFiles.serverBuild, + { + condition: generator => generator.buildTool === GRADLE, + templates: [{ file: 'gradle/kotlin.gradle' }], + }, + { + templates: [{ file: `${kotlinConstants.DETEKT_CONFIG_FILE}` }], + }, + ], +}; + /* eslint-disable no-template-curly-in-string */ function writeFiles() { return { @@ -69,7 +85,8 @@ function writeFiles() { }, writeFiles() { - return this.writeFilesToDisk(serverFiles); + // TODO: to be compatible with Reactor + return this.writeFilesToDisk(coroutineServerFiles); }, ...writeCouchbaseFiles(), @@ -363,8 +380,8 @@ async function updateGradle(generator) { _this.fs.write(fullPath, content.replace('classes/java/main', 'classes/kotlin/main')); } - module.exports = { writeFiles, serverFiles, + coroutineServerFiles, }; diff --git a/generators/server/prompts.js b/generators/server/prompts.js index dc492fc11..8958d140a 100644 --- a/generators/server/prompts.js +++ b/generators/server/prompts.js @@ -69,7 +69,7 @@ function askForKotlinServerSideOpts() { when: answers => answers.reactive, type: 'confirm', name: 'coroutine', - message: 'Using coroutines for reactive application?', + message: 'Do you want to using coroutines for reactive application?', default: false, }, { diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs new file mode 100644 index 000000000..18a06724c --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs @@ -0,0 +1,50 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.repository + +import <%= packageName %>.domain.Authority +import org.springframework.stereotype.Repository +import org.springframework.data.repository.kotlin.CoroutineSortingRepository +<% if (databaseTypeSql) { %> +/** + * Spring Data R2DBC repository for the [Authority] entity. + */ +<% } %><% if (databaseTypeMongodb) { %> +/** + * Spring Data MongoDB repository for the [Authority] entity. + */ +<% } %><%_ if (databaseTypeNeo4j) { _%> +/** +* Spring Data Neo4j repository for the {@link Authority} entity. +*/ +<%_ } _%><% if (databaseTypeCouchbase) { %> +/** + * Spring Data Couchbase repository for the [Authority] entity. + */ +<% } %> +@Repository +<%_ + let listOrFlux = reactive ? 'Flux' : 'List'; +_%> +interface AuthorityRepository: CoroutineSortingRepository { + <%_ if (databaseTypeNeo4j) { _%> + <% if (!reactive) { %>// See https://github.com/neo4j/sdn-rx/issues/51<%_ } _%> + override fun findAll(): <% if (reactive) { %>Flux<% } else { %>List<% } %> + <%_ } _%> +} diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs new file mode 100644 index 000000000..d8899d1f5 --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs @@ -0,0 +1,586 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.repository + +<%_ if (databaseTypeSql) { _%> +import <%= packageName %>.domain.Authority +<%_ } _%> +import <%= packageName %>.domain.<%= asEntity('User') %> + +<%_ if (databaseTypeCassandra) { _%> +import com.datastax.oss.driver.api.core.CqlIdentifier +import com.datastax.oss.driver.api.core.cql.BatchStatement +import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder +import com.datastax.oss.driver.api.core.cql.BoundStatement +import com.datastax.oss.driver.api.core.cql.DefaultBatchType +import com.datastax.oss.driver.api.core.cql.PreparedStatement +import com.datastax.oss.driver.api.core.cql.SimpleStatement +import com.datastax.oss.driver.api.querybuilder.QueryBuilder +import com.datastax.oss.driver.api.querybuilder.insert.RegularInsert +<%_ } _%> +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactor.* +<%_ if (databaseTypeSql) { _%> +import org.apache.commons.beanutils.BeanComparator +<%_ } _%> +<%_ if (cacheManagerIsAvailable) { _%> +import org.springframework.cache.annotation.Cacheable +<%_ } _%> +<%_ if (databaseTypeSql || databaseTypeCouchbase || databaseTypeMongodb || databaseTypeNeo4j) { _%> +import org.springframework.data.domain.* +<%_ } _%> +<%_ if (databaseTypeSql) { _%> +import org.springframework.data.domain.Sort +import org.springframework.r2dbc.core.DatabaseClient +import org.springframework.data.r2dbc.convert.R2dbcConverter +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.relational.core.query.Criteria.where +import org.springframework.data.relational.core.query.Query.query +<%_ } _%> +import org.springframework.data.repository.kotlin.CoroutineSortingRepository +<%_ if (databaseTypeCouchbase) { _%> +import org.springframework.data.couchbase.repository.Query +<%_ } _%> +<%_ if (databaseTypeCassandra) { _%> +import org.springframework.data.cassandra.ReactiveResultSet +import org.springframework.data.cassandra.ReactiveSession +import org.springframework.data.cassandra.core.ReactiveCassandraTemplate +import org.springframework.data.cassandra.core.convert.CassandraConverter +import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity +<%_ } _%> +import org.springframework.stereotype.Repository +<%_ if (databaseTypeCassandra) { _%> +import org.springframework.util.StringUtils +<%_ } _%> +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +<%_ if (databaseTypeSql) { _%> +import reactor.util.function.Tuple2 +import reactor.util.function.Tuples +<%_ } _%> + +<%_ if (databaseTypeCassandra) { _%> +import javax.validation.ConstraintViolation +import javax.validation.ConstraintViolationException +import javax.validation.Validator +<%_ } _%> +<%_ if (databaseTypeSql) { _%> + <%_ if (!authenticationTypeOauth2) { _%> +import java.time.LocalDateTime + <%_ } _%> +import java.util.Optional +import java.util.stream.Collectors +<%_ } _%> +<%_ if (user.primaryKey.hasUUID) { _%> +import java.util.UUID +<%_ } _%> +<%_ if (!databaseTypeCassandra && !databaseTypeSql && !authenticationTypeOauth2) { _%> +import java.time.Instant +<%_ } _%> + +/** + * Spring Data <% if (databaseTypeSql) { %>R2DBC<% } else if (databaseTypeMongodb) { %>MongoDB<% } else if (databaseTypeCouchbase) { %>Couchbase<% } else if (databaseTypeCassandra) { %>Cassandra<% } else if (databaseTypeNeo4j) { %>Neo4j<% } %> repository for the {@link <%= asEntity('User') %>} entity. + */ +<%_ + let optionalOrMono = reactive ? 'Mono' : 'Optional'; + let listOrFlux = reactive ? 'Flux' : 'List'; + let pageOrFlux = (reactive) ? 'Flux' : 'Page'; + let toListSuffix = reactive ? '' : '.toMutableList()'; +_%> +<%_ if (databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +@Repository +interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, String> { + + <%_ if (!authenticationTypeOauth2) { _%> + <%_ if (databaseTypeCouchbase) { _%> + @JvmDefault + <%_ } _%> + suspend fun findOneByActivationKey(activationKey: String): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { + return findIdByActivationKey(activationKey) + .map(User::id) + .flatMap(this::findById); + } + @Query(FIND_IDS_QUERY + " AND activationKey = $1") + suspend fun findIdByActivationKey(activationKey: String): <%= asEntity('User') %>? + <% } %> + <%_ } _%> + <%_ if (!authenticationTypeOauth2) { _%> + + <%_ if (databaseTypeCouchbase) { _%> + @JvmDefault + <%_ } _%> + fun findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime: Instant): Flow<<%= asEntity('User') %>><% if (databaseTypeCouchbase) { %> { + return findAllById(toIds(findAllIdsByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime)<%= toListSuffix %>)) + } + + @Query(FIND_IDS_QUERY + " AND activated = false AND activationKey IS NOT NULL AND createdDate < $1") + fun findAllIdsByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime: Instant): Flow<%= asEntity('User') %> + <% } %> + <%_ } _%> + + <%_ if (!authenticationTypeOauth2) { _%> + <%_ if (databaseTypeCouchbase) { _%> + @JvmDefault + <%_ } _%> + suspend fun findOneByResetKey(resetKey: String): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { + return findIdByResetKey(resetKey) + .map(User::id) + .flatMap(this::findById) + } + + @Query(FIND_IDS_QUERY + " AND resetKey = $1") + suspend fun findIdByResetKey(resetKey: String): <%= asEntity('User') %>? + <% } %> + <%_ } _%> + + <%_ if (!authenticationTypeOauth2) { _%> + <%_ if (databaseTypeCouchbase || databaseTypeMongodb || databaseTypeNeo4j) { _%> + <%_ if (cacheManagerIsAvailable) { _%> + @Cacheable(cacheNames = [USERS_BY_EMAIL_CACHE]) + <%_ } _%> + <%_ } _%> + <%_ if (databaseTypeCouchbase) { _%> + @JvmDefault + <%_ } _%> + suspend fun findOneByEmailIgnoreCase(email: String?): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { + return findIdByEmailIgnoreCase(email!!) + .map(User::id) + .flatMap(this::findById); + } + + @Query(FIND_IDS_QUERY + " AND LOWER(email) = LOWER($1)") + suspend fun findIdByEmailIgnoreCase(email: String): <%= asEntity('User') %>? + <% } %> + <%_ } _%> + + <%_ if (databaseTypeCouchbase) { _%> + <%_ if (cacheManagerIsAvailable) { _%> + @Cacheable(cacheNames = [USERS_BY_LOGIN_CACHE]) + <%_ } _%> + @JvmDefault + suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? { + return findById(login) + } + <%_ } else if (databaseTypeMongodb || databaseTypeNeo4j) { _%> + <%_ if (cacheManagerIsAvailable) { _%> + @Cacheable(cacheNames = [USERS_BY_LOGIN_CACHE]) + <%_ } _%> + suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? + <%_ } else { _%> + suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? + <%_ } _%> + + <%_ if (databaseTypeNeo4j) { _%> + override fun findAll(): Flow<%= asEntity('User') %> + <%_ } _%> + + <%_ if (!databaseTypeCouchbase) { _%> + fun findAllByIdNotNull(pageable: Pageable): Flow<<%= asEntity('User') %>> + <%_ } _%> + + <%_ if (databaseTypeCouchbase) { _%> + @JvmDefault + fun findAllByActivatedIsTrue(pageable: Pageable): Flow<<%= asEntity('User') %>>{ + return findAllById(toIds(findAllIdsByActivatedIsTrue(pageable))) + } + + @Query(FIND_IDS_QUERY + " AND activated = true") + fun findAllIdsByActivatedIsTrue(pageable: Pageable): Flow<<%= asEntity('User') %>> + <%_ } else { _%> + fun findAllByIdNotNullAndActivatedIsTrue(pageable: Pageable): Flow<<%= asEntity('User') %>> + <%_ } _%> + + <%_ if (cacheManagerIsAvailable) { _%> + + companion object { + + const val USERS_BY_LOGIN_CACHE: String = "usersByLogin" + + const val USERS_BY_EMAIL_CACHE: String = "usersByEmail" + } + <%_ } _%> +} +<%_ } else if (databaseTypeSql) { _%> +@Repository +interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, <% if (authenticationTypeOauth2) { %>String<% } else { %>Long<% } %>>, UserRepositoryInternal { + + <%_ if (!authenticationTypeOauth2) { _%> + suspend fun findOneByActivationKey(activationKey: String): <%= asEntity('User') %>? + + fun findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime: LocalDateTime): Flow<<%= asEntity('User') %>> + + suspend fun findOneByResetKey(resetKey: String): <%= asEntity('User') %>? + + suspend fun findOneByEmailIgnoreCase(email: String): <%= asEntity('User') %>? + + <%_ } _%> + + suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? + + fun findAllByIdNotNull(pageable: Pageable): Flow<<%= asEntity('User') %>> + + fun findAllByIdNotNullAndActivatedIsTrue(pageable: Pageable): Flow<<%= asEntity('User') %>> + + @Query("INSERT INTO <%= jhiTablePrefix %>_user_authority VALUES(:userId, :authority)") + suspend fun saveUserAuthority(userId: <%= user.primaryKey.type %>, authority: String) + + @Query("DELETE FROM <%= jhiTablePrefix %>_user_authority") + suspend fun deleteAllUserAuthorities() + + @Query("DELETE FROM <%= jhiTablePrefix %>_user_authority WHERE user_id = :userId") + suspend fun deleteUserAuthorities(userId: <%= user.primaryKey.type %>) +} + + <%_ if (!authenticationTypeOauth2) { _%> +interface DeleteExtended { + suspend fun delete(user: T) +} + + <%_ } _%> +interface UserRepositoryInternal<% if (!authenticationTypeOauth2) { %> : DeleteExtended<<%= asEntity('User') %>><% } %> { + + suspend fun findOneWithAuthoritiesByLogin(login: String): <%= asEntity('User') %>? + + <%_ if (!authenticationTypeOauth2) { _%> + suspend fun findOneWithAuthoritiesByEmailIgnoreCase(email: String): <%= asEntity('User') %>? + <%_ } _%> + + <%_ if (authenticationTypeOauth2) { _%> + suspend fun create(user: <%= asEntity('User') %>): <%= asEntity('User') %>? + <%_ } _%> + + fun findAllWithAuthorities(pageable: Pageable): Flow<<%= asEntity('User') %>> +} + +class UserRepositoryInternalImpl(val db: DatabaseClient, val r2dbcEntityTemplate: R2dbcEntityTemplate, val r2dbcConverter: R2dbcConverter): UserRepositoryInternal { + + override suspend fun findOneWithAuthoritiesByLogin(login: String): <%= asEntity('User') %>? { + return findOneWithAuthoritiesBy("login", login) + } + + <%_ if (!authenticationTypeOauth2) { _%> + override suspend fun findOneWithAuthoritiesByEmailIgnoreCase(email: String): <%= asEntity('User') %>? { + return findOneWithAuthoritiesBy("email", email.lowercase()) + } + + <%_ } _%> + + override fun findAllWithAuthorities(pageable: Pageable): Flow<<%= asEntity('User') %>> { + val property = pageable.sort.map(Sort.Order::getProperty).first() ?: "id" + val direction = pageable.sort.map(Sort.Order::getDirection).first() ?: Sort.DEFAULT_DIRECTION + val comparator = if (direction == Sort.DEFAULT_DIRECTION) { BeanComparator(property) } else { BeanComparator(property).reversed() } + val page = pageable.pageNumber + val size = pageable.pageSize + + return db + .sql("SELECT * FROM <%= jhiTablePrefix %>_user u LEFT JOIN <%= jhiTablePrefix %>_user_authority ua ON u.id=ua.user_id") + .map { row, metadata -> + return@map Tuples.of( + r2dbcConverter.read(<%= asEntity('User') %>::class.java, row, metadata), + Optional.ofNullable(row.get("authority_name", String::class.java)) + ) + }.all() + .groupBy { it.t1.login } + .flatMap { it.collectList().map { t -> updateUserWithAuthorities(t[0].t1, t) } } + .sort(comparator) + .skip((page * size).toLong()) + .take(size.toLong()) + .asFlow() + } + + <%_ if (!authenticationTypeOauth2) { _%> + override suspend fun delete(user: <%= asEntity('User') %>) { + db.sql("DELETE FROM <%= jhiTablePrefix %>_user_authority WHERE user_id = :userId") + .bind("userId", user.id) + .then() + .then(r2dbcEntityTemplate.delete(<%= asEntity('User') %>::class.java) + .matching(query(where("id").`is`(user.id))).all() + .then() + ) + } + + <%_ } else { _%> + override suspend fun create(user: <%= asEntity('User') %>): <%= asEntity('User') %>? { + return r2dbcEntityTemplate.insert(<%= asEntity('User') %>::class.java).using(user).awaitSingleOrNull() + } + <%_ } _%> + + private fun findOneWithAuthoritiesBy(fieldName: String, fieldValue: Any): <%= asEntity('User') %>? { + return db.sql("SELECT * FROM <%= jhiTablePrefix %>_user u LEFT JOIN <%= jhiTablePrefix %>_user_authority ua ON u.id=ua.user_id WHERE u.$fieldName = :$fieldName") + .bind(fieldName, fieldValue) + .map { row, metadata -> + return@map Tuples.of( + r2dbcConverter.read(<%= asEntity('User') %>::class.java, row, metadata), + Optional.ofNullable(row.get("authority_name", String::class.java)) + ) + }.all() + .collectList() + .filter { it.isNotEmpty() } + .map { l -> updateUserWithAuthorities(l[0].t1, l) } + .block() + } + + private fun updateUserWithAuthorities(user: <%= asEntity('User') %>, tuples: List, Optional>>): <%= asEntity('User') %> { + user.authorities = tuples.filter { it.t2.isPresent } + .map { + val authority = Authority() + authority.name = it.t2.get() + authority + }.toMutableSet() + return user + } +} + +<%_ } else if (databaseTypeCassandra) { _%> +@Repository +class UserRepository( + private val cqlTemplate: ReactiveCassandraTemplate, + private val session: ReactiveSession, + private val validator: Validator +) { + + private val findAllStmt = session.prepare("SELECT * FROM user").block() + private val findOneByActivationKeyStmt = session.prepare( + "SELECT id " + + "FROM user_by_activation_key " + + "WHERE activation_key = :activation_key" + ).block() + + private val findOneByResetKeyStmt = session.prepare( + "SELECT id " + + "FROM user_by_reset_key " + + "WHERE reset_key = :reset_key" + ).block() + + private val insertByActivationKeyStmt = session.prepare( + "INSERT INTO user_by_activation_key (activation_key, id) " + + "VALUES (:activation_key, :id)" + ).block() + + private val insertByResetKeyStmt = session.prepare( + "INSERT INTO user_by_reset_key (reset_key, id) " + + "VALUES (:reset_key, :id)" + ).block() + + private val deleteByIdStmt = session.prepare( + "DELETE FROM user " + + "WHERE id = :id" + ).block() + + private val deleteByActivationKeyStmt = session.prepare( + "DELETE FROM user_by_activation_key " + + "WHERE activation_key = :activation_key" + ).block() + + private val deleteByResetKeyStmt = session.prepare( + "DELETE FROM user_by_reset_key " + + "WHERE reset_key = :reset_key" + ).block() + + private val findOneByLoginStmt = session.prepare( + "SELECT id " + + "FROM user_by_login " + + "WHERE login = :login" + ).block() + + private val insertByLoginStmt = session.prepare( + "INSERT INTO user_by_login (login, id) " + + "VALUES (:login, :id)" + ).block() + + private val deleteByLoginStmt = session.prepare( + "DELETE FROM user_by_login " + + "WHERE login = :login" + ).block() + + private val findOneByEmailStmt = session.prepare( + "SELECT id " + + "FROM user_by_email " + + "WHERE email = :email" + ).block() + + private val insertByEmailStmt = session.prepare( + "INSERT INTO user_by_email (email, id) " + + "VALUES (:email, :id)" + ).block() + + private val deleteByEmailStmt = session.prepare( + "DELETE FROM user_by_email " + + "WHERE email = :email" + ).block() + + private val truncateStmt = session.prepare("TRUNCATE user").block() + + private val truncateByResetKeyStmt = session.prepare("TRUNCATE user_by_reset_key").block() + + private val truncateByLoginStmt = session.prepare("TRUNCATE user_by_login").block() + + private val truncateByEmailStmt = session.prepare("TRUNCATE user_by_email").block() + + suspend fun findById(id: String): <%= asEntity('User') %>? { + return cqlTemplate.selectOneById(id, <%= asEntity('User') %>::class.java) + .awaitSingleOrNull() + } + + suspend fun findOneByActivationKey(activationKey: String): <%= asEntity('User') %>? { + val stmt = findOneByActivationKeyStmt!!.bind().setString("activation_key", activationKey) + return findOneFromIndex(stmt) + } + + suspend fun findOneByResetKey(resetKey: String): <%= asEntity('User') %>? { + val stmt = findOneByResetKeyStmt.bind().setString("reset_key", resetKey) + return findOneFromIndex(stmt) + } + + <%_ if (cacheManagerIsAvailable) { _%> + @Cacheable(cacheNames = [USERS_BY_EMAIL_CACHE]) + <%_ } _%> + suspend fun findOneByEmailIgnoreCase(email: String?): <%= asEntity('User') %>? { + val stmt = findOneByEmailStmt.bind().setString("email", email?.lowercase()) + return findOneFromIndex(stmt) + } + + <%_ if (cacheManagerIsAvailable) { _%> + @Cacheable(cacheNames = [USERS_BY_LOGIN_CACHE]) + <%_ } _%> + suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? { + val stmt = findOneByLoginStmt.bind().setString("login", login) + return findOneFromIndex(stmt) + } + + fun findAll(): Flow<<%= asEntity('User') %>> { + return cqlTemplate.select(findAllStmt!!.bind(), <%= asEntity('User') %>::class.java).asFlow() + } + + suspend fun save(user: <%= asEntity('User') %>): <%= asEntity('User') %>? { + val violations = validator.validate(user) + if (violations != null && violations.isNotEmpty()) { + throw ConstraintViolationException(violations) + } + this.findById(user.id!!) + ?.let { oldUser -> + var flag = false + if (!StringUtils.isEmpty(oldUser.activationKey) && oldUser.activationKey != user.activationKey) { + flag = true + session.execute( + deleteByActivationKeyStmt!!.bind().setString("activation_key", oldUser.activationKey) + ).awaitSingle() + } + if (!StringUtils.isEmpty(oldUser.resetKey) && oldUser.resetKey != user.resetKey) { + flag = true + session.execute( + deleteByResetKeyStmt!!.bind().setString("reset_key", oldUser.resetKey) + ).awaitSingle() + } + if (!StringUtils.isEmpty(oldUser.login) && oldUser.login != user.login) { + flag = true + session.execute( + deleteByLoginStmt!!.bind().setString("login", oldUser.login) + ).awaitSingle() + } + if (!StringUtils.isEmpty(oldUser.email) && oldUser.email != user.email) { + flag = true + session.execute( + deleteByEmailStmt!!.bind().setString("email", oldUser.email?.lowercase()) + ).awaitSingle() + } + if (flag) null else true + } ?: run { + val batch = BatchStatement.builder(DefaultBatchType.LOGGED) + batch.addStatement(getInsertStatement(user)) + if (!StringUtils.isEmpty(user.activationKey)) { + batch.addStatement( + insertByActivationKeyStmt!!.bind() + .setString("activation_key", user.activationKey) + .setString("id", user.id) + ) + } + if (!StringUtils.isEmpty(user.resetKey)) { + batch.addStatement( + insertByResetKeyStmt!!.bind() + .setString("reset_key", user.resetKey) + .setString("id", user.id) + ) + } + batch.addStatement( + insertByLoginStmt!!.bind() + .setString("login", user.login) + .setString("id", user.id) + ) + batch.addStatement( + insertByEmailStmt!!.bind() + .setString("email", user.email?.lowercase()) + .setString("id", user.id) + ) + session.execute(batch.build()).subscribe() + } + return user + } + + private fun getInsertStatement(user: <%= asEntity('User') %>): SimpleStatement { + val converter = cqlTemplate.converter + val persistentEntity = converter.mappingContext.getRequiredPersistentEntity(user::class.java) + val toInsert = mutableMapOf() + converter.write(user, toInsert, persistentEntity) + val insert = QueryBuilder.insertInto(persistentEntity.tableName) + .value("id", QueryBuilder.literal(user.id)) + toInsert.forEach { (key, value) -> insert.value(key, QueryBuilder.literal(value)) } + return insert.build() + } + + suspend fun delete(user: <%= asEntity('User') %>) { + val batch = BatchStatement.builder(DefaultBatchType.LOGGED) + batch.addStatement(deleteByIdStmt!!.bind().setString("id", user.id)) + if (!user.activationKey.isNullOrBlank()) { + batch.addStatement(deleteByActivationKeyStmt!!.bind().setString("activation_key", user.activationKey)) + } + if (!user.resetKey.isNullOrBlank()) { + batch.addStatement(deleteByResetKeyStmt!!.bind().setString("reset_key", user.resetKey)) + } + batch.addStatement(deleteByLoginStmt!!.bind().setString("login", user.login)) + batch.addStatement(deleteByEmailStmt!!.bind().setString("email", user.email!!.lowercase())) + session.execute(batch.build()).subscribe() + } + + private suspend fun findOneFromIndex(stmt: BoundStatement): <%= asEntity('User') %>? { + return session.execute(stmt) + .flatMap { it.rows().next() } + .mapNotNull { it.getString("id") } + .awaitSingleOrNull() + ?.let { findById(it) } + } + + suspend fun deleteAll() { + listOfNotNull(truncateStmt, truncateByEmailStmt, truncateByLoginStmt, truncateByResetKeyStmt) + .map(PreparedStatement::bind) + .map(session::execute) + .forEach(Mono::subscribe) + } + +<%_ if (cacheManagerIsAvailable) { _%> + companion object { + const val USERS_BY_LOGIN_CACHE = "usersByLogin" + + const val USERS_BY_EMAIL_CACHE = "usersByEmail" + } +<%_ } _%> +} +<%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs new file mode 100644 index 000000000..63427fed8 --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs @@ -0,0 +1,74 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.repository.search + +import <%= packageName %>.domain.<%= asEntity('User') %> +<%_ if (reactive) { _%> +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate +<%_ } else { _%> +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate +<%_ } _%> +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery +import org.springframework.data.elasticsearch.core.SearchHit +import org.springframework.data.elasticsearch.repository.<% if (reactive) {%>Reactive<% } %>ElasticsearchRepository +<%_ if (reactive) { _%> +import reactor.core.publisher.Flux +<%_ } else { _%> +import java.util.stream.Stream +<%_ } _%> +<%_ if (databaseTypeCassandra) { _%> +import java.util.UUID +<%_ } _%> + +import org.elasticsearch.index.query.QueryBuilders.queryStringQuery + +/** + * Spring Data Elasticsearch repository for the User entity. + */ +interface UserSearchRepository : <% if (reactive) {%>Reactive<% } %>ElasticsearchRepository<<%= asEntity('User') %>, <% if (databaseTypeSql && !authenticationTypeOauth2) { %>Long<% } %><% if ((databaseTypeCassandra || databaseTypeMongodb || databaseTypeNeo4j) || authenticationTypeOauth2) { %>String<% } %>>, UserSearchRepositoryInternal + +<%_ if (reactive) { _%> +interface UserSearchRepositoryInternal { + fun search(query: String): Flux +} + +class UserSearchRepositoryInternalImpl(private val reactiveElasticsearchTemplate: ReactiveElasticsearchTemplate) : UserSearchRepositoryInternal { + override fun search(query: String): Flux { + val nativeSearchQuery = NativeSearchQuery(queryStringQuery(query)) + return reactiveElasticsearchTemplate.search(nativeSearchQuery, User::class.java) + .map(SearchHit::getContent) + } +} +<%_ } else { _%> + +interface UserSearchRepositoryInternal { + fun search(query: String): List +} + +class UserSearchRepositoryInternalImpl(private val elasticsearchTemplate: ElasticsearchRestTemplate): UserSearchRepositoryInternal { + + override fun search(query: String): List { + val nativeSearchQuery = NativeSearchQuery(queryStringQuery(query)) + return elasticsearchTemplate + .search(nativeSearchQuery, User::class.java) + .map(SearchHit::getContent) + .toList() + } +} +<%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs new file mode 100644 index 000000000..5552b833f --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs @@ -0,0 +1,86 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.security + +import <%= packageName %>.domain.<%= asEntity('User') %> +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { _%> +import <%= packageName %>.domain.Authority +<%_ } _%> +import <%= packageName %>.repository.UserRepository +import kotlinx.coroutines.runBlocking +import reactor.kotlin.core.publisher.toMono +import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator +import org.slf4j.LoggerFactory +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.ReactiveUserDetailsService +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.stereotype.Component +<%_ if (databaseTypeSql) { _%> +import org.springframework.transaction.annotation.Transactional +<%_ } _%> +import reactor.core.publisher.Mono + +import java.util.Locale + +/** + * Authenticate a user from the database. + */ +@Component("userDetailsService") +class DomainUserDetailsService(private val userRepository: UserRepository) : ReactiveUserDetailsService { + + private val log = LoggerFactory.getLogger(javaClass) + + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + override fun findByUsername(login: String): Mono { + log.debug("Authenticating $login") + + if (EmailValidator().isValid(login, null)) { + return runBlocking { + userRepository.<% if (databaseTypeSql) { %>findOneWithAuthoritiesByEmailIgnoreCase<% } else { %>findOneByEmailIgnoreCase<% } %>(login) + ?.let { createSpringSecurityUser(login, it) } + ?.toMono() + ?: throw UsernameNotFoundException("User with email $login was not found in the database") + } + } + + val lowercaseLogin = login.lowercase(Locale.ENGLISH) + return runBlocking { + userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(lowercaseLogin) + ?.let { createSpringSecurityUser(lowercaseLogin, it) } + ?.toMono() + ?: throw UsernameNotFoundException("User $lowercaseLogin was not found in the database") + } + } + + private fun createSpringSecurityUser(lowercaseLogin: String, user: <%= asEntity('User') %>) + : org.springframework.security.core.userdetails.User { + if (user.activated == null || user.activated == false) { + throw UserNotActivatedException("User $lowercaseLogin was not activated") + } + val grantedAuthorities = user.authorities.map { SimpleGrantedAuthority(it<% if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { %>.name<% } %>) } + return org.springframework.security.core.userdetails.User( + user.login!!, + user.password!!, + grantedAuthorities + ) + } +} diff --git a/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs new file mode 100644 index 000000000..ab5bdc796 --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs @@ -0,0 +1,672 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.service + +import <%= packageName %>.config.DEFAULT_LANGUAGE +import <%= packageName %>.config.SYSTEM_ACCOUNT +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import <%= packageName %>.domain.Authority +<%_ } _%> +<%_ if (!databaseTypeNo) { _%> +import <%= packageName %>.service.dto.<%= asDto('AdminUser') %> +import <%= packageName %>.domain.<%= asEntity('User') %> + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import <%= packageName %>.repository.AuthorityRepository + <%_ } _%> +import <%= packageName %>.repository.UserRepository + <%_ if (searchEngineElasticsearch) { _%> +import <%= packageName %>.repository.search.UserSearchRepository + <%_ } _%> + <%_ if (!authenticationTypeOauth2) { _%> +import <%= packageName %>.security.USER + <%_ } _%> +import <%= packageName %>.security.getCurrentUserLogin +<%_ } _%> +import <%= packageName %>.service.dto.<%= asDto('User') %> +<%_ if (!authenticationTypeOauth2) { _%> +import tech.jhipster.security.RandomUtil +<%_ } _%> +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.reactor.awaitSingle +import kotlinx.coroutines.runBlocking +<%_ if (!databaseTypeNo) { _%> +import org.slf4j.LoggerFactory +<%_ } _%> +<%_ if (cacheManagerIsAvailable) { _%> +import org.springframework.cache.CacheManager +<%_ } _%> +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import org.springframework.data.domain.Pageable + <%_ if (!authenticationTypeOauth2) { _%> +import org.springframework.scheduling.annotation.Scheduled + <%_ } _%> +<%_ } _%> +<%_ if (authenticationTypeOauth2) { _%> +import org.springframework.security.authentication.AbstractAuthenticationToken +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken +<%_ } _%> +<%_ if (!authenticationTypeOauth2) { _%> +import org.springframework.security.crypto.password.PasswordEncoder +<%_ } _%> +import org.springframework.stereotype.Service +<%_ if (databaseTypeSql) { _%> +import org.springframework.transaction.annotation.Transactional +<%_ } _%> +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers + +<%_ if ((databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) && authenticationTypeSession && !reactive) { _%> +import java.time.LocalDate +<%_ } _%> +<%_ if (!databaseTypeNo) { _%> +import java.time.Instant +<%_ } _%> +<%_ if (databaseTypeSql && !authenticationTypeOauth2) { _%> +import java.time.LocalDateTime +import java.time.ZoneOffset +<%_ } _%> +<%_ if (!authenticationTypeOauth2) { _%> +import java.time.temporal.ChronoUnit +<%_ } _%> +<%_ if (authenticationTypeOauth2) { _%> +import java.util.Date +<%_ } _%> +import java.util.Optional +<%_ if (databaseTypeCassandra) { _%> +import java.util.UUID +<%_ } _%> + +/** + * Service class for managing users. + */ +@Service +class UserService<% if (!databaseTypeNo) { %>( + private val userRepository: UserRepository<%_ if (!authenticationTypeOauth2) { _%>, + private val passwordEncoder: PasswordEncoder<%_ } if (searchEngineElasticsearch) { _%>, + private val userSearchRepository: UserSearchRepository<%_ } if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%>, + private val authorityRepository: AuthorityRepository<%_ } if (cacheManagerIsAvailable) { _%>, + private val cacheManager: CacheManager + <%_ } _%> +)<%_ } %> { +<%_ if (!databaseTypeNo) { _%> + + private val log = LoggerFactory.getLogger(javaClass) +<%_ if (!authenticationTypeOauth2) { _%> + + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun activateRegistration(key: String): <%= asEntity('User') %>? { + log.debug("Activating user for activation key $key") + return userRepository.findOneByActivationKey(key) + ?.apply { + // activate given user for the registration key. + activated = true + activationKey = null + saveUser(this) + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.save(this) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(this) + <%_ } _%> + log.debug("Activated user: $this") + } + } + + <%_ if (databaseTypeSql && reactive) { _%> + @Transactional + <%_ } _%> + suspend fun completePasswordReset(newPassword: String, key: String): <%= asEntity('User') %>? { + log.debug("Reset user password for reset key $key") + userRepository.findOneByResetKey(key)?.apply { + if(true == resetDate?.isAfter(Instant.now().minus(1, ChronoUnit.DAYS))){ + password = passwordEncoder.encode(newPassword) + resetKey = null + resetDate = null + val updatedUser = saveUser(this)<% if (cacheManagerIsAvailable) { %> + clearUserCaches(this)<% } %> + return updatedUser + } + } + return null + } + + <%_ if (databaseTypeSql && reactive) { _%> + @Transactional + <%_ } _%> + suspend fun requestPasswordReset(mail: String): <%= asEntity('User') %>? { + userRepository.findOneByEmailIgnoreCase(mail) + ?.let { + it.resetKey = RandomUtil.generateResetKey() + it.resetDate = Instant.now() + val updatedUser = saveUser(it)<% if (cacheManagerIsAvailable) { %> + clearUserCaches(this)<% } %> + return updatedUser + } + return null + } + + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun registerUser(userDTO: <%= asDto('AdminUser') %>, password: String): <%= asEntity('User') %>? { + val login = userDTO.login ?: throw IllegalArgumentException("Empty login not allowed") + val email = userDTO.email + userRepository.findOneByLogin(login.lowercase())?.apply { + if(false == activated){ + userRepository.delete(this) + } else { + throw UsernameAlreadyUsedException() + } + } + userRepository.findOneByEmailIgnoreCase(email!!)?.apply { + if(false == activated){ + userRepository.delete(this) + } else { + throw EmailAlreadyUsedException() + } + } + val savedUser = saveUser( + User().apply { + <%_ if (databaseTypeCassandra) { _%> + id = UUID.randomUUID().toString() + <%_ } _%> + val encryptedPassword = passwordEncoder.encode(password) + this.login = login.lowercase() + // new user gets initially a generated password + this.password = encryptedPassword + firstName = userDTO.firstName + lastName = userDTO.lastName + this.email = email.lowercase() + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + imageUrl = userDTO.imageUrl + <%_ } _%> + langKey = userDTO.langKey + // new user is not active + activated = false + // new user gets registration key + activationKey = RandomUtil.generateActivationKey() + authorities = arrayOf(USER) + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { _%> + .mapNotNull { authorityRepository.findById(it) } + <%_ } _%> + .toMutableSet() + } + )!! + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.save(savedUser) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(savedUser) + <%_ } _%> + log.debug("Created Information for User: $savedUser") + return savedUser + } + + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun createUser(userDTO: <%= asDto('AdminUser') %>): <%= asEntity('User') %>? { + val encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()) + val savedUser = saveUser( + <%= asEntity('User') %>( + <%_ if (databaseTypeCassandra) { _%> + id = UUID.randomUUID().toString(), + <%_ } _%> + login = userDTO.login?.lowercase(), + firstName = userDTO.firstName, + lastName = userDTO.lastName, + email = userDTO.email?.lowercase(), + <%_ if (databaseTypeSql || databaseTypeCouchbase || databaseTypeMongodb || databaseTypeNeo4j) { _%> + imageUrl = userDTO.imageUrl, + <%_ } _%> + // default language + langKey = userDTO.langKey ?: DEFAULT_LANGUAGE, + password = encryptedPassword, + resetKey = RandomUtil.generateResetKey(), + resetDate = Instant.now(), + activated = true, + authorities = userDTO.authorities + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { _%> + .mapNotNull { authorityRepository.findById(it) } + <%_ } _%> + .toMutableSet() + ) + )!! + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.save(savedUser) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(savedUser) + <%_ } _%> + log.debug("Changed Information for User: $savedUser") + return savedUser + } + + /** + * Update all information for a specific user, and return the modified user. + * + * @param userDTO user to update. + * @return updated user. + */ + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun updateUser(userDTO: <%= asDto('AdminUser') %>): <%= asDto('AdminUser') %>? { + return userRepository.findById(userDTO.id!!) + ?.apply { + <%_ if (databaseTypeCouchbase) { _%> + if (login != userDTO.login) { + userRepository.deleteById(userDTO.id!!) + } + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(this) + <%_ } _%> + login = userDTO.login?.lowercase() + firstName = userDTO.firstName + lastName = userDTO.lastName + email = userDTO.email?.lowercase() + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + imageUrl = userDTO.imageUrl + <%_ } _%> + activated = userDTO.activated + langKey = userDTO.langKey + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { _%> + authorities.clear() + <%_ if (databaseTypeSql) { _%> + userRepository.deleteUserAuthorities(id!!) + <%_ } _%> + userDTO.authorities + .mapNotNull { authorityRepository.findById(it) } + .forEach(authorities::add) + <%_ } else { /* Cassandra & Couchbase */ _%> + authorities = userDTO.authorities.toMutableSet() + <%_ } _%> + }?.let { + saveUser(it) + }?.let { + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.save(it) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(it) + <%_ } _%> + log.debug("Changed Information for User: $it") + AdminUserDTO(it) + } + } + + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun deleteUser(login: String) { + userRepository.findOneByLogin(login)?.let { + userRepository.delete(it) + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.delete(it) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(this) + <%_ } _%> + log.debug("Changed Information for User: $it") + } + } +<%_ } /* !authenticationTypeOauth2 */ _%> + /** + * 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. + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeNeo4j) { _%> + * @param imageUrl image URL of user. + <%_ } _%> + */ + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun updateUser(firstName: String?, lastName: String?, email: String?, langKey: String?<% if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeNeo4j) { %>, imageUrl: String?<% } %>) { + getCurrentUserLogin() + .awaitSingle() + .let { userRepository.findOneByLogin(it) } + ?.let { + it.firstName = firstName + it.lastName = lastName + it.email = email?.lowercase() + it.langKey = langKey + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeNeo4j) { _%> + it.imageUrl = imageUrl + <%_ } _%> + saveUser(it) + }?.let { + <%_ if (searchEngineElasticsearch) { _%> + userSearchRepository.save(it) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(it) + <%_ } _%> + log.debug("Changed Information for User: $it") + } + } + + <%_ if (databaseTypeSql && authenticationTypeOauth2) { _%> + @Transactional + suspend fun saveUser(user: <%= asEntity('User') %>) = saveUser(user, false) + <%_ } _%> + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + <% if (!databaseTypeSql) { %>private <% } %> suspend fun saveUser(user: <%= asEntity('User') %><% if (databaseTypeSql && authenticationTypeOauth2) { %>, forceCreate: Boolean<% } %>): <%= asEntity('User') %>? { + <%_ if (databaseTypeCassandra) { _%> + return userRepository.save(user) + <%_ } else { _%> + val login = getCurrentUserLogin().switchIfEmpty(Mono.just(SYSTEM_ACCOUNT)).awaitSingle() + if (user.createdBy == null) { + user.createdBy = login + } + user.lastModifiedBy = login + <%_ if (databaseTypeSql) { _%> + // Saving the relationship can be done in an entity callback + // once https://github.com/spring-projects/spring-data-r2dbc/issues/215 is done + <%_ if (authenticationTypeOauth2) { _%> + val updatedUser = if (forceCreate) { userRepository.create(user) } else { userRepository.save(user) }!! + <%_ } else { _%> + val updatedUser = userRepository.save(user) + <%_ } _%> + updatedUser.authorities.forEach { + userRepository.saveUserAuthority(updatedUser.id!!, it.name!!) + } + return updatedUser + <%_ } else { _%> + return userRepository.save(user) + <%_ } _%> + <%_ } _%> + } + +<%_ if (!authenticationTypeOauth2) { _%> + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun changePassword(currentClearTextPassword: String, newPassword: String) { + getCurrentUserLogin() + .awaitSingle() + .let { userRepository.findOneByLogin(it) } + ?.apply { + val currentEncryptedPassword = password + if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) { + throw InvalidPasswordException() + } + val encryptedPassword = passwordEncoder.encode(newPassword) + password = encryptedPassword + }?.let { saveUser(it) } + ?.let { + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(it) + <%_ } _%> + log.debug("Changed password for User: $it") + } + } +<%_ } _%> + + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + suspend fun getAllManagedUsers(pageable: Pageable): Flow<<%= asDto('AdminUser') %>> { + return userRepository.findAll<% if (databaseTypeSql) { %>WithAuthorities<% } else { %>By<% if (!databaseTypeCouchbase) { %>IdNotNull<% }} %>(pageable).map { <%= asDto('AdminUser') %>(it) } + } + + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + suspend fun getAllPublicUsers(pageable: Pageable): Flow<<%= asDto('User') %>> { + return userRepository.findAllBy<% if (!databaseTypeCouchbase) { %>IdNotNullAnd<% } %>ActivatedIsTrue(pageable).map { <%= asDto('User') %>(it) } + } + + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + suspend fun countManagedUsers() = userRepository.count() + <%_ } else { /* Cassandra */ _%> + + suspend fun getAllManagedUsers() = userRepository.findAll().map { <%= asDto('AdminUser') %>(it) } + + suspend fun getAllPublicUsers() = userRepository.findAll() + .filter { it.activated == true } + .map { <%= asDto('User') %>(it) } + <%_ } _%> + + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + suspend fun getUserWithAuthoritiesByLogin(login: String): <%= asEntity('User') %>? = + userRepository.<% if (databaseTypeSql) { %>findOneWithAuthoritiesByLogin(login)<% } else { %>findOneByLogin(login)<% } %> + + <%_ if (!authenticationTypeOauth2) { _%> + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + suspend fun getUserWithAuthorities(): <%= asEntity('User') %>? = + getCurrentUserLogin() + .awaitSingle() + .let { userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(it) } + <%_ } _%> + + <%_ if (!authenticationTypeOauth2 && (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase)) { _%> + + /** + * 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 * * ?") + fun removeNotActivatedUsers() { + runBlocking { + removeNotActivatedUsersReactively().collect() + } + } + + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun removeNotActivatedUsersReactively(): Flow<<%= asEntity('User') %>> { + return userRepository + .findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(<%_ if (databaseTypeSql) { _%>LocalDateTime.ofInstant(Instant.now().minus(3, ChronoUnit.DAYS), ZoneOffset.UTC)<%_ } else { _%>Instant.now().minus(3, ChronoUnit.DAYS)<%_ } _%>) + .onEach(userRepository::delete) + <%_ if (searchEngineElasticsearch) { _%> + .onEach(userSearchRepository::delete) + <%_ } _%> + <%_ if (cacheManagerIsAvailable) { _%> + .onEach(this::clearUserCaches) + <%_ } _%> + .onEach { log.debug("Deleted User: $it") } + } + <%_ } _%> + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + + /** + * @return a list of all the authorities + */ + <%_ if (databaseTypeSql) { _%> + @Transactional(readOnly = true) + <%_ } _%> + suspend fun getAuthorities() = + authorityRepository.findAll().map{ it.name } + <%_ } _%> + <%_ if (authenticationTypeOauth2) { _%> + + private suspend fun syncUserWithIdP(details: Map, user: <%= asEntity('User') %>): <%= asEntity('User') %> { + // save authorities in to sync user roles/groups between IdP and JHipster's local database + val dbAuthorities = getAuthorities().toCollection(mutableListOf()) + <%_ if (!databaseTypeCouchbase) { _%> + val userAuthorities = user.authorities.mapTo(mutableListOf(), Authority::name) + <%_ } else { _%> + val userAuthorities = user.authorities + <%_ } _%> + for (authority in userAuthorities) { + if (!dbAuthorities.contains(authority)) { + log.debug("Saving authority '$authority' in local database") + val authorityToSave = Authority(name = authority) + authorityRepository.save(authorityToSave) + } + } + // save account in to sync users between IdP and JHipster's local database + userRepository.findOneByLogin(user.login!!)?.apply { + // if IdP sends last updated information, use it to determine if an update should happen + if (details["updated_at"] != null) { + val dbModifiedDate = lastModifiedDate + val idpModifiedDate: Instant = if (details["updated_at"] is Instant) details["updated_at"] as Instant else Instant.ofEpochSecond(details["updated_at"] as Long) + if (idpModifiedDate.isAfter(dbModifiedDate)) { + log.debug("Updating user '${user.login}' in local database") + updateUser(user.firstName, user.lastName, user.email, user.langKey, user.imageUrl) + } + // no last updated info, blindly update + } else { + log.debug("Updating user '${user.login}' in local database") + updateUser(user.firstName, user.lastName, user.email, user.langKey, user.imageUrl) + } + } ?: run { + log.debug("Saving user '${user.login}' in local database") + userRepository.save(user) + <%_ if (cacheManagerIsAvailable) { _%> + clearUserCaches(user) + <%_ } _%> + } + return user + } + <%_ } _%> + +<%_ } /* !databaseTypeNo */ _%> + <%_ if (authenticationTypeOauth2) { _%> + /** + * Returns the user from an OAuth 2.0 login or resource server with JWT. + <%_ if (!databaseTypeNo) { _%> + * Synchronizes the user in the local repository. + <%_ } _%> + * + * @param authToken the authentication token. + * @return the user from the authentication. + */ + <%_ if (databaseTypeSql) { _%> + @Transactional + <%_ } _%> + suspend fun getUserFromAuthentication(authToken: AbstractAuthenticationToken): <%= asDto('AdminUser') %> { + val attributes: Map = + when (authToken) { + is OAuth2AuthenticationToken -> authToken.principal.attributes + is JwtAuthenticationToken -> authToken.tokenAttributes + else -> throw IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!") + } + + val user = getUser(attributes) + user.authorities = authToken.authorities.asSequence() + .map(GrantedAuthority::getAuthority) + <%_ if (['sql', 'mongodb', 'neo4j'].includes(databaseType)) { _%> + .map { Authority(name = it) } + <%_ } _%> + .toMutableSet() + <%_ if (databaseTypeNo) { _%> + return user + <%_ } else { _%> + return <%= asDto('AdminUser') %>(syncUserWithIdP(attributes, user)) + <%_ } _%> + } + <%_ } _%> + <%_ if (cacheManagerIsAvailable && !databaseTypeNo) { _%> + + private fun clearUserCaches(user: <%= asEntity('User') %>) { + user.login?.let{ + cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)?.evict(it) + } + user.email?.let { + cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)?.evict(it) + } + } + <%_ } _%> + <%_ if (authenticationTypeOauth2) { _%> + + companion object { + + @JvmStatic + private fun getUser(details: Map): <%= databaseTypeNo ? asDto('AdminUser') : asEntity('User') %> { + var activated = true + val sub = details["sub"] as String; + val username = details["preferred_username"]?.let { it as String } ?: null + val user = <%= databaseTypeNo ? asDto('AdminUser') : asEntity('User') %>() + // handle resource server JWT, where sub claim is email and uid is ID + if (details["uid"] != null) { + user.id = details["uid"] as String + user.login = sub + } else { + user.id = sub + } + if (username != null) { + user.login = username.lowercase() + } else if (user.login == null) { + user.login = user.id + } + if (details["given_name"] != null) { + user.firstName = details["given_name"] as String + } else if (details.get("name") != null) { + user.firstName = details.get("name") as String + } + if (details["family_name"] != null) { + user.lastName = details["family_name"] as String + } + if (details["email_verified"] != null) { + activated = details["email_verified"] as Boolean + } + if (details["email"] != null) { + user.email = (details["email"] as String).lowercase() + }else if (sub.contains("|") && (username != null && username.contains("@"))) { + // special handling for Auth0 + user.email = username + } else { + user.email = sub + } + if (details["langKey"] != null) { + user.langKey = details["langKey"] as String + } else if (details["locale"] != null) { + // trim off country code if it exists + var locale = details["locale"] as String + if (locale.contains("_")) { + locale = locale.substring(0, locale.indexOf("_")) + } else if (locale.contains("-")) { + locale = locale.substring(0, locale.indexOf("-")) + } + user.langKey = locale.lowercase() + } else { + // set langKey to default if not specified by IdP + user.langKey = DEFAULT_LANGUAGE + } + if (details["picture"] != null) { + user.imageUrl = details["picture"] as String + } + user.activated = activated + return user + } + } + <%_ } _%> +} diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs new file mode 100644 index 000000000..10b3ae434 --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs @@ -0,0 +1,327 @@ +<%# +Copyright 2013-2020 the original author or authors from the JHipster project. + +This file is part of the JHipster project, see https://www.jhipster.tech/ +for more information. + +Licensed under the Apache License, Version 2.0 (the " +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-%> +package <%= packageName %>.web.rest + +<%_ if (authenticationTypeOauth2) { _%> +import <%= packageName %>.security.getCurrentUserLogin +import <%= packageName %>.service.UserService +import <%= packageName %>.service.dto.<%= asDto('AdminUser') %> + +import org.slf4j.LoggerFactory +import org.springframework.security.authentication.AbstractAuthenticationToken +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono +import java.security.Principal + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +class AccountResource(private val userService: UserService) { + + internal class AccountResourceException(message: String) : RuntimeException(message) + + private val log = LoggerFactory.getLogger(javaClass) + + /** + * `GET /account` : get the current user. + * + * @param principal the current user; resolves to `null` if not authenticated. + * @return the current user. + * @throws AccountResourceException `500 (Internal Server Error)` if the user couldn't be returned. + */ + @GetMapping("/account") + suspend fun getAccount(principal: Principal?): <%= asDto('AdminUser') %> = + if (principal is AbstractAuthenticationToken) { + userService.getUserFromAuthentication(principal) + } else { + throw AccountResourceException("User could not be found") + } + + /** + * {@code GET /authenticate} : check if the user is authenticated, and return its login. + * + * @param request the HTTP request. + * @return the login if the user is authenticated. + */ + @GetMapping("/authenticate") + suspend fun isAuthenticated(request: ServerWebExchange): String? { + log.debug("REST request to check if the current user is authenticated") + return request.getPrincipal().map(Principal::getName).awaitSingleOrNull() + } + + companion object { + private const val serialVersionUID = 1L + } +} +<%_ } else if (skipUserManagement) { _%> +import com.fasterxml.jackson.annotation.JsonCreator +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.context.ReactiveSecurityContextHolder +import org.springframework.security.core.context.SecurityContext +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono + +@RestController +@RequestMapping("/api") +class AccountResource { + + private val log = LoggerFactory.getLogger(javaClass) + + internal class AccountResourceException : RuntimeException() + + /** + * {@code GET /account} : get the current user. + * + * @return the current user. + * @throws AccountResourceException {@code 500 (Internal Server Error)} if the user couldn't be returned. + */ + @GetMapping("/account") + <%_ if (reactive) { _%> + fun getAccount(): Mono { + return ReactiveSecurityContextHolder.getContext() + .map { SecurityContext.getAuthentication(it) } + .map { authentication -> { + var login = "" + if (authentication.principal is UserDetails) { + login = authentication.principal.username + } else if (authentication.principal is String) { + login = authentication.principal + } else { + throw AccountResourceException() + } + val authorities = authentication.authorities() + .map { GrantedAuthority.getAuthority(it) } + .toSet() + return UserVM(login, authorities) + }} + .switchIfEmpty(Mono.error(AccountResourceException())) + <%_ } else { _%> + fun getAccount(): UserVM { + val login = getCurrentUserLogin() + .orElseThrow { AccountResourceException() } + val authorities = SecurityContextHolder.getContext().authentication.authorities + .mapNotNullTo(mutableSetOf()) { it.authority } + return UserVM(login, authorities) + <%_ } _%> + } + + /** + * {@code GET /authenticate} : check if the user is authenticated, and return its login. + * + * @param request the HTTP request. + * @return the login if the user is authenticated. + */ + @GetMapping("/authenticate") + <%_ if (reactive) { _%> + fun isAuthenticated(request: ServerWebExchange): Mono { + log.debug("REST request to check if the current user is authenticated") + return request.getPrincipal().map(Principal::getName) + <%_ } else { _%> + fun isAuthenticated(request: HttpServletRequest): String? { + log.debug("REST request to check if the current user is authenticated") + return request.remoteUser + <%_ } _%> + } + + data class UserVM @JsonCreator constructor(val login: String, val authorities: Set) { + + fun isActivated() = true + } +} +<%_ } else { _%> +import <%= packageName %>.repository.UserRepository +import <%= packageName %>.security.getCurrentUserLogin +import <%= packageName %>.service.MailService +import <%= packageName %>.service.UserService +import <%= packageName %>.service.dto.PasswordChangeDTO +import <%= packageName %>.service.dto.<%= asDto('AdminUser') %> +import <%= packageName %>.web.rest.errors.EmailAlreadyUsedException +import <%= packageName %>.web.rest.errors.InvalidPasswordException +import <%= packageName %>.web.rest.errors.LoginAlreadyUsedException +import <%= packageName %>.web.rest.vm.KeyAndPasswordVM +import <%= packageName %>.web.rest.vm.ManagedUserVM +import kotlinx.coroutines.reactor.* + +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono + +import javax.validation.Valid +import java.security.Principal + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +class AccountResource( + private val userRepository: UserRepository, + private val userService: UserService, + private val mailService: MailService<% if (authenticationTypeSession && !reactive) { %>, + private val persistentTokenRepository: PersistentTokenRepository<%_ } %> +) { + + internal class AccountResourceException(message: String) : RuntimeException(message) + + private val log = LoggerFactory.getLogger(javaClass) + + /** + * `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) + suspend fun registerAccount(@Valid @RequestBody managedUserVM: ManagedUserVM){ + if (isPasswordLengthInvalid(managedUserVM.password)) { + throw InvalidPasswordException() + } + val user = userService.registerUser(managedUserVM, managedUserVM.password!!)!! + 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") + suspend fun activateAccount(@RequestParam(value = "key") key: String){ + userService.activateRegistration(key) + ?: throw AccountResourceException("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") + suspend fun isAuthenticated(request: ServerWebExchange): String? { + log.debug("REST request to check if the current user is authenticated") + return request.getPrincipal().awaitSingle().name + } + + /** + * `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") + suspend fun getAccount(): <%= asDto('AdminUser') %>? = + userService.getUserWithAuthorities() + ?.let { AdminUserDTO(it) } + ?: throw AccountResourceException("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") + suspend fun saveAccount(@Valid @RequestBody userDTO: <%= asDto('AdminUser') %>) { + val userLogin = getCurrentUserLogin().awaitSingleOrNull() ?: throw AccountResourceException("") + val existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.email!!)!! + if (!existingUser.login.equals(userLogin, ignoreCase = true)) { + throw EmailAlreadyUsedException() + } + userRepository.findOneByLogin(userLogin) ?: throw AccountResourceException("User could not be found") + userService.updateUser( + userDTO.firstName, userDTO.lastName, userDTO.email, + userDTO.langKey<% if (['sql', 'mongodb', 'couchbase', 'neo4j'].includes(databaseType)) { %>, userDTO.imageUrl<% } %> + ) + } + + + /** + * 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"]) + suspend fun changePassword(@RequestBody passwordChangeDto: PasswordChangeDTO){ + if (isPasswordLengthInvalid(passwordChangeDto.newPassword)) { + throw InvalidPasswordException() + } + userService.changePassword(passwordChangeDto.currentPassword!!, passwordChangeDto.newPassword!!) + } + + /** + * POST /account/reset-password/init : Send an email to reset the password of the user + * + * @param mail the mail of the user + */ + @PostMapping(path = ["/account/reset-password/init"]) + suspend fun requestPasswordReset(@RequestBody mail: String) { + userService.requestPasswordReset(mail) + ?.apply { mailService.sendPasswordResetMail(this) } + // Pretend the request has been successful to prevent checking which emails really exist + // but log that an invalid attempt has been made + ?: log.warn("Password reset requested for non existing mail") + } + + /** + * `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"]) + suspend fun finishPasswordReset(@RequestBody keyAndPassword: KeyAndPasswordVM){ + if (isPasswordLengthInvalid(keyAndPassword.newPassword)) { + throw InvalidPasswordException() + } + + userService.completePasswordReset(keyAndPassword.newPassword!!, keyAndPassword.key!!) + ?: throw AccountResourceException("No user was found for this reset key") + } +} + +private fun isPasswordLengthInvalid(password: String?) = password.isNullOrEmpty() || password.length < ManagedUserVM.PASSWORD_MIN_LENGTH || password.length > ManagedUserVM.PASSWORD_MAX_LENGTH +<%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs new file mode 100644 index 000000000..c5cb29aac --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs @@ -0,0 +1,141 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + +This file is part of the JHipster project, see https://jhipster.github.io/ + for more information. + + Licensed under the Apache License, Version 2.0 (the "License") + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.web.rest + +<%_ if (searchEngineElasticsearch) { _%> +import <%= packageName %>.repository.search.UserSearchRepository +<%_ } else if (searchEngineCouchbase) { _%> +import <%= packageName %>.repository.UserRepository +import <%= packageName %>.domain.User +<%_ } _%> +<%_ if (!authenticationTypeOauth2) { _%> +import org.springframework.data.domain.Sort +import java.util.Collections +<%_ } _%> +import <%= packageName %>.service.UserService +import <%= packageName %>.service.dto.<%= asDto('User') %> +import kotlinx.coroutines.flow.* + +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import tech.jhipster.web.util.PaginationUtil +<%_ } _%> + +import org.slf4j.LoggerFactory +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.Pageable +<%_ } _%> +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.web.bind.annotation.* +import org.springframework.web.server.ResponseStatusException +import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.util.ArrayList +import java.util.List +import java.util.Arrays + +@RestController +@RequestMapping("/api") +class PublicUserResource( + <%_ if (searchEngineElasticsearch) { _%> + private val userSearchRepository: UserSearchRepository, + <%_ } _%> + <%_ if (searchEngineCouchbase) { _%> + private val userRepository: UserRepository, + <%_ } _%> + private val userService: UserService +) { + <%_ if (!authenticationTypeOauth2) { _%> + companion object { + private val ALLOWED_ORDERED_PROPERTIES = arrayOf("id", "login", "firstName", "lastName", "email", "activated", "langKey") + } + <%_ } _%> + + private val log = LoggerFactory.getLogger(javaClass) + + /** + * {@code GET /users} : get all users with only the public informations - calling this are allowed for anyone. + *<% if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { %> + * @param request a {@link ServerHttpRequest} request. + * @param pageable the pagination information.<% } %> + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + suspend fun getAllPublicUsers(request: ServerHttpRequest, @org.springdoc.api.annotations.ParameterObject pageable: Pageable): ResponseEntity>> { + log.debug("REST request to get all public User names") + <%_ if (!authenticationTypeOauth2) { _%> + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build() + } + <%_ } _%> + + return PageImpl<<%= asDto('User') %>>(listOf(), pageable, userService.countManagedUsers()) + .let { PaginationUtil.generatePaginationHttpHeaders(UriComponentsBuilder.fromHttpRequest(request), it) } + .let { ResponseEntity.ok().headers(it).body(userService.getAllPublicUsers(pageable)) } + } + <%_ if (!authenticationTypeOauth2) { _%> + private fun onlyContainsAllowedProperties(pageable: Pageable) = + pageable.sort.map(Sort.Order::getProperty).all(ALLOWED_ORDERED_PROPERTIES::contains) + <%_ } _%> + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/authorities") + suspend fun getAuthorities() = userService.getAuthorities() + + <%_ } else { /* Cassandra */ _%> + suspend fun getAllPublicUsers(): Flow<<%= asDto('User') %>> = userService.getAllPublicUsers() + + <%_ } _%> + + <%_ if (!!searchEngine) { _%> + + /** + * {@code SEARCH /_search/users/:query} : search for the User corresponding to the query. + * + * @param query the query to search. + * @return the result of the search. + */ + @GetMapping("/_search/users/{query}") + fun search(@PathVariable query: String): <% if(reactive) { %>MonoList<<%= asDto('User') %>><% if(reactive) { %>><% } %> { + <%/* TODO fix this */%> + <%_ if (searchEngineElasticsearch) { _%> + <%_ if (reactive) { _%> + return userSearchRepository.search(query).map { <%= asDto('User') %>(it) }.collectList() + <%_ } else { _%> + return userSearchRepository.search(query).map { <%= asDto('User') %>(it) } + <%_ } _%> + <%_ } else { _%> + <%_ if (reactive) { _%> + return userRepository.search(query).map { <%= asDto('User') %>(it) }.collectList() + <%_ } else { _%> + return userRepository.search(query) + .map { <%= asDto('User') %>(it) } + <%_ } _%> + <%_ } _%> + } +<%_ } _%> + +} diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs new file mode 100644 index 000000000..7ab93817b --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs @@ -0,0 +1,66 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.web.rest + +import <%= packageName %>.security.jwt.JWTFilter +import <%= packageName %>.security.jwt.TokenProvider +import <%= packageName %>.web.rest.vm.LoginVM +import kotlinx.coroutines.reactor.awaitSingle +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.ReactiveAuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers + +import javax.validation.Valid + +/** + * Controller to authenticate users. + */ +@RestController +@RequestMapping("/api") +class UserJWTController( + private val tokenProvider: TokenProvider, + private val authenticationManager: ReactiveAuthenticationManager +) { + + @PostMapping("/authenticate") + suspend fun authorize(@Valid @RequestBody loginVM: LoginVM): ResponseEntity { + return authenticationManager.authenticate(UsernamePasswordAuthenticationToken(loginVM.username, loginVM.password)) + .map { tokenProvider.createToken(it, loginVM.isRememberMe ?: false) } + .map { jwt -> + val httpHeaders = HttpHeaders() + httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer $jwt") + ResponseEntity(JWTToken(jwt), httpHeaders, HttpStatus.OK) + } + .awaitSingle() + } + + /** + * Object to return as body in JWT Authentication. + */ + class JWTToken(@get:JsonProperty("id_token") var idToken: String?) +} diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs new file mode 100644 index 000000000..bb858e34c --- /dev/null +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs @@ -0,0 +1,227 @@ +<%# + Copyright 2013-2020 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed under the Apache License, Version 2.0 (the " + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +-%> +package <%= packageName %>.web.rest + +import <%= packageName %>.config.LOGIN_REGEX +import <%= packageName %>.domain.<%= asEntity('User') %> +import <%= packageName %>.repository.UserRepository +import <%= packageName %>.security.ADMIN +import <%= packageName %>.service.MailService +import org.springframework.data.domain.Sort +import <%= packageName %>.service.UserService +import <%= packageName %>.service.dto.<%= asDto('AdminUser') %> +import <%= packageName %>.web.rest.errors.BadRequestAlertException +import <%= packageName %>.web.rest.errors.EmailAlreadyUsedException +import <%= packageName %>.web.rest.errors.LoginAlreadyUsedException + +import tech.jhipster.web.util.HeaderUtil +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import tech.jhipster.web.util.PaginationUtil +<%_ } _%> +import tech.jhipster.web.util.ResponseUtil +import kotlinx.coroutines.flow.Flow +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.Pageable +<%_ } _%> +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException +import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +import javax.validation.constraints.Pattern +import javax.validation.Valid +import java.net.URI +import java.net.URISyntaxException +import java.util.* + +/** + * REST controller for managing users. + * + * This class accesses the {@link <%= packageName %>.domain.<%= user.persistClass %>} entity, and needs to fetch its collection of authorities. + * + * For a normal use-case, it would be better to have an eager relationship between User and Authority, + * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join + * which would be good for performance. + * + * We use a View Model and a DTO for 3 reasons: + * + * * We want to keep a lazy association between the user and the authorities, because people will + * quite often do relationships with the user, and we don't want them to get the authorities all + * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' + * application because of this use-case. + * * Not having an outer join causes n+1 requests to the database. This is not a real issue as + * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, + * but then all authorities come from the cache, so in fact it's much better than doing an outer join + * (which will get lots of data from the database, for each HTTP call). + * * As this manages users, for security reasons, we'd rather have a DTO layer. + * + * Another option would be to have a specific JPA entity graph to handle this case. + */ +@RestController +@RequestMapping("/api/admin") +class UserResource( + private val userService: UserService, + private val userRepository: UserRepository, + private val mailService: MailService +) { + companion object { + private val ALLOWED_ORDERED_PROPERTIES = arrayOf("id", "login", "firstName", "lastName", "email", "activated", "langKey", "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate") + } + + private val log = LoggerFactory.getLogger(javaClass) + + @Value("\${jhipster.clientApp.name}") + private val applicationName: String? = null + + /** + * `POST /admin/users` : Creates a new user. + * + * Creates a new user if the login and email are not already used, and sends an + * mail with an activation link. + * The user needs to be activated on creation. + * + * @param userDTO the user to create. + * @return the `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("hasAuthority(\"$ADMIN\")") + @Throws(URISyntaxException::class) + suspend fun createUser(@Valid @RequestBody userDTO: <%= asDto('AdminUser') %>): ResponseEntity<<%= asEntity('User') %>> { + log.debug("REST request to save User : $userDTO") + + userDTO.id ?: throw BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists") + userRepository.findOneByLogin(userDTO.login!!.lowercase()) ?: throw LoginAlreadyUsedException() + userRepository.findOneByEmailIgnoreCase(userDTO.email!!) ?: throw EmailAlreadyUsedException() + + val newUser = userService.createUser(userDTO)!! + mailService.sendCreationEmail(newUser) + return ResponseEntity.created(URI("/api/admin/users/${newUser.login}")) + .headers(HeaderUtil.createAlert(applicationName, <% if (enableTranslation) {%>"userManagement.created"<% } else { %> "A user is created with identifier ${newUser.login}"<% } %>, newUser.login)) + .body(newUser) + } + + /** + * `PUT /admin/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("hasAuthority(\"$ADMIN\")") + suspend fun updateUser(@Valid @RequestBody userDTO: <%= asDto('AdminUser') %>): ResponseEntity<<%= asDto('AdminUser') %>> { + log.debug("REST request to update User : $userDTO") + + when{ + userRepository.findOneByEmailIgnoreCase(userDTO.email!!)?.id != userDTO.id -> throw EmailAlreadyUsedException() + userRepository.findOneByLogin(userDTO.login!!.lowercase())?.id != userDTO.id -> throw LoginAlreadyUsedException() + else -> { + val updatedUser = userService.updateUser(userDTO).let { Optional.ofNullable(it) } + return ResponseUtil.wrapOrNotFound( + updatedUser, + HeaderUtil.createAlert(applicationName, <% if (enableTranslation) { %>"userManagement.updated"<% } else { %>"A user is updated with identifier $userDTO.login"<% } %>, userDTO.login) + ) + } + } + } + + /** + * `GET /admin/users` : get all users with all the details - calling this are only allowed for the administrators. + *<% if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { %> + * @param request a [ServerHttpRequest] request. + * @param pageable the pagination information.<% } %> + * @return the `ResponseEntity` with status `200 (OK)` and with body all users. + */ + @GetMapping("/users") + @PreAuthorize("hasAuthority(\"$ADMIN\")") + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> + suspend fun getAllUsers( + request: ServerHttpRequest, + @org.springdoc.api.annotations.ParameterObject pageable: Pageable + ): ResponseEntity>> { + log.debug("REST request to get all User for an admin") + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build() + } + val total = userService.countManagedUsers() + val content = userService.getAllManagedUsers(pageable) + val page = PageImpl(mutableListOf<<%= asDto('AdminUser') %>>(), pageable, total) + val headers = PaginationUtil.generatePaginationHttpHeaders(UriComponentsBuilder.fromHttpRequest(request), page) + return ResponseEntity(content, headers, HttpStatus.OK) + } + + private fun onlyContainsAllowedProperties(pageable: Pageable) = + pageable.sort.map(Sort.Order::getProperty).all { ALLOWED_ORDERED_PROPERTIES.contains(it) } + + <%_ } else { // Cassandra _%> + suspend fun getAllUsers() = userService.getAllManagedUsers() + <%_ } _%> + + /** + * `GET /admin/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}") + @PreAuthorize("hasAuthority(\"$ADMIN\")") + suspend fun getUser(@PathVariable @Pattern(regexp = LOGIN_REGEX) login: String): ResponseEntity<<%= asDto('AdminUser') %>> { + log.debug("REST request to get User : $login") + return ResponseUtil.wrapOrNotFound( + userService.getUserWithAuthoritiesByLogin(login) + ?.let { AdminUserDTO(it) } + ?.let { Optional.ofNullable(it) } + ) + } + + /** + * `DELETE /admin/users/:login` : delete the "login" User. + * + * @param login the login of the user to delete. + * @return the `ResponseEntity` with status `204 (NO_CONTENT)`. + */ + @DeleteMapping("/users/{login}") + @PreAuthorize("hasAuthority(\"$ADMIN\")") + suspend fun deleteUser(@PathVariable @Pattern(regexp = LOGIN_REGEX) login: String): ResponseEntity { + log.debug("REST request to delete User: $login") + userService.deleteUser(login) + return ResponseEntity.noContent() + .headers(HeaderUtil.createAlert(applicationName, <% if (enableTranslation) {%>"userManagement.deleted"<% } else { %> "A user is deleted with identifier $login"<% } %>, login)).build() + } +} From 82287d5df468bdb066a773b53c1304316d4260ae Mon Sep 17 00:00:00 2001 From: Piper Han Date: Sat, 3 Dec 2022 17:16:48 +0800 Subject: [PATCH 3/4] Support for Elasticsearch --- generators/server/files-coroutine-list.js | 4 +- .../JHipsterCouchbaseRepository.kt.ejs | 95 ++++++++++++++++++- .../repository/AuthorityRepository.kt.ejs | 13 +-- .../repository/UserRepository.kt.ejs | 29 ++---- .../search/UserSearchRepository.kt.ejs | 43 ++------- .../coroutine/service/UserService.kt.ejs | 2 +- .../coroutine/web/rest/AccountResource.kt.ejs | 50 +++------- .../web/rest/PublicUserResource.kt.ejs | 25 ++--- .../web/rest/UserJWTController.kt.ejs | 14 ++- .../coroutine/web/rest/UserResource.kt.ejs | 33 +++---- .../DatabaseConfiguration_couchbase.kt.ejs | 21 +++- .../DatabaseConfiguration_mongodb.kt.ejs | 10 +- .../config/DatabaseConfiguration_neo4j.kt.ejs | 15 ++- .../config/DatabaseConfiguration_sql.kt.ejs | 4 + 14 files changed, 199 insertions(+), 159 deletions(-) diff --git a/generators/server/files-coroutine-list.js b/generators/server/files-coroutine-list.js index d1ee9efc1..fdcd433e6 100644 --- a/generators/server/files-coroutine-list.js +++ b/generators/server/files-coroutine-list.js @@ -1553,7 +1553,7 @@ const baseServerFiles = { path: SERVER_MAIN_SRC_DIR, templates: [ { - file: 'package/repository/search/UserSearchRepository.java', + file: 'coroutine/repository/search/UserSearchRepository.java', renameTo: generator => `${generator.javaDir}repository/search/UserSearchRepository.java`, }, ], @@ -1638,7 +1638,7 @@ const baseServerFiles = { path: SERVER_MAIN_SRC_DIR, templates: [ { - file: 'package/repository/search/UserSearchRepository.java', + file: 'coroutine/repository/search/UserSearchRepository.java', renameTo: generator => `${generator.javaDir}repository/search/UserSearchRepository.java`, }, ], diff --git a/generators/server/templates/couchbase/src/main/kotlin/package/repository/JHipsterCouchbaseRepository.kt.ejs b/generators/server/templates/couchbase/src/main/kotlin/package/repository/JHipsterCouchbaseRepository.kt.ejs index 2a36812bb..56e338e0e 100644 --- a/generators/server/templates/couchbase/src/main/kotlin/package/repository/JHipsterCouchbaseRepository.kt.ejs +++ b/generators/server/templates/couchbase/src/main/kotlin/package/repository/JHipsterCouchbaseRepository.kt.ejs @@ -18,6 +18,92 @@ -%> package <%= packageName %>.repository +<% if(jhipsterConfig.coroutine) { %> +import com.couchbase.client.java.query.QueryScanConsistency +import kotlinx.coroutines.flow.* +import org.springframework.data.couchbase.repository.* +import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation +import org.springframework.data.domain.* +import org.springframework.data.repository.NoRepositoryBean +import org.springframework.data.repository.kotlin.CoroutineSortingRepository + +const val FIND_IDS_QUERY = "SELECT meta().id as __id, 0 as __cas FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}" + +/** + * Couchbase specific {@link org.springframework.data.repository.Repository} interface uses N1QL for all requests. + */ +@NoRepositoryBean +@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) +interface JHipsterCouchbaseRepository : CoroutineSortingRepository, RepositoryInternal { + + companion object { + fun pageableStatement(pageable: Pageable, prefix: String): String? { + val sort = Sort.by( + pageable.sort + .map { order -> + val property = order.property + if ("id" == property) { + order.withProperty("meta($prefix).id") + } + if (prefix.isEmpty()) { + order + } + order.withProperty("$prefix $property") + } + .toList() + ) + return org.springframework.data.couchbase.core.query.Query() + .limit(pageable.pageSize) + .skip(pageable.offset) + .with(sort) + .export() + } + } + + override fun findAll(): Flow { + return findAllById(toIds(findAllIds())) + } + + fun findAllBy(pageable: Pageable): Flow { + return findAllById(toIds(findAllIds(pageable))) + } + + override fun findAll(sort: Sort): Flow { + return findAllById(toIds(findAllIds(sort))) + } + + override suspend fun deleteAll() { + deleteAllById(toIds(findAllIds()).toList()) + } + + @Query(FIND_IDS_QUERY) + fun findAllIds(): Flow + + @Query(FIND_IDS_QUERY) + fun findAllIds(pageable: Pageable): Flow + + @Query(FIND_IDS_QUERY) + fun findAllIds(sort: Sort): Flow + + @SuppressWarnings("unchecked") + fun toIds(entities: Flow): Flow { + return entities.mapNotNull { getEntityInformation().getId(it) as ID } + } +} + +interface RepositoryInternal { + fun getEntityInformation(): CouchbaseEntityInformation +} + +class RepositoryInternalImpl(private val entityInformation: CouchbaseEntityInformation) : + RepositoryInternal { + + override fun getEntityInformation(): CouchbaseEntityInformation { + return entityInformation + } + +} +<% } else { %> import com.couchbase.client.java.query.QueryScanConsistency import org.springframework.data.couchbase.repository.* import org.springframework.data.domain.* @@ -98,13 +184,13 @@ interface JHipsterCouchbaseRepository: <% if (reactive) { %>Reactive<% } } @Query(FIND_IDS_QUERY) - fun findAllIds(): <%= listOrFlux %> + fun findAllIds(): <%= listOrFlux %> @Query(FIND_IDS_QUERY) - fun findAllIds(pageable: Pageable): <%= pageOrFlux %> + fun findAllIds(pageable: Pageable): <%= pageOrFlux %> @Query(FIND_IDS_QUERY) - fun findAllIds(sort: Sort): <%= listOrFlux %> + fun findAllIds(sort: Sort): <%= listOrFlux %> @SuppressWarnings("unchecked") fun toIds(entities: <%= listOrFlux %>): <%= listOrFlux %> { @@ -114,4 +200,5 @@ interface JHipsterCouchbaseRepository: <% if (reactive) { %>Reactive<% } return entities.map { entityInformation.getId(it) as ID }.toMutableList() <%_ } _%> } -} \ No newline at end of file +} +<% } %> diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs index 18a06724c..a063d1474 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/AuthorityRepository.kt.ejs @@ -19,7 +19,7 @@ package <%= packageName %>.repository import <%= packageName %>.domain.Authority -import org.springframework.stereotype.Repository +import kotlinx.coroutines.flow.Flow import org.springframework.data.repository.kotlin.CoroutineSortingRepository <% if (databaseTypeSql) { %> /** @@ -38,13 +38,4 @@ import org.springframework.data.repository.kotlin.CoroutineSortingRepository * Spring Data Couchbase repository for the [Authority] entity. */ <% } %> -@Repository -<%_ - let listOrFlux = reactive ? 'Flux' : 'List'; -_%> -interface AuthorityRepository: CoroutineSortingRepository { - <%_ if (databaseTypeNeo4j) { _%> - <% if (!reactive) { %>// See https://github.com/neo4j/sdn-rx/issues/51<%_ } _%> - override fun findAll(): <% if (reactive) { %>Flux<% } else { %>List<% } %> - <%_ } _%> -} +interface AuthorityRepository: CoroutineSortingRepository diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs index d8899d1f5..53ba3b5ca 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs @@ -66,7 +66,6 @@ import org.springframework.data.cassandra.core.ReactiveCassandraTemplate import org.springframework.data.cassandra.core.convert.CassandraConverter import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity <%_ } _%> -import org.springframework.stereotype.Repository <%_ if (databaseTypeCassandra) { _%> import org.springframework.util.StringUtils <%_ } _%> @@ -106,17 +105,14 @@ import java.time.Instant let toListSuffix = reactive ? '' : '.toMutableList()'; _%> <%_ if (databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> -@Repository -interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, String> { +interface UserRepository: <% if (databaseTypeCouchbase) { %>JHipsterCouchbaseRepository<<%= asEntity('User') %>, String><%if (searchEngineCouchbase) { %>, CouchbaseSearchRepository<<%= asEntity('User') %>, String><% } } else { %> CoroutineSortingRepository<<%= asEntity('User') %>, String> <% } %> { <%_ if (!authenticationTypeOauth2) { _%> <%_ if (databaseTypeCouchbase) { _%> @JvmDefault <%_ } _%> suspend fun findOneByActivationKey(activationKey: String): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { - return findIdByActivationKey(activationKey) - .map(User::id) - .flatMap(this::findById); + return findIdByActivationKey(activationKey)?.id?.let { findById(it) } } @Query(FIND_IDS_QUERY + " AND activationKey = $1") suspend fun findIdByActivationKey(activationKey: String): <%= asEntity('User') %>? @@ -132,7 +128,7 @@ interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, St } @Query(FIND_IDS_QUERY + " AND activated = false AND activationKey IS NOT NULL AND createdDate < $1") - fun findAllIdsByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime: Instant): Flow<%= asEntity('User') %> + fun findAllIdsByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(dateTime: Instant): Flow<<%= asEntity('User') %>> <% } %> <%_ } _%> @@ -141,9 +137,7 @@ interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, St @JvmDefault <%_ } _%> suspend fun findOneByResetKey(resetKey: String): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { - return findIdByResetKey(resetKey) - .map(User::id) - .flatMap(this::findById) + return findIdByResetKey(resetKey)?.id?.let { findById(it) } } @Query(FIND_IDS_QUERY + " AND resetKey = $1") @@ -161,9 +155,7 @@ interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, St @JvmDefault <%_ } _%> suspend fun findOneByEmailIgnoreCase(email: String?): <%= asEntity('User') %>?<% if (databaseTypeCouchbase) { %> { - return findIdByEmailIgnoreCase(email!!) - .map(User::id) - .flatMap(this::findById); + return findIdByEmailIgnoreCase(email!!)?.id?.let { findById(it) } } @Query(FIND_IDS_QUERY + " AND LOWER(email) = LOWER($1)") @@ -186,10 +178,6 @@ interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, St suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? <%_ } else { _%> suspend fun findOneByLogin(login: String): <%= asEntity('User') %>? - <%_ } _%> - - <%_ if (databaseTypeNeo4j) { _%> - override fun findAll(): Flow<%= asEntity('User') %> <%_ } _%> <%_ if (!databaseTypeCouchbase) { _%> @@ -219,7 +207,6 @@ interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, St <%_ } _%> } <%_ } else if (databaseTypeSql) { _%> -@Repository interface UserRepository: CoroutineSortingRepository<<%= asEntity('User') %>, <% if (authenticationTypeOauth2) { %>String<% } else { %>Long<% } %>>, UserRepositoryInternal { <%_ if (!authenticationTypeOauth2) { _%> @@ -323,7 +310,7 @@ class UserRepositoryInternalImpl(val db: DatabaseClient, val r2dbcEntityTemplate } <%_ } _%> - private fun findOneWithAuthoritiesBy(fieldName: String, fieldValue: Any): <%= asEntity('User') %>? { + private suspend fun findOneWithAuthoritiesBy(fieldName: String, fieldValue: Any): <%= asEntity('User') %>? { return db.sql("SELECT * FROM <%= jhiTablePrefix %>_user u LEFT JOIN <%= jhiTablePrefix %>_user_authority ua ON u.id=ua.user_id WHERE u.$fieldName = :$fieldName") .bind(fieldName, fieldValue) .map { row, metadata -> @@ -335,7 +322,7 @@ class UserRepositoryInternalImpl(val db: DatabaseClient, val r2dbcEntityTemplate .collectList() .filter { it.isNotEmpty() } .map { l -> updateUserWithAuthorities(l[0].t1, l) } - .block() + .awaitSingleOrNull() } private fun updateUserWithAuthorities(user: <%= asEntity('User') %>, tuples: List, Optional>>): <%= asEntity('User') %> { @@ -350,6 +337,8 @@ class UserRepositoryInternalImpl(val db: DatabaseClient, val r2dbcEntityTemplate } <%_ } else if (databaseTypeCassandra) { _%> +import org.springframework.stereotype.Repository + @Repository class UserRepository( private val cqlTemplate: ReactiveCassandraTemplate, diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs index 63427fed8..7074c5eab 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/search/UserSearchRepository.kt.ejs @@ -19,56 +19,33 @@ package <%= packageName %>.repository.search import <%= packageName %>.domain.<%= asEntity('User') %> -<%_ if (reactive) { _%> +import <%= packageName %>.config.SearchRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.reactive.asFlow import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate -<%_ } else { _%> -import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate -<%_ } _%> import org.springframework.data.elasticsearch.core.query.NativeSearchQuery import org.springframework.data.elasticsearch.core.SearchHit -import org.springframework.data.elasticsearch.repository.<% if (reactive) {%>Reactive<% } %>ElasticsearchRepository -<%_ if (reactive) { _%> -import reactor.core.publisher.Flux -<%_ } else { _%> -import java.util.stream.Stream -<%_ } _%> <%_ if (databaseTypeCassandra) { _%> import java.util.UUID <%_ } _%> - import org.elasticsearch.index.query.QueryBuilders.queryStringQuery +import org.springframework.data.repository.kotlin.CoroutineSortingRepository /** * Spring Data Elasticsearch repository for the User entity. */ -interface UserSearchRepository : <% if (reactive) {%>Reactive<% } %>ElasticsearchRepository<<%= asEntity('User') %>, <% if (databaseTypeSql && !authenticationTypeOauth2) { %>Long<% } %><% if ((databaseTypeCassandra || databaseTypeMongodb || databaseTypeNeo4j) || authenticationTypeOauth2) { %>String<% } %>>, UserSearchRepositoryInternal +@SearchRepository +interface UserSearchRepository : CoroutineSortingRepository<<%= asEntity('User') %>, <% if (databaseTypeSql && !authenticationTypeOauth2) { %>Long<% } %><% if ((databaseTypeCassandra || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) || authenticationTypeOauth2) { %>String<% } %>>, UserSearchRepositoryInternal -<%_ if (reactive) { _%> interface UserSearchRepositoryInternal { - fun search(query: String): Flux + fun search(query: String): Flow } class UserSearchRepositoryInternalImpl(private val reactiveElasticsearchTemplate: ReactiveElasticsearchTemplate) : UserSearchRepositoryInternal { - override fun search(query: String): Flux { + override fun search(query: String): Flow { val nativeSearchQuery = NativeSearchQuery(queryStringQuery(query)) return reactiveElasticsearchTemplate.search(nativeSearchQuery, User::class.java) - .map(SearchHit::getContent) - } -} -<%_ } else { _%> - -interface UserSearchRepositoryInternal { - fun search(query: String): List -} - -class UserSearchRepositoryInternalImpl(private val elasticsearchTemplate: ElasticsearchRestTemplate): UserSearchRepositoryInternal { - - override fun search(query: String): List { - val nativeSearchQuery = NativeSearchQuery(queryStringQuery(query)) - return elasticsearchTemplate - .search(nativeSearchQuery, User::class.java) - .map(SearchHit::getContent) - .toList() + .map(SearchHit::getContent) + .asFlow() } } -<%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs index ab5bdc796..87fcd2404 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/service/UserService.kt.ejs @@ -513,7 +513,7 @@ class UserService<% if (!databaseTypeNo) { %>( @Transactional(readOnly = true) <%_ } _%> suspend fun getAuthorities() = - authorityRepository.findAll().map{ it.name } + authorityRepository.findAll().mapNotNull { it.name } <%_ } _%> <%_ if (authenticationTypeOauth2) { _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs index 10b3ae434..aeebbb313 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs @@ -18,10 +18,11 @@ limitations under the License. -%> package <%= packageName %>.web.rest -<%_ if (authenticationTypeOauth2) { _%> import <%= packageName %>.security.getCurrentUserLogin +<%_ if (authenticationTypeOauth2) { _%> import <%= packageName %>.service.UserService import <%= packageName %>.service.dto.<%= asDto('AdminUser') %> +import kotlinx.coroutines.reactor.* import org.slf4j.LoggerFactory import org.springframework.security.authentication.AbstractAuthenticationToken @@ -80,13 +81,12 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.context.ReactiveSecurityContextHolder -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.userdetails.UserDetails import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono +import kotlinx.coroutines.reactor.* +import java.security.Principal @RestController @RequestMapping("/api") @@ -103,33 +103,12 @@ class AccountResource { * @throws AccountResourceException {@code 500 (Internal Server Error)} if the user couldn't be returned. */ @GetMapping("/account") - <%_ if (reactive) { _%> - fun getAccount(): Mono { - return ReactiveSecurityContextHolder.getContext() - .map { SecurityContext.getAuthentication(it) } - .map { authentication -> { - var login = "" - if (authentication.principal is UserDetails) { - login = authentication.principal.username - } else if (authentication.principal is String) { - login = authentication.principal - } else { - throw AccountResourceException() - } - val authorities = authentication.authorities() - .map { GrantedAuthority.getAuthority(it) } - .toSet() - return UserVM(login, authorities) - }} - .switchIfEmpty(Mono.error(AccountResourceException())) - <%_ } else { _%> - fun getAccount(): UserVM { - val login = getCurrentUserLogin() - .orElseThrow { AccountResourceException() } - val authorities = SecurityContextHolder.getContext().authentication.authorities - .mapNotNullTo(mutableSetOf()) { it.authority } + suspend fun getAccount(): UserVM { + val login = getCurrentUserLogin().awaitSingleOrNull() ?: throw AccountResourceException() + val authorities = ReactiveSecurityContextHolder.getContext() + .awaitSingle().authentication.authorities + .mapNotNullTo(mutableSetOf()) { it.authority } return UserVM(login, authorities) - <%_ } _%> } /** @@ -139,15 +118,9 @@ class AccountResource { * @return the login if the user is authenticated. */ @GetMapping("/authenticate") - <%_ if (reactive) { _%> - fun isAuthenticated(request: ServerWebExchange): Mono { - log.debug("REST request to check if the current user is authenticated") - return request.getPrincipal().map(Principal::getName) - <%_ } else { _%> - fun isAuthenticated(request: HttpServletRequest): String? { + suspend fun isAuthenticated(request: ServerWebExchange): String? { log.debug("REST request to check if the current user is authenticated") - return request.remoteUser - <%_ } _%> + return request.getPrincipal().map(Principal::getName).awaitSingleOrNull() } data class UserVM @JsonCreator constructor(val login: String, val authorities: Set) { @@ -157,7 +130,6 @@ class AccountResource { } <%_ } else { _%> import <%= packageName %>.repository.UserRepository -import <%= packageName %>.security.getCurrentUserLogin import <%= packageName %>.service.MailService import <%= packageName %>.service.UserService import <%= packageName %>.service.dto.PasswordChangeDTO diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs index c5cb29aac..51c5c9bbb 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/PublicUserResource.kt.ejs @@ -81,7 +81,7 @@ class PublicUserResource( */ @GetMapping("/users") <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> - suspend fun getAllPublicUsers(request: ServerHttpRequest, @org.springdoc.api.annotations.ParameterObject pageable: Pageable): ResponseEntity>> { + suspend fun getAllPublicUsers(request: ServerHttpRequest, @org.springdoc.api.annotations.ParameterObject pageable: Pageable): ResponseEntity>> { log.debug("REST request to get all public User names") <%_ if (!authenticationTypeOauth2) { _%> if (!onlyContainsAllowedProperties(pageable)) { @@ -91,7 +91,7 @@ class PublicUserResource( return PageImpl<<%= asDto('User') %>>(listOf(), pageable, userService.countManagedUsers()) .let { PaginationUtil.generatePaginationHttpHeaders(UriComponentsBuilder.fromHttpRequest(request), it) } - .let { ResponseEntity.ok().headers(it).body(userService.getAllPublicUsers(pageable)) } + .let { ResponseEntity.ok().headers(it).body(userService.getAllPublicUsers(pageable).toList().toTypedArray()) } } <%_ if (!authenticationTypeOauth2) { _%> private fun onlyContainsAllowedProperties(pageable: Pageable) = @@ -103,10 +103,11 @@ class PublicUserResource( * @return a string list of all roles. */ @GetMapping("/authorities") - suspend fun getAuthorities() = userService.getAuthorities() + suspend fun getAuthorities() = userService.getAuthorities().toList().toTypedArray() <%_ } else { /* Cassandra */ _%> - suspend fun getAllPublicUsers(): Flow<<%= asDto('User') %>> = userService.getAllPublicUsers() + suspend fun getAllPublicUsers(): Array<<%= asDto('User') %>> = + userService.getAllPublicUsers().toList().toTypedArray() <%_ } _%> @@ -119,21 +120,11 @@ class PublicUserResource( * @return the result of the search. */ @GetMapping("/_search/users/{query}") - fun search(@PathVariable query: String): <% if(reactive) { %>MonoList<<%= asDto('User') %>><% if(reactive) { %>><% } %> { - <%/* TODO fix this */%> + suspend fun search(@PathVariable query: String): Array<<%= asDto('User') %>> { <%_ if (searchEngineElasticsearch) { _%> - <%_ if (reactive) { _%> - return userSearchRepository.search(query).map { <%= asDto('User') %>(it) }.collectList() - <%_ } else { _%> - return userSearchRepository.search(query).map { <%= asDto('User') %>(it) } - <%_ } _%> + return userSearchRepository.search(query).map { <%= asDto('User') %>(it) }.toList().toTypedArray() <%_ } else { _%> - <%_ if (reactive) { _%> - return userRepository.search(query).map { <%= asDto('User') %>(it) }.collectList() - <%_ } else { _%> - return userRepository.search(query) - .map { <%= asDto('User') %>(it) } - <%_ } _%> + return userRepository.search(query).map { <%= asDto('User') %>(it) }.toList().toTypedArray() <%_ } _%> } <%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs index 7ab93817b..2f5dea73a 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserJWTController.kt.ejs @@ -49,14 +49,12 @@ class UserJWTController( @PostMapping("/authenticate") suspend fun authorize(@Valid @RequestBody loginVM: LoginVM): ResponseEntity { - return authenticationManager.authenticate(UsernamePasswordAuthenticationToken(loginVM.username, loginVM.password)) - .map { tokenProvider.createToken(it, loginVM.isRememberMe ?: false) } - .map { jwt -> - val httpHeaders = HttpHeaders() - httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer $jwt") - ResponseEntity(JWTToken(jwt), httpHeaders, HttpStatus.OK) - } - .awaitSingle() + val authenticationToken = UsernamePasswordAuthenticationToken(loginVM.username, loginVM.password) + val authentication = authenticationManager.authenticate(authenticationToken).awaitSingle() + val jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe ?: false) + val httpHeaders = HttpHeaders() + httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer $jwt") + return ResponseEntity(JWTToken(jwt), httpHeaders, HttpStatus.OK) } /** diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs index bb858e34c..3d65d51c4 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs @@ -35,7 +35,7 @@ import tech.jhipster.web.util.HeaderUtil import tech.jhipster.web.util.PaginationUtil <%_ } _%> import tech.jhipster.web.util.ResponseUtil -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.toList import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCouchbase) { _%> @@ -46,16 +46,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.http.server.reactive.ServerHttpRequest import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.PutMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* import org.springframework.web.server.ResponseStatusException import org.springframework.web.util.UriComponentsBuilder import reactor.core.publisher.Flux @@ -124,9 +115,15 @@ class UserResource( suspend fun createUser(@Valid @RequestBody userDTO: <%= asDto('AdminUser') %>): ResponseEntity<<%= asEntity('User') %>> { log.debug("REST request to save User : $userDTO") - userDTO.id ?: throw BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists") - userRepository.findOneByLogin(userDTO.login!!.lowercase()) ?: throw LoginAlreadyUsedException() - userRepository.findOneByEmailIgnoreCase(userDTO.email!!) ?: throw EmailAlreadyUsedException() + userDTO.id?.apply { + throw BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists") + } + userRepository.findOneByLogin(userDTO.login!!.lowercase())?.apply { + throw LoginAlreadyUsedException() + } + userRepository.findOneByEmailIgnoreCase(userDTO.email!!)?.apply { + throw EmailAlreadyUsedException() + } val newUser = userService.createUser(userDTO)!! mailService.sendCreationEmail(newUser) @@ -174,13 +171,13 @@ class UserResource( suspend fun getAllUsers( request: ServerHttpRequest, @org.springdoc.api.annotations.ParameterObject pageable: Pageable - ): ResponseEntity>> { + ): ResponseEntity>> { log.debug("REST request to get all User for an admin") if (!onlyContainsAllowedProperties(pageable)) { return ResponseEntity.badRequest().build() } val total = userService.countManagedUsers() - val content = userService.getAllManagedUsers(pageable) + val content = userService.getAllManagedUsers(pageable).toList().toTypedArray() val page = PageImpl(mutableListOf<<%= asDto('AdminUser') %>>(), pageable, total) val headers = PaginationUtil.generatePaginationHttpHeaders(UriComponentsBuilder.fromHttpRequest(request), page) return ResponseEntity(content, headers, HttpStatus.OK) @@ -190,7 +187,7 @@ class UserResource( pageable.sort.map(Sort.Order::getProperty).all { ALLOWED_ORDERED_PROPERTIES.contains(it) } <%_ } else { // Cassandra _%> - suspend fun getAllUsers() = userService.getAllManagedUsers() + suspend fun getAllUsers() = userService.getAllManagedUsers().toList().toTypedArray() <%_ } _%> /** @@ -206,7 +203,7 @@ class UserResource( return ResponseUtil.wrapOrNotFound( userService.getUserWithAuthoritiesByLogin(login) ?.let { AdminUserDTO(it) } - ?.let { Optional.ofNullable(it) } + .let { Optional.ofNullable(it) } ) } diff --git a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_couchbase.kt.ejs b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_couchbase.kt.ejs index c89bea82e..08a001148 100644 --- a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_couchbase.kt.ejs +++ b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_couchbase.kt.ejs @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.* +import org.springframework.context.annotation.ComponentScan.Filter import org.springframework.core.convert.TypeDescriptor import org.springframework.core.convert.converter.Converter import org.springframework.core.convert.converter.GenericConverter @@ -41,10 +42,12 @@ import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversion import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener +import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository <%_ if (!reactive) { _%> import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing <%_ } _%> import org.springframework.data.couchbase.repository.config.Enable<%_ if (reactive) { _%>Reactive<% } %>CouchbaseRepositories +import org.springframework.data.repository.kotlin.CoroutineSortingRepository import org.springframework.data.repository.util.QueryExecutionConverters import org.springframework.util.StringUtils import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean @@ -58,6 +61,10 @@ import java.time.ZonedDateTime import java.time.Duration import java.util.* +<% if(searchEngineElasticsearch) { %> +annotation class SearchRepository +<% } %> + @Configuration @EnableConfigurationProperties(CouchbaseProperties::class) <%_ if (searchEngineElasticsearch) { _%> @@ -65,7 +72,13 @@ import java.util.* <%_ } _%> @Profile("!" + JHipsterConstants.SPRING_PROFILE_CLOUD) @Enable<% if (reactive) { %>Reactive<% } %>CouchbaseRepositories(basePackages = ["<%= packageName %>.repository"]<%_ if (searchEngineElasticsearch) { %>, - includeFilters = [Filter(type = FilterType.ASSIGNABLE_TYPE, value = [<% if (reactive) { %>Reactive<% } %>CouchbaseRepository::class])]<%_ } _%>) + includeFilters = [ + Filter(type = FilterType.ASSIGNABLE_TYPE, value = [<% if (reactive) { %>CoroutineSortingRepository::class, Reactive<% } %>CouchbaseRepository::class])], + excludeFilters = [ + Filter(type = FilterType.ANNOTATION, value = [SearchRepository::class]) + ] +<%_ } _%> +) <%_ if (!reactive) { _%> @EnableCouchbaseAuditing(auditorAwareRef = "springSecurityAuditorAware", dateTimeProviderRef = "") <%_ } _%> @@ -77,7 +90,7 @@ class DatabaseConfiguration( private val CHANGELOG_COLLECTION = "changelog" private val TYPE_KEY = "type" private val log = LoggerFactory.getLogger(javaClass) - + <%_ if (reactive) { _%> init { allowPageable() @@ -109,7 +122,7 @@ class DatabaseConfiguration( override fun getUserName() = couchbaseProperties.username - + override fun getPassword() = couchbaseProperties.password @@ -135,7 +148,7 @@ class DatabaseConfiguration( StringToUUIDConverter ) ) - + override fun mappingCouchbaseConverter(couchbaseMappingContext: CouchbaseMappingContext, couchbaseCustomConversions: CouchbaseCustomConversions): MappingCouchbaseConverter { val mappingCouchbaseConverter = super.mappingCouchbaseConverter(couchbaseMappingContext, couchbaseCustomConversions) (mappingCouchbaseConverter.conversionService as (GenericConversionService)).addConverter(StringToObjectConverter) diff --git a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_mongodb.kt.ejs b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_mongodb.kt.ejs index 82df4d48c..e45ac886a 100644 --- a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_mongodb.kt.ejs +++ b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_mongodb.kt.ejs @@ -47,10 +47,15 @@ import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventL import org.springframework.data.mongodb.repository.<% if (reactive) { %>Reactive<% } %>MongoRepository <%_ } _%> import org.springframework.data.mongodb.repository.config.Enable<% if (reactive) { %>Reactive<% } %>MongoRepositories +import org.springframework.data.repository.kotlin.CoroutineSortingRepository import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean import java.util.ArrayList import java.util.List +<% if(searchEngineElasticsearch) { %> + annotation class SearchRepository +<% } %> + @Configuration @EnableMongock <%_ if (searchEngineElasticsearch) { _%> @@ -58,7 +63,10 @@ import java.util.List @Enable<% if (reactive) { %>Reactive<% } %>MongoRepositories( basePackages = ["<%= packageName %>.repository"], includeFilters = [ - Filter(type=FilterType.ASSIGNABLE_TYPE,value = [<% if (reactive) { %>Reactive<% } %>MongoRepository::class]) + Filter(type=FilterType.ASSIGNABLE_TYPE,value = [<% if (reactive) { %>CoroutineSortingRepository::class, Reactive<% } %>MongoRepository::class]) + ], + excludeFilters = [ + Filter(type = FilterType.ANNOTATION, value = [SearchRepository::class]) ] ) <%_ } else { _%> diff --git a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_neo4j.kt.ejs b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_neo4j.kt.ejs index 12877bad0..520e5800d 100644 --- a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_neo4j.kt.ejs +++ b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_neo4j.kt.ejs @@ -32,6 +32,7 @@ import org.springframework.data.neo4j.core.<% if (reactive) { %>Reactive<% } %>D import org.springframework.data.neo4j.core.transaction.<% if (reactive) { %>Reactive<% } %>Neo4jTransactionManager import org.springframework.data.neo4j.repository.config.Enable<% if (reactive) { %>Reactive<% } %>Neo4jRepositories +import org.springframework.data.repository.kotlin.CoroutineSortingRepository import org.springframework.data.neo4j.repository.config.<% if (reactive) { %>Reactive<% } %>Neo4jRepositoryConfigurationExtension <%_ if (searchEngineElasticsearch) { _%> @@ -40,9 +41,21 @@ import org.springframework.data.neo4j.repository.<% if (reactive) { %>Reactive<% import org.springframework.transaction.<% if (reactive) { %>Reactive<% } %>TransactionManager +<% if(searchEngineElasticsearch) { %> + annotation class SearchRepository +<% } %> + @Configuration <%_ if (searchEngineElasticsearch) { _%> -@Enable<% if (reactive) { %>Reactive<% } %>Neo4jRepositories(basePackages = "<%= packageName %>.repository", includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value =[<% if (reactive) { %>Reactive<% } %>Neo4jRepository::class])) +@Enable<% if (reactive) { %>Reactive<% } %>Neo4jRepositories( + basePackages = ["<%= packageName %>.repository"], + includeFilters = [ + Filter(type = FilterType.ASSIGNABLE_TYPE, value =[<% if (reactive) { %>Reactive<% } %>Neo4jRepository::class, CoroutineSortingRepository::class]) + ], + excludeFilters = [ + Filter(type = FilterType.ANNOTATION, value = [SearchRepository::class]) + ] +) <%_ } else { _%> @Enable<% if (reactive) { %>Reactive<% } %>Neo4jRepositories("<%= packageName %>.repository") <%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_sql.kt.ejs b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_sql.kt.ejs index 7c7b13215..e18ddd73f 100644 --- a/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_sql.kt.ejs +++ b/generators/server/templates/src/main/kotlin/package/config/DatabaseConfiguration_sql.kt.ejs @@ -82,6 +82,10 @@ import java.util.UUID <%_ } _%> <%_ } _%> +<% if(searchEngineElasticsearch) { %> + annotation class SearchRepository +<% } %> + @Configuration <%_ if (reactive) { _%> @EnableR2dbcRepositories(<%- domains.map(domain => `"${domain}.repository"`).join(', ') %>) From c309309350fc348933860b1d1b82323791875490 Mon Sep 17 00:00:00 2001 From: Piper Han Date: Tue, 6 Dec 2022 16:48:31 +0800 Subject: [PATCH 4/4] Finish server test --- .../server/templates/gradle/kotlin.gradle.ejs | 4 +- .../repository/UserRepository.kt.ejs | 11 + .../security/DomainUserDetailsService.kt.ejs | 20 +- .../coroutine/web/rest/AccountResource.kt.ejs | 2 +- .../coroutine/web/rest/UserResource.kt.ejs | 24 +- .../DomainUserDetailsServiceIT.kt.ejs | 25 +- .../package/service/UserServiceIT.kt.ejs | 123 ++++---- .../package/web/rest/AccountResourceIT.kt.ejs | 268 +++++++++--------- .../web/rest/PublicUserResourceIT.kt.ejs | 19 +- .../web/rest/UserJWTControllerIT.kt.ejs | 31 +- .../package/web/rest/UserResourceIT.kt.ejs | 105 ++++--- 11 files changed, 323 insertions(+), 309 deletions(-) diff --git a/generators/server/templates/gradle/kotlin.gradle.ejs b/generators/server/templates/gradle/kotlin.gradle.ejs index 2925cac22..2555a736b 100644 --- a/generators/server/templates/gradle/kotlin.gradle.ejs +++ b/generators/server/templates/gradle/kotlin.gradle.ejs @@ -49,8 +49,10 @@ dependencies { kapt "com.datastax.oss:java-driver-mapper-processor:${cassandraDriverVersion}" <%_ } _%> + <% if (jhipsterConfig.coroutine) { /* TODO change version */ %> + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" + <% } %> testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}" - testImplementation "org.mockito.kotlin:mockito-kotlin:<%= MOCKITO_KOTLIN_VERSION %>" }<%_ if (databaseTypeSql) { %> diff --git a/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs index 53ba3b5ca..37787142b 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/repository/UserRepository.kt.ejs @@ -56,6 +56,7 @@ import org.springframework.data.relational.core.query.Criteria.where import org.springframework.data.relational.core.query.Query.query <%_ } _%> import org.springframework.data.repository.kotlin.CoroutineSortingRepository +import reactor.core.publisher.Mono <%_ if (databaseTypeCouchbase) { _%> import org.springframework.data.couchbase.repository.Query <%_ } _%> @@ -163,6 +164,11 @@ interface UserRepository: <% if (databaseTypeCouchbase) { %>JHipsterCouchbaseRep <% } %> <%_ } _%> + // for DomainUserDetailService + fun getOneByEmailIgnoreCase(email: String): Mono + + fun getOneByLogin(email: String): Mono + <%_ if (databaseTypeCouchbase) { _%> <%_ if (cacheManagerIsAvailable) { _%> @Cacheable(cacheNames = [USERS_BY_LOGIN_CACHE]) @@ -447,6 +453,11 @@ class UserRepository( return findOneFromIndex(stmt) } + // for DomainUserDetailService + fun getOneByEmailIgnoreCase(email: String): Mono + + fun getOneByLogin(email: String): Mono + <%_ if (cacheManagerIsAvailable) { _%> @Cacheable(cacheNames = [USERS_BY_LOGIN_CACHE]) <%_ } _%> diff --git a/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs index 5552b833f..0445cef17 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/security/DomainUserDetailsService.kt.ejs @@ -54,25 +54,19 @@ class DomainUserDetailsService(private val userRepository: UserRepository) : Rea log.debug("Authenticating $login") if (EmailValidator().isValid(login, null)) { - return runBlocking { - userRepository.<% if (databaseTypeSql) { %>findOneWithAuthoritiesByEmailIgnoreCase<% } else { %>findOneByEmailIgnoreCase<% } %>(login) - ?.let { createSpringSecurityUser(login, it) } - ?.toMono() - ?: throw UsernameNotFoundException("User with email $login was not found in the database") - } + return userRepository.getOne<% if (databaseTypeSql) { %>WithAuthoritiesByEmailIgnoreCase<% } else { %>ByEmailIgnoreCase<% } %>(login) + .map { createSpringSecurityUser(login, it) } + .switchIfEmpty(Mono.error(UsernameNotFoundException("User with email $login was not found in the database"))) } val lowercaseLogin = login.lowercase(Locale.ENGLISH) - return runBlocking { - userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(lowercaseLogin) - ?.let { createSpringSecurityUser(lowercaseLogin, it) } - ?.toMono() - ?: throw UsernameNotFoundException("User $lowercaseLogin was not found in the database") - } + return userRepository.getOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(lowercaseLogin) + .map { createSpringSecurityUser(lowercaseLogin, it) } + .switchIfEmpty(Mono.error(UsernameNotFoundException("User $lowercaseLogin was not found in the database"))) } private fun createSpringSecurityUser(lowercaseLogin: String, user: <%= asEntity('User') %>) - : org.springframework.security.core.userdetails.User { + : org.springframework.security.core.userdetails.UserDetails { if (user.activated == null || user.activated == false) { throw UserNotActivatedException("User $lowercaseLogin was not activated") } diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs index aeebbb313..73c26151e 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/AccountResource.kt.ejs @@ -212,7 +212,7 @@ class AccountResource( @GetMapping("/authenticate") suspend fun isAuthenticated(request: ServerWebExchange): String? { log.debug("REST request to check if the current user is authenticated") - return request.getPrincipal().awaitSingle().name + return request.getPrincipal().map(Principal::getName).awaitSingleOrNull() } /** diff --git a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs index 3d65d51c4..0c26afbc5 100644 --- a/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs +++ b/generators/server/templates/src/main/kotlin/coroutine/web/rest/UserResource.kt.ejs @@ -144,18 +144,20 @@ class UserResource( @PreAuthorize("hasAuthority(\"$ADMIN\")") suspend fun updateUser(@Valid @RequestBody userDTO: <%= asDto('AdminUser') %>): ResponseEntity<<%= asDto('AdminUser') %>> { log.debug("REST request to update User : $userDTO") - - when{ - userRepository.findOneByEmailIgnoreCase(userDTO.email!!)?.id != userDTO.id -> throw EmailAlreadyUsedException() - userRepository.findOneByLogin(userDTO.login!!.lowercase())?.id != userDTO.id -> throw LoginAlreadyUsedException() - else -> { - val updatedUser = userService.updateUser(userDTO).let { Optional.ofNullable(it) } - return ResponseUtil.wrapOrNotFound( - updatedUser, - HeaderUtil.createAlert(applicationName, <% if (enableTranslation) { %>"userManagement.updated"<% } else { %>"A user is updated with identifier $userDTO.login"<% } %>, userDTO.login) - ) - } + var existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.email!!) + if (existingUser != null && existingUser.id != userDTO.id) { + throw EmailAlreadyUsedException() + } + existingUser = userRepository.findOneByLogin(userDTO.login!!.lowercase()) + if (existingUser != null && existingUser.id != userDTO.id) { + throw LoginAlreadyUsedException() } + + val updatedUser = userService.updateUser(userDTO) + return ResponseUtil.wrapOrNotFound( + Optional.ofNullable(updatedUser), + HeaderUtil.createAlert(applicationName, "userManagement.updated", userDTO.login) + ) } /** diff --git a/generators/server/templates/src/test/kotlin/package/security/DomainUserDetailsServiceIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/security/DomainUserDetailsServiceIT.kt.ejs index ab821fd29..a7ccbca59 100644 --- a/generators/server/templates/src/test/kotlin/package/security/DomainUserDetailsServiceIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/security/DomainUserDetailsServiceIT.kt.ejs @@ -50,6 +50,7 @@ import java.util.Locale import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType +import kotlinx.coroutines.test.runTest private const val USER_ONE_LOGIN = "test-user-one" private const val USER_ONE_EMAIL = "test-user-one@localhost" @@ -78,13 +79,13 @@ class DomainUserDetailsServiceIT { private lateinit var domainUserDetailsService: <% if (reactive) { %>Reactive<% } %>UserDetailsService @BeforeEach - fun init() { + fun init()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { <%_ if (databaseTypeSql && reactive) { _%> userRepository.deleteAllUserAuthorities().block() <%_ } _%> <%_ if (!databaseTypeSql || reactive) { _%> - userRepository.deleteAll()<% if (reactive) { %>.block()<% } %> - + userRepository.deleteAll()<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + <%_ } _%> val userOne = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -101,7 +102,7 @@ class DomainUserDetailsServiceIT { <%_ } _%> langKey = "en" ) - userRepository.save(userOne)<% if (reactive) { %>.block()<% } %> + userRepository.save(userOne)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userTwo = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -118,7 +119,7 @@ class DomainUserDetailsServiceIT { <%_ } _%> langKey = "en" ) - userRepository.save(userTwo)<% if (reactive) { %>.block()<% } %> + userRepository.save(userTwo)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userThree = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -135,46 +136,46 @@ class DomainUserDetailsServiceIT { <%_ } _%> langKey = "en" ) - userRepository.save(userThree)<% if (reactive) { %>.block()<% } %> + userRepository.save(userThree)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } @Test - fun assertThatUserCanBeFoundByLogin() { + fun assertThatUserCanBeFoundByLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDetails = domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_ONE_LOGIN)<% if (reactive) { %>.block()<% } %> assertThat(userDetails).isNotNull assertThat(userDetails.username).isEqualTo(USER_ONE_LOGIN) } @Test - fun assertThatUserCanBeFoundByLoginIgnoreCase() { + fun assertThatUserCanBeFoundByLoginIgnoreCase()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDetails = domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_ONE_LOGIN.toUpperCase(Locale.ENGLISH))<% if (reactive) { %>.block()<% } %> assertThat(userDetails).isNotNull assertThat(userDetails.username).isEqualTo(USER_ONE_LOGIN) } @Test - fun assertThatUserCanBeFoundByEmail() { + fun assertThatUserCanBeFoundByEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDetails = domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_TWO_EMAIL)<% if (reactive) { %>.block()<% } %> assertThat(userDetails).isNotNull assertThat(userDetails.username).isEqualTo(USER_TWO_LOGIN) } @Test - fun assertThatUserCanBeFoundByEmailIgnoreCase() { + fun assertThatUserCanBeFoundByEmailIgnoreCase()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDetails = domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_TWO_EMAIL.toUpperCase(Locale.ENGLISH))<% if (reactive) { %>.block()<% } %> assertThat(userDetails).isNotNull assertThat(userDetails.username).isEqualTo(USER_TWO_LOGIN) } @Test - fun assertThatEmailIsPrioritizedOverLogin() { + fun assertThatEmailIsPrioritizedOverLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDetails = domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_ONE_EMAIL)<% if (reactive) { %>.block()<% } %> assertThat(userDetails).isNotNull assertThat(userDetails.username).isEqualTo(USER_ONE_LOGIN) } @Test - fun assertThatUserNotActivatedExceptionIsThrownForNotActivatedUsers() { + fun assertThatUserNotActivatedExceptionIsThrownForNotActivatedUsers()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { assertThatExceptionOfType(UserNotActivatedException::class.java).isThrownBy { domainUserDetailsService.<% if (reactive) { %>find<% } else { %>loadUser<% } %>ByUsername(USER_THREE_LOGIN)<% if (reactive) { %>.block()<% } %> } diff --git a/generators/server/templates/src/test/kotlin/package/service/UserServiceIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/service/UserServiceIT.kt.ejs index 5b3663779..d7a5721a0 100644 --- a/generators/server/templates/src/test/kotlin/package/service/UserServiceIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/service/UserServiceIT.kt.ejs @@ -45,6 +45,9 @@ import <%= packageName %>.security.ANONYMOUS import tech.jhipster.security.RandomUtil <%_ } _%> +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest + <%_ if (!authenticationTypeOauth2) { _%> import org.apache.commons.lang3.RandomStringUtils <%_ } _%> @@ -118,7 +121,7 @@ import org.mockito.Mockito.`any` <%_ if (databaseTypeSql && !reactive && !authenticationTypeOauth2) { _%> import org.mockito.Mockito.`when` <%_ } _%> -import kotlin.test.assertNotNull +import kotlin.test.* private const val DEFAULT_LOGIN = "johndoe" private const val DEFAULT_EMAIL = "johndoe@localhost" @@ -178,7 +181,7 @@ class UserServiceIT { <%_ } _%> @BeforeEach - fun init() { + fun init()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { <%_ if (databaseTypeCouchbase) { _%> mockAuthentication() <%_ } _%> @@ -189,7 +192,7 @@ class UserServiceIT { userRepository.deleteAllUserAuthorities().block() <%_ } _%> <%_ if ((reactive && !databaseTypeNo) || databaseTypeMongodb || databaseTypeNeo4j || databaseTypeCassandra || databaseTypeCouchbase) { _%> - userRepository.deleteAll()<% if (reactive) { %>.block()<% } %> + userRepository.deleteAll()<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ } _%> <%_ if (!databaseTypeNo) { _%> user = <%= asEntity('User') %>( @@ -235,7 +238,7 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testRemoveOldPersistentTokens() { + fun testRemoveOldPersistentTokens()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userRepository.save<% if (databaseTypeSql) { %>AndFlush<% } %>(user) val existingCount = persistentTokenRepository.findByUser(user).size val today = LocalDate.now() @@ -252,101 +255,101 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatUserMustExistToResetPassword() { - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> - var maybeUser = userService.requestPasswordReset("invalid.login@localhost")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isNotPresent - - maybeUser = userService.requestPasswordReset(user.email!!)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isPresent - assertThat(maybeUser.orElse(null).email).isEqualTo(user.email) - assertThat(maybeUser.orElse(null).resetDate).isNotNull() - assertThat(maybeUser.orElse(null).resetKey).isNotNull() + fun assertThatUserMustExistToResetPassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + var maybeUser = userService.requestPasswordReset("invalid.login@localhost")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(maybeUser) + + maybeUser = userService.requestPasswordReset(user.email!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(maybeUser) + assertThat(maybeUser.email).isEqualTo(user.email) + assertThat(maybeUser.resetDate).isNotNull() + assertThat(maybeUser.resetKey).isNotNull() } @Test <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatOnlyActivatedUserCanRequestPasswordReset() { + fun assertThatOnlyActivatedUserCanRequestPasswordReset()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { user.activated = false - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> - val maybeUser = userService.requestPasswordReset(user.login!!)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isNotPresent - userRepository.delete(user)<% if (reactive) { %>.block()<% } %> + val maybeUser = userService.requestPasswordReset(user.login!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(maybeUser) + userRepository.delete(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } @Test <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatResetKeyMustNotBeOlderThan24Hours() { + fun assertThatResetKeyMustNotBeOlderThan24Hours()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val daysAgo = Instant.now().minus(25, ChronoUnit.HOURS) val resetKey = RandomUtil.generateResetKey() user.activated = true user.resetDate = daysAgo user.resetKey = resetKey - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> - val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isNotPresent - userRepository.delete(user)<% if (reactive) { %>.block()<% } %> + val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(maybeUser) + userRepository.delete(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } @Test <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatResetKeyMustBeValid() { + fun assertThatResetKeyMustBeValid()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val daysAgo = Instant.now().minus(25, ChronoUnit.HOURS) user.activated = true user.resetDate = daysAgo user.resetKey = "1234" - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> - val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isNotPresent - userRepository.delete(user)<% if (reactive) { %>.block()<% } %> + val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(maybeUser) + userRepository.delete(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } @Test <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatUserCanResetPassword() { + fun assertThatUserCanResetPassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val oldPassword = user.password val daysAgo = Instant.now().minus(2, ChronoUnit.HOURS) val resetKey = RandomUtil.generateResetKey() user.activated = true user.resetDate = daysAgo user.resetKey = resetKey - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> - val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeUser).isPresent - assertThat(maybeUser.orElse(null).resetDate).isNull() - assertThat(maybeUser.orElse(null).resetKey).isNull() - assertThat(maybeUser.orElse(null).password).isNotEqualTo(oldPassword) + val maybeUser = userService.completePasswordReset("johndoe2", user.resetKey!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(maybeUser) + assertThat(maybeUser.resetDate).isNull() + assertThat(maybeUser.resetKey).isNull() + assertThat(maybeUser.password).isNotEqualTo(oldPassword) - userRepository.delete(user)<% if (reactive) { %>.block()<% } %> + userRepository.delete(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } @Test <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatNotActivatedUsersWithNotNullActivationKeyCreatedBefore3DaysAreDeleted() { + fun assertThatNotActivatedUsersWithNotNullActivationKeyCreatedBefore3DaysAreDeleted()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val now = Instant.now() <%_ if (databaseTypeSql && !reactive) { _%> `when`>(dateTimeProvider.now).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))) <%_ } _%> user.activated = false user.activationKey = RandomStringUtils.random(20) - val dbUser = userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + val dbUser = userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertNotNull(dbUser) dbUser.createdDate = now.minus(4, ChronoUnit.DAYS) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (databaseTypeSql && reactive) { _%> val threeDaysAgo = LocalDateTime.ofInstant(now.minus(3, ChronoUnit.DAYS), ZoneOffset.UTC) <%_ } else { _%> @@ -354,13 +357,13 @@ class UserServiceIT { <%_ } _%> var users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore( threeDaysAgo - )<% if (reactive) { %>.collectList().block()<% } %> + )<% if (jhipsterConfig.coroutine) { %>.toList()<% } else if (reactive) { %>.collectList().block()<% } %> assertThat(users).isNotEmpty userService.removeNotActivatedUsers() users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore( threeDaysAgo - )<% if (reactive) { %>.collectList().block()<% } %> + )<% if (jhipsterConfig.coroutine) { %>.toList()<% } else if (reactive) { %>.collectList().block()<% } %> assertThat(users).isEmpty() <%_ if (searchEngineElasticsearch) { _%> @@ -373,15 +376,15 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun assertThatNotActivatedUsersWithNullActivationKeyCreatedBefore3DaysAreNotDeleted() { + fun assertThatNotActivatedUsersWithNullActivationKeyCreatedBefore3DaysAreNotDeleted()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val now = Instant.now() <%_ if (databaseTypeSql && !reactive) { _%> `when`(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS)<% if (databaseTypeSql && reactive) { %>.atOffset(ZoneOffset.UTC)<% } %>)) <%_ } _%> user.activated = false - val dbUser = userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + val dbUser = userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> dbUser.createdDate = now.minus(4, ChronoUnit.DAYS) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (databaseTypeSql && reactive) { _%> val threeDaysAgo = LocalDateTime.ofInstant(now.minus(3, ChronoUnit.DAYS), ZoneOffset.UTC) <%_ } else { _%> @@ -390,11 +393,11 @@ class UserServiceIT { val users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore( threeDaysAgo - )<% if (reactive) { %>.collectList().block()<% } %> + )<% if (jhipsterConfig.coroutine) { %>.toList()<% } else if (reactive) { %>.collectList().block()<% } %> assertThat(users).isEmpty() userService.removeNotActivatedUsers() - val maybeDbUser = userRepository.findById(dbUser.id)<% if (reactive) { %>.blockOptional()<% } %> - assertThat(maybeDbUser).contains(dbUser) + val maybeDbUser = userRepository.findById(dbUser.id!!)<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertThat(maybeDbUser)<% if (jhipsterConfig.coroutine) { %>.isEqualTo(dbUser)<% } else { %>.contains(dbUser)<% } %> <%_ if (searchEngineElasticsearch) { _%> // Verify Elasticsearch mock @@ -426,9 +429,9 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testDefaultUserDetails() { + fun testDefaultUserDetails()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.login).isEqualTo(DEFAULT_LOGIN) assertThat(userDTO.firstName).isEqualTo(DEFAULT_FIRSTNAME) @@ -444,10 +447,10 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testUserDetailsWithUsername() { + fun testUserDetailsWithUsername()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userDetails["preferred_username"] = "TEST" val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.login).isEqualTo("test") } @@ -455,11 +458,11 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testUserDetailsWithLangKey() { + fun testUserDetailsWithLangKey()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userDetails["langKey"] = DEFAULT_LANGKEY userDetails["locale"] = "en-US" val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.langKey).isEqualTo(DEFAULT_LANGKEY) } @@ -467,10 +470,10 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testUserDetailsWithLocale() { + fun testUserDetailsWithLocale()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userDetails["locale"] = "it-IT" val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.langKey).isEqualTo("it") } @@ -478,10 +481,10 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testUserDetailsWithUSLocaleUnderscore() { + fun testUserDetailsWithUSLocaleUnderscore()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userDetails["locale"] = "en_US" val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.langKey).isEqualTo("en") } @@ -489,10 +492,10 @@ class UserServiceIT { <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> - fun testUserDetailsWithUSLocaleDash() { + fun testUserDetailsWithUSLocaleDash()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { userDetails["locale"] = "en-US" val authentication = createMockOAuth2AuthenticationToken(userDetails) - val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive) { %>.block()<% } %> + val userDTO = userService.getUserFromAuthentication(authentication)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> assertThat(userDTO.langKey).isEqualTo("en") } diff --git a/generators/server/templates/src/test/kotlin/package/web/rest/AccountResourceIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/web/rest/AccountResourceIT.kt.ejs index f4e5d6894..a08a6666a 100644 --- a/generators/server/templates/src/test/kotlin/package/web/rest/AccountResourceIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/web/rest/AccountResourceIT.kt.ejs @@ -110,13 +110,11 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* <%_ } _%> <%_ if (databaseTypeCouchbase) { _%> import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultHandlers.exportTestSecurityContext -<%_ } _%> +<%_ } _%> -<%_ if (reactive) { _%> +import kotlin.test.* +import kotlinx.coroutines.test.runTest -import kotlin.test.assertNotNull -<%_ } _%> -import kotlin.test.assertContains <%_ if (databaseTypeSql && reactive) { _%> import <%= packageName %>.config.SYSTEM_ACCOUNT <%_ } _%> @@ -161,14 +159,14 @@ class AccountResourceIT { <%_ if (databaseTypeMongodb || databaseTypeNeo4j) { _%> @BeforeEach - fun setup() { - userRepository.deleteAll()<% if (reactive) { %>.block()<% } %> + fun setup()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { + userRepository.deleteAll()<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> } <%_ } _%> <%_ if (reactive && testsNeedCsrf) { _%> @BeforeEach - fun setupCsrf() { + fun setupCsrf()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { accountWebTestClient = accountWebTestClient.mutateWith(csrf()) } @@ -178,14 +176,14 @@ class AccountResourceIT { @WithUnauthenticatedMockUser @Throws(Exception::class) <%_ if (reactive) { _%> - fun testNonAuthenticatedUser() { + fun testNonAuthenticatedUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { accountWebTestClient.get().uri("/api/authenticate") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isOk .expectBody().isEmpty <%_ } else { _%> - fun testNonAuthenticatedUser() { + fun testNonAuthenticatedUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { restAccountMockMvc.perform( get("/api/authenticate") .accept(MediaType.APPLICATION_JSON) @@ -197,7 +195,7 @@ class AccountResourceIT { @Test <%_ if (reactive) { _%> - fun testAuthenticatedUser() { + fun testAuthenticatedUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { accountWebTestClient .get().uri("/api/authenticate") .accept(MediaType.APPLICATION_JSON) @@ -206,7 +204,7 @@ class AccountResourceIT { .expectBody().isEqualTo(TEST_USER_LOGIN) <%_ } else { _%> @Throws(Exception::class) - fun testAuthenticatedUser() { + fun testAuthenticatedUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { restAccountMockMvc.perform(get("/api/authenticate") .with { request -> request.remoteUser = TEST_USER_LOGIN @@ -222,7 +220,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testGetExistingAccount() { + fun testGetExistingAccount()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val authorities = mutableSetOf(ADMIN) val user = <%= asDto('AdminUser') %>( login = TEST_USER_LOGIN, @@ -235,7 +233,7 @@ class AccountResourceIT { langKey = "en", authorities = authorities ) - userService.createUser(user)<% if (reactive) { %>.block()<% } %> + userService.createUser(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.get().uri("/api/account") @@ -274,14 +272,14 @@ class AccountResourceIT { @Test <%_ if (reactive) { _%> - fun testGetUnknownAccount() { + fun testGetUnknownAccount()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { accountWebTestClient.get().uri("/api/account") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) <%_ } else { _%> @Throws(Exception::class) - fun testGetUnknownAccount() { + fun testGetUnknownAccount()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { restAccountMockMvc.perform(get("/api/account") .accept(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(status().isInternalServerError) @@ -291,7 +289,7 @@ class AccountResourceIT { @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterValid() { + fun testRegisterValid()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val validUser = ManagedUserVM().apply { login = "test-register-valid" password = "password" @@ -304,7 +302,7 @@ class AccountResourceIT { langKey = DEFAULT_LANGUAGE authorities = mutableSetOf(USER) } - assertThat(userRepository.findOneByLogin("test-register-valid")<% if (reactive) { %>.blockOptional()<% } %>).isEmpty + assertNull(userRepository.findOneByLogin("test-register-valid")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %>) <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/register") @@ -320,20 +318,20 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> - assertThat(userRepository.findOneByLogin("test-register-valid")<% if (reactive) { %>.blockOptional()<% } %>).isPresent + assertNotNull(userRepository.findOneByLogin("test-register-valid")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %>) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterInvalidLogin() { + fun testRegisterInvalidLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val invalidUser = ManagedUserVM().apply { login = "funky-log(n" // <-- invalid password = "password" - firstName = "Funky" + firstName = "funky" lastName = "One" email = "funky@example.com" activated = true @@ -361,17 +359,17 @@ class AccountResourceIT { .andDo(exportTestSecurityContext()) <%_ } _%> .andExpect(status().isBadRequest) - + <%_ } _%> - val user = userRepository.findOneByEmailIgnoreCase("funky@example.com")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(user).isEmpty + val user = userRepository.findOneByEmailIgnoreCase("funky@example.com")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(user) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterInvalidEmail() { + fun testRegisterInvalidEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val invalidUser = ManagedUserVM().apply { login = "bob" password = "password" @@ -400,17 +398,17 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isBadRequest) - + <%_ } _%> - val user = userRepository.findOneByLogin("bob")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(user).isEmpty + val user = userRepository.findOneByLogin("bob")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(user) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterInvalidPassword() { + fun testRegisterInvalidPassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val invalidUser = ManagedUserVM().apply { login = "bob" password = "123" // password with only 3 digits @@ -439,17 +437,17 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isBadRequest) - + <%_ } _%> - val user = userRepository.findOneByLogin("bob")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(user).isEmpty + val user = userRepository.findOneByLogin("bob")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(user) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterNullPassword() { + fun testRegisterNullPassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val invalidUser = ManagedUserVM().apply { login = "bob" password = null // invalid null password @@ -478,17 +476,17 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isBadRequest) - + <%_ } _%> - val user = userRepository.findOneByLogin("bob")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(user).isEmpty + val user = userRepository.findOneByLogin("bob")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(user) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterDuplicateLogin() { + fun testRegisterDuplicateLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // First registration val firstUser = ManagedUserVM().apply { login = "alice" @@ -538,7 +536,7 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> // Second (non activated) user @@ -559,13 +557,13 @@ class AccountResourceIT { .andDo(exportTestSecurityContext()) <%_ } _%> .andExpect(status().isCreated) - + <%_ } _%> - val testUser = userRepository.findOneByEmailIgnoreCase("alice2@example.com")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(testUser).isPresent - testUser.get().activated = true - userRepository.save(testUser.get())<% if (reactive) { %>.block()<% } %> + val testUser = userRepository.findOneByEmailIgnoreCase("alice2@example.com")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(testUser) + testUser.activated = true + userRepository.save(testUser)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> // Second (already activated) user <%_ if (reactive) { _%> @@ -582,14 +580,14 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().is4xxClientError) - + <%_ } _%> } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterDuplicateEmail() { + fun testRegisterDuplicateEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // First user val firstUser = ManagedUserVM().apply { login = "test-register-duplicate-email" @@ -619,11 +617,11 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> - val testUser1 = userRepository.findOneByLogin("test-register-duplicate-email")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(testUser1).isPresent + val testUser1 = userRepository.findOneByLogin("test-register-duplicate-email")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(testUser1) // Duplicate email, different login val secondUser = ManagedUserVM().apply { @@ -654,14 +652,14 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> - val testUser2 = userRepository.findOneByLogin("test-register-duplicate-email")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(testUser2).isEmpty + val testUser2 = userRepository.findOneByLogin("test-register-duplicate-email")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNull(testUser2) - val testUser3 = userRepository.findOneByLogin("test-register-duplicate-email-2")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(testUser3).isPresent + val testUser3 = userRepository.findOneByLogin("test-register-duplicate-email-2")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(testUser3) // Duplicate email - with uppercase email address val userWithUpperCaseEmail = ManagedUserVM().apply { @@ -693,15 +691,15 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> - val testUser4 = userRepository.findOneByLogin("test-register-duplicate-email-3")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(testUser4).isPresent - assertThat(testUser4.get().email).isEqualTo("test-register-duplicate-email@example.com") + val testUser4 = userRepository.findOneByLogin("test-register-duplicate-email-3")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(testUser4) + assertThat(testUser4.email).isEqualTo("test-register-duplicate-email@example.com") - testUser4.get().activated = true - userService.updateUser((<%= asDto('AdminUser') %>(testUser4.get())))<% if (reactive) { %>.block()<% } %> + testUser4.activated = true + userService.updateUser((<%= asDto('AdminUser') %>(testUser4)))<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> // Register 4th (already activated) user <%_ if (reactive) { _%> @@ -718,14 +716,14 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().is4xxClientError) - + <%_ } _%> } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testRegisterAdminIsIgnored() { + fun testRegisterAdminIsIgnored()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val validUser = ManagedUserVM().apply { login = "badguy" password = "password" @@ -754,13 +752,13 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isCreated) - + <%_ } _%> - val userDup = userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin("badguy")<% if (reactive) { %>.blockOptional()<% } %> - assertThat(userDup).isPresent - assertThat(userDup.get().authorities).hasSize(1) - assertContains(userDup.get().authorities, <% if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { %>authorityRepository.findById(USER).<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %><% if (databaseTypeCassandra || databaseTypeCouchbase) { %>USER<% } %>) + val userDup = userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin("badguy")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %> + assertNotNull(userDup) + assertThat(userDup.authorities).hasSize(1) + assertContains(userDup.authorities, <% if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { %>authorityRepository.findById(USER)<% if (!jhipsterConfig.coroutine) { %>.<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %><% } %><% if (databaseTypeCassandra || databaseTypeCouchbase) { %>USER<% } %>) } @Test<% if (databaseTypeSql && !reactive) { %> @@ -768,7 +766,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testActivateAccount() { + fun testActivateAccount()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val activationKey = "some activation key" var user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -784,7 +782,7 @@ class AccountResourceIT { activationKey = activationKey ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.get().uri("/api/activate?key={activationKey}", activationKey) @@ -795,7 +793,7 @@ class AccountResourceIT { .andExpect(status().isOk) <%_ } _%> - user = userRepository.findOneByLogin(user.login!!)<%_ if (reactive) { _%>.block()!!<% } else { %>.orElse(null)<% } %> + user = userRepository.findOneByLogin(user.login!!)<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %>!! assertThat(user.activated).isTrue } @@ -803,7 +801,7 @@ class AccountResourceIT { @Transactional<% } %> <%_ if (!reactive) { _%> @Throws(Exception::class)<% } %> - fun testActivateAccountWithWrongKey() { + fun testActivateAccountWithWrongKey()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { <%_ if (reactive) { _%> accountWebTestClient.get().uri("/api/activate?key=wrongActivationKey") .exchange() @@ -818,7 +816,7 @@ class AccountResourceIT { @Transactional<% } %> @WithMockUser("save-account") @Throws(Exception::class) - fun testSaveAccount() { + fun testSaveAccount()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -832,7 +830,7 @@ class AccountResourceIT { activated = true ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userDTO = <%= asDto('AdminUser') %>( login = "not-used", firstName = "firstname", @@ -860,10 +858,10 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isOk) - + <%_ } _%> - val updatedUser = userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(user?.login!!)<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> + val updatedUser = userRepository.findOne<% if (databaseTypeSql) { %>WithAuthorities<% } %>ByLogin(user?.login!!)<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> assertThat(updatedUser?.firstName).isEqualTo(userDTO.firstName) assertThat(updatedUser?.lastName).isEqualTo(userDTO.lastName) assertThat(updatedUser?.email).isEqualTo(userDTO.email) @@ -873,7 +871,7 @@ class AccountResourceIT { assertThat(updatedUser?.imageUrl).isEqualTo(userDTO.imageUrl) <%_ } _%> assertThat(updatedUser?.activated).isTrue - assertThat(updatedUser?.authorities).isEmpty() + assertNotNull(updatedUser?.authorities) } @Test<% if (databaseTypeSql && !reactive) { %> @@ -882,7 +880,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testSaveInvalidEmail() { + fun testSaveInvalidEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -896,7 +894,7 @@ class AccountResourceIT { activated = true ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userDTO = <%= asDto('AdminUser') %>( login = "not-used", @@ -928,10 +926,10 @@ class AccountResourceIT { .andDo(exportTestSecurityContext()) <%_ } _%> .andExpect(status().isBadRequest) - + <%_ } _%> - assertThat(userRepository.findOneByEmailIgnoreCase("invalid email")<% if (reactive) { %>.blockOptional()<% } %>).isNotPresent + assertNull(userRepository.findOneByEmailIgnoreCase("invalid email")<% if (reactive && !jhipsterConfig.coroutine) { %>.blockOptional()<% } %>) } @Test<% if (databaseTypeSql && !reactive) { %> @@ -940,7 +938,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testSaveExistingEmail() { + fun testSaveExistingEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -954,7 +952,7 @@ class AccountResourceIT { activated = true ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val anotherUser = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -969,7 +967,7 @@ class AccountResourceIT { activated = true ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userDTO = <%= asDto('AdminUser') %>( login = "not-used", @@ -998,18 +996,18 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isBadRequest) - + <%_ } _%> - val updatedUser = userRepository.findOneByLogin("save-existing-email")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(updatedUser.email).isEqualTo("save-existing-email@example.com") + val updatedUser = userRepository.findOneByLogin("save-existing-email")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(updatedUser?.email).isEqualTo("save-existing-email@example.com") } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("save-existing-email-and-login") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testSaveExistingEmailAndLogin() { + fun testSaveExistingEmailAndLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1023,7 +1021,7 @@ class AccountResourceIT { activated = true ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val userDTO = <%= asDto('AdminUser') %>( login = "not-used", firstName = "firstname", @@ -1051,18 +1049,18 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isOk) - + <%_ } _%> - val updatedUser = userRepository.findOneByLogin("save-existing-email-and-login")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(updatedUser.email).isEqualTo("save-existing-email-and-login@example.com") + val updatedUser = userRepository.findOneByLogin("save-existing-email-and-login")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(updatedUser?.email).isEqualTo("save-existing-email-and-login@example.com") } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("change-password-wrong-existing-password") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testChangePasswordWrongExistingPassword() { + fun testChangePasswordWrongExistingPassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val currentPassword = RandomStringUtils.randomAlphanumeric(60) val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -1076,7 +1074,7 @@ class AccountResourceIT { email = "change-password-wrong-existing-password@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/account/change-password") @@ -1095,16 +1093,16 @@ class AccountResourceIT { .andExpect(status().isBadRequest) <%_ } _%> - val updatedUser = userRepository.findOneByLogin("change-password-wrong-existing-password")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(passwordEncoder.matches("new password", updatedUser.password)).isFalse - assertThat(passwordEncoder.matches(currentPassword, updatedUser.password)).isTrue + val updatedUser = userRepository.findOneByLogin("change-password-wrong-existing-password")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(passwordEncoder.matches("new password", updatedUser?.password)).isFalse + assertThat(passwordEncoder.matches(currentPassword, updatedUser?.password)).isTrue } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("change-password") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testChangePassword() { + fun testChangePassword()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val currentPassword = RandomStringUtils.randomAlphanumeric(60) val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -1118,7 +1116,7 @@ class AccountResourceIT { email = "change-password@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/account/change-password") @@ -1137,15 +1135,15 @@ class AccountResourceIT { .andExpect(status().isOk) <%_ } _%> - val updatedUser = userRepository.findOneByLogin("change-password")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(passwordEncoder.matches("new password", updatedUser.password)).isTrue + val updatedUser = userRepository.findOneByLogin("change-password")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(passwordEncoder.matches("new password", updatedUser?.password)).isTrue } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("change-password-too-small") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testChangePasswordTooSmall() { + fun testChangePasswordTooSmall()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val currentPassword = RandomStringUtils.randomAlphanumeric(60) val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -1159,7 +1157,7 @@ class AccountResourceIT { email = "change-password-too-small@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1) @@ -1180,15 +1178,15 @@ class AccountResourceIT { .andExpect(status().isBadRequest) <%_ } _%> - val updatedUser = userRepository.findOneByLogin("change-password-too-small")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(updatedUser.password).isEqualTo(user.password) + val updatedUser = userRepository.findOneByLogin("change-password-too-small")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(updatedUser?.password).isEqualTo(user.password) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("change-password-too-long") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testChangePasswordTooLong() { + fun testChangePasswordTooLong()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val currentPassword = RandomStringUtils.randomAlphanumeric(60) val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -1202,7 +1200,7 @@ class AccountResourceIT { email = "change-password-too-long@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1) @@ -1223,15 +1221,15 @@ class AccountResourceIT { .andExpect(status().isBadRequest) <%_ } _%> - val updatedUser = userRepository.findOneByLogin("change-password-too-long")<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(updatedUser.password).isEqualTo(user.password) + val updatedUser = userRepository.findOneByLogin("change-password-too-long")<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(updatedUser?.password).isEqualTo(user.password) } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @WithMockUser("change-password-empty") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testChangePasswordEmpty() { + fun testChangePasswordEmpty()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val currentPassword = RandomStringUtils.randomAlphanumeric(60) val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> @@ -1245,7 +1243,7 @@ class AccountResourceIT { email = "change-password-empty@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/account/change-password") @@ -1264,8 +1262,8 @@ class AccountResourceIT { .andExpect(status().isBadRequest) <%_ } _%> - val updatedUser = userRepository.findOneByLogin("change-password-empty")<% if (reactive) { %>.block()<% } else { %>.orElse(null)<% } %> - assertThat(updatedUser.password).isEqualTo(user.password) + val updatedUser = userRepository.findOneByLogin("change-password-empty")<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + assertThat(updatedUser?.password).isEqualTo(user.password) } <%_ if (authenticationTypeSession && !reactive) { _%> @@ -1273,7 +1271,7 @@ class AccountResourceIT { @Transactional<% } %> @WithMockUser("current-sessions") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testGetCurrentSessions() { + fun testGetCurrentSessions()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1286,7 +1284,7 @@ class AccountResourceIT { email = "current-sessions@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val token = PersistentToken( series = "current-sessions", @@ -1321,7 +1319,7 @@ class AccountResourceIT { @Transactional<% } %> @WithMockUser("invalidate-session") <%_ if (!reactive) { _%>@Throws(Exception::class)<%_ } _%> - fun testInvalidateSession() { + fun testInvalidateSession()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1334,7 +1332,7 @@ class AccountResourceIT { email = "invalidate-session@example.com" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val token = PersistentToken( series = "invalidate-session", @@ -1362,7 +1360,7 @@ class AccountResourceIT { restAccountMockMvc.perform(delete("/api/account/sessions/invalidate-session")<%_ if (testsNeedCsrf) { _%>.with(csrf())<%_ } _%>) .andExpect(status().isOk) - assertThat(persistentTokenRepository.findByUser(user)).isEmpty() + assertNotNull(persistentTokenRepository.findByUser(user)) } <%_ } _%> @@ -1371,7 +1369,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testRequestPasswordReset() { + fun testRequestPasswordReset()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1386,7 +1384,7 @@ class AccountResourceIT { langKey = "en" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/account/reset-password/init") @@ -1409,7 +1407,7 @@ class AccountResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun testRequestPasswordResetUpperCaseEmail() { + fun testRequestPasswordResetUpperCaseEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1424,7 +1422,7 @@ class AccountResourceIT { langKey = "en" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> accountWebTestClient.post().uri("/api/account/reset-password/init") @@ -1444,14 +1442,14 @@ class AccountResourceIT { @Test <%_ if (reactive) { _%> - fun testRequestPasswordResetWrongEmail() { + fun testRequestPasswordResetWrongEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { accountWebTestClient.post().uri("/api/account/reset-password/init") .bodyValue("password-reset-wrong-email@example.com") .exchange() .expectStatus().isOk <%_ } else { _%> @Throws(Exception::class) - fun testRequestPasswordResetWrongEmail() { + fun testRequestPasswordResetWrongEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { restAccountMockMvc.perform( post("/api/account/reset-password/init") .content("password-reset-wrong-email@example.com")<% if (testsNeedCsrf) { %> @@ -1464,7 +1462,7 @@ class AccountResourceIT { @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testFinishPasswordReset() { + fun testFinishPasswordReset()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1479,7 +1477,7 @@ class AccountResourceIT { resetKey = "reset key" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val keyAndPassword = KeyAndPasswordVM(key = user.resetKey, newPassword = "new password") @@ -1497,17 +1495,17 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isOk) - + <%_ } _%> - val updatedUser = userRepository.findOneByLogin(user.login!!)<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(passwordEncoder.matches(keyAndPassword.newPassword, updatedUser.password)).isTrue + val updatedUser = userRepository.findOneByLogin(user.login!!)<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(passwordEncoder.matches(keyAndPassword.newPassword, updatedUser?.password)).isTrue } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testFinishPasswordResetTooSmall() { + fun testFinishPasswordResetTooSmall()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -1522,7 +1520,7 @@ class AccountResourceIT { resetKey = "reset key too small" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val keyAndPassword = KeyAndPasswordVM(key = user.resetKey, newPassword = "foo") @@ -1540,17 +1538,17 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isBadRequest) - + <%_ } _%> - val updatedUser = userRepository.findOneByLogin(user.login!!)<%_ if (reactive) { _%>.block()<% } else { %>.orElse(null)<% } %> - assertThat(passwordEncoder.matches(keyAndPassword.newPassword, updatedUser.password)).isFalse + val updatedUser = userRepository.findOneByLogin(user.login!!)<%_ if (reactive && !jhipsterConfig.coroutine) { _%>.block()<% } %> + assertThat(passwordEncoder.matches(keyAndPassword.newPassword, updatedUser?.password)).isFalse } @Test<% if (databaseTypeSql && !reactive) { %> @Transactional<% } %> @Throws(Exception::class) - fun testFinishPasswordResetWrongKey() { + fun testFinishPasswordResetWrongKey()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val keyAndPassword = KeyAndPasswordVM(key = "wrong reset key", newPassword = "new password") <%_ if (reactive) { _%> @@ -1567,7 +1565,7 @@ class AccountResourceIT { .with(csrf())<% } %> ) .andExpect(status().isInternalServerError) - + <%_ } _%> } } diff --git a/generators/server/templates/src/test/kotlin/package/web/rest/PublicUserResourceIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/web/rest/PublicUserResourceIT.kt.ejs index aaf7fc9bf..c29a49cbf 100644 --- a/generators/server/templates/src/test/kotlin/package/web/rest/PublicUserResourceIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/web/rest/PublicUserResourceIT.kt.ejs @@ -93,6 +93,9 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* <%_ } _%> +import kotlinx.coroutines.test.runTest + + /** * Integration tests for the {@link UserResource} REST controller. */ @@ -146,7 +149,7 @@ class PublicUserResourceIT { <%_ if (cacheManagerIsAvailable) { _%> @BeforeEach - fun setup() { + fun setup()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear() cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear() } @@ -154,13 +157,13 @@ class PublicUserResourceIT { <%_ } _%> <%_ if (reactive && testsNeedCsrf) { _%> @BeforeEach - fun setupCsrf() { + fun setupCsrf()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { webTestClient = webTestClient.mutateWith(csrf()) } <%_ } _%> @BeforeEach - fun initTest() { + fun initTest()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { user = UserResourceIT.initTestUser(userRepository<% if (databaseTypeSql) { %>, em<% } %>) } @@ -171,9 +174,9 @@ class PublicUserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun getAllPublicUsers() { + fun getAllPublicUsers()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %>save<% } %><% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %>save<% } %><% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> // Get all the users <%_ if (reactive) { _%> @@ -214,7 +217,7 @@ class PublicUserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun getAllAuthorities() { + fun getAllAuthorities()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { <%_ if (reactive) { _%> webTestClient.get().uri("/api/authorities") .accept(MediaType.APPLICATION_JSON) @@ -243,9 +246,9 @@ class PublicUserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun getAllUsersSortedByParameters() { + fun getAllUsersSortedByParameters()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<% if (reactive) { %>save<% } else { %>saveAndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<% if (reactive) { %>save<% } else { %>saveAndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (reactive) { _%> webTestClient.get().uri("/api/users?sort=resetKey,desc").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isBadRequest diff --git a/generators/server/templates/src/test/kotlin/package/web/rest/UserJWTControllerIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/web/rest/UserJWTControllerIT.kt.ejs index 1835c4d6c..ace62c1e3 100644 --- a/generators/server/templates/src/test/kotlin/package/web/rest/UserJWTControllerIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/web/rest/UserJWTControllerIT.kt.ejs @@ -62,6 +62,9 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header import org.hamcrest.Matchers.* <%_ } _%> +import kotlinx.coroutines.test.runTest + + /** * Integration tests for the [UserJWTController] REST controller. */ @@ -84,9 +87,9 @@ class UserJWTControllerIT { @Autowired <%_ if (reactive) { _%> private lateinit var webTestClient: WebTestClient - <%_ } else { _%> + <%_ } else { _%> private lateinit var mockMvc: MockMvc - <%_ }  _%> + <%_ } _%> @Test <%_ if (!skipUserManagement) { _%> @@ -94,7 +97,7 @@ class UserJWTControllerIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun testAuthorize() { + fun testAuthorize()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -108,12 +111,12 @@ class UserJWTControllerIT { password = passwordEncoder.encode("test") ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val login = LoginVM(username = "user-jwt-controller", password = "test") <%_ } else { _%> @Throws(Exception::class) - fun testAuthorize() { + fun testAuthorize()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val login = LoginVM(username ="test", password = "test") <%_ } _%> <%_ if (reactive) { _%> @@ -125,7 +128,7 @@ class UserJWTControllerIT { .expectHeader().valueMatches("Authorization", "Bearer .+") .expectBody() .jsonPath("\$.id_token").isNotEmpty - <%_ } else { _%> + <%_ } else { _%> mockMvc.perform( post("/api/authenticate") .contentType(MediaType.APPLICATION_JSON) @@ -141,11 +144,11 @@ class UserJWTControllerIT { @Test <%_ if (!skipUserManagement) { _%> - <%_ if (databaseTypeSql && !reactive) { _%> + <%_ if (databaseTypeSql && !reactive) { _%> @Transactional <%_ } _%> @Throws(Exception::class) - fun testAuthorizeWithRememberMe() { + fun testAuthorizeWithRememberMe()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val user = <%= asEntity('User') %>( <%_ if (databaseTypeCassandra) { _%> id = UUID.randomUUID().toString(), @@ -155,11 +158,11 @@ class UserJWTControllerIT { activated = true, <%_ if (databaseTypeSql && reactive) { _%> createdBy = SYSTEM_ACCOUNT, - <%_ } _%> + <%_ } _%> password = passwordEncoder.encode("test") ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val login = LoginVM( username = "user-jwt-controller-remember-me", @@ -168,7 +171,7 @@ class UserJWTControllerIT { ) <%_ } else { _%> @Throws(Exception::class) - fun testAuthorizeWithRememberMe() { + fun testAuthorizeWithRememberMe()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val login = LoginVM( username ="test", password = "test", @@ -184,7 +187,7 @@ class UserJWTControllerIT { .expectHeader().valueMatches("Authorization", "Bearer .+") .expectBody() .jsonPath("\$.id_token").isNotEmpty - <%_ } else { _%> + <%_ } else { _%> mockMvc.perform( post("/api/authenticate") .contentType(MediaType.APPLICATION_JSON) @@ -200,7 +203,7 @@ class UserJWTControllerIT { @Test @Throws(Exception::class) - fun testAuthorizeFails() { + fun testAuthorizeFails()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val login = LoginVM(username = "wrong-user", password = "wrong password") <%_ if (reactive) { _%> webTestClient.post().uri("/api/authenticate") @@ -211,7 +214,7 @@ class UserJWTControllerIT { .expectHeader().doesNotExist("Authorization") .expectBody() .jsonPath("\$.id_token").doesNotExist() - <%_ } else { _%> + <%_ } else { _%> mockMvc.perform( post("/api/authenticate") .contentType(MediaType.APPLICATION_JSON) diff --git a/generators/server/templates/src/test/kotlin/package/web/rest/UserResourceIT.kt.ejs b/generators/server/templates/src/test/kotlin/package/web/rest/UserResourceIT.kt.ejs index 7fa2c3bde..0c7b1f405 100644 --- a/generators/server/templates/src/test/kotlin/package/web/rest/UserResourceIT.kt.ejs +++ b/generators/server/templates/src/test/kotlin/package/web/rest/UserResourceIT.kt.ejs @@ -52,6 +52,7 @@ import <%= packageName %>.service.mapper.UserMapper <%_ if (!authenticationTypeOauth2) { _%> import <%= packageName %>.web.rest.vm.ManagedUserVM <%_ } _%> +import kotlinx.coroutines.flow.toList import org.apache.commons.lang3.RandomStringUtils import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -121,6 +122,9 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull <%_ } _%> +import kotlinx.coroutines.test.runTest + + /** * Integration tests for the [UserResource] REST controller. */ @@ -172,7 +176,7 @@ class UserResourceIT { <%_ if (cacheManagerIsAvailable) { _%> @BeforeEach - fun setup() { + fun setup()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)!!.clear() cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)!!.clear() } @@ -180,13 +184,13 @@ class UserResourceIT { <%_ } _%> <%_ if (reactive && testsNeedCsrf) { _%> @BeforeEach - fun setupCsrf() { + fun setupCsrf()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { webTestClient = webTestClient.mutateWith(csrf()) } <%_ } _%> @BeforeEach - fun initTest() { + fun initTest()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { user = initTestUser(userRepository<% if (databaseTypeSql ) { %>, em<% } _%>) } <%_ if (!authenticationTypeOauth2) { _%> @@ -196,9 +200,8 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun createUser() { - val databaseSizeBeforeCreate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + fun createUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { + val databaseSizeBeforeCreate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size // Create the User val managedUserVM = ManagedUserVM().apply { login = DEFAULT_LOGIN @@ -256,9 +259,8 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun createUserWithExistingId() { - val databaseSizeBeforeCreate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + fun createUserWithExistingId()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { + val databaseSizeBeforeCreate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size val managedUserVM = ManagedUserVM().apply { <%_ if (databaseTypeCassandra) { _%> @@ -315,14 +317,13 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun createUserWithExistingLogin() { + fun createUserWithExistingLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> - userSearchRepository.save(user)<% if (reactive) { %>.block()<% } %>; + userSearchRepository.save(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %>; <%_ } _%> - val databaseSizeBeforeCreate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + val databaseSizeBeforeCreate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size val managedUserVM = ManagedUserVM().apply { login = DEFAULT_LOGIN // this login should already be used @@ -369,14 +370,13 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun createUserWithExistingEmail() { + fun createUserWithExistingEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> - userSearchRepository.save(user)<% if (reactive) { %>.block()<% } %> + userSearchRepository.save(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ } _%> - val databaseSizeBeforeCreate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + val databaseSizeBeforeCreate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size val managedUserVM = ManagedUserVM().apply { login = "anotherlogin" @@ -424,9 +424,9 @@ class UserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun getAllUsers() { + fun getAllUsers()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %><%= saveMethod %><% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %><%= saveMethod %><% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (databaseTypeSql && reactive && !applicationTypeMicroservice) { _%> authorityRepository .findById(USER) @@ -485,9 +485,9 @@ class UserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun getUser() { + fun getUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %><%= saveMethod %><% } %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<% if (databaseTypeSql && reactive && authenticationTypeOauth2) { %>create<% } else { %><%= saveMethod %><% } %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (databaseTypeSql && reactive) { _%> authorityRepository .findById(USER) @@ -556,7 +556,7 @@ class UserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun getNonExistingUser() { + fun getNonExistingUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { <%_ if (reactive) { _%> webTestClient.get().uri("/api/admin/users/unknown") .exchange() @@ -572,14 +572,13 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun updateUser() { + fun updateUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> - val databaseSizeBeforeUpdate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + val databaseSizeBeforeUpdate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size // Update the user - val updatedUser = userRepository.findById(user.id!!).<% if (reactive) { %>block<% } else { %>get<% } %>() + val updatedUser = userRepository.findById(user.id!!)<% if(!jhipsterConfig.coroutine){ %>.<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %> assertNotNull(updatedUser) val managedUserVM = ManagedUserVM().apply { @@ -642,14 +641,13 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun updateUserLogin() { + fun updateUserLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> - val databaseSizeBeforeUpdate = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + val databaseSizeBeforeUpdate = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size // Update the user - val updatedUser = userRepository.findById(user.id!!).<% if (reactive) { %>block<% } else { %>get<% } %>() + val updatedUser = userRepository.findById(user.id!!)<% if(!jhipsterConfig.coroutine){ %>.<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %> assertNotNull(updatedUser) val managedUserVM = ManagedUserVM().apply { @@ -713,9 +711,9 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun updateUserExistingEmail() { + fun updateUserExistingEmail()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database with 2 users - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> userSearchRepository.save(user) <%_ } _%> @@ -738,13 +736,13 @@ class UserResourceIT { <%_ } _%> langKey = "en" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> userSearchRepository.save(anotherUser) <%_ } _%> // Update the user - val updatedUser = userRepository.findById(user.id!!).<% if (reactive) { %>block<% } else { %>get<% } %>() + val updatedUser = userRepository.findById(user.id!!)<% if(!jhipsterConfig.coroutine){ %>.<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %> assertNotNull(updatedUser) val managedUserVM = ManagedUserVM().apply { @@ -795,9 +793,9 @@ class UserResourceIT { @Transactional <%_ } _%> @Throws(Exception::class) - fun updateUserExistingLogin() { + fun updateUserExistingLogin()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> userSearchRepository.save(user) <%_ } _%> @@ -820,13 +818,13 @@ class UserResourceIT { <%_ } _%> langKey = "en" ) - userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive) { %>.block()<% } %> + userRepository.save<% if (databaseTypeSql && !reactive) { %>AndFlush<% } %>(anotherUser)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> <%_ if (searchEngineElasticsearch) { _%> userSearchRepository.save(anotherUser) <%_ } _%> // Update the user - val updatedUser = userRepository.findById(user.id!!).<% if (reactive) { %>block<% } else { %>get<% } %>() + val updatedUser = userRepository.findById(user.id!!)<% if(!jhipsterConfig.coroutine){ %>.<% if (reactive) { %>block<% } else { %>get<% } %>()<% } %> assertNotNull(updatedUser) val managedUserVM = ManagedUserVM().apply { @@ -879,11 +877,10 @@ class UserResourceIT { <%_ if (!reactive) { _%> @Throws(Exception::class) <%_ } _%> - fun deleteUser() { + fun deleteUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { // Initialize the database - userRepository.<%= saveMethod %>(user)<% if (reactive) { %>.block()<% } %> - val databaseSizeBeforeDelete = userRepository.findAll()<% if (reactive) { %> - .collectList().block()!!<% } %>.size + userRepository.<%= saveMethod %>(user)<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> + val databaseSizeBeforeDelete = userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList() <% } else if (reactive) { %>.collectList().block()!!<% } %>.size // Delete the user <%_ if (reactive) { _%> @@ -911,7 +908,7 @@ class UserResourceIT { @Test @Throws(Exception::class) - fun testUserEquals() { + fun testUserEquals()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { equalsVerifier(<%= asEntity('User') %>::class) val user1 = <%= asEntity('User') %>(id = DEFAULT_ID) val user2 = <%= asEntity('User') %>(id = user1.id) @@ -923,7 +920,7 @@ class UserResourceIT { } @Test - fun testUserDTOtoUser() { + fun testUserDTOtoUser()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val userDTO = <%= asDto('AdminUser') %>( id = DEFAULT_ID, login = DEFAULT_LOGIN, @@ -964,7 +961,7 @@ class UserResourceIT { } @Test - fun testUserToUserDTO() { + fun testUserToUserDTO()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { user.id = DEFAULT_ID <%_ if (!databaseTypeCassandra) { _%> user.createdBy = DEFAULT_LOGIN @@ -1002,7 +999,7 @@ class UserResourceIT { <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeNeo4j) { _%> @Test - fun testAuthorityEquals() { + fun testAuthorityEquals()<% if (jhipsterConfig.coroutine) { %> = runTest<% } %> { val authorityA = Authority() assertThat(authorityA) .isNotEqualTo(null) @@ -1121,11 +1118,11 @@ class UserResourceIT { * Setups the database with one user. */ @JvmStatic - fun initTestUser(userRepository: UserRepository<% if (databaseTypeSql ) { %>, em: EntityManager<% } _%>): <%= asEntity('User') %> { + <% if (jhipsterConfig.coroutine) { %>suspend <% } %>fun initTestUser(userRepository: UserRepository<% if (databaseTypeSql ) { %>, em: EntityManager<% } _%>): <%= asEntity('User') %> { <%_ if (databaseTypeSql && reactive) { _%> userRepository.deleteAllUserAuthorities().block() <%_ } _%> - userRepository.deleteAll()<% if (reactive) { %>.block()<% } %> + userRepository.deleteAll()<% if (reactive && !jhipsterConfig.coroutine) { %>.block()<% } %> val user = createEntity(<% if (databaseTypeSql ) { %>em<% } _%>) <%_ if (databaseTypeSql) { _%> user.login = DEFAULT_LOGIN @@ -1135,11 +1132,11 @@ class UserResourceIT { } } - fun assertPersistedUsers(userAssertion: (List<<%= asEntity('User') %>>) -> Unit) { + <% if (jhipsterConfig.coroutine) { %>suspend <% } %>fun assertPersistedUsers(userAssertion: (List<<%= asEntity('User') %>>) -> Unit) { <%_ if (databaseTypeCouchbase) { _%> // The security filter chain clears the security context after remote calls SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()) <%_ } _%> - userAssertion(userRepository.findAll()<% if (reactive) { %>.collectList().block()<% } %>) + userAssertion(userRepository.findAll()<% if (jhipsterConfig.coroutine) { %>.toList()<% } else if (reactive) { %>.collectList().block()<% } %>) } }