Repository: jhipster/generator-jhipster (ts)

Issue:
Improve User with OAuth Microservices
JWT authentication seems to have the notion of a `User` when you create a gateway. This means that you can create relationships with `User` when generating code and importing JDL. However, the OAuth implementation of a gateway does not have a `User`. 

I also noticed that when importing JDL into a microservice app, it allows a relationship to `User` to go through, but then it fails on startup. 

I’d like to fix this and here are some thoughts on how we might do it:

1. If generating a gateway with OAuth, use user sync just like we do with a monolith. This means a user object/table exists and when you hit `/api/account`, the user is updated in the database with the values from the IdP. There is no write to the user table exposed to the UI or in the API.

2. Fix [the issue](https://github.com/jhipster/generator-jhipster/issues/7008) where a user doesn’t have an id and store the “sub” claim as the user’s id. This will require changing the “id” property of a user from a `Long` to a `String` for both microservices and monoliths with OAuth.

3. Fail fast or block relationships to `User` in microservice apps. Another alternative might be to allow relationships to `User`, then have a Feign client that talks back to the gateway for user information.

Pull request description:
Auto-add User for microservices and OAuth
This adds a `UserEntityListener` (and config) to auto-add users in microservices. Hopefully, this will be the final piece of the puzzle to fix #7065.


Patch:
diff --git a/generators/client/templates/react/src/main/webapp/app/modules/administration/metrics/metrics.tsx.ejs b/generators/client/templates/react/src/main/webapp/app/modules/administration/metrics/metrics.tsx.ejs
index e8bda1ac99..153033dddc 100644
--- a/generators/client/templates/react/src/main/webapp/app/modules/administration/metrics/metrics.tsx.ejs
+++ b/generators/client/templates/react/src/main/webapp/app/modules/administration/metrics/metrics.tsx.ejs
@@ -513,7 +513,7 @@ export class MetricsPage extends React.Component<any, IMetricsPageState> {
             </Row>
           : ''}
 
-          { metrics.gauges && metrics.gauges['HikariPool-1.pool.TotalConnections'].value > 0 ?
+          { metrics.gauges && metrics.gauges['HikariPool-1.pool.TotalConnections'] && metrics.gauges['HikariPool-1.pool.TotalConnections'].value > 0 ?
             <Row>
               <Col sm="12">
                 <h3>DataSource statistics (time in millisecond)</h3>
diff --git a/generators/entity-server/templates/src/main/java/package/common/inject_template.ejs b/generators/entity-server/templates/src/main/java/package/common/inject_template.ejs
index 1e44ac5f0f..e4bfa42003 100644
--- a/generators/entity-server/templates/src/main/java/package/common/inject_template.ejs
+++ b/generators/entity-server/templates/src/main/java/package/common/inject_template.ejs
@@ -39,8 +39,12 @@
     private final <%= entityClass %>QueryService <%= entityInstance %>QueryService;
     <%_ } _%>
 <%_ } _%>
+<%_ if (saveUserSnapshot) { _%>
 
-    public <%= constructorName %>(<% if (!viaService) { %><%= entityClass %>Repository <%= entityInstance %>Repository<% if (dto === 'mapstruct') { %>, <%= entityClass %>Mapper <%= entityInstance %>Mapper<% } %><% if (searchEngine === 'elasticsearch') { %>, <%= entityClass %>SearchRepository <%= entityInstance %>SearchRepository<% } %><% if (reactiveRepositories) { %>, <%= entityClass %>ReactiveRepository <%= entityInstance %>ReactiveRepository<% } %><%  } else { %><%= entityClass %>Service <%= entityInstance %>Service<% } %><% if (queryService && viaService) { %>, <%= entityClass %>QueryService <%= entityInstance %>QueryService<% } %>) {
+    private final UserRepository userRepository;
+<%_ } _%>
+
+    public <%= constructorName %>(<% if (!viaService) { %><%= entityClass %>Repository <%= entityInstance %>Repository<% if (dto === 'mapstruct') { %>, <%= entityClass %>Mapper <%= entityInstance %>Mapper<% } %><% if (saveUserSnapshot) { %>, UserRepository userRepository<% } %><% if (searchEngine === 'elasticsearch') { %>, <%= entityClass %>SearchRepository <%= entityInstance %>SearchRepository<% } %><% if (reactiveRepositories) { %>, <%= entityClass %>ReactiveRepository <%= entityInstance %>ReactiveRepository<% } %><%  } else { %><%= entityClass %>Service <%= entityInstance %>Service<% } %><% if (queryService && viaService) { %>, <%= entityClass %>QueryService <%= entityInstance %>QueryService<% } %>) {
         <%_ if (!viaService) { _%>
         this.<%= entityInstance %>Repository = <%= entityInstance %>Repository;
             <%_ if (dto === 'mapstruct') { _%>
@@ -57,5 +61,8 @@
             <%_ if (queryService) {  _%>
         this.<%= entityInstance %>QueryService = <%= entityInstance %>QueryService;
             <%_ } _%>
+        <%_ }
+        if (saveUserSnapshot) { _%>
+        this.userRepository = userRepository;
         <%_ } _%>
     }
diff --git a/generators/entity-server/templates/src/main/java/package/web/rest/EntityResource.java.ejs b/generators/entity-server/templates/src/main/java/package/web/rest/EntityResource.java.ejs
index cad53f008a..3f0f1c1f4c 100644
--- a/generators/entity-server/templates/src/main/java/package/web/rest/EntityResource.java.ejs
+++ b/generators/entity-server/templates/src/main/java/package/web/rest/EntityResource.java.ejs
@@ -33,6 +33,9 @@ import <%=packageName%>.repository.reactive.<%= entityClass %>ReactiveRepository
 import <%=packageName%>.repository.search.<%= entityClass %>SearchRepository;
     <%_ } _%>
 <%_ } _%>
+<%_ if (saveUserSnapshot) { _%>
+import <%=packageName%>.repository.UserRepository;
+<%_ } _%>
 import <%=packageName%>.web.rest.errors.BadRequestAlertException;
 import <%=packageName%>.web.rest.util.HeaderUtil;<% if (pagination !== 'no') { %>
 import <%=packageName%>.web.rest.util.PaginationUtil;<% } %>
@@ -111,7 +114,12 @@ public class <%= entityClass %>Resource {
         log.debug("REST request to save <%= entityClass %> : {}", <%= instanceName %>);
         if (<%= instanceName %>.getId() != null) {
             throw new BadRequestAlertException("A new <%= entityInstance %> cannot already have an ID", ENTITY_NAME, "idexists");
-        }<%- include('../../common/save_template', {viaService: viaService, returnDirectly: false}); -%>
+        }<%_ if (saveUserSnapshot) { %>
+        if (<%= instanceName %>.getUser() != null) {
+            // Save user in case it's new and only exists in gateway
+            userRepository.save(<%= instanceName %>.getUser());
+        }<%_ } _%>
+        <%- include('../../common/save_template', {viaService: viaService, returnDirectly: false}); -%>
         return ResponseEntity.created(new URI("/api/<%= entityApiUrl %>/" + result.getId()))
             .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
             .body(result);
@@ -132,7 +140,12 @@ public class <%= entityClass %>Resource {
         log.debug("REST request to update <%= entityClass %> : {}", <%= instanceName %>);
         if (<%= instanceName %>.getId() == null) {
             throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull");
-        }<%- include('../../common/save_template', {viaService: viaService, returnDirectly: false}); -%>
+        }<%_ if (saveUserSnapshot) { %>
+        if (<%= instanceName %>.getUser() != null) {
+            // Save user in case it's new and only exists in gateway
+            userRepository.save(<%= instanceName %>.getUser());
+        }<%_ } _%>
+        <%- include('../../common/save_template', {viaService: viaService, returnDirectly: false}); -%>
         return ResponseEntity.ok()
             .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, <%= instanceName %>.getId().toString()))
             .body(result);
diff --git a/generators/entity/index.js b/generators/entity/index.js
index 992307c888..bee137e659 100644
--- a/generators/entity/index.js
+++ b/generators/entity/index.js
@@ -601,6 +601,7 @@ module.exports = class extends BaseGenerator {
                         context.validation = true;
                     }
                 });
+                context.hasUserField = context.saveUserSnapshot = false;
                 // Load in-memory data for relationships
                 context.relationships.forEach((relationship) => {
                     if (_.isUndefined(relationship.relationshipNameCapitalized)) {
@@ -660,6 +661,7 @@ module.exports = class extends BaseGenerator {
 
                     if (otherEntityName === 'user') {
                         relationship.otherEntityTableName = `${jhiTablePrefix}_user`;
+                        context.hasUserField = true;
                     } else {
                         relationship.otherEntityTableName = otherEntityData ? otherEntityData.entityTableName : null;
                         if (!relationship.otherEntityTableName) {
@@ -670,6 +672,7 @@ module.exports = class extends BaseGenerator {
                             relationship.otherEntityTableName = `${jhiTablePrefix}_${otherEntityTableName}`;
                         }
                     }
+                    context.saveUserSnapshot = context.applicationType === 'microservice' && context.authenticationType === 'oauth2' && context.hasUserField;
 
                     if (_.isUndefined(relationship.otherEntityNamePlural)) {
                         relationship.otherEntityNamePlural = pluralize(relationship.otherEntityName);
diff --git a/package-lock.json b/package-lock.json
index 3738f1e242..7eeee6af63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "generator-jhipster",
-  "version": "4.14.0",
+  "version": "5.0.0-beta.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {


Test patch:
diff --git a/generators/entity-server/templates/src/test/java/package/web/rest/EntityResourceIntTest.java.ejs b/generators/entity-server/templates/src/test/java/package/web/rest/EntityResourceIntTest.java.ejs
index 37eabda7fb..6e380d6568 100644
--- a/generators/entity-server/templates/src/test/java/package/web/rest/EntityResourceIntTest.java.ejs
+++ b/generators/entity-server/templates/src/test/java/package/web/rest/EntityResourceIntTest.java.ejs
@@ -30,6 +30,9 @@ import <%=packageName%>.domain.<%= entityClass %>;
         if ((relationshipValidate !== null && relationshipValidate === true) || jpaMetamodelFiltering) { _%>
 import <%=packageName%>.domain.<%= otherEntityNameCapitalized %>;
 <%_ } } _%>
+<%_ if (saveUserSnapshot) { _%>
+import <%=packageName%>.repository.UserRepository;
+<%_ } _%>
 import <%=packageName%>.repository.<%= entityClass %>Repository;
 <%_ if (searchEngine === 'elasticsearch') { _%>
 import <%=packageName%>.repository.search.<%= entityClass %>SearchRepository;
@@ -278,7 +281,11 @@ _%>
 
     @Autowired
     private <%= entityClass %>Repository <%= entityInstance %>Repository;
+    <%_ if (saveUserSnapshot) { _%>
 
+    @Autowired
+    private UserRepository userRepository;
+    <%_ } _%>
     <%_ if (reactiveRepositories && service === 'no') { _%>
     @Autowired
     private <%= entityClass %>ReactiveRepository <%= entityInstance %>ReactiveRepository;
@@ -334,7 +341,7 @@ _%>
         <%_ if (service !== 'no') { _%>
         final <%= entityClass %>Resource <%= entityInstance %>Resource = new <%= entityClass %>Resource(<%= entityInstance %>Service<% if (jpaMetamodelFiltering) { %>, <%= entityInstance %>QueryService<% } %>);
         <%_ } else { _%>
-        final <%= entityClass %>Resource <%= entityInstance %>Resource = new <%= entityClass %>Resource(<%= entityInstance %>Repository<% if (dto === 'mapstruct') { %>, <%= entityInstance %>Mapper<% } %><% if (searchEngine === 'elasticsearch') { %>, mock<%= entityClass %>SearchRepository<% } %><% if (reactiveRepositories) { %>, <%= entityInstance %>ReactiveRepository<% } %><% if (jpaMetamodelFiltering) { %>, <%= entityInstance %>QueryService<% } %>);
+        final <%= entityClass %>Resource <%= entityInstance %>Resource = new <%= entityClass %>Resource(<%= entityInstance %>Repository<% if (dto === 'mapstruct') { %>, <%= entityInstance %>Mapper<% } %><% if (saveUserSnapshot) { %>, userRepository<% } %><% if (searchEngine === 'elasticsearch') { %>, mock<%= entityClass %>SearchRepository<% } %><% if (reactiveRepositories) { %>, <%= entityInstance %>ReactiveRepository<% } %><% if (jpaMetamodelFiltering) { %>, <%= entityInstance %>QueryService<% } %>);
         <%_ } _%>
         this.rest<%= entityClass %>MockMvc = MockMvcBuilders.standaloneSetup(<%= entityInstance %>Resource)
             .setCustomArgumentResolvers(pageableArgumentResolver)