├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── build.yml │ ├── docs.yml │ └── junie.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── crud-generation.png ├── index.md ├── server-generation-1.png └── server-generation-2.png ├── generators ├── app │ └── index.js ├── base-generator.js ├── common │ └── files │ │ ├── gradle │ │ ├── gitignore │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ └── gradlew.bat │ │ └── maven │ │ ├── .mvn │ │ └── wrapper │ │ │ └── maven-wrapper.properties │ │ ├── gitignore │ │ ├── mvnw │ │ └── mvnw.cmd ├── constants.js ├── controller │ ├── index.js │ ├── prompts.js │ └── templates │ │ └── app │ │ └── src │ │ ├── main │ │ ├── java │ │ │ ├── entities │ │ │ │ └── Entity.java │ │ │ ├── exception │ │ │ │ └── NotFoundException.java │ │ │ ├── mapper │ │ │ │ └── Mapper.java │ │ │ ├── model │ │ │ │ ├── query │ │ │ │ │ └── FindQuery.java │ │ │ │ ├── request │ │ │ │ │ └── Request.java │ │ │ │ └── response │ │ │ │ │ └── Response.java │ │ │ ├── repositories │ │ │ │ └── Repository.java │ │ │ ├── services │ │ │ │ └── Service.java │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── Controller.java │ │ └── resources │ │ │ └── db │ │ │ └── migration │ │ │ ├── flyway │ │ │ ├── V1__new_table_no_seq.sql │ │ │ └── V1__new_table_with_seq.sql │ │ │ └── liquibase │ │ │ └── changelog │ │ │ ├── 01-new_table_no_seq.sql │ │ │ ├── 01-new_table_no_seq.xml │ │ │ ├── 01-new_table_no_seq.yaml │ │ │ ├── 01-new_table_with_seq.sql │ │ │ ├── 01-new_table_with_seq.xml │ │ │ └── 01-new_table_with_seq.yaml │ │ └── test │ │ └── java │ │ ├── services │ │ └── ServiceTest.java │ │ └── web │ │ └── controllers │ │ ├── ControllerIT.java │ │ └── ControllerTest.java └── server │ ├── index.js │ ├── prompts.js │ └── templates │ ├── app │ ├── .github │ │ └── workflows │ │ │ ├── gradle.yml │ │ │ └── maven.yml │ ├── .localstack │ │ └── 01_init.sh │ ├── Dockerfile │ ├── Jenkinsfile │ ├── README.md │ ├── config │ │ ├── elk │ │ │ └── logstash.conf │ │ ├── grafana │ │ │ └── provisioning │ │ │ │ ├── dashboards │ │ │ │ ├── basic-dashboard.json │ │ │ │ ├── dashboard.yml │ │ │ │ └── jvm-micrometer_rev10.json │ │ │ │ └── datasources │ │ │ │ └── datasource.yml │ │ └── prometheus │ │ │ └── prometheus.yml │ ├── docker │ │ ├── docker-compose-app.yml │ │ ├── docker-compose-elk.yml │ │ ├── docker-compose-monitoring.yml │ │ └── docker-compose.yml │ ├── lombok.config │ ├── sonar-project.properties │ └── src │ │ ├── main │ │ ├── java │ │ │ ├── Application.java │ │ │ ├── config │ │ │ │ ├── ApplicationProperties.java │ │ │ │ ├── GlobalExceptionHandler.java │ │ │ │ ├── Initializer.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ ├── WebMvcConfig.java │ │ │ │ └── logging │ │ │ │ │ ├── Loggable.java │ │ │ │ │ └── LoggingAspect.java │ │ │ ├── exception │ │ │ │ └── ResourceNotFoundException.java │ │ │ ├── model │ │ │ │ └── response │ │ │ │ │ └── PagedResult.java │ │ │ └── utils │ │ │ │ └── AppConstants.java │ │ └── resources │ │ │ ├── application-local.properties │ │ │ ├── application.properties │ │ │ ├── db │ │ │ └── migration │ │ │ │ ├── flyway │ │ │ │ └── V1__01_init.sql │ │ │ │ └── liquibase │ │ │ │ └── changelog │ │ │ │ ├── 01-init.sql │ │ │ │ ├── 01-init.xml │ │ │ │ ├── 01-init.yaml │ │ │ │ └── db.changelog-master.yaml │ │ │ └── logback-spring.xml │ │ └── test │ │ ├── java │ │ ├── ApplicationIntegrationTest.java │ │ ├── SchemaValidationTest.java │ │ ├── SqsListenerIntegrationTest.java │ │ ├── TestApplication.java │ │ └── common │ │ │ ├── AbstractIntegrationTest.java │ │ │ └── ContainersConfig.java │ │ └── resources │ │ ├── application-test.properties │ │ └── logback-test.xml │ ├── gradle │ ├── build.gradle │ ├── code-quality.gradle │ ├── gradle.properties │ ├── owasp.gradle │ └── settings.gradle │ └── maven │ └── pom.xml ├── mkdocs.yml ├── package-lock.json ├── package.json ├── renovate.json └── test ├── controller.spec.js ├── server.spec.js └── templates ├── basic-microservice-flyway └── .yo-rc.json └── basic-microservice-liquibase └── .yo-rc.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Java", 3 | "image": "mcr.microsoft.com/devcontainers/java:1-21", 4 | "features": { 5 | "ghcr.io/devcontainers/features/java:1": { 6 | "version": "none", 7 | "installMaven": "true", 8 | "mavenVersion": "3.8.6", 9 | "installGradle": "true" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Generator SpringBoot 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | jobs: 7 | build: 8 | name: npm test 9 | runs-on: ${{ matrix.os }} 10 | timeout-minutes: 20 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | node_version: [22.x, 20.x] 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: ${{ matrix.node_version }} 22 | - run: npm install 23 | - run: npm test 24 | env: 25 | CI: true 26 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs via GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-python@v5 14 | with: 15 | python-version: 3.x 16 | - run: pip install mkdocs-material 17 | - run: mkdocs gh-deploy --force 18 | -------------------------------------------------------------------------------- /.github/workflows/junie.yml: -------------------------------------------------------------------------------- 1 | name: Junie 2 | run-name: Junie run ${{ inputs.run_id }} 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | packages: read 8 | 9 | on: 10 | workflow_dispatch: 11 | inputs: 12 | run_id: 13 | description: "id of workflow process" 14 | required: true 15 | workflow_params: 16 | description: "stringified params" 17 | required: true 18 | 19 | jobs: 20 | call-workflow-passing-data: 21 | uses: jetbrains-junie/junie-workflows/.github/workflows/ej-issue.yml@main 22 | with: 23 | workflow_params: ${{ inputs.workflow_params }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | .DS_Store 4 | node_modules/ 5 | /testapp 6 | /myservice/ 7 | logs/ 8 | generators/server/templates/gradle/.gradle/ 9 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | USER gitpod 4 | 5 | RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \ 6 | && sdk install java 17.0.12-graal \ 7 | && sdk default java 17.0.12-graal" -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | image: 6 | file: .gitpod.Dockerfile 7 | 8 | tasks: 9 | - before: npm install -g yo 10 | init: npm install 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "java.compile.nullAnalysis.mode": "automatic" 4 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Version 0.1.6 4 | * Upgrade Spring Boot version to 3.4.4 5 | * Upgraded Maven and Gradle versions 6 | * Supports running application with Java 21 and Spring Boot 3.4.x 7 | 8 | ## Version 0.1.5 9 | * Upgrade Spring Boot version to 3.1.5 10 | * Upgraded Maven and Gradle versions 11 | * Implemented best practices to not expose entity 12 | * Upgraded generator dependencies (chai, sinon, etc) 13 | * Supports running application with Java 21 and Spring Boot 3.2.0 14 | 15 | ## Version 0.1.4 16 | * Upgrade Spring Boot version to 3.1.3 17 | * Upgraded Maven and Gradle versions 18 | * Refine Testcontainers configuration 19 | * Simplify LocalStack configuration 20 | * Removed H2 database config 21 | 22 | ## Version 0.1.3 23 | * Upgrade Spring Boot version to 3.1.1 24 | * Introduced Testcontainers support for dev mode 25 | * Fixed LocalStack configuration issues 26 | * Upgraded generator dependencies (chai, sinon, etc) 27 | * Upgraded Maven and Gradle versions 28 | * Updated googleJavaFormat configuration. 29 | 30 | ## Version 0.1.2 31 | * Fixes issue with mysql and mariadb when flyway is selected ([#58](https://github.com/sivaprasadreddy/generator-springboot/issues/58)) 32 | * Support Mariadb Sequences ([#59](https://github.com/sivaprasadreddy/generator-springboot/issues/59)) 33 | * Support other liquibase formats ([#69](https://github.com/sivaprasadreddy/generator-springboot/issues/69)) 34 | * Upgraded SpringBoot to 3.0.2 and other library versions 35 | 36 | ## Version 0.1.1 37 | * Upgraded SpringBoot to 3.0.0 and other library versions 38 | * Upgraded AWS to 3.0.0-M3, compatible version with SpringBoot 3 which uses AWS 2.0 API 39 | * Tweaked code to get All entries from datasource using pagination 40 | * Supporting developing application in VSCode 41 | * Enhanced support for logback encoder when elk stack is selected 42 | * Fixes issue while generating api and tables when `tablename` contains camelCase([#47](https://github.com/sivaprasadreddy/generator-springboot/issues/47)) 43 | * Upgraded liquibase configuration to use Out of the Box format and location 44 | 45 | ## Version 0.1.0 46 | * Upgraded SpringBoot to 2.7.4 and other library versions 47 | * Fixed code formatting 48 | * Fixed Flyway with MySQL and MariaDB issue 49 | 50 | ## Version 0.0.10 51 | * Upgraded SpringBoot to 2.6.7 and library versions 52 | * Updated Spring Cloud AWS setup to use new https://awspring.io/ based configuration 53 | * Removed `springfox-boot-starter` and used `springdoc-openapi-ui` 54 | * Added google-java-format support 55 | * Upgraded plugins versions 56 | * Removed Checkstyle, PMD plugins 57 | 58 | ## Version 0.0.8 59 | * Configured Checkstyle, PMD, SonarQube, google-java-format plugins 60 | * Added Localstack autoconfiguration support 61 | 62 | ## Version 0.0.7 63 | * Removed support for generation of `config-server` and `service-registry` 64 | * Updated SpringBoot and other libraries version 65 | 66 | ## Version 0.0.6 67 | * Updated to use testcontainers-spring-boot https://github.com/testcontainers/testcontainers-spring-boot 68 | * Generate Zipkin docker-compose file when Distributed Tracing is selected 69 | * Fixed Flyway/Liquibase db migration script generation issue 70 | * Added tests for sanity check 71 | 72 | ## Version 0.0.5 73 | * Added support for generating docker-compose yml files for application, ELK, Prometheus, Grafana 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 K. Siva Prasad Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-springboot 2 | The Yeoman generator for generating Spring Boot microservices. 3 | 4 | ## Prerequisites 5 | * Node 20+ 6 | * JDK 17+ 7 | 8 | ## Installation 9 | ```shell 10 | $ npm install -g yo 11 | $ npm install -g generator-springboot 12 | ``` 13 | 14 | ## How to use? 15 | Run the following command and answer the questions: 16 | 17 | ```shell 18 | $ yo springboot 19 | ``` 20 | 21 | ## Features 22 | The generator-springboot generates a Spring Boot application with the following features configured: 23 | 24 | * Spring Boot project with Maven and Gradle support 25 | * Spring Data JPA integration with an option to select databases like MySQL, Postgresql, MariaDB. 26 | * Flyway and Liquibase database migration support. 27 | * Spring Cloud AWS support with LocalStack configuration. 28 | * CORS configuration 29 | * Swagger UI Integration 30 | * SpringBoot Actuator configuration 31 | * Testcontainers based Testing and Local dev mode setup 32 | * DockerCompose configuration for application, ELK, Prometheus, Grafana 33 | * GitHub Actions Configuration 34 | * Dockerfile 35 | * Jenkinsfile 36 | * SonarQube and JaCoCo based static analysis tools configuration 37 | * Code formatting using Spotless and google-java-format 38 | * JUnit 5 39 | 40 | ### Generate a SpringBoot Microservice 41 | After installing the `generator-springboot`, you can generate a new Spring Boot application as follows: 42 | 43 | ```shell 44 | $ yo springboot 45 | Generating SpringBoot Application 46 | ? What is the application name? blog 47 | ? What is the default package name? com.sivalabs.blog 48 | ? Which type of database you want to use? Postgresql 49 | ? Which type of database migration tool you want to use? FlywayDB 50 | ? Select the features you want? ELK Docker configuration, Prometheus, Grafana Docker configuration, Localstack Docker configuration 51 | ? Which build tool do you want to use? Maven 52 | force blog/.yo-rc.json 53 | create blog/mvnw 54 | create blog/mvnw.cmd 55 | create blog/.gitignore 56 | create blog/.mvn/wrapper/maven-wrapper.jar 57 | create blog/.mvn/wrapper/maven-wrapper.properties 58 | create blog/pom.xml 59 | create blog/Dockerfile 60 | create blog/Jenkinsfile 61 | create blog/lombok.config 62 | create blog/sonar-project.properties 63 | create blog/README.md 64 | create blog/.github/workflows/maven.yml 65 | create blog/src/main/resources/db/migration/postgresql/V1__01_init.sql 66 | create blog/docker/docker-compose.yml 67 | create blog/docker/docker-compose-app.yml 68 | create blog/docker/docker-compose-monitoring.yml 69 | create blog/config/prometheus/prometheus.yml 70 | create blog/config/grafana/provisioning/dashboards/basic-dashboard.json 71 | create blog/config/grafana/provisioning/dashboards/dashboard.yml 72 | create blog/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json 73 | create blog/config/grafana/provisioning/datasources/datasource.yml 74 | create blog/docker/docker-compose-elk.yml 75 | create blog/config/elk/logstash.conf 76 | create blog/.localstack/01_init.sh 77 | create blog/src/main/java/com/sivalabs/blog/Application.java 78 | create blog/src/main/java/com/sivalabs/blog/config/WebMvcConfig.java 79 | create blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java 80 | create blog/src/main/java/com/sivalabs/blog/config/ApplicationProperties.java 81 | create blog/src/main/java/com/sivalabs/blog/config/Initializer.java 82 | create blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java 83 | create blog/src/main/java/com/sivalabs/blog/config/logging/Loggable.java 84 | create blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java 85 | create blog/src/main/java/com/sivalabs/blog/exception/ResourceNotFoundException.java 86 | create blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java 87 | create blog/src/main/java/com/sivalabs/blog/utils/AppConstants.java 88 | create blog/src/main/resources/application.properties 89 | create blog/src/main/resources/application-local.properties 90 | create blog/src/main/resources/logback-spring.xml 91 | create blog/src/test/java/com/sivalabs/blog/ApplicationIntegrationTest.java 92 | create blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java 93 | create blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java 94 | create blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java 95 | create blog/src/test/java/com/sivalabs/blog/TestApplication.java 96 | create blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java 97 | create blog/src/test/resources/application-test.properties 98 | create blog/src/test/resources/logback-test.xml 99 | 100 | No change to package.json was detected. No package manager install will be executed. 101 | Picked up JAVA_TOOL_OPTIONS: -Xmx3489m 102 | [INFO] Scanning for projects... 103 | [INFO] 104 | [INFO] -----------------------< com.sivalabs.blog:blog >----------------------- 105 | [INFO] Building blog 0.0.1-SNAPSHOT 106 | [INFO] from pom.xml 107 | [INFO] --------------------------------[ jar ]--------------------------------- 108 | [INFO] 109 | [INFO] --- spotless:2.39.0:apply (default-cli) @ blog --- 110 | [INFO] Index file does not exist. Fallback to an empty index 111 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java 112 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java 113 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java 114 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/exception/ResourceNotFoundException.java 115 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java 116 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java 117 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java 118 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java 119 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/TestApplication.java 120 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java 121 | [INFO] Spotless.Java is keeping 17 files clean - 10 were changed to be clean, 7 were already clean, 0 were skipped because caching determined they were already clean 122 | [INFO] ------------------------------------------------------------------------ 123 | [INFO] BUILD SUCCESS 124 | [INFO] ------------------------------------------------------------------------ 125 | [INFO] Total time: 4.454 s 126 | [INFO] Finished at: 2023-10-25T16:57:22Z 127 | [INFO] ------------------------------------------------------------------------ 128 | ========================================== 129 | Your application is generated successfully 130 | cd blog 131 | > ./mvnw spring-boot:run 132 | ========================================== 133 | 134 | ``` 135 | 136 | ### Generate REST API with CRUD operations 137 | You can generate REST API with CRUD operation using the following command: 138 | 139 | **IMPORTANT:** You should run the following command from within the generated project folder. 140 | 141 | ```shell 142 | $ cd blog 143 | $ yo springboot:controller Customer --base-path /api/customers 144 | ``` 145 | 146 | This sub-generator will generate the following: 147 | 148 | * JPA entity 149 | * Spring Data JPA Repository 150 | * Service 151 | * Spring MVC REST Controller with CRUD operations 152 | * Unit and Integration Tests for REST Controller 153 | * Flyway or Liquibase migration to create table 154 | 155 | ```shell 156 | $ yo springboot:controller Customer --base-path /api/customers 157 | Generating JPA entity, repository, service and controller 158 | EntityName: Customer, basePath: /api/customers 159 | force .yo-rc.json 160 | create src/main/java/com/sivalabs/blog/entities/Customer.java 161 | create src/main/java/com/sivalabs/blog/exception/CustomerNotFoundException.java 162 | create src/main/java/com/sivalabs/blog/mapper/CustomerMapper.java 163 | create src/main/java/com/sivalabs/blog/model/query/FindCustomersQuery.java 164 | create src/main/java/com/sivalabs/blog/model/request/CustomerRequest.java 165 | create src/main/java/com/sivalabs/blog/model/response/CustomerResponse.java 166 | create src/main/java/com/sivalabs/blog/repositories/CustomerRepository.java 167 | create src/main/java/com/sivalabs/blog/services/CustomerService.java 168 | create src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java 169 | create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java 170 | create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java 171 | create src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java 172 | create src/main/resources/db/migration/postgresql/V2__create_customers_table.sql 173 | 174 | No change to package.json was detected. No package manager install will be executed. 175 | Picked up JAVA_TOOL_OPTIONS: -Xmx3489m 176 | [INFO] Scanning for projects... 177 | [INFO] 178 | [INFO] -----------------------< com.sivalabs.blog:blog >----------------------- 179 | [INFO] Building blog 0.0.1-SNAPSHOT 180 | [INFO] from pom.xml 181 | [INFO] --------------------------------[ jar ]--------------------------------- 182 | [INFO] 183 | [INFO] --- spotless:2.39.0:apply (default-cli) @ blog --- 184 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/exception/CustomerNotFoundException.java 185 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/query/FindCustomersQuery.java 186 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/model/request/CustomerRequest.java 187 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/entities/Customer.java 188 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/mapper/CustomerMapper.java 189 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/services/CustomerService.java 190 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java 191 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java 192 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java 193 | [INFO] Writing clean file: /workspace/generator-springboot/blog/src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java 194 | [INFO] Spotless.Java is keeping 28 files clean - 10 were changed to be clean, 1 were already clean, 17 were skipped because caching determined they were already clean 195 | [INFO] ------------------------------------------------------------------------ 196 | [INFO] BUILD SUCCESS 197 | [INFO] ------------------------------------------------------------------------ 198 | [INFO] Total time: 2.246 s 199 | [INFO] Finished at: 2023-10-25T16:59:48Z 200 | [INFO] ------------------------------------------------------------------------ 201 | ``` 202 | 203 | ## Local Development Setup 204 | 205 | ```shell 206 | $ git clone https://github.com/sivaprasadreddy/generator-springboot.git 207 | $ cd generator-springboot 208 | $ npm install -g yo 209 | $ npm install 210 | $ npm link 211 | $ yo springboot 212 | ``` 213 | 214 | ## Releasing a new version 215 | Before publishing a new release, make sure to update the version number in `package.json` updated. 216 | 217 | ```shell 218 | $ npm login 219 | $ npm publish 220 | ``` 221 | 222 | ## License 223 | The **generator-springboot** is an Open Source software released under the [MIT Licence](https://opensource.org/license/mit/) 224 | -------------------------------------------------------------------------------- /docs/crud-generation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/docs/crud-generation.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Generator SpringBoot 2 | The Yeoman generator for generating Spring Boot microservices. 3 | 4 | ## Prerequisites 5 | * Node 16+ 6 | * JDK 17+ 7 | 8 | ## Installation 9 | ```shell 10 | $ npm install -g yo 11 | $ npm install -g generator-springboot 12 | ``` 13 | 14 | ## How to use? 15 | Run the following command and answer the questions: 16 | 17 | ```shell 18 | $ yo springboot 19 | ``` 20 | 21 | ## Features 22 | The generator-springboot generates a Spring Boot application with the following features configured: 23 | 24 | * Spring Boot project with Maven and Gradle support 25 | * Spring Data JPA integration with an option to select databases like MySQL, Postgresql, MariaDB. 26 | * Flyway and Liquibase database migration support. 27 | * Spring Cloud AWS support with LocalStack configuration. 28 | * CORS configuration 29 | * Swagger UI Integration 30 | * SpringBoot Actuator configuration 31 | * Testcontainers based Testing and Local dev mode setup 32 | * DockerCompose configuration for application, ELK, Prometheus, Grafana 33 | * GitHub Actions Configuration 34 | * Dockerfile 35 | * Jenkinsfile 36 | * SonarQube and JaCoCo based static analysis tools configuration 37 | * Code formatting using Spotless and google-java-format 38 | * JUnit 5 39 | 40 | ### Generate a SpringBoot Microservice 41 | After installing the `generator-springboot`, you can generate a new Spring Boot application as follows: 42 | 43 | ```shell 44 | $ yo springboot 45 | Generating SpringBoot Application 46 | ? What is the application name? blog 47 | ? What is the default package name? com.sivalabs.blog 48 | ? Which type of database you want to use? Postgresql 49 | ? Which type of database migration tool you want to use? FlywayDB 50 | ? Select the features you want? ELK Docker configuration, Prometheus, Grafana Docker configuration, Localstack Docker configuration 51 | ? Which build tool do you want to use? Maven 52 | force blog/.yo-rc.json 53 | create blog/mvnw 54 | create blog/mvnw.cmd 55 | create blog/.gitignore 56 | create blog/.mvn/wrapper/maven-wrapper.jar 57 | create blog/.mvn/wrapper/maven-wrapper.properties 58 | create blog/pom.xml 59 | create blog/Dockerfile 60 | create blog/Jenkinsfile 61 | create blog/lombok.config 62 | create blog/sonar-project.properties 63 | create blog/README.md 64 | create blog/.github/workflows/maven.yml 65 | create blog/src/main/resources/db/migration/postgresql/V1__01_init.sql 66 | create blog/docker/docker-compose.yml 67 | create blog/docker/docker-compose-app.yml 68 | create blog/docker/docker-compose-monitoring.yml 69 | create blog/config/prometheus/prometheus.yml 70 | create blog/config/grafana/provisioning/dashboards/basic-dashboard.json 71 | create blog/config/grafana/provisioning/dashboards/dashboard.yml 72 | create blog/config/grafana/provisioning/dashboards/jvm-micrometer_rev10.json 73 | create blog/config/grafana/provisioning/datasources/datasource.yml 74 | create blog/docker/docker-compose-elk.yml 75 | create blog/config/elk/logstash.conf 76 | create blog/.localstack/01_init.sh 77 | create blog/src/main/java/com/sivalabs/blog/Application.java 78 | create blog/src/main/java/com/sivalabs/blog/config/WebMvcConfig.java 79 | create blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java 80 | create blog/src/main/java/com/sivalabs/blog/config/ApplicationProperties.java 81 | create blog/src/main/java/com/sivalabs/blog/config/Initializer.java 82 | create blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java 83 | create blog/src/main/java/com/sivalabs/blog/config/logging/Loggable.java 84 | create blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java 85 | create blog/src/main/java/com/sivalabs/blog/utils/AppConstants.java 86 | create blog/src/main/resources/application.properties 87 | create blog/src/main/resources/application-local.properties 88 | create blog/src/main/resources/logback-spring.xml 89 | create blog/src/test/java/com/sivalabs/blog/ApplicationIntegrationTest.java 90 | create blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java 91 | create blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java 92 | create blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java 93 | create blog/src/test/java/com/sivalabs/blog/TestApplication.java 94 | create blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java 95 | create blog/src/test/resources/application-test.properties 96 | create blog/src/test/resources/logback-test.xml 97 | 98 | No change to package.json was detected. No package manager install will be executed. 99 | [INFO] Scanning for projects... 100 | [INFO] 101 | [INFO] -----------------------< com.sivalabs.blog:blog >----------------------- 102 | [INFO] Building blog 0.0.1-SNAPSHOT 103 | [INFO] from pom.xml 104 | [INFO] --------------------------------[ jar ]--------------------------------- 105 | [INFO] 106 | [INFO] --- spotless:2.39.0:apply (default-cli) @ blog --- 107 | [INFO] Index file does not exist. Fallback to an empty index 108 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/TestApplication.java 109 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/SqsListenerIntegrationTest.java 110 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/SchemaValidationTest.java 111 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/common/AbstractIntegrationTest.java 112 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/common/ContainersConfig.java 113 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/GlobalExceptionHandler.java 114 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/SwaggerConfig.java 115 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/config/logging/LoggingAspect.java 116 | [INFO] Spotless.Java is keeping 15 files clean - 8 were changed to be clean, 7 were already clean, 0 were skipped because caching determined they were already clean 117 | [INFO] ------------------------------------------------------------------------ 118 | [INFO] BUILD SUCCESS 119 | [INFO] ------------------------------------------------------------------------ 120 | [INFO] Total time: 1.192 s 121 | [INFO] Finished at: 2023-08-30T11:30:00+05:30 122 | [INFO] ------------------------------------------------------------------------ 123 | ========================================== 124 | Your application is generated successfully 125 | cd blog 126 | > ./mvnw spring-boot:run 127 | ========================================== 128 | ``` 129 | 130 | ### Generate REST API with CRUD operations 131 | You can generate REST API with CRUD operation using the following command: 132 | 133 | **IMPORTANT:** You should run the following command from within the generated project folder. 134 | 135 | ```shell 136 | $ cd blog 137 | $ yo springboot:controller Customer --base-path /api/customers 138 | ``` 139 | 140 | This sub-generator will generate the following: 141 | 142 | * JPA entity 143 | * Spring Data JPA Repository 144 | * Service 145 | * Spring MVC REST Controller with CRUD operations 146 | * Unit and Integration Tests for REST Controller 147 | * Flyway or Liquibase migration to create table 148 | 149 | ```shell 150 | $ yo springboot:controller Customer --base-path /api/customers 151 | Generating JPA entity, repository, service and controller 152 | EntityName: Customer, basePath: /api/customers 153 | force .yo-rc.json 154 | create src/main/java/com/sivalabs/blog/entities/Customer.java 155 | create src/main/java/com/sivalabs/blog/model/response/PagedResult.java 156 | create src/main/java/com/sivalabs/blog/repositories/CustomerRepository.java 157 | create src/main/java/com/sivalabs/blog/services/CustomerService.java 158 | create src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java 159 | create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java 160 | create src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java 161 | create src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java 162 | create src/main/resources/db/migration/postgresql/V2__create_customers_table.sql 163 | 164 | No change to package.json was detected. No package manager install will be executed. 165 | [INFO] Scanning for projects... 166 | [INFO] 167 | [INFO] -----------------------< com.sivalabs.blog:blog >----------------------- 168 | [INFO] Building blog 0.0.1-SNAPSHOT 169 | [INFO] from pom.xml 170 | [INFO] --------------------------------[ jar ]--------------------------------- 171 | [INFO] 172 | [INFO] --- spotless:2.39.0:apply (default-cli) @ blog --- 173 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerTest.java 174 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/web/controllers/CustomerControllerIT.java 175 | [INFO] Writing clean file: /Users/siva/blog/src/test/java/com/sivalabs/blog/services/CustomerServiceTest.java 176 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/web/controllers/CustomerController.java 177 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/model/response/PagedResult.java 178 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/services/CustomerService.java 179 | [INFO] Writing clean file: /Users/siva/blog/src/main/java/com/sivalabs/blog/entities/Customer.java 180 | [INFO] Spotless.Java is keeping 23 files clean - 7 were changed to be clean, 1 were already clean, 15 were skipped because caching determined they were already clean 181 | [INFO] ------------------------------------------------------------------------ 182 | [INFO] BUILD SUCCESS 183 | [INFO] ------------------------------------------------------------------------ 184 | [INFO] Total time: 1.190 s 185 | [INFO] Finished at: 2023-08-30T11:32:50+05:30 186 | [INFO] ------------------------------------------------------------------------ 187 | ``` 188 | -------------------------------------------------------------------------------- /docs/server-generation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/docs/server-generation-1.png -------------------------------------------------------------------------------- /docs/server-generation-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/docs/server-generation-2.png -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseGenerator = require('../base-generator'); 4 | const serverPrompts = require('../server/prompts'); 5 | 6 | module.exports = class extends BaseGenerator { 7 | 8 | constructor(args, opts) { 9 | super(args, opts); 10 | this.configOptions = this.options.configOptions || {}; 11 | } 12 | 13 | initializing() { 14 | this.logSuccess('Initializing SpringBoot Generator'); 15 | } 16 | 17 | // Use the same prompting logic as the server generator 18 | async prompting() { 19 | await serverPrompts.prompting.call(this); 20 | } 21 | 22 | // Pass the collected answers to the server generator with skipPrompts flag 23 | configuring() { 24 | // Merge any existing config with our answers 25 | Object.assign(this.configOptions, this.config.getAll()); 26 | 27 | // Compose with server but skip its prompts since we've already done them 28 | this.composeWith(require.resolve('../server'), { 29 | configOptions: this.configOptions, 30 | skipPrompts: true 31 | }); 32 | } 33 | 34 | // These methods are intentionally empty as the server generator will handle them 35 | writing() {} 36 | 37 | install() {} 38 | 39 | end() {} 40 | }; 41 | -------------------------------------------------------------------------------- /generators/base-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { default: Generator } = require('yeoman-generator'); 3 | const chalk = require('chalk'); 4 | const _ = require('lodash'); 5 | const log = console.log; 6 | const shell = require('shelljs'); 7 | 8 | module.exports = class extends Generator { 9 | 10 | constructor(args, opts) { 11 | super(args, opts); 12 | } 13 | 14 | logSuccess(msg) { 15 | log(chalk.bold.green(msg)); 16 | } 17 | 18 | logInfo(msg) { 19 | log(chalk.blue(msg)); 20 | } 21 | 22 | logWarn(msg) { 23 | log(chalk.keyword('orange')(msg)); 24 | } 25 | 26 | logError(msg) { 27 | log(chalk.bold.red(msg)); 28 | } 29 | 30 | generateMainJavaCode(configOptions, templates) { 31 | const mainJavaRootDir = 'src/main/java/'; 32 | this._generateCode(configOptions, templates, 'app/', mainJavaRootDir, configOptions.packageFolder); 33 | } 34 | 35 | generateMainResCode(configOptions, templates) { 36 | const mainResRootDir = 'src/main/resources/'; 37 | this._generateCode(configOptions, templates, 'app/', mainResRootDir, ''); 38 | } 39 | 40 | generateTestJavaCode(configOptions, templates) { 41 | const testJavaRootDir = 'src/test/java/'; 42 | this._generateCode(configOptions, templates, 'app/', testJavaRootDir, configOptions.packageFolder); 43 | } 44 | 45 | generateTestResCode(configOptions, templates) { 46 | const testResRootDir = 'src/test/resources/'; 47 | this._generateCode(configOptions, templates, 'app/', testResRootDir, ''); 48 | } 49 | 50 | generateFiles(configOptions, templates, srcRoot, baseFolder) { 51 | this._generateCode(configOptions, templates, srcRoot, baseFolder, ''); 52 | } 53 | 54 | _generateCode(configOptions, templates, srcRoot, baseFolder, packageFolder) { 55 | templates.forEach(tmpl => { 56 | if (_.isString(tmpl)) { 57 | this.renderTemplate( 58 | this.templatePath(srcRoot + baseFolder + tmpl), 59 | this.destinationPath(baseFolder + packageFolder + '/' + tmpl), 60 | configOptions 61 | ); 62 | } else { 63 | this.renderTemplate( 64 | this.templatePath(srcRoot + baseFolder + tmpl.src), 65 | this.destinationPath(baseFolder + packageFolder + '/' + tmpl.dest), 66 | configOptions 67 | ); 68 | } 69 | }); 70 | } 71 | 72 | _formatCode(configOptions, baseDir) { 73 | if (configOptions.buildTool === 'maven') { 74 | this._formatCodeMaven(configOptions, baseDir); 75 | } else { 76 | this._formatCodeGradle(configOptions, baseDir); 77 | } 78 | } 79 | 80 | _verifyBuild(configOptions, baseDir) { 81 | // Add a flag to indicate the method was called 82 | this.buildVerified = true; 83 | 84 | this.logInfo('Verifying build...'); 85 | if (configOptions.buildTool === 'maven') { 86 | this._verifyBuildMaven(configOptions, baseDir); 87 | } else { 88 | this._verifyBuildGradle(configOptions, baseDir); 89 | } 90 | } 91 | 92 | _verifyBuildMaven(configOptions, baseDir) { 93 | const command = this._isWin() ? 'mvnw' : './mvnw'; 94 | const args = ['clean', 'verify']; 95 | 96 | try { 97 | this.logInfo(`Running Maven build: ${command} ${args.join(' ')}`); 98 | let result; 99 | 100 | if(baseDir) { 101 | shell.cd(configOptions.appName); 102 | result = this.spawnCommandSync(command, args, { stdio: 'pipe' }); 103 | shell.cd('..'); 104 | } else { 105 | result = this.spawnCommandSync(command, args, { stdio: 'pipe' }); 106 | } 107 | 108 | if (!result || result.status !== 0) { 109 | this.logError('Maven build failed. Please check the generated code for errors.'); 110 | this.log(result && (result.stderr || result.stdout) || 'No output captured'); 111 | } else { 112 | this.logSuccess('Maven build successful! All tests passed.'); 113 | } 114 | } catch (error) { 115 | this.logError(`Maven build error: ${error.message}`); 116 | if (error.stack) { 117 | this.log(`Stack trace: ${error.stack}`); 118 | } 119 | } 120 | } 121 | 122 | _verifyBuildGradle(configOptions, baseDir) { 123 | const command = this._isWin() ? 'gradlew' : './gradlew'; 124 | const args = ['clean', 'build']; 125 | 126 | try { 127 | this.logInfo(`Running Gradle build: ${command} ${args.join(' ')}`); 128 | let result; 129 | 130 | if(baseDir) { 131 | shell.cd(configOptions.appName); 132 | result = this.spawnCommandSync(command, args, { stdio: 'pipe' }); 133 | shell.cd('..'); 134 | } else { 135 | result = this.spawnCommandSync(command, args, { stdio: 'pipe' }); 136 | } 137 | 138 | if (!result || result.status !== 0) { 139 | this.logError('Gradle build failed. Please check the generated code for errors.'); 140 | this.log(result && (result.stderr || result.stdout) || 'No output captured'); 141 | } else { 142 | this.logSuccess('Gradle build successful! All tests passed.'); 143 | } 144 | } catch (error) { 145 | this.logError(`Gradle build error: ${error.message}`); 146 | if (error.stack) { 147 | this.log(`Stack trace: ${error.stack}`); 148 | } 149 | } 150 | } 151 | 152 | _formatCodeMaven(configOptions, baseDir) { 153 | const command = this._isWin() ? 'mvnw' : './mvnw'; 154 | if(baseDir) { 155 | shell.cd(configOptions.appName); 156 | shell.exec(`${command} spotless:apply`); 157 | shell.cd('..'); 158 | } else { 159 | shell.exec(`${command} spotless:apply`); 160 | } 161 | } 162 | 163 | _formatCodeGradle(configOptions, baseDir) { 164 | const command = this._isWin() ? 'gradlew' : './gradlew'; 165 | if(baseDir) { 166 | shell.cd(configOptions.appName); 167 | shell.exec(`${command} spotlessApply`); 168 | shell.cd('..'); 169 | } else { 170 | shell.exec(`${command} spotlessApply`); 171 | } 172 | } 173 | 174 | _isWin() { 175 | return process.platform === 'win32'; 176 | } 177 | }; 178 | -------------------------------------------------------------------------------- /generators/common/files/gradle/gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Misc ### 40 | *.log 41 | .DS_Store -------------------------------------------------------------------------------- /generators/common/files/gradle/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/generators/common/files/gradle/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /generators/common/files/gradle/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /generators/common/files/gradle/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /generators/common/files/gradle/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /generators/common/files/maven/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /generators/common/files/maven/gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store -------------------------------------------------------------------------------- /generators/common/files/maven/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /generators/common/files/maven/mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /generators/constants.js: -------------------------------------------------------------------------------- 1 | const LOCALSTACK_IMAGE_VERSION = '4.2.0'; 2 | const POSTGRESQL_IMAGE_VERSION = '17.4-alpine'; 3 | const MARIADB_IMAGE_VERSION = '11.7'; 4 | const MYSQL_IMAGE_VERSION = '9.2'; 5 | 6 | module.exports = { 7 | JAVA_VERSION: '17', 8 | SPRING_BOOT_VERSION: '3.4.4', 9 | SPRING_CLOUD_VERSION: '2024.0.1', 10 | SPRING_CLOUD_AWS_VERSION: '3.3.0', 11 | SPRING_DEP_MNGMNT_VERSION: '1.1.7', 12 | DEFAULT_APP_VERSION: '0.0.1-SNAPSHOT', 13 | SPRINGDOC_OPENAPI_VERSION: '2.8.6', 14 | COMMONS_IO_VERSION: '2.19.0', 15 | LOGSTASH_LOGBACK_ENCODER: 8.1, 16 | PALANTIR_JAVA_FORMAT_VERSION: '2.58.0', 17 | 18 | JACOCO_MIN_COVERAGE_REQUIRED: '0.80', 19 | 20 | GRADLE_GIT_PROPERTIES_PLUGIN_VERSION: '2.5.0', 21 | GRADLE_JACOCO_PLUGIN_VERSION: '0.8.13', 22 | GRADLE_SONAR_PLUGIN_VERSION: '6.1.0.5360', 23 | GRADLE_OWASP_PLUGIN_VERSION: '12.1.1', 24 | GRADLE_BENMANES_VERSIONS_PLUGIN_VERSION: '0.52.0', 25 | GRADLE_SPOTLESS_PLUGIN_VERSION: '7.0.3', 26 | 27 | MAVEN_DEPENDENCY_CHECK_PLUGIN_VERSION: '12.1.1', 28 | MAVEN_PROPERTIES_PLUGIN_VERSION: '1.2.1', 29 | MAVEN_SONAR_PLUGIN_VERSION: '5.1.0.4751', 30 | MAVEN_JACOCO_PLUGIN_VERSION: '0.8.13', 31 | MAVEN_SPOTLESS_PLUGIN_VERSION: '2.44.4', 32 | 33 | KEY_FLYWAY_MIGRATION_COUNTER: 'flywayMigrationCounter', 34 | KEY_LIQUIBASE_MIGRATION_COUNTER: 'liquibaseMigrationCounter', 35 | 36 | LOCALSTACK_IMAGE_VERSION: LOCALSTACK_IMAGE_VERSION, 37 | LOCALSTACK_IMAGE: 'localstack/localstack:' + LOCALSTACK_IMAGE_VERSION, 38 | 39 | POSTGRESQL_IMAGE_VERSION: POSTGRESQL_IMAGE_VERSION, 40 | POSTGRESQL_IMAGE: 'postgres:' + POSTGRESQL_IMAGE_VERSION, 41 | 42 | MARIADB_IMAGE_VERSION: MARIADB_IMAGE_VERSION, 43 | MARIADB_IMAGE: 'mariadb:' + MARIADB_IMAGE_VERSION, 44 | 45 | MYSQL_IMAGE_VERSION: MYSQL_IMAGE_VERSION, 46 | MYSQL_IMAGE: 'mysql:' + MYSQL_IMAGE_VERSION, 47 | } 48 | -------------------------------------------------------------------------------- /generators/controller/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const BaseGenerator = require('../base-generator'); 3 | const constants = require('../constants'); 4 | const _ = require('lodash'); 5 | 6 | module.exports = class extends BaseGenerator { 7 | 8 | constructor(args, opts) { 9 | super(args, opts); 10 | this.configOptions = this.options.configOptions || {}; 11 | 12 | this.argument("entityName", { 13 | type: String, 14 | required: true, 15 | description: "Entity name" 16 | }); 17 | 18 | this.option('base-path', { 19 | type: String, 20 | desc: "Base URL path for REST Controller" 21 | }) 22 | 23 | this.option('skip-build', { 24 | type: Boolean, 25 | desc: "Skip verification build after generation", 26 | default: true 27 | }) 28 | } 29 | 30 | get initializing() { 31 | this.logSuccess('Generating JPA entity, repository, service and controller'); 32 | return { 33 | validateEntityName() { 34 | const context = this.context; 35 | console.log(`EntityName: ${this.options.entityName}, basePath: ${this.options.basePath}`); 36 | //this.env.error("The entity name is invalid"); 37 | } 38 | } 39 | } 40 | 41 | /*get prompting() { 42 | return prompts.prompting; 43 | }*/ 44 | 45 | configuring() { 46 | this.configOptions = Object.assign({}, this.configOptions, this.config.getAll()); 47 | this.configOptions.basePath = this.options['base-path']; 48 | this.configOptions.entityName = this.options.entityName; 49 | this.configOptions.entityVarName = _.camelCase(this.options.entityName); 50 | this.configOptions.tableName = _.snakeCase(this.options.entityName)+'s'; 51 | this.configOptions.doesNotSupportDatabaseSequences = 52 | this.configOptions.databaseType === 'mysql'; 53 | this.configOptions.formatCode = this.options.formatCode !== false; 54 | 55 | // Ensure packageName, appName, and buildTool are available from options if provided 56 | if (this.options.packageName) { 57 | this.configOptions.packageName = this.options.packageName; 58 | } 59 | if (this.options.appName) { 60 | this.configOptions.appName = this.options.appName; 61 | } else { 62 | // Default appName to a safe value if not provided 63 | this.configOptions.appName = this.configOptions.appName || "myapp"; 64 | } 65 | if (this.options.buildTool) { 66 | this.configOptions.buildTool = this.options.buildTool; 67 | } 68 | } 69 | 70 | writing() { 71 | this._generateAppCode(this.configOptions); 72 | this._generateDbMigrationConfig(this.configOptions) 73 | } 74 | 75 | end() { 76 | if(this.configOptions.formatCode !== false) { 77 | this._formatCode(this.configOptions, null); 78 | } 79 | 80 | if(!this.options['skip-build']) { 81 | this._verifyBuild(this.configOptions, null); 82 | } 83 | } 84 | 85 | _generateAppCode(configOptions) { 86 | const mainJavaTemplates = [ 87 | {src: 'entities/Entity.java', dest: 'entities/'+configOptions.entityName+'.java'}, 88 | {src: 'exception/NotFoundException.java', dest: 'exception/'+configOptions.entityName+'NotFoundException.java'}, 89 | {src: 'mapper/Mapper.java', dest: 'mapper/'+configOptions.entityName+'Mapper.java'}, 90 | {src: 'model/query/FindQuery.java', dest: 'model/query/Find'+configOptions.entityName+'sQuery.java'}, 91 | {src: 'model/request/Request.java', dest: 'model/request/'+configOptions.entityName+'Request.java'}, 92 | {src: 'model/response/Response.java', dest: 'model/response/'+configOptions.entityName+'Response.java'}, 93 | {src: 'repositories/Repository.java', dest: 'repositories/'+configOptions.entityName+'Repository.java'}, 94 | {src: 'services/Service.java', dest: 'services/'+configOptions.entityName+'Service.java'}, 95 | {src: 'web/controllers/Controller.java', dest: 'web/controllers/'+configOptions.entityName+'Controller.java'}, 96 | ]; 97 | this.generateMainJavaCode(configOptions, mainJavaTemplates); 98 | 99 | const testJavaTemplates = [ 100 | {src: 'web/controllers/ControllerTest.java', dest: 'web/controllers/'+configOptions.entityName+'ControllerTest.java'}, 101 | {src: 'web/controllers/ControllerIT.java', dest: 'web/controllers/'+configOptions.entityName+'ControllerIT.java'}, 102 | {src: 'services/ServiceTest.java', dest: 'services/'+configOptions.entityName+'ServiceTest.java'}, 103 | ]; 104 | this.generateTestJavaCode(configOptions, testJavaTemplates); 105 | } 106 | 107 | _generateDbMigrationConfig(configOptions) { 108 | 109 | if(configOptions.dbMigrationTool === 'flywaydb') { 110 | this._generateFlywayMigration(configOptions) 111 | } 112 | 113 | if(configOptions.dbMigrationTool === 'liquibase') { 114 | this._generateLiquibaseMigration(configOptions); 115 | } 116 | } 117 | 118 | _generateFlywayMigration(configOptions) { 119 | const counter = configOptions[constants.KEY_FLYWAY_MIGRATION_COUNTER] + 1; 120 | let vendor = configOptions.databaseType; 121 | const scriptTemplate = configOptions.doesNotSupportDatabaseSequences ? 122 | "V1__new_table_no_seq.sql" : "V1__new_table_with_seq.sql"; 123 | 124 | this.renderTemplate( 125 | this.templatePath('app/src/main/resources/db/migration/flyway/'+scriptTemplate), 126 | this.destinationPath('src/main/resources/db/migration/'+vendor+ 127 | '/V'+counter+'__create_'+configOptions.tableName+'_table.sql'), 128 | configOptions 129 | ); 130 | const flywayMigrantCounter = { 131 | [constants.KEY_FLYWAY_MIGRATION_COUNTER]: counter 132 | }; 133 | this.config.set(flywayMigrantCounter); 134 | } 135 | 136 | _generateLiquibaseMigration(configOptions) { 137 | const dbFmt = configOptions.dbMigrationFormat; 138 | const counter = configOptions[constants.KEY_LIQUIBASE_MIGRATION_COUNTER] + 1; 139 | const scriptTemplate = configOptions.doesNotSupportDatabaseSequences ? 140 | `01-new_table_no_seq.${dbFmt}` : `01-new_table_with_seq.${dbFmt}`; 141 | this.renderTemplate( 142 | this.templatePath('app/src/main/resources/db/migration/liquibase/changelog/'+scriptTemplate), 143 | this.destinationPath('src/main/resources/db/changelog/migration/0'+counter+'-create_'+configOptions.tableName+'_table.'+dbFmt), 144 | configOptions 145 | ); 146 | const liquibaseMigrantCounter = { 147 | [constants.KEY_LIQUIBASE_MIGRATION_COUNTER]: counter 148 | }; 149 | //const updatedConfig = Object.assign({}, this.config.getAll(), liquibaseMigrantCounter); 150 | this.config.set(liquibaseMigrantCounter); 151 | } 152 | }; 153 | -------------------------------------------------------------------------------- /generators/controller/prompts.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/generators/controller/prompts.js -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/entities/Entity.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.entities; 2 | 3 | import java.util.Objects; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | import lombok.Setter; 14 | import org.hibernate.Hibernate; 15 | 16 | @Entity 17 | @Table(name = "<%= tableName %>") 18 | @Getter 19 | @Setter 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | public class <%= entityName %> { 23 | 24 | @Id 25 | <%_ if (!doesNotSupportDatabaseSequences) { _%> 26 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 27 | <%_ } _%> 28 | <%_ if (doesNotSupportDatabaseSequences) { _%> 29 | @GeneratedValue(strategy = GenerationType.IDENTITY) 30 | <%_ } _%> 31 | private Long id; 32 | 33 | @Column(nullable = false) 34 | private String text; 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; 40 | <%= entityName %> <%= entityVarName %> = (<%= entityName %>) o; 41 | return id != null && Objects.equals(id, <%= entityVarName %>.id); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return getClass().hashCode(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.exception; 2 | 3 | public class <%= entityName %>NotFoundException extends ResourceNotFoundException { 4 | 5 | public <%= entityName %>NotFoundException(Long id) { 6 | super("<%= entityName %> with Id '%d' not found".formatted(id)); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/mapper/Mapper.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.mapper; 2 | 3 | import <%= packageName %>.entities.<%= entityName %>; 4 | import <%= packageName %>.model.request.<%= entityName %>Request; 5 | import <%= packageName %>.model.response.<%= entityName %>Response; 6 | import java.util.List; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class <%= entityName %>Mapper { 11 | 12 | public <%= entityName %> toEntity(<%= entityName %>Request <%= entityVarName %>Request) { 13 | <%= entityName %> <%= entityVarName %> = new <%= entityName %>(); 14 | <%= entityVarName %>.setText(<%= entityVarName %>Request.text()); 15 | return <%= entityVarName %>; 16 | } 17 | 18 | public void map<%= entityName %>WithRequest(<%= entityName %> <%= entityVarName %>, <%= entityName %>Request <%= entityVarName %>Request) { 19 | <%= entityVarName %>.setText(<%= entityVarName %>Request.text()); 20 | } 21 | 22 | public <%= entityName %>Response toResponse(<%= entityName %> <%= entityVarName %>) { 23 | return new <%= entityName %>Response(<%= entityVarName %>.getId(), <%= entityVarName %>.getText()); 24 | } 25 | 26 | public List<<%= entityName %>Response> toResponseList(List<<%= entityName %>> <%= entityVarName %>List) { 27 | return <%= entityVarName %>List.stream().map(this::toResponse).toList(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/model/query/FindQuery.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.model.query; 2 | 3 | public record Find<%= entityName %>sQuery(int pageNo, int pageSize, String sortBy, String sortDir) {} -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/model/request/Request.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.model.request; 2 | 3 | import jakarta.validation.constraints.NotEmpty; 4 | 5 | public record <%= entityName %>Request(@NotEmpty(message = "Text cannot be empty") String text) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/model/response/Response.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.model.response; 2 | 3 | public record <%= entityName %>Response(Long id, String text) {} 4 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/repositories/Repository.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.repositories; 2 | 3 | import <%= packageName %>.entities.<%= entityName %>; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface <%= entityName %>Repository extends JpaRepository<<%= entityName %>, Long> {} 7 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/services/Service.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.services; 2 | 3 | import <%= packageName %>.entities.<%= entityName %>; 4 | import <%= packageName %>.exception.<%= entityName %>NotFoundException; 5 | import <%= packageName %>.mapper.<%= entityName %>Mapper; 6 | import <%= packageName %>.model.query.Find<%= entityName %>sQuery; 7 | import <%= packageName %>.model.request.<%= entityName %>Request; 8 | import <%= packageName %>.model.response.<%= entityName %>Response; 9 | import <%= packageName %>.model.response.PagedResult; 10 | import <%= packageName %>.repositories.<%= entityName %>Repository; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.data.domain.Page; 16 | import org.springframework.data.domain.PageRequest; 17 | import org.springframework.data.domain.Pageable; 18 | import org.springframework.data.domain.Sort; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.transaction.annotation.Transactional; 21 | 22 | @Service 23 | @Transactional(readOnly = true) 24 | @RequiredArgsConstructor 25 | public class <%= entityName %>Service { 26 | 27 | private final <%= entityName %>Repository <%= entityVarName %>Repository; 28 | private final <%= entityName %>Mapper <%= entityVarName %>Mapper; 29 | 30 | public PagedResult<<%= entityName %>Response> findAll<%= entityName %>s( 31 | Find<%= entityName %>sQuery find<%= entityName %>sQuery) { 32 | 33 | // create Pageable instance 34 | Pageable pageable = createPageable(find<%= entityName %>sQuery); 35 | 36 | Page<<%= entityName %>> <%= entityVarName %>sPage = <%= entityVarName %>Repository.findAll(pageable); 37 | 38 | List<<%= entityName %>Response> <%= entityVarName %>ResponseList = <%= entityVarName %>Mapper.toResponseList(<%= entityVarName %>sPage.getContent()); 39 | 40 | return new PagedResult<>(<%= entityVarName %>sPage, <%= entityVarName %>ResponseList); 41 | } 42 | 43 | private Pageable createPageable(Find<%= entityName %>sQuery find<%= entityName %>sQuery) { 44 | int pageNo = Math.max(find<%= entityName %>sQuery.pageNo() - 1, 0); 45 | Sort sort = 46 | Sort.by( 47 | find<%= entityName %>sQuery.sortDir().equalsIgnoreCase(Sort.Direction.ASC.name()) 48 | ? Sort.Order.asc(find<%= entityName %>sQuery.sortBy()) 49 | : Sort.Order.desc(find<%= entityName %>sQuery.sortBy())); 50 | return PageRequest.of(pageNo, find<%= entityName %>sQuery.pageSize(), sort); 51 | } 52 | 53 | public Optional<<%= entityName %>Response> find<%= entityName %>ById(Long id) { 54 | return <%= entityVarName %>Repository.findById(id).map(<%= entityVarName %>Mapper::toResponse); 55 | } 56 | 57 | @Transactional 58 | public <%= entityName %>Response save<%= entityName %>(<%= entityName %>Request <%= entityVarName %>Request) { 59 | <%= entityName %> <%= entityVarName %> = <%= entityVarName %>Mapper.toEntity(<%= entityVarName %>Request); 60 | <%= entityName %> saved<%= entityName %> = <%= entityVarName %>Repository.save(<%= entityVarName %>); 61 | return <%= entityVarName %>Mapper.toResponse(saved<%= entityName %>); 62 | } 63 | 64 | @Transactional 65 | public <%= entityName %>Response update<%= entityName %>(Long id, <%= entityName %>Request <%= entityVarName %>Request) { 66 | <%= entityName %> <%= entityVarName %> = 67 | <%= entityVarName %>Repository 68 | .findById(id) 69 | .orElseThrow(() -> new <%= entityName %>NotFoundException(id)); 70 | 71 | // Update the <%= entityVarName %> object with data from <%= entityVarName %>Request 72 | <%= entityVarName %>Mapper.map<%= entityName %>WithRequest(<%= entityVarName %>, <%= entityVarName %>Request); 73 | 74 | // Save the updated <%= entityVarName %> object 75 | <%= entityName %> updated<%= entityName %> = <%= entityVarName %>Repository.save(<%= entityVarName %>); 76 | 77 | return <%= entityVarName %>Mapper.toResponse(updated<%= entityName %>); 78 | } 79 | 80 | @Transactional 81 | public void delete<%= entityName %>ById(Long id) { 82 | <%= entityVarName %>Repository.deleteById(id); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/java/web/controllers/Controller.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.web.controllers; 2 | 3 | import <%= packageName %>.exception.<%= entityName %>NotFoundException; 4 | import <%= packageName %>.model.query.Find<%= entityName %>sQuery; 5 | import <%= packageName %>.model.request.<%= entityName %>Request; 6 | import <%= packageName %>.model.response.<%= entityName %>Response; 7 | import <%= packageName %>.model.response.PagedResult; 8 | import <%= packageName %>.services.<%= entityName %>Service; 9 | import <%= packageName %>.utils.AppConstants; 10 | import java.util.List; 11 | import java.net.URI; 12 | import jakarta.validation.Valid; 13 | import lombok.extern.slf4j.Slf4j; 14 | import lombok.RequiredArgsConstructor; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.validation.annotation.Validated; 18 | import org.springframework.web.bind.annotation.DeleteMapping; 19 | import org.springframework.web.bind.annotation.GetMapping; 20 | import org.springframework.web.bind.annotation.PathVariable; 21 | import org.springframework.web.bind.annotation.PostMapping; 22 | import org.springframework.web.bind.annotation.PutMapping; 23 | import org.springframework.web.bind.annotation.RequestBody; 24 | import org.springframework.web.bind.annotation.RequestMapping; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | import org.springframework.web.bind.annotation.RestController; 27 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 28 | 29 | @RestController 30 | @RequestMapping("<%= basePath %>") 31 | @Slf4j 32 | @RequiredArgsConstructor 33 | class <%= entityName %>Controller { 34 | 35 | private final <%= entityName %>Service <%= entityVarName %>Service; 36 | 37 | @GetMapping 38 | PagedResult<<%= entityName %>Response> getAll<%= entityName %>s( 39 | @RequestParam( 40 | value = "pageNo", 41 | defaultValue = AppConstants.DEFAULT_PAGE_NUMBER, 42 | required = false) 43 | int pageNo, 44 | @RequestParam( 45 | value = "pageSize", 46 | defaultValue = AppConstants.DEFAULT_PAGE_SIZE, 47 | required = false) 48 | int pageSize, 49 | @RequestParam( 50 | value = "sortBy", 51 | defaultValue = AppConstants.DEFAULT_SORT_BY, 52 | required = false) 53 | String sortBy, 54 | @RequestParam( 55 | value = "sortDir", 56 | defaultValue = AppConstants.DEFAULT_SORT_DIRECTION, 57 | required = false) 58 | String sortDir 59 | ) { 60 | Find<%= entityName %>sQuery find<%= entityName %>sQuery = 61 | new Find<%= entityName %>sQuery(pageNo, pageSize, sortBy, sortDir); 62 | return <%= entityVarName %>Service.findAll<%= entityName %>s(find<%= entityName %>sQuery); 63 | } 64 | 65 | @GetMapping("/{id}") 66 | ResponseEntity<<%= entityName %>Response> get<%= entityName %>ById(@PathVariable Long id) { 67 | return <%= entityVarName %>Service 68 | .find<%= entityName %>ById(id) 69 | .map(ResponseEntity::ok) 70 | .orElseThrow(() -> new <%= entityName %>NotFoundException(id)); 71 | } 72 | 73 | @PostMapping 74 | ResponseEntity<<%= entityName %>Response> create<%= entityName %>(@RequestBody @Validated <%= entityName %>Request <%= entityVarName %>Request) { 75 | <%= entityName %>Response response = <%= entityVarName %>Service.save<%= entityName %>(<%= entityVarName %>Request); 76 | URI location = 77 | ServletUriComponentsBuilder.fromCurrentRequest() 78 | .path("<%= basePath %>/{id}") 79 | .buildAndExpand(response.id()) 80 | .toUri(); 81 | return ResponseEntity.created(location).body(response); 82 | } 83 | 84 | @PutMapping("/{id}") 85 | ResponseEntity<<%= entityName %>Response> update<%= entityName %>( 86 | @PathVariable Long id, @RequestBody @Valid <%= entityName %>Request <%= entityVarName %>Request) { 87 | return ResponseEntity.ok(<%= entityVarName %>Service.update<%= entityName %>(id, <%= entityVarName %>Request)); 88 | } 89 | 90 | @DeleteMapping("/{id}") 91 | ResponseEntity<<%= entityName %>Response> delete<%= entityName %>(@PathVariable Long id) { 92 | return <%= entityVarName %>Service 93 | .find<%= entityName %>ById(id) 94 | .map( 95 | <%= entityVarName %> -> { 96 | <%= entityVarName %>Service.delete<%= entityName %>ById(id); 97 | return ResponseEntity.ok(<%= entityVarName %>); 98 | }) 99 | .orElseThrow(() -> new <%= entityName %>NotFoundException(id)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_no_seq.sql: -------------------------------------------------------------------------------- 1 | create table <%= tableName %> ( 2 | id bigint not null auto_increment, 3 | <%_ if (databaseType != 'postgresql') { _%> 4 | text varchar(1024) not null, 5 | <%_ } _%> 6 | <%_ if (databaseType === 'postgresql') { _%> 7 | text text not null, 8 | <%_ } _%> 9 | primary key (id) 10 | ); 11 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/flyway/V1__new_table_with_seq.sql: -------------------------------------------------------------------------------- 1 | create sequence <%= tableName %>_seq start with 1 increment by 50; 2 | 3 | create table <%= tableName %> ( 4 | <%_ if (databaseType != 'mariadb') { _%> 5 | id bigint DEFAULT nextval('<%= tableName %>_seq') not null, 6 | <%_ } _%> 7 | <%_ if (databaseType === 'mariadb') { _%> 8 | id bigint DEFAULT nextval(`<%= tableName %>_seq`) not null, 9 | <%_ } _%> 10 | <%_ if (databaseType != 'postgresql') { _%> 11 | text varchar(1024) not null, 12 | <%_ } _%> 13 | <%_ if (databaseType === 'postgresql') { _%> 14 | text text not null, 15 | <%_ } _%> 16 | primary key (id) 17 | ); 18 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.sql: -------------------------------------------------------------------------------- 1 | -- liquibase formatted sql 2 | -- changeset author:app id:createTable-<% tableName %> 3 | -- see https://docs.liquibase.com/concepts/changelogs/sql-format.html 4 | 5 | create table <%= tableName %> ( 6 | id bigint not null auto_increment, 7 | <%_ if (databaseType != 'postgresql') { _%> 8 | text varchar(1024) not null, 9 | <%_ } _%> 10 | <%_ if (databaseType === 'postgresql') { _%> 11 | text text not null, 12 | <%_ } _%> 13 | primary key (id) 14 | ); 15 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_no_seq.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.liquibase.com/concepts/changelogs/yaml-format.html 2 | databaseChangeLog: 3 | - property: 4 | dbms: postgresql 5 | name: string.type 6 | value: text 7 | - property: 8 | dbms: "!postgresql" 9 | name: string.type 10 | value: varchar(255) 11 | - changeSet: 12 | author: author 13 | id: createTable-<%= tableName %> 14 | changes: 15 | - createTable: 16 | tableName: <%= tableName %> 17 | columns: 18 | - column: 19 | name: id 20 | type: bigint 21 | autoIncrement: true 22 | constraints: 23 | primaryKey: true 24 | nullable: false 25 | - column: 26 | name: text 27 | type: ${string.type} 28 | constraints: 29 | nullable: false 30 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.sql: -------------------------------------------------------------------------------- 1 | -- liquibase formatted sql 2 | -- changeset author:app id:createTable-<% tableName %> 3 | -- see https://docs.liquibase.com/concepts/changelogs/sql-format.html 4 | 5 | create sequence <%= tableName %>_seq start with 1 increment by 50; 6 | 7 | create table <%= tableName %> ( 8 | <%_ if (databaseType != 'mariadb') { _%> 9 | id bigint DEFAULT nextval('<%= tableName %>_seq') not null, 10 | <%_ } _%> 11 | <%_ if (databaseType === 'mariadb') { _%> 12 | id bigint DEFAULT nextval(`<%= tableName %>_seq`) not null, 13 | <%_ } _%> 14 | <%_ if (databaseType != 'postgresql') { _%> 15 | text varchar(1024) not null, 16 | <%_ } _%> 17 | <%_ if (databaseType === 'postgresql') { _%> 18 | text text not null, 19 | <%_ } _%> 20 | primary key (id) 21 | ); 22 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/main/resources/db/migration/liquibase/changelog/01-new_table_with_seq.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.liquibase.com/concepts/changelogs/yaml-format.html 2 | databaseChangeLog: 3 | - property: 4 | dbms: postgresql 5 | name: string.type 6 | value: text 7 | - property: 8 | dbms: "!postgresql" 9 | name: string.type 10 | value: varchar(255) 11 | - changeSet: 12 | author: author 13 | id: createTable-<%= tableName %> 14 | changes: 15 | - createSequence: 16 | sequenceName: <%= tableName %>_seq 17 | incrementBy: 50 18 | startValue: 1 19 | - createTable: 20 | tableName: <%= tableName %> 21 | columns: 22 | - column: 23 | name: id 24 | type: bigint 25 | defaultValueSequenceNext: <%= tableName %>_seq 26 | constraints: 27 | primaryKey: true 28 | nullable: false 29 | - column: 30 | name: text 31 | type: ${string.type} 32 | constraints: 33 | nullable: false 34 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/test/java/services/ServiceTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.services; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.ArgumentMatchers.any; 5 | import static org.mockito.BDDMockito.given; 6 | import static org.mockito.BDDMockito.times; 7 | import static org.mockito.BDDMockito.verify; 8 | import static org.mockito.BDDMockito.willDoNothing; 9 | 10 | import <%= packageName %>.entities.<%= entityName %>; 11 | import <%= packageName %>.mapper.<%= entityName %>Mapper; 12 | import <%= packageName %>.model.query.Find<%= entityName %>sQuery; 13 | import <%= packageName %>.model.response.<%= entityName %>Response; 14 | import <%= packageName %>.model.response.PagedResult; 15 | import <%= packageName %>.repositories.<%= entityName %>Repository; 16 | import java.util.List; 17 | import java.util.Optional; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.extension.ExtendWith; 20 | import org.mockito.InjectMocks; 21 | import org.mockito.Mock; 22 | import org.mockito.junit.jupiter.MockitoExtension; 23 | import org.springframework.data.domain.Page; 24 | import org.springframework.data.domain.PageImpl; 25 | import org.springframework.data.domain.PageRequest; 26 | import org.springframework.data.domain.Pageable; 27 | import org.springframework.data.domain.Sort; 28 | 29 | @ExtendWith(MockitoExtension.class) 30 | class <%= entityName %>ServiceTest { 31 | 32 | @Mock private <%= entityName %>Repository <%= entityVarName %>Repository; 33 | @Mock private <%= entityName %>Mapper <%= entityVarName %>Mapper; 34 | 35 | @InjectMocks private <%= entityName %>Service <%= entityVarName %>Service; 36 | 37 | @Test 38 | void find<%= entityName %>ById() { 39 | // given 40 | given(<%= entityVarName %>Repository.findById(1L)).willReturn(Optional.of(get<%= entityName %>())); 41 | given(<%= entityVarName %>Mapper.toResponse(any(<%= entityName %>.class))).willReturn(get<%= entityName %>Response()); 42 | // when 43 | Optional<<%= entityName %>Response> optional<%= entityName %> = <%= entityVarName %>Service.find<%= entityName %>ById(1L); 44 | // then 45 | assertThat(optional<%= entityName %>).isPresent(); 46 | <%= entityName %>Response <%= entityVarName %> = optional<%= entityName %>.get(); 47 | assertThat(<%= entityVarName %>.id()).isEqualTo(1L); 48 | assertThat(<%= entityVarName %>.text()).isEqualTo("junitTest"); 49 | } 50 | 51 | @Test 52 | void delete<%= entityName %>ById() { 53 | // given 54 | willDoNothing().given(<%= entityVarName %>Repository).deleteById(1L); 55 | // when 56 | <%= entityVarName %>Service.delete<%= entityName %>ById(1L); 57 | // then 58 | verify(<%= entityVarName %>Repository, times(1)).deleteById(1L); 59 | } 60 | 61 | private <%= entityName %> get<%= entityName %>() { 62 | <%= entityName %> <%= entityVarName %> = new <%= entityName %>(); 63 | <%= entityVarName %>.setId(1L); 64 | <%= entityVarName %>.setText("junitTest"); 65 | return <%= entityVarName %>; 66 | } 67 | 68 | private <%= entityName %>Response get<%= entityName %>Response() { 69 | return new <%= entityName %>Response(1L, "junitTest"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/test/java/web/controllers/ControllerIT.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.web.controllers; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.CoreMatchers.notNullValue; 5 | import static org.hamcrest.Matchers.hasSize; 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 7 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 8 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 9 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 13 | 14 | import <%= packageName %>.common.AbstractIntegrationTest; 15 | import <%= packageName %>.entities.<%= entityName %>; 16 | import <%= packageName %>.model.request.<%= entityName %>Request; 17 | import <%= packageName %>.repositories.<%= entityName %>Repository; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.http.HttpHeaders; 24 | import org.springframework.http.MediaType; 25 | 26 | class <%= entityName %>ControllerIT extends AbstractIntegrationTest { 27 | 28 | @Autowired private <%= entityName %>Repository <%= entityVarName %>Repository; 29 | 30 | private List<<%= entityName %>> <%= entityVarName %>List = null; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | <%= entityVarName %>Repository.deleteAllInBatch(); 35 | 36 | <%= entityVarName %>List = new ArrayList<>(); 37 | <%= entityVarName %>List.add(new <%= entityName %>(null, "First <%= entityName %>")); 38 | <%= entityVarName %>List.add(new <%= entityName %>(null, "Second <%= entityName %>")); 39 | <%= entityVarName %>List.add(new <%= entityName %>(null, "Third <%= entityName %>")); 40 | <%= entityVarName %>List = <%= entityVarName %>Repository.saveAll(<%= entityVarName %>List); 41 | } 42 | 43 | @Test 44 | void shouldFetchAll<%= entityName %>s() throws Exception { 45 | this.mockMvc 46 | .perform(get("<%= basePath %>")) 47 | .andExpect(status().isOk()) 48 | .andExpect(jsonPath("$.data.size()", is(<%= entityVarName %>List.size()))) 49 | .andExpect(jsonPath("$.totalElements", is(3))) 50 | .andExpect(jsonPath("$.pageNumber", is(1))) 51 | .andExpect(jsonPath("$.totalPages", is(1))) 52 | .andExpect(jsonPath("$.isFirst", is(true))) 53 | .andExpect(jsonPath("$.isLast", is(true))) 54 | .andExpect(jsonPath("$.hasNext", is(false))) 55 | .andExpect(jsonPath("$.hasPrevious", is(false))); 56 | } 57 | 58 | @Test 59 | void shouldFind<%= entityName %>ById() throws Exception { 60 | <%= entityName %> <%= entityVarName %> = <%= entityVarName %>List.get(0); 61 | Long <%= entityVarName %>Id = <%= entityVarName %>.getId(); 62 | 63 | this.mockMvc 64 | .perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id)) 65 | .andExpect(status().isOk()) 66 | .andExpect(jsonPath("$.id", is(<%= entityVarName %>.getId()), Long.class)) 67 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.getText()))); 68 | } 69 | 70 | @Test 71 | void shouldCreateNew<%= entityName %>() throws Exception { 72 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("New <%= entityName %>"); 73 | this.mockMvc 74 | .perform( 75 | post("<%= basePath %>") 76 | .contentType(MediaType.APPLICATION_JSON) 77 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 78 | .andExpect(status().isCreated()) 79 | .andExpect(header().exists(HttpHeaders.LOCATION)) 80 | .andExpect(jsonPath("$.id", notNullValue())) 81 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>Request.text()))); 82 | } 83 | 84 | @Test 85 | void shouldReturn400WhenCreateNew<%= entityName %>WithoutText() throws Exception { 86 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request(null); 87 | 88 | this.mockMvc 89 | .perform( 90 | post("<%= basePath %>") 91 | .contentType(MediaType.APPLICATION_JSON) 92 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 93 | .andExpect(status().isBadRequest()) 94 | .andExpect(header().string("Content-Type", is("application/problem+json"))) 95 | .andExpect(jsonPath("$.type", is("about:blank"))) 96 | .andExpect(jsonPath("$.title", is("Constraint Violation"))) 97 | .andExpect(jsonPath("$.status", is(400))) 98 | .andExpect(jsonPath("$.detail", is("Invalid request content."))) 99 | .andExpect(jsonPath("$.instance", is("<%= basePath %>"))) 100 | .andExpect(jsonPath("$.violations", hasSize(1))) 101 | .andExpect(jsonPath("$.violations[0].field", is("text"))) 102 | .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty"))) 103 | .andReturn(); 104 | } 105 | 106 | @Test 107 | void shouldUpdate<%= entityName %>() throws Exception { 108 | Long <%= entityVarName %>Id = <%= entityVarName %>List.get(0).getId(); 109 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated <%= entityName %>"); 110 | 111 | this.mockMvc 112 | .perform( 113 | put("<%= basePath %>/{id}", <%= entityVarName %>Id) 114 | .contentType(MediaType.APPLICATION_JSON) 115 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 116 | .andExpect(status().isOk()) 117 | .andExpect(jsonPath("$.id", is(<%= entityVarName %>Id), Long.class)) 118 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>Request.text()))); 119 | } 120 | 121 | @Test 122 | void shouldDelete<%= entityName %>() throws Exception { 123 | <%= entityName %> <%= entityVarName %> = <%= entityVarName %>List.get(0); 124 | 125 | this.mockMvc 126 | .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>.getId())) 127 | .andExpect(status().isOk()) 128 | .andExpect(jsonPath("$.id", is(<%= entityVarName %>.getId()), Long.class)) 129 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.getText()))); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /generators/controller/templates/app/src/test/java/web/controllers/ControllerTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.web.controllers; 2 | 3 | import static <%= packageName %>.utils.AppConstants.PROFILE_TEST; 4 | import static org.hamcrest.CoreMatchers.is; 5 | import static org.hamcrest.CoreMatchers.notNullValue; 6 | import static org.hamcrest.Matchers.hasSize; 7 | import static org.mockito.ArgumentMatchers.any; 8 | import static org.mockito.ArgumentMatchers.eq; 9 | import static org.mockito.BDDMockito.given; 10 | import static org.mockito.Mockito.doNothing; 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import <%= packageName %>.entities.<%= entityName %>; 21 | import <%= packageName %>.exception.<%= entityName %>NotFoundException; 22 | import <%= packageName %>.model.query.Find<%= entityName %>sQuery; 23 | import <%= packageName %>.model.request.<%= entityName %>Request; 24 | import <%= packageName %>.model.response.<%= entityName %>Response; 25 | import <%= packageName %>.model.response.PagedResult; 26 | import <%= packageName %>.services.<%= entityName %>Service; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Optional; 30 | import org.junit.jupiter.api.BeforeEach; 31 | import org.junit.jupiter.api.Test; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 34 | import org.springframework.boot.test.mock.mockito.MockBean; 35 | import org.springframework.data.domain.Page; 36 | import org.springframework.data.domain.PageImpl; 37 | import org.springframework.http.HttpHeaders; 38 | import org.springframework.http.MediaType; 39 | import org.springframework.test.context.ActiveProfiles; 40 | import org.springframework.test.context.bean.override.mockito.MockitoBean; 41 | import org.springframework.test.web.servlet.MockMvc; 42 | 43 | @WebMvcTest(controllers = <%= entityName %>Controller.class) 44 | @ActiveProfiles(PROFILE_TEST) 45 | class <%= entityName %>ControllerTest { 46 | 47 | @Autowired private MockMvc mockMvc; 48 | 49 | @MockitoBean private <%= entityName %>Service <%= entityVarName %>Service; 50 | 51 | @Autowired private ObjectMapper objectMapper; 52 | 53 | private List<<%= entityName %>> <%= entityVarName %>List; 54 | 55 | @BeforeEach 56 | void setUp() { 57 | this.<%= entityVarName %>List = new ArrayList<>(); 58 | this.<%= entityVarName %>List.add(new <%= entityName %>(1L, "text 1")); 59 | this.<%= entityVarName %>List.add(new <%= entityName %>(2L, "text 2")); 60 | this.<%= entityVarName %>List.add(new <%= entityName %>(3L, "text 3")); 61 | } 62 | 63 | @Test 64 | void shouldFetchAll<%= entityName %>s() throws Exception { 65 | 66 | Page<<%= entityName %>> page = new PageImpl<>(<%= entityVarName %>List); 67 | PagedResult<<%= entityName %>Response> <%= entityVarName %>PagedResult = new PagedResult<>(page, get<%= entityName %>ResponseList()); 68 | Find<%= entityName %>sQuery find<%= entityName %>sQuery = new Find<%= entityName %>sQuery(0, 10, "id", "asc"); 69 | given(<%= entityVarName %>Service.findAll<%= entityName %>s(find<%= entityName %>sQuery)).willReturn(<%= entityVarName %>PagedResult); 70 | 71 | this.mockMvc 72 | .perform(get("<%= basePath %>")) 73 | .andExpect(status().isOk()) 74 | .andExpect(jsonPath("$.data.size()", is(<%= entityVarName %>List.size()))) 75 | .andExpect(jsonPath("$.totalElements", is(3))) 76 | .andExpect(jsonPath("$.pageNumber", is(1))) 77 | .andExpect(jsonPath("$.totalPages", is(1))) 78 | .andExpect(jsonPath("$.isFirst", is(true))) 79 | .andExpect(jsonPath("$.isLast", is(true))) 80 | .andExpect(jsonPath("$.hasNext", is(false))) 81 | .andExpect(jsonPath("$.hasPrevious", is(false))); 82 | } 83 | 84 | @Test 85 | void shouldFind<%= entityName %>ById() throws Exception { 86 | Long <%= entityVarName %>Id = 1L; 87 | <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "text 1"); 88 | given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.of(<%= entityVarName %>)); 89 | 90 | this.mockMvc 91 | .perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id)) 92 | .andExpect(status().isOk()) 93 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text()))); 94 | } 95 | 96 | @Test 97 | void shouldReturn404WhenFetchingNonExisting<%= entityName %>() throws Exception { 98 | Long <%= entityVarName %>Id = 1L; 99 | given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.empty()); 100 | 101 | this.mockMvc.perform(get("<%= basePath %>/{id}", <%= entityVarName %>Id)) 102 | .andExpect(status().isNotFound()) 103 | .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE))) 104 | .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found"))) 105 | .andExpect(jsonPath("$.title", is("Not Found"))) 106 | .andExpect(jsonPath("$.status", is(404))) 107 | .andExpect( 108 | jsonPath("$.detail") 109 | .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id))); 110 | } 111 | 112 | @Test 113 | void shouldCreateNew<%= entityName %>() throws Exception { 114 | 115 | <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(1L, "some text"); 116 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("some text"); 117 | given(<%= entityVarName %>Service.save<%= entityName %>(any(<%= entityName %>Request.class))) 118 | .willReturn(<%= entityVarName %>); 119 | 120 | this.mockMvc 121 | .perform( 122 | post("<%= basePath %>") 123 | .contentType(MediaType.APPLICATION_JSON) 124 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 125 | .andExpect(status().isCreated()) 126 | .andExpect(header().exists(HttpHeaders.LOCATION)) 127 | .andExpect(jsonPath("$.id", notNullValue())) 128 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text()))); 129 | } 130 | 131 | @Test 132 | void shouldReturn400WhenCreateNew<%= entityName %>WithoutText() throws Exception { 133 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request(null); 134 | 135 | this.mockMvc 136 | .perform( 137 | post("<%= basePath %>") 138 | .contentType(MediaType.APPLICATION_JSON) 139 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 140 | .andExpect(status().isBadRequest()) 141 | .andExpect(header().string("Content-Type", is("application/problem+json"))) 142 | .andExpect(jsonPath("$.type", is("about:blank"))) 143 | .andExpect(jsonPath("$.title", is("Constraint Violation"))) 144 | .andExpect(jsonPath("$.status", is(400))) 145 | .andExpect(jsonPath("$.detail", is("Invalid request content."))) 146 | .andExpect(jsonPath("$.instance", is("<%= basePath %>"))) 147 | .andExpect(jsonPath("$.violations", hasSize(1))) 148 | .andExpect(jsonPath("$.violations[0].field", is("text"))) 149 | .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty"))) 150 | .andReturn(); 151 | } 152 | 153 | @Test 154 | void shouldUpdate<%= entityName %>() throws Exception { 155 | Long <%= entityVarName %>Id = 1L; 156 | <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "Updated text"); 157 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated text"); 158 | given(<%= entityVarName %>Service.update<%= entityName %>(eq(<%= entityVarName %>Id), any(<%= entityName %>Request.class))) 159 | .willReturn(<%= entityVarName %>); 160 | 161 | this.mockMvc 162 | .perform( 163 | put("<%= basePath %>/{id}", <%= entityVarName %>Id) 164 | .contentType(MediaType.APPLICATION_JSON) 165 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 166 | .andExpect(status().isOk()) 167 | .andExpect(jsonPath("$.id", is(<%= entityVarName %>Id), Long.class)) 168 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text()))); 169 | } 170 | 171 | @Test 172 | void shouldReturn404WhenUpdatingNonExisting<%= entityName %>() throws Exception { 173 | Long <%= entityVarName %>Id = 1L; 174 | <%= entityName %>Request <%= entityVarName %>Request = new <%= entityName %>Request("Updated text"); 175 | given(<%= entityVarName %>Service.update<%= entityName %>(eq(<%= entityVarName %>Id), any(<%= entityName %>Request.class))) 176 | .willThrow(new <%= entityName %>NotFoundException(<%= entityVarName %>Id)); 177 | 178 | this.mockMvc 179 | .perform( 180 | put("<%= basePath %>/{id}", <%= entityVarName %>Id) 181 | .contentType(MediaType.APPLICATION_JSON) 182 | .content(objectMapper.writeValueAsString(<%= entityVarName %>Request))) 183 | .andExpect(status().isNotFound()) 184 | .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE))) 185 | .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found"))) 186 | .andExpect(jsonPath("$.title", is("Not Found"))) 187 | .andExpect(jsonPath("$.status", is(404))) 188 | .andExpect( 189 | jsonPath("$.detail") 190 | .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id))); 191 | } 192 | 193 | @Test 194 | void shouldDelete<%= entityName %>() throws Exception { 195 | Long <%= entityVarName %>Id = 1L; 196 | <%= entityName %>Response <%= entityVarName %> = new <%= entityName %>Response(<%= entityVarName %>Id, "Some text"); 197 | given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.of(<%= entityVarName %>)); 198 | doNothing().when(<%= entityVarName %>Service).delete<%= entityName %>ById(<%= entityVarName %>Id); 199 | 200 | this.mockMvc 201 | .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>Id)) 202 | .andExpect(status().isOk()) 203 | .andExpect(jsonPath("$.text", is(<%= entityVarName %>.text()))); 204 | } 205 | 206 | @Test 207 | void shouldReturn404WhenDeletingNonExisting<%= entityName %>() throws Exception { 208 | Long <%= entityVarName %>Id = 1L; 209 | given(<%= entityVarName %>Service.find<%= entityName %>ById(<%= entityVarName %>Id)).willReturn(Optional.empty()); 210 | 211 | this.mockMvc 212 | .perform(delete("<%= basePath %>/{id}", <%= entityVarName %>Id)) 213 | .andExpect(header().string("Content-Type", is(MediaType.APPLICATION_PROBLEM_JSON_VALUE))) 214 | .andExpect(jsonPath("$.type", is("http://api.<%= appName %>.com/errors/not-found"))) 215 | .andExpect(jsonPath("$.title", is("Not Found"))) 216 | .andExpect(jsonPath("$.status", is(404))) 217 | .andExpect( 218 | jsonPath("$.detail") 219 | .value("<%= entityName %> with Id '%d' not found".formatted(<%= entityVarName %>Id))); 220 | } 221 | 222 | List<<%= entityName %>Response> get<%= entityName %>ResponseList() { 223 | return <%= entityVarName %>List.stream() 224 | .map(<%= entityVarName %> -> new <%= entityName %>Response(<%= entityVarName %>.getId(), <%= entityVarName %>.getText())) 225 | .toList(); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /generators/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const BaseGenerator = require('../base-generator'); 3 | const constants = require('../constants'); 4 | const prompts = require('./prompts'); 5 | const path = require('path'); 6 | 7 | module.exports = class extends BaseGenerator { 8 | 9 | constructor(args, opts) { 10 | super(args, opts); 11 | this.configOptions = this.options.configOptions || {}; 12 | this.skipPrompts = this.options.skipPrompts || false; 13 | } 14 | 15 | initializing() { 16 | this.logSuccess('Generating SpringBoot Application') 17 | } 18 | 19 | get prompting() { 20 | // Skip prompts if skipPrompts flag is set to true (when called from app generator) 21 | if (this.skipPrompts) { 22 | return {}; 23 | } 24 | return prompts.prompting; 25 | } 26 | 27 | configuring() { 28 | this.destinationRoot(path.join(this.destinationRoot(), '/'+this.configOptions.appName)); 29 | this.config.set(this.configOptions); 30 | Object.assign(this.configOptions, constants); 31 | this.configOptions.formatCode = this.options.formatCode !== false 32 | } 33 | 34 | writing() { 35 | this._generateBuildToolConfig(this.configOptions); 36 | this._generateDockerConfig(this.configOptions); 37 | this._generateJenkinsFile(this.configOptions); 38 | this._generateMiscFiles(this.configOptions); 39 | this._generateGithubActionsFiles(this.configOptions); 40 | this._generateDbMigrationConfig(this.configOptions); 41 | this._generateDockerComposeFiles(this.configOptions); 42 | this._generateLocalstackConfig(this.configOptions); 43 | this._generateAppCode(this.configOptions); 44 | } 45 | 46 | end() { 47 | if(this.configOptions.formatCode !== false) { 48 | this._formatCode(this.configOptions, this.configOptions.appName); 49 | } 50 | this._printGenerationSummary(this.configOptions); 51 | } 52 | 53 | _printGenerationSummary(configOptions) { 54 | this.logError("=========================================="); 55 | this.logSuccess("Your application is generated successfully"); 56 | this.logSuccess(` cd ${configOptions.appName}`); 57 | if (configOptions.buildTool === 'maven') { 58 | this.logSuccess(" > ./mvnw spring-boot:run"); 59 | } else { 60 | this.logSuccess(" > ./gradlew bootRun"); 61 | } 62 | this.logError("=========================================="); 63 | } 64 | 65 | _generateBuildToolConfig(configOptions) { 66 | if (configOptions.buildTool === 'maven') { 67 | this._generateMavenConfig(configOptions); 68 | } else { 69 | this._generateGradleConfig(configOptions); 70 | } 71 | } 72 | 73 | _generateDockerConfig(configOptions) { 74 | this.renderTemplate( 75 | this.templatePath('app/Dockerfile'), 76 | this.destinationPath('Dockerfile'), 77 | configOptions 78 | ); 79 | } 80 | 81 | _generateJenkinsFile(configOptions) { 82 | this.renderTemplate( 83 | this.templatePath('app/Jenkinsfile'), 84 | this.destinationPath('Jenkinsfile'), 85 | configOptions 86 | ); 87 | } 88 | 89 | _generateMiscFiles(configOptions) { 90 | this.renderTemplate(this.templatePath('app/lombok.config'), this.destinationPath('lombok.config'), configOptions); 91 | this.renderTemplate(this.templatePath('app/sonar-project.properties'), this.destinationPath('sonar-project.properties'), configOptions); 92 | this.renderTemplate(this.templatePath('app/README.md'), this.destinationPath('README.md'), configOptions); 93 | } 94 | 95 | _generateGithubActionsFiles(configOptions) { 96 | const ciFile = '.github/workflows/' + configOptions.buildTool + '.yml'; 97 | 98 | this.renderTemplate( 99 | this.templatePath('app/' + ciFile), 100 | this.destinationPath(ciFile), 101 | configOptions 102 | ); 103 | } 104 | 105 | _generateMavenConfig(configOptions) { 106 | this._copyMavenWrapper(configOptions); 107 | this._generateMavenPOMXml(configOptions); 108 | } 109 | 110 | _generateGradleConfig(configOptions) { 111 | this._copyGradleWrapper(configOptions); 112 | this._generateGradleBuildScript(configOptions); 113 | } 114 | 115 | _copyMavenWrapper(configOptions) { 116 | const commonMavenConfigDir = '../../common/files/maven/'; 117 | 118 | ['mvnw', 'mvnw.cmd'].forEach(tmpl => { 119 | this.renderTemplate( 120 | this.templatePath(commonMavenConfigDir + tmpl), 121 | this.destinationPath(tmpl) 122 | ); 123 | }); 124 | 125 | this.renderTemplate( 126 | this.templatePath(commonMavenConfigDir + 'gitignore'), 127 | this.destinationPath('.gitignore') 128 | ); 129 | 130 | this.fs.copy( 131 | this.templatePath(commonMavenConfigDir + '.mvn'), 132 | this.destinationPath('.mvn') 133 | ); 134 | } 135 | 136 | _generateMavenPOMXml(configOptions) { 137 | const mavenConfigDir = 'maven/'; 138 | this.renderTemplate( 139 | this.templatePath(mavenConfigDir + 'pom.xml'), 140 | this.destinationPath('pom.xml'), 141 | configOptions 142 | ); 143 | } 144 | 145 | _copyGradleWrapper(configOptions) { 146 | const commonGradleConfigDir = '../../common/files/gradle/'; 147 | 148 | ['gradlew', 'gradlew.bat'].forEach(tmpl => { 149 | this.renderTemplate( 150 | this.templatePath(commonGradleConfigDir + tmpl), 151 | this.destinationPath(tmpl) 152 | ); 153 | }); 154 | 155 | this.renderTemplate( 156 | this.templatePath(commonGradleConfigDir + 'gitignore'), 157 | this.destinationPath('.gitignore') 158 | ); 159 | 160 | this.fs.copy( 161 | this.templatePath(commonGradleConfigDir + 'gradle'), 162 | this.destinationPath('gradle') 163 | ); 164 | } 165 | 166 | _generateGradleBuildScript(configOptions) { 167 | const gradleConfigDir = 'gradle/'; 168 | 169 | ['build.gradle', 'settings.gradle', 'gradle.properties'].forEach(tmpl => { 170 | this.renderTemplate( 171 | this.templatePath(gradleConfigDir + tmpl), 172 | this.destinationPath(tmpl), 173 | configOptions 174 | ); 175 | }); 176 | ['code-quality.gradle', 'owasp.gradle'].forEach(tmpl => { 177 | this.renderTemplate( 178 | this.templatePath(gradleConfigDir + tmpl), 179 | this.destinationPath('gradle/' + tmpl), 180 | configOptions 181 | ); 182 | }); 183 | } 184 | 185 | _generateAppCode(configOptions) { 186 | const mainJavaTemplates = [ 187 | 'Application.java', 188 | 'config/WebMvcConfig.java', 189 | 'config/SwaggerConfig.java', 190 | 'config/ApplicationProperties.java', 191 | 'config/Initializer.java', 192 | 'config/GlobalExceptionHandler.java', 193 | 'config/logging/Loggable.java', 194 | 'config/logging/LoggingAspect.java', 195 | 'exception/ResourceNotFoundException.java', 196 | 'model/response/PagedResult.java', 197 | 'utils/AppConstants.java' 198 | ]; 199 | this.generateMainJavaCode(configOptions, mainJavaTemplates); 200 | 201 | const mainResTemplates = [ 202 | 'application.properties', 203 | 'application-local.properties', 204 | 'logback-spring.xml' 205 | ]; 206 | this.generateMainResCode(configOptions, mainResTemplates); 207 | 208 | const testJavaTemplates = [ 209 | 'ApplicationIntegrationTest.java', 210 | 'SchemaValidationTest.java', 211 | 'common/ContainersConfig.java', 212 | 'common/AbstractIntegrationTest.java', 213 | 'TestApplication.java' 214 | ]; 215 | if(configOptions.features.includes("localstack")) { 216 | testJavaTemplates.push('SqsListenerIntegrationTest.java'); 217 | } 218 | this.generateTestJavaCode(configOptions, testJavaTemplates); 219 | 220 | const testResTemplates = [ 221 | 'application-test.properties', 222 | 'logback-test.xml' 223 | ]; 224 | this.generateTestResCode(configOptions, testResTemplates); 225 | } 226 | 227 | _generateDbMigrationConfig(configOptions) { 228 | if(configOptions.dbMigrationTool === 'flywaydb') { 229 | let vendor = configOptions.databaseType; 230 | const resTemplates = [ 231 | {src: 'db/migration/flyway/V1__01_init.sql', dest: 'db/migration/'+ vendor +'/V1__01_init.sql'}, 232 | 233 | ]; 234 | this.generateFiles(configOptions, resTemplates, 'app/','src/main/resources/'); 235 | const flywayMigrantCounter = { 236 | [constants.KEY_FLYWAY_MIGRATION_COUNTER]: 1 237 | }; 238 | Object.assign(configOptions, flywayMigrantCounter); 239 | this.config.set(flywayMigrantCounter); 240 | } 241 | 242 | if(configOptions.dbMigrationTool === 'liquibase') { 243 | const dbFmt = configOptions.dbMigrationFormat || 'xml'; 244 | const resTemplates = [ 245 | {src: 'db/migration/liquibase/changelog/db.changelog-master.yaml', dest: 'db/changelog/db.changelog-master.yaml'}, 246 | {src: `db/migration/liquibase/changelog/01-init.${dbFmt}`, dest: `db/changelog/migration/01-init.${dbFmt}`}, 247 | 248 | ]; 249 | this.generateFiles(configOptions, resTemplates, 'app/','src/main/resources/'); 250 | const liquibaseMigrantCounter = { 251 | [constants.KEY_LIQUIBASE_MIGRATION_COUNTER]: 1 252 | }; 253 | Object.assign(configOptions, liquibaseMigrantCounter); 254 | this.config.set(liquibaseMigrantCounter); 255 | } 256 | } 257 | 258 | _generateLocalstackConfig(configOptions) { 259 | if(configOptions.features.includes('localstack')) { 260 | this.fs.copy( 261 | this.templatePath('app/.localstack'), 262 | this.destinationPath('./.localstack') 263 | ); 264 | } 265 | } 266 | 267 | _generateDockerComposeFiles(configOptions) { 268 | this._generateAppDockerComposeFile(configOptions); 269 | if(configOptions.features.includes('monitoring')) { 270 | this._generateMonitoringConfig(configOptions); 271 | } 272 | if(configOptions.features.includes('elk')) { 273 | this._generateELKConfig(configOptions); 274 | } 275 | } 276 | 277 | _generateAppDockerComposeFile(configOptions) { 278 | const resTemplates = [ 279 | 'docker-compose.yml', 280 | 'docker-compose-app.yml', 281 | ]; 282 | this.generateFiles(configOptions, resTemplates, 'app/','docker/'); 283 | } 284 | 285 | _generateELKConfig(configOptions) { 286 | const resTemplates = [ 287 | 'docker/docker-compose-elk.yml', 288 | 'config/elk/logstash.conf', 289 | ]; 290 | this.generateFiles(configOptions, resTemplates, 'app/','./'); 291 | } 292 | 293 | _generateMonitoringConfig(configOptions) { 294 | const resTemplates = [ 295 | 'docker/docker-compose-monitoring.yml', 296 | 'config/prometheus/prometheus.yml', 297 | ]; 298 | this.generateFiles(configOptions, resTemplates, 'app/','./'); 299 | 300 | this.fs.copy( 301 | this.templatePath('app/config/grafana'), 302 | this.destinationPath('config/grafana') 303 | ); 304 | } 305 | 306 | }; 307 | -------------------------------------------------------------------------------- /generators/server/prompts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompting 3 | }; 4 | 5 | async function prompting() { 6 | const prompts = [ 7 | { 8 | type: 'string', 9 | name: 'appName', 10 | validate: input => 11 | /^([a-z_][a-z0-9_\-]*)$/.test(input) 12 | ? true 13 | : 'The application name you have provided is not valid', 14 | message: 'What is the application name?', 15 | default: 'myservice' 16 | }, 17 | { 18 | type: 'string', 19 | name: 'packageName', 20 | validate: input => 21 | /^([a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*)$/.test(input) 22 | ? true 23 | : 'The package name you have provided is not a valid Java package name.', 24 | message: 'What is the default package name?', 25 | default: 'com.mycompany.myservice' 26 | }, 27 | { 28 | type: 'list', 29 | name: 'databaseType', 30 | message: 'Which type of database you want to use?', 31 | choices: [ 32 | { 33 | value: 'postgresql', 34 | name: 'Postgresql' 35 | }, 36 | { 37 | value: 'mysql', 38 | name: 'MySQL' 39 | }, 40 | { 41 | value: 'mariadb', 42 | name: 'MariaDB' 43 | } 44 | ], 45 | default: 'postgresql' 46 | }, 47 | { 48 | type: 'list', 49 | name: 'dbMigrationTool', 50 | message: 'Which type of database migration tool you want to use?', 51 | choices: [ 52 | { 53 | value: 'flywaydb', 54 | name: 'FlywayDB' 55 | }, 56 | { 57 | value: 'liquibase', 58 | name: 'Liquibase' 59 | }, 60 | { 61 | value: 'none', 62 | name: 'None' 63 | } 64 | ], 65 | default: 'flywaydb' 66 | }, 67 | { 68 | when: (answers) => answers.dbMigrationTool === 'liquibase', 69 | type: 'list', 70 | name: 'dbMigrationFormat', 71 | message: 'Which format do you want to use for database migrations?', 72 | choices: [ 73 | { 74 | value: 'xml', 75 | name: 'XML (like \'001-init.xml\')' 76 | }, 77 | { 78 | value: 'yaml', 79 | name: 'YAML (like \'001-init.yaml\')' 80 | }, 81 | { 82 | value: 'sql', 83 | name: 'SQL (like \'001-init.sql\')' 84 | } 85 | ], 86 | default: 'xml' 87 | }, 88 | { 89 | type: 'checkbox', 90 | name: 'features', 91 | message: 'Select the features you want?', 92 | choices: [ 93 | { 94 | value: 'elk', 95 | name: 'ELK Docker configuration' 96 | }, 97 | { 98 | value: 'monitoring', 99 | name: 'Prometheus, Grafana Docker configuration' 100 | }, 101 | { 102 | value: 'localstack', 103 | name: 'Localstack Docker configuration' 104 | } 105 | ] 106 | }, 107 | { 108 | type: 'list', 109 | name: 'buildTool', 110 | message: 'Which build tool do you want to use?', 111 | choices: [ 112 | { 113 | value: 'maven', 114 | name: 'Maven' 115 | }, 116 | { 117 | value: 'gradle', 118 | name: 'Gradle' 119 | } 120 | ], 121 | default: 'maven' 122 | } 123 | ]; 124 | 125 | const answers = await this.prompt(prompts); 126 | Object.assign(this.configOptions, answers); 127 | this.configOptions.packageFolder = this.configOptions.packageName.replace(/\./g, '/'); 128 | this.configOptions.features = this.configOptions.features || []; 129 | } 130 | -------------------------------------------------------------------------------- /generators/server/templates/app/.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | distribution: [ 'temurin' ] 15 | java: [ '<%= JAVA_VERSION %>' ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Java ${{ matrix.java }} 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: ${{ matrix.java }} 23 | distribution: ${{ matrix.distribution }} 24 | cache: 'gradle' 25 | 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | 29 | - name: Build with Gradle 30 | run: ./gradlew build 31 | 32 | - if: ${{ github.ref == 'refs/heads/main' }} 33 | name: SonarQube Scan 34 | run: ./gradlew sonarqube 35 | env: 36 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 37 | 38 | - if: ${{ github.ref == 'refs/heads/main' }} 39 | name: Build and Publish Docker Image 40 | run: | 41 | docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} 42 | ./gradlew bootBuildImage --imageName=${{ secrets.DOCKER_USERNAME }}/<%= appName %> 43 | docker push ${{ secrets.DOCKER_USERNAME }}/<%= appName %> 44 | -------------------------------------------------------------------------------- /generators/server/templates/app/.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | distribution: [ 'temurin' ] 15 | java: [ '<%= JAVA_VERSION %>' ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Java ${{ matrix.java }} 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: ${{ matrix.java }} 23 | distribution: ${{ matrix.distribution }} 24 | cache: 'maven' 25 | 26 | - name: Grant execute permission for mvnw 27 | run: chmod +x mvnw 28 | 29 | - name: Build with Maven 30 | run: ./mvnw clean verify 31 | 32 | - if: ${{ github.ref == 'refs/heads/main' }} 33 | name: SonarQube Scan 34 | run: ./mvnw compile sonar:sonar -Dsonar.login=${{ secrets.SONAR_TOKEN }} 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - if: ${{ github.ref == 'refs/heads/main' }} 39 | name: Build and Publish Docker Image 40 | run: | 41 | docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} 42 | ./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=${{ secrets.DOCKER_USERNAME }}/<%= appName %> 43 | docker push ${{ secrets.DOCKER_USERNAME }}/<%= appName %> 44 | -------------------------------------------------------------------------------- /generators/server/templates/app/.localstack/01_init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | awslocal s3 mb s3://testbucket 4 | echo "List of S3 buckets:" 5 | echo "-------------------------------" 6 | awslocal s3 ls 7 | 8 | awslocal sqs create-queue --queue-name test_queue 9 | echo "List of SQS Queues:" 10 | echo "-------------------------------" 11 | awslocal sqs list-queues 12 | -------------------------------------------------------------------------------- /generators/server/templates/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17.0.14_7-jre-focal as builder 2 | WORKDIR application 3 | <%_ if (buildTool === 'maven') { _%> 4 | ARG JAR_FILE=target/<%= appName %>-<%= DEFAULT_APP_VERSION %>.jar 5 | <%_ } _%> 6 | <%_ if (buildTool === 'gradle') { _%> 7 | ARG JAR_FILE=build/libs/<%= appName %>-<%= DEFAULT_APP_VERSION %>.jar 8 | <%_ } _%> 9 | COPY ${JAR_FILE} application.jar 10 | RUN java -Djarmode=layertools -jar application.jar extract 11 | 12 | # the second stage of our build will copy the extracted layers 13 | FROM eclipse-temurin:17.0.14_7-jre-focal 14 | WORKDIR application 15 | COPY --from=builder application/dependencies/ ./ 16 | COPY --from=builder application/spring-boot-loader/ ./ 17 | COPY --from=builder application/snapshot-dependencies/ ./ 18 | COPY --from=builder application/application/ ./ 19 | ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] 20 | -------------------------------------------------------------------------------- /generators/server/templates/app/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('* * * * *') 6 | } 7 | 8 | environment { 9 | APPLICATION_NAME = '<%= appName %>' 10 | } 11 | 12 | stages { 13 | stage('Build') { 14 | steps { 15 | <%_ if (buildTool === 'maven') { _%> 16 | sh './mvnw clean verify' 17 | <%_ } _%> 18 | <%_ if (buildTool === 'gradle') { _%> 19 | sh './gradlew clean build' 20 | <%_ } _%> 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /generators/server/templates/app/README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | <%_ if (buildTool === 'maven') { _%> 4 | ### Format code 5 | 6 | ```shell 7 | $ ./mvnw spotless:apply 8 | ``` 9 | 10 | ### Run tests 11 | 12 | ```shell 13 | $ ./mvnw clean verify 14 | ``` 15 | 16 | ### Run locally 17 | 18 | ```shell 19 | $ docker-compose -f docker/docker-compose.yml up -d 20 | $ ./mvnw spring-boot:run -Dspring-boot.run.profiles=local 21 | ``` 22 | 23 | ### Using Testcontainers at Development Time 24 | You can run `TestApplication.java` from your IDE directly. 25 | You can also run the application using Maven as follows: 26 | 27 | ```shell 28 | ./mvnw spring-boot:test-run 29 | ``` 30 | <%_ } _%> 31 | 32 | <%_ if (buildTool === 'gradle') { _%> 33 | ### Format code 34 | 35 | ```shell 36 | $ ./gradlew spotlessApply 37 | ``` 38 | 39 | ### Run tests 40 | 41 | ```shell 42 | $ ./gradlew clean build 43 | ``` 44 | 45 | ### Run locally 46 | 47 | ```shell 48 | $ docker-compose -f docker/docker-compose.yml up -d 49 | $ ./gradlew bootRun -Plocal 50 | ``` 51 | 52 | ### Using Testcontainers at Development Time 53 | You can run `TestApplication.java` from your IDE directly. 54 | You can also run the application using Gradle as follows: 55 | 56 | ```shell 57 | $ ./gradlew bootTestRun 58 | ``` 59 | <%_ } _%> 60 | 61 | ### Useful Links 62 | * Swagger UI: http://localhost:8080/swagger-ui.html 63 | * Actuator Endpoint: http://localhost:8080/actuator 64 | <%_ if (features.includes('elk')) { _%> 65 | * Prometheus: http://localhost:9090/ 66 | * Grafana: http://localhost:3000/ (admin/admin) 67 | * Kibana: http://localhost:5601/ 68 | <%_ } _%> 69 | -------------------------------------------------------------------------------- /generators/server/templates/app/config/elk/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 5000 4 | codec => json_lines 5 | } 6 | } 7 | 8 | ## Add your filters / logstash plugins configuration here 9 | 10 | output { 11 | elasticsearch { 12 | hosts => "elasticsearch:9200" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /generators/server/templates/app/config/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Prometheus' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | editable: true 10 | options: 11 | path: /etc/grafana/provisioning/dashboards 12 | -------------------------------------------------------------------------------- /generators/server/templates/app/config/grafana/provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | # name of the datasource. Required 13 | - name: Prometheus 14 | # datasource type. Required 15 | type: prometheus 16 | uid : PBFA97CFB590B2093 17 | # access mode. direct or proxy. Required 18 | access: proxy 19 | # org id. will default to orgId 1 if not specified 20 | orgId: 1 21 | # url 22 | url: http://prometheus:9090 23 | # database password, if used 24 | password: 25 | # database user, if used 26 | user: 27 | # database name, if used 28 | database: 29 | # enable/disable basic auth 30 | basicAuth: true 31 | # basic auth username 32 | basicAuthUser: admin 33 | # basic auth password 34 | basicAuthPassword: foobar 35 | # enable/disable with credentials headers 36 | withCredentials: 37 | # mark as default datasource. Max one per org 38 | isDefault: true 39 | # fields that will be converted to json and stored in json_data 40 | jsonData: 41 | graphiteVersion: "1.1" 42 | tlsAuth: false 43 | tlsAuthWithCACert: false 44 | # json object of data that will be encrypted. 45 | secureJsonData: 46 | tlsCACert: "..." 47 | tlsClientCert: "..." 48 | tlsClientKey: "..." 49 | version: 1 50 | # allow users to edit datasources from the UI. 51 | editable: true 52 | -------------------------------------------------------------------------------- /generators/server/templates/app/config/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 4 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Alertmanager configuration 8 | alerting: 9 | alertmanagers: 10 | - static_configs: 11 | - targets: 12 | # - alertmanager:9093 13 | 14 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 15 | rule_files: 16 | # - "first_rules.yml" 17 | # - "second_rules.yml" 18 | 19 | # A scrape configuration containing exactly one endpoint to scrape: 20 | # Here it's Prometheus itself. 21 | scrape_configs: 22 | # The job name is added as a label `job=` to any timeseries scraped from this config. 23 | - job_name: 'prometheus' 24 | 25 | # metrics_path defaults to '/metrics' 26 | # scheme defaults to 'http'. 27 | static_configs: 28 | - targets: ['localhost:9090', 'host.docker.internal:9090'] 29 | labels: 30 | application: 'prometheus' 31 | 32 | - job_name: '<%= appName %>' 33 | 34 | metrics_path: '/actuator/prometheus' 35 | scrape_interval: 5s 36 | static_configs: 37 | - targets: ['<%= appName %>:8080', 'host.docker.internal:8080' ] 38 | labels: 39 | application: '<%= appName %>' 40 | -------------------------------------------------------------------------------- /generators/server/templates/app/docker/docker-compose-app.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | <%= appName %>: 5 | build: .. 6 | ports: 7 | - "18080:8080" 8 | - "18787:8787" 9 | restart: always 10 | depends_on: 11 | - <%= databaseType %>db 12 | environment: 13 | - SPRING_PROFILES_ACTIVE=docker 14 | <%_ if (databaseType === 'postgresql') { _%> 15 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver 16 | - SPRING_DATASOURCE_URL=jdbc:postgresql://<%= databaseType %>db:5432/appdb 17 | <%_ } _%> 18 | <%_ if (databaseType === 'mysql') { _%> 19 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.jdbc.Driver 20 | - SPRING_DATASOURCE_URL=jdbc:mysql://<%= databaseType %>db:3306/appdb 21 | <%_ } _%> 22 | <%_ if (databaseType === 'mariadb') { _%> 23 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver 24 | - SPRING_DATASOURCE_URL=jdbc:mariadb://<%= databaseType %>db:3306/appdb 25 | <%_ } _%> 26 | - SPRING_DATASOURCE_USERNAME=appuser 27 | - SPRING_DATASOURCE_PASSWORD=secret 28 | -------------------------------------------------------------------------------- /generators/server/templates/app/docker/docker-compose-elk.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | elasticsearch: 5 | image: docker.elastic.co/elasticsearch/elasticsearch:8.17.4 6 | container_name: elasticsearch 7 | environment: 8 | - cluster.name=docker-cluster 9 | - bootstrap.memory_lock=true 10 | - xpack.security.enabled=false 11 | - "ES_JAVA_OPTS=-Xmx2048m -Xms2048m" 12 | volumes: 13 | - esdata1:/usr/share/elasticsearch/data 14 | ports: 15 | - "9200:9200" 16 | - "9300:9300" 17 | ulimits: 18 | memlock: 19 | soft: -1 20 | hard: -1 21 | 22 | logstash: 23 | image: docker.elastic.co/logstash/logstash:8.18.0 24 | container_name: logstash 25 | command: logstash -f /etc/logstash/conf.d/logstash.conf 26 | volumes: 27 | - ../config/elk:/etc/logstash/conf.d 28 | ports: 29 | - "5000:5000" 30 | links: 31 | - elasticsearch 32 | 33 | kibana: 34 | image: docker.elastic.co/kibana/kibana:8.17.4 35 | container_name: kibana 36 | environment: 37 | - ELASTICSEARCH_URL=http://elasticsearch:9200 38 | ports: 39 | - "5601:5601" 40 | links: 41 | - elasticsearch 42 | 43 | volumes: 44 | esdata1: 45 | driver: local 46 | 47 | -------------------------------------------------------------------------------- /generators/server/templates/app/docker/docker-compose-monitoring.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | grafana: 5 | image: grafana/grafana:11.6.0 6 | extra_hosts: ['host.docker.internal:host-gateway'] 7 | ports: 8 | - "3000:3000" 9 | volumes: 10 | - grafana_data:/var/lib/grafana 11 | - ../config/grafana/provisioning/:/etc/grafana/provisioning/ 12 | environment: 13 | - GF_SECURITY_ADMIN_PASSWORD=admin 14 | - GF_SECURITY_ADMIN_USER=admin 15 | - GF_USERS_ALLOW_SIGN_UP=false 16 | depends_on: 17 | - prometheus 18 | 19 | prometheus: 20 | image: prom/prometheus:v2.55.1 21 | extra_hosts: ['host.docker.internal:host-gateway'] 22 | ports: 23 | - "9090:9090" 24 | volumes: 25 | - ../config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 26 | - prometheus_data:/prometheus 27 | command: 28 | - --config.file=/etc/prometheus/prometheus.yml 29 | 30 | volumes: 31 | prometheus_data: {} 32 | grafana_data: {} 33 | esdata1: 34 | driver: local 35 | 36 | -------------------------------------------------------------------------------- /generators/server/templates/app/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | <%_ if (databaseType === 'postgresql') { _%> 5 | postgresqldb: 6 | image: <%= POSTGRESQL_IMAGE %> 7 | environment: 8 | - POSTGRES_USER=appuser 9 | - POSTGRES_PASSWORD=secret 10 | - POSTGRES_DB=appdb 11 | ports: 12 | - "5432:5432" 13 | <%_ } _%> 14 | <%_ if (databaseType === 'mysql') { _%> 15 | mysqldb: 16 | image: <%= MYSQL_IMAGE %> 17 | environment: 18 | - MYSQL_DATABASE=appdb 19 | - MYSQL_USER=appuser 20 | - MYSQL_PASSWORD=secret 21 | - MYSQL_ROOT_PASSWORD=root 22 | ports: 23 | - "3306:3306" 24 | 25 | <%_ } _%> 26 | <%_ if (databaseType === 'mariadb') { _%> 27 | mariadb: 28 | image: <%= MARIADB_IMAGE %> 29 | environment: 30 | - MYSQL_DATABASE=appdb 31 | - MYSQL_USER=appuser 32 | - MYSQL_PASSWORD=secret 33 | - MYSQL_ROOT_PASSWORD=root 34 | ports: 35 | - "3306:3306" 36 | <%_ } _%> 37 | 38 | <%_ if (features.includes('localstack')) { _%> 39 | localstack: 40 | image: <%= LOCALSTACK_IMAGE %> 41 | ports: 42 | - "4566:4566" 43 | environment: 44 | - SERVICES=s3,sqs 45 | - DEFAULT_REGION=us-east-1 46 | - DOCKER_HOST=unix:///var/run/docker.sock 47 | - USE_SSL=0 48 | - AWS_CBOR_DISABLE=1 49 | volumes: 50 | - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" 51 | - "/var/run/docker.sock:/var/run/docker.sock" 52 | - "../.localstack:/etc/localstack/init/ready.d" # ready hook 53 | <%_ } _%> 54 | -------------------------------------------------------------------------------- /generators/server/templates/app/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true 2 | -------------------------------------------------------------------------------- /generators/server/templates/app/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=sonar_projectkey 3 | sonar.organization=sonar_org 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/*.*,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | <%_ if (buildTool === 'maven') { _%> 12 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 13 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 14 | <%_ } _%> 15 | <%_ if (buildTool === 'gradle') { _%> 16 | sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml 17 | sonar.junit.reportPaths=build/test-results/test,build/test-results/integrationTest 18 | <%_ } _%> 19 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/Application.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>; 2 | 3 | import <%= packageName %>.config.ApplicationProperties; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | 8 | @SpringBootApplication 9 | @EnableConfigurationProperties({ApplicationProperties.class}) 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 6 | 7 | @Data 8 | @ConfigurationProperties("application") 9 | public class ApplicationProperties { 10 | 11 | @NestedConfigurationProperty 12 | private Cors cors = new Cors(); 13 | 14 | @Data 15 | public static class Cors { 16 | private String pathPattern = "/api/**"; 17 | private String allowedMethods = "*"; 18 | private String allowedHeaders = "*"; 19 | private String allowedOriginPatterns = "*"; 20 | private boolean allowCredentials = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config; 2 | 3 | import <%= packageName %>.exception.ResourceNotFoundException; 4 | import java.net.URI; 5 | import java.time.Instant; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.HttpStatusCode; 13 | import org.springframework.http.ProblemDetail; 14 | import org.springframework.validation.FieldError; 15 | import org.springframework.web.bind.MethodArgumentNotValidException; 16 | import org.springframework.web.bind.annotation.ControllerAdvice; 17 | import org.springframework.web.bind.annotation.ExceptionHandler; 18 | import org.springframework.web.bind.annotation.ResponseStatus; 19 | 20 | @ControllerAdvice 21 | @Order(Ordered.HIGHEST_PRECEDENCE) 22 | class GlobalExceptionHandler { 23 | 24 | @ExceptionHandler(MethodArgumentNotValidException.class) 25 | @ResponseStatus(HttpStatus.BAD_REQUEST) 26 | ProblemDetail onException(MethodArgumentNotValidException methodArgumentNotValidException) { 27 | ProblemDetail problemDetail = 28 | ProblemDetail.forStatusAndDetail( 29 | HttpStatusCode.valueOf(400), "Invalid request content."); 30 | problemDetail.setTitle("Constraint Violation"); 31 | List validationErrorsList = 32 | methodArgumentNotValidException.getAllErrors().stream() 33 | .map( 34 | objectError -> { 35 | FieldError fieldError = (FieldError) objectError; 36 | return new ApiValidationError( 37 | fieldError.getObjectName(), 38 | fieldError.getField(), 39 | fieldError.getRejectedValue(), 40 | Objects.requireNonNull(fieldError.getDefaultMessage(), "")); 41 | }) 42 | .sorted(Comparator.comparing(ApiValidationError::field)) 43 | .toList(); 44 | problemDetail.setProperty("violations", validationErrorsList); 45 | return problemDetail; 46 | } 47 | 48 | @ExceptionHandler(Exception.class) 49 | ProblemDetail onException(Exception exception) { 50 | if (exception instanceof ResourceNotFoundException resourceNotFoundException) { 51 | ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail( 52 | resourceNotFoundException.getHttpStatus(), resourceNotFoundException.getMessage()); 53 | problemDetail.setTitle("Not Found"); 54 | problemDetail.setType(URI.create("http://api.<%= appName %>.com/errors/not-found")); 55 | problemDetail.setProperty("errorCategory", "Generic"); 56 | problemDetail.setProperty("timestamp", Instant.now()); 57 | return problemDetail; 58 | } else { 59 | return ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage()); 60 | } 61 | } 62 | 63 | record ApiValidationError(String object, String field, Object rejectedValue, String message) {} 64 | 65 | } 66 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/Initializer.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @RequiredArgsConstructor 10 | @Slf4j 11 | class Initializer implements CommandLineRunner { 12 | 13 | private final ApplicationProperties properties; 14 | 15 | @Override 16 | public void run(String... args) { 17 | log.info("Running Initializer....."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.info.Info; 5 | import io.swagger.v3.oas.annotations.servers.Server; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration(proxyBeanMethods = false) 9 | @OpenAPIDefinition( 10 | info = @Info(title = "<%= appName %>", version = "v1"), 11 | servers = @Server(url = "/")) 12 | class SwaggerConfig {} 13 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | @RequiredArgsConstructor 10 | class WebMvcConfig implements WebMvcConfigurer { 11 | private final ApplicationProperties properties; 12 | 13 | @Override 14 | public void addCorsMappings(CorsRegistry registry) { 15 | registry.addMapping(properties.getCors().getPathPattern()) 16 | .allowedMethods(properties.getCors().getAllowedMethods()) 17 | .allowedHeaders(properties.getCors().getAllowedHeaders()) 18 | .allowedOriginPatterns(properties.getCors().getAllowedOriginPatterns()) 19 | .allowCredentials(properties.getCors().isAllowCredentials()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config.logging; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | @Target({TYPE, METHOD}) 12 | @Retention(RUNTIME) 13 | @Inherited 14 | public @interface Loggable {} 15 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/config/logging/LoggingAspect.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.config.logging; 2 | 3 | import <%= packageName %>.utils.AppConstants; 4 | import org.aspectj.lang.JoinPoint; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.AfterThrowing; 7 | import org.aspectj.lang.annotation.Around; 8 | import org.aspectj.lang.annotation.Aspect; 9 | import org.aspectj.lang.annotation.Pointcut; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.core.env.Profiles; 14 | import org.springframework.stereotype.Component; 15 | 16 | @Aspect 17 | @Component 18 | class LoggingAspect { 19 | 20 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 21 | 22 | private final Environment env; 23 | 24 | public LoggingAspect(Environment env) { 25 | this.env = env; 26 | } 27 | 28 | @Pointcut( 29 | "within(@org.springframework.stereotype.Repository *)" 30 | + " || within(@org.springframework.stereotype.Service *)" 31 | + " || within(@org.springframework.web.bind.annotation.RestController *)") 32 | public void springBeanPointcut() { 33 | // pointcut definition 34 | } 35 | 36 | @Pointcut( 37 | "@within(<%= packageName %>.config.logging.Loggable) || " 38 | + "@annotation(<%= packageName %>.config.logging.Loggable)") 39 | public void applicationPackagePointcut() { 40 | // pointcut definition 41 | } 42 | 43 | @AfterThrowing(pointcut = "applicationPackagePointcut()", throwing = "e") 44 | public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { 45 | if (env.acceptsProfiles(Profiles.of(AppConstants.PROFILE_NOT_PROD))) { 46 | log.error( 47 | "Exception in {}.{}() with cause = '{}' and exception = '{}'", 48 | joinPoint.getSignature().getDeclaringTypeName(), 49 | joinPoint.getSignature().getName(), 50 | e.getCause() == null ? "NULL" : e.getCause(), 51 | e.getMessage(), 52 | e); 53 | 54 | } else { 55 | log.error( 56 | "Exception in {}.{}() with cause = {}", 57 | joinPoint.getSignature().getDeclaringTypeName(), 58 | joinPoint.getSignature().getName(), 59 | e.getCause() == null ? "NULL" : e.getCause()); 60 | } 61 | } 62 | 63 | @Around("applicationPackagePointcut()") 64 | public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { 65 | if (log.isTraceEnabled()) { 66 | log.trace( 67 | "Enter: {}.{}()", 68 | joinPoint.getSignature().getDeclaringTypeName(), 69 | joinPoint.getSignature().getName()); 70 | } 71 | long start = System.currentTimeMillis(); 72 | Object result = joinPoint.proceed(); 73 | long end = System.currentTimeMillis(); 74 | if (log.isTraceEnabled()) { 75 | log.trace( 76 | "Exit: {}.{}(). Time taken: {} millis", 77 | joinPoint.getSignature().getDeclaringTypeName(), 78 | joinPoint.getSignature().getName(), 79 | end - start); 80 | } 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.HttpStatusCode; 5 | 6 | public class ResourceNotFoundException extends RuntimeException { 7 | 8 | private static final HttpStatus httpStatus = HttpStatus.NOT_FOUND; 9 | 10 | public ResourceNotFoundException(String errorMessage) { 11 | super(errorMessage); 12 | } 13 | 14 | public HttpStatusCode getHttpStatus() { 15 | return httpStatus; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/model/response/PagedResult.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.model.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | import org.springframework.data.domain.Page; 8 | 9 | public record PagedResult( 10 | List data, 11 | long totalElements, 12 | int pageNumber, 13 | int totalPages, 14 | @JsonProperty("isFirst") 15 | boolean isFirst, 16 | @JsonProperty("isLast") 17 | boolean isLast, 18 | @JsonProperty("hasNext") 19 | boolean hasNext, 20 | @JsonProperty("hasPrevious") 21 | boolean hasPrevious 22 | ) { 23 | public PagedResult(Page page, List data) { 24 | this(data, 25 | page.getTotalElements(), 26 | page.getNumber() + 1, 27 | page.getTotalPages(), 28 | page.isFirst(), 29 | page.isLast(), 30 | page.hasNext(), 31 | page.hasPrevious()); 32 | } 33 | } -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/java/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.utils; 2 | 3 | public final class AppConstants { 4 | public static final String PROFILE_PROD = "prod"; 5 | public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD; 6 | public static final String PROFILE_TEST = "test"; 7 | public static final String PROFILE_NOT_TEST = "!" + PROFILE_TEST; 8 | public static final String DEFAULT_PAGE_NUMBER = "0"; 9 | public static final String DEFAULT_PAGE_SIZE = "10"; 10 | public static final String DEFAULT_SORT_BY = "id"; 11 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 12 | } 13 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | <%_ if (databaseType === 'postgresql') { _%> 2 | spring.datasource.driver-class-name=org.postgresql.Driver 3 | spring.datasource.url=jdbc:postgresql://localhost:5432/appdb 4 | <%_ } _%> 5 | <%_ if (databaseType === 'mysql') { _%> 6 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 7 | spring.datasource.url=jdbc:mysql://localhost:3306/appdb 8 | <%_ } _%> 9 | <%_ if (databaseType === 'mariadb') { _%> 10 | spring.datasource.driver-class-name=org.mariadb.jdbc.Driver 11 | spring.datasource.url=jdbc:mariadb://localhost:3306/appdb 12 | <%_ } _%> 13 | spring.datasource.username=appuser 14 | spring.datasource.password=secret 15 | 16 | <%_ if (features.includes('localstack')) { _%> 17 | ###AWS 18 | spring.cloud.aws.endpoint=http://localhost:4566 19 | 20 | spring.cloud.aws.credentials.access-key=noop 21 | spring.cloud.aws.credentials.secret-key=noop 22 | spring.cloud.aws.region.static=us-east-1 23 | <%_ } _%> 24 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=<%= appName %> 2 | server.port=8080 3 | server.shutdown=graceful 4 | spring.main.allow-bean-definition-overriding=true 5 | spring.jmx.enabled=false 6 | spring.mvc.problemdetails.enabled=true 7 | 8 | ################ Actuator ##################### 9 | management.endpoints.web.exposure.include=configprops,env,health,info,logfile,loggers,metrics,prometheus 10 | management.endpoint.health.show-details=always 11 | 12 | ################ Database ##################### 13 | spring.jpa.show-sql=false 14 | spring.jpa.open-in-view=false 15 | spring.data.jpa.repositories.bootstrap-mode=deferred 16 | spring.datasource.hikari.auto-commit=false 17 | spring.datasource.hikari.data-source-properties.ApplicationName=${spring.application.name} 18 | spring.jpa.hibernate.ddl-auto=none 19 | #spring.jpa.properties.hibernate.format_sql=true 20 | spring.jpa.properties.hibernate.jdbc.time_zone=UTC 21 | spring.jpa.properties.hibernate.generate_statistics=false 22 | spring.jpa.properties.hibernate.jdbc.batch_size=25 23 | spring.jpa.properties.hibernate.order_inserts=true 24 | spring.jpa.properties.hibernate.order_updates=true 25 | spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true 26 | spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true 27 | spring.jpa.properties.hibernate.query.plan_cache_max_size=4096 28 | spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true 29 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 30 | <%_ if (dbMigrationTool === 'flywaydb') { _%> 31 | spring.flyway.locations=classpath:/db/migration/{vendor} 32 | <%_ } _%> 33 | <%_ if (features.includes('elk')) { _%> 34 | application.logstash-host=localhost 35 | <%_ } _%> 36 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/db/migration/flyway/V1__01_init.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/generator-springboot/fef4882f6b2443cd4d9ec94fc4d4e1432467dd68/generators/server/templates/app/src/main/resources/db/migration/flyway/V1__01_init.sql -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/db/migration/liquibase/changelog/01-init.sql: -------------------------------------------------------------------------------- 1 | -- liquibase formatted sql 2 | -- see https://docs.liquibase.com/concepts/changelogs/sql-format.html 3 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/db/migration/liquibase/changelog/01-init.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/db/migration/liquibase/changelog/01-init.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.liquibase.com/concepts/changelogs/yaml-format.html 2 | databaseChangeLog: [] 3 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/db/migration/liquibase/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - includeAll: 3 | path: migration/ 4 | errorIfMissingOrEmpty: true 5 | relativeToChangelogFile: true -------------------------------------------------------------------------------- /generators/server/templates/app/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%_ if (features.includes('elk')) { _%> 8 | 10 | 11 | 12 | 13 | ${logstashHost}:5000 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | { 26 | 27 | 28 | "app-name": ${spring.application.name} 29 | } 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | <%_ } _%> 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | <%_ if (features.includes('elk')) { _%> 56 | 57 | <%_ } _%> 58 | 59 | 60 | 61 | 62 | 63 | <%_ if (features.includes('localstack')) { _%> 64 | 65 | 66 | <%_ } _%> 67 | 68 | 69 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/ApplicationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>; 2 | 3 | import <%= packageName %>.common.AbstractIntegrationTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class ApplicationIntegrationTest extends AbstractIntegrationTest { 7 | 8 | @Test 9 | void contextLoads() {} 10 | } 11 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/SchemaValidationTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 5 | 6 | @DataJpaTest(properties = { 7 | "spring.jpa.hibernate.ddl-auto=validate", 8 | "spring.test.database.replace=none", 9 | <%_ if (databaseType === 'postgresql') { _%> 10 | "spring.datasource.url=jdbc:tc:postgresql:<%= POSTGRESQL_IMAGE_VERSION %>:///db" 11 | <%_ } _%> 12 | <%_ if (databaseType === 'mysql') { _%> 13 | "spring.datasource.url=jdbc:tc:mysql:<%= MYSQL_IMAGE_VERSION %>:///db" 14 | <%_ } _%> 15 | <%_ if (databaseType === 'mariadb') { _%> 16 | "spring.datasource.url=jdbc:tc:mariadb:<%= MARIADB_IMAGE_VERSION %>:///db" 17 | <%_ } _%> 18 | }) 19 | class SchemaValidationTest { 20 | 21 | @Test 22 | void validateJpaMappingsWithDbSchema() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/SqsListenerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import <%= packageName %>.common.AbstractIntegrationTest; 6 | import java.time.Duration; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.concurrent.CompletableFuture; 10 | import java.util.concurrent.ExecutionException; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.awaitility.Awaitility; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 16 | import software.amazon.awssdk.services.sqs.model.CreateQueueRequest; 17 | import software.amazon.awssdk.services.sqs.model.CreateQueueResponse; 18 | import software.amazon.awssdk.services.sqs.model.Message; 19 | import software.amazon.awssdk.services.sqs.model.QueueAttributeName; 20 | import software.amazon.awssdk.services.sqs.model.ReceiveMessageResponse; 21 | 22 | @Slf4j 23 | class SqsListenerIntegrationTest extends AbstractIntegrationTest { 24 | 25 | @Autowired private SqsAsyncClient sqsAsyncClient; 26 | 27 | @Test 28 | void shouldSendAndReceiveSqsMessage() throws ExecutionException, InterruptedException { 29 | String queueName = "test_queue"; 30 | String queueURL = 31 | this.createQueue(queueName).thenApply(CreateQueueResponse::queueUrl).get(); 32 | 33 | this.sqsAsyncClient 34 | .sendMessage(request -> request.messageBody("test message").queueUrl(queueURL)) 35 | .thenRun(() -> log.info("Message sent successfully to the Amazon sqs.")); 36 | 37 | Awaitility.given() 38 | .atMost(Duration.ofSeconds(30)) 39 | .pollInterval(Duration.ofSeconds(3)) 40 | .await() 41 | .untilAsserted( 42 | () -> { 43 | List messages = 44 | sqsAsyncClient 45 | .receiveMessage(builder -> builder.queueUrl(queueURL)) 46 | .thenApply(ReceiveMessageResponse::messages) 47 | .get(); 48 | assertThat(messages).isNotEmpty(); 49 | }); 50 | } 51 | 52 | private CompletableFuture createQueue(String queueName) { 53 | CreateQueueRequest createQueueRequest = 54 | CreateQueueRequest.builder() 55 | .queueName(queueName) 56 | .attributes(Map.of(QueueAttributeName.MESSAGE_RETENTION_PERIOD, "86400")) 57 | .build(); 58 | 59 | return sqsAsyncClient.createQueue(createQueueRequest); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/TestApplication.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>; 2 | 3 | import <%= packageName %>.common.ContainersConfig; 4 | import org.springframework.boot.SpringApplication; 5 | 6 | public class TestApplication { 7 | 8 | public static void main(String[] args) { 9 | SpringApplication.from(Application::main) 10 | .withAdditionalProfiles("local") 11 | .with(ContainersConfig.class) 12 | .run(args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/common/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.common; 2 | 3 | import static <%= packageName %>.utils.AppConstants.PROFILE_TEST; 4 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 5 | 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | 13 | @ActiveProfiles({PROFILE_TEST}) 14 | @SpringBootTest(webEnvironment = RANDOM_PORT, classes = {ContainersConfig.class}) 15 | @AutoConfigureMockMvc 16 | public abstract class AbstractIntegrationTest { 17 | 18 | @Autowired protected MockMvc mockMvc; 19 | 20 | @Autowired protected ObjectMapper objectMapper; 21 | } 22 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/java/common/ContainersConfig.java: -------------------------------------------------------------------------------- 1 | package <%= packageName %>.common; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | <%_ if (databaseType === 'postgresql') { _%> 7 | import org.testcontainers.containers.PostgreSQLContainer; 8 | <%_ } _%> 9 | <%_ if (databaseType === 'mysql') { _%> 10 | import org.testcontainers.containers.MySQLContainer; 11 | <%_ } _%> 12 | <%_ if (databaseType === 'mariadb') { _%> 13 | import org.testcontainers.containers.MariaDBContainer; 14 | <%_ } _%> 15 | <%_ if (features.includes('localstack')) { _%> 16 | import org.testcontainers.containers.localstack.LocalStackContainer; 17 | <%_ } _%> 18 | import org.testcontainers.utility.DockerImageName; 19 | 20 | @TestConfiguration(proxyBeanMethods = false) 21 | public class ContainersConfig { 22 | 23 | @Bean 24 | @ServiceConnection 25 | <%_ if (databaseType === 'postgresql') { _%> 26 | PostgreSQLContainer postgreSQLContainer() { 27 | return new PostgreSQLContainer<>(DockerImageName.parse("<%= POSTGRESQL_IMAGE %>")); 28 | } 29 | <%_ } _%> 30 | <%_ if (databaseType === 'mysql') { _%> 31 | MySQLContainer sqlContainer () { 32 | return new MySQLContainer<>(DockerImageName.parse("<%= MYSQL_IMAGE %>")); 33 | } 34 | <%_ } _%> 35 | <%_ if (databaseType === 'mariadb') { _%> 36 | MariaDBContainer sqlContainer () { 37 | return new MariaDBContainer<>(DockerImageName.parse("<%= MARIADB_IMAGE %>")); 38 | } 39 | <%_ } _%> 40 | 41 | <%_ if (features.includes('localstack')) { _%> 42 | @Bean 43 | @ServiceConnection 44 | LocalStackContainer localStackContainer() { 45 | return new LocalStackContainer(DockerImageName.parse("<%= LOCALSTACK_IMAGE %>")); 46 | } 47 | <%_ } _%> 48 | } 49 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | <%_ if (features.includes('localstack')) { _%> 2 | spring.cloud.aws.endpoint=http://localhost:4566 3 | 4 | spring.cloud.aws.credentials.access-key=noop 5 | spring.cloud.aws.credentials.secret-key=noop 6 | spring.cloud.aws.region.static=us-east-1 7 | <%_ } _%> 8 | -------------------------------------------------------------------------------- /generators/server/templates/app/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /generators/server/templates/gradle/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "org.owasp:dependency-check-gradle:${owasp_plugin_version}" 7 | } 8 | } 9 | plugins { 10 | id "org.springframework.boot" 11 | id "io.spring.dependency-management" 12 | id "java-library" 13 | id "com.gorylenko.gradle-git-properties" 14 | id "com.github.ben-manes.versions" 15 | id "com.diffplug.spotless" 16 | id "org.sonarqube" apply false 17 | } 18 | apply from: "gradle/code-quality.gradle" 19 | if (project.hasProperty("ci")) { 20 | apply from: "gradle/owasp.gradle" 21 | } 22 | 23 | group = "<%= packageName %>" 24 | version = "<%= DEFAULT_APP_VERSION %>" 25 | sourceCompatibility = <%= JAVA_VERSION %> 26 | targetCompatibility = <%= JAVA_VERSION %> 27 | 28 | configurations { 29 | compileOnly { 30 | extendsFrom annotationProcessor 31 | } 32 | } 33 | 34 | repositories { 35 | mavenCentral() 36 | } 37 | 38 | dependencies { 39 | implementation "org.springframework.boot:spring-boot-starter-actuator" 40 | implementation "org.springframework.boot:spring-boot-starter-validation" 41 | implementation "org.springframework.boot:spring-boot-starter-web" 42 | developmentOnly "org.springframework.boot:spring-boot-devtools" 43 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" 44 | compileOnly "org.projectlombok:lombok" 45 | annotationProcessor "org.projectlombok:lombok" 46 | <%_ if (features.includes("localstack")) { _%> 47 | implementation "io.awspring.cloud:spring-cloud-aws-starter" 48 | implementation "io.awspring.cloud:spring-cloud-aws-starter-sqs" 49 | <%_ } _%> 50 | <%_ if (features.includes("elk")) { _%> 51 | implementation "net.logstash.logback:logstash-logback-encoder:${logstash_logback_encoder_version}" 52 | <%_ } _%> 53 | implementation "org.springframework.boot:spring-boot-starter-data-jpa" 54 | <%_ if (databaseType === "mysql") { _%> 55 | runtimeOnly "com.mysql:mysql-connector-j" 56 | <%_ } _%> 57 | <%_ if (databaseType === "postgresql") { _%> 58 | runtimeOnly "org.postgresql:postgresql" 59 | <%_ } _%> 60 | <%_ if (databaseType === "mariadb") { _%> 61 | runtimeOnly "org.mariadb.jdbc:mariadb-java-client" 62 | <%_ } _%> 63 | <%_ if (dbMigrationTool === "flywaydb") { _%> 64 | implementation "org.flywaydb:flyway-core" 65 | <%_ } _%> 66 | <%_ if (dbMigrationTool === "flywaydb" && (databaseType === "mysql" || databaseType === "mariadb")) { _%> 67 | implementation "org.flywaydb:flyway-mysql" 68 | <%_ } _%> 69 | <%_ if (dbMigrationTool === "flywaydb" && databaseType === "postgresql") { _%> 70 | implementation 'org.flywaydb:flyway-database-postgresql' 71 | <%_ } _%> 72 | <%_ if (dbMigrationTool === "liquibase") { _%> 73 | implementation "org.liquibase:liquibase-core" 74 | <%_ } _%> 75 | implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${springdoc_openapi_version}" 76 | implementation "org.apache.commons:commons-lang3" 77 | implementation "commons-io:commons-io:${commons_io_version}" 78 | <%_ if (features.includes("monitoring")) { _%> 79 | runtimeOnly "io.micrometer:micrometer-registry-prometheus" 80 | <%_ } _%> 81 | 82 | testImplementation "org.springframework.boot:spring-boot-starter-test" 83 | testImplementation "org.springframework.boot:spring-boot-testcontainers" 84 | testImplementation "org.projectlombok:lombok" 85 | testImplementation "org.testcontainers:junit-jupiter" 86 | <%_ if (databaseType === "mysql") { _%> 87 | testImplementation "org.testcontainers:mysql" 88 | <%_ } _%> 89 | <%_ if (databaseType === "postgresql") { _%> 90 | testImplementation "org.testcontainers:postgresql" 91 | <%_ } _%> 92 | <%_ if (databaseType === "mariadb") { _%> 93 | testImplementation "org.testcontainers:mariadb" 94 | <%_ } _%> 95 | <%_ if (features.includes("localstack")) { _%> 96 | testImplementation "org.testcontainers:localstack" 97 | testImplementation "io.awspring.cloud:spring-cloud-aws-testcontainers" 98 | <%_ } _%> 99 | testAnnotationProcessor "org.projectlombok:lombok" 100 | } 101 | 102 | dependencyManagement { 103 | imports { 104 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${spring_cloud_version}" 105 | <%_ if (features.includes("localstack")) { _%> 106 | mavenBom "io.awspring.cloud:spring-cloud-aws-dependencies:${spring_cloud_aws_version}" 107 | <%_ } _%> 108 | } 109 | } 110 | 111 | defaultTasks "bootRun" 112 | 113 | springBoot { 114 | buildInfo() 115 | } 116 | 117 | bootJar { 118 | //launchScript() 119 | } 120 | 121 | bootBuildImage { 122 | imageName = "DOCKER_USERNAME/<%= appName %>" 123 | } 124 | 125 | compileJava.dependsOn processResources 126 | processResources.dependsOn bootBuildInfo 127 | 128 | if (project.hasProperty("local")) { 129 | bootRun { 130 | args = ["--spring.profiles.active=local"] 131 | } 132 | } 133 | 134 | gitProperties { 135 | failOnNoGitDirectory = false 136 | keys = [ 137 | "git.branch", 138 | "git.commit.id.abbrev", 139 | "git.commit.user.name", 140 | "git.commit.message.full" 141 | ] 142 | } 143 | 144 | spotless { 145 | java { 146 | importOrder() 147 | removeUnusedImports() 148 | palantirJavaFormat("<%= PALANTIR_JAVA_FORMAT_VERSION %>") 149 | formatAnnotations() 150 | } 151 | } 152 | 153 | check.dependsOn spotlessCheck 154 | 155 | test { 156 | useJUnitPlatform() 157 | exclude "**/*IT*", "**/*IntegrationTest*", "**/*IntTest*" 158 | testLogging { 159 | events = ["PASSED", "FAILED", "SKIPPED"] 160 | showStandardStreams = true 161 | exceptionFormat = "full" 162 | } 163 | } 164 | 165 | task integrationTest(type: Test) { 166 | useJUnitPlatform() 167 | 168 | include "**/*IT*", "**/*IntegrationTest*", "**/*IntTest*" 169 | shouldRunAfter test 170 | 171 | testLogging { 172 | events = ["PASSED", "FAILED", "SKIPPED"] 173 | showStandardStreams = true 174 | exceptionFormat = "full" 175 | } 176 | } 177 | 178 | check.dependsOn integrationTest 179 | check.dependsOn jacocoTestReport 180 | 181 | tasks.register('testReport', TestReport) { 182 | destinationDirectory = file("$buildDir/reports/tests") 183 | testResults.from(test) 184 | } 185 | 186 | tasks.register('integrationTestReport', TestReport) { 187 | destinationDirectory = file("$buildDir/reports/tests") 188 | testResults.from(integrationTest) 189 | } 190 | 191 | -------------------------------------------------------------------------------- /generators/server/templates/gradle/code-quality.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'jacoco' 2 | apply plugin: "org.sonarqube" 3 | 4 | jacoco { 5 | toolVersion = "${jacoco_plugin_version}" 6 | } 7 | 8 | def jacocoExcludes = [ 9 | '**/*Application.*', 10 | '**/config/**', 11 | '**/models/**', 12 | '**/exceptions/**', 13 | '**/dtos/**', 14 | '**/*Constants*', 15 | ] 16 | 17 | jacocoTestReport { 18 | executionData tasks.withType(Test) 19 | classDirectories.from = files(sourceSets.main.output.classesDirs) 20 | sourceDirectories.from = files(sourceSets.main.java.srcDirs) 21 | 22 | afterEvaluate { 23 | getClassDirectories().setFrom(files(classDirectories.files.collect { 24 | fileTree(dir: it, exclude: jacocoExcludes) 25 | })) 26 | } 27 | 28 | reports { 29 | xml.required = true 30 | html.required = true 31 | } 32 | } 33 | 34 | jacocoTestCoverageVerification { 35 | dependsOn ":integrationTest" 36 | executionData fileTree(project.buildDir.absolutePath).include("jacoco/*.exec") 37 | afterEvaluate { 38 | getClassDirectories().setFrom(files(classDirectories.files.collect { 39 | fileTree(dir: it, exclude: jacocoExcludes) 40 | })) 41 | } 42 | violationRules { 43 | rule { 44 | element = 'BUNDLE' 45 | limit { 46 | counter = 'LINE' 47 | value = 'COVEREDRATIO' 48 | minimum = new BigDecimal("${jacoco_min_coverage_required}") 49 | } 50 | excludes = jacocoExcludes 51 | } 52 | } 53 | } 54 | 55 | check.dependsOn jacocoTestCoverageVerification 56 | 57 | file("sonar-project.properties").withReader { 58 | Properties sonarProperties = new Properties() 59 | sonarProperties.load(it) 60 | 61 | sonarProperties.each { key, value -> 62 | sonarqube { 63 | properties { 64 | property key, value 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /generators/server/templates/gradle/gradle.properties: -------------------------------------------------------------------------------- 1 | ## Plugins ## 2 | git_properties_plugin_version=<%= GRADLE_GIT_PROPERTIES_PLUGIN_VERSION %> 3 | jacoco_plugin_version=<%= GRADLE_JACOCO_PLUGIN_VERSION %> 4 | sonar_plugin_version=<%= GRADLE_SONAR_PLUGIN_VERSION %> 5 | owasp_plugin_version=<%= GRADLE_OWASP_PLUGIN_VERSION %> 6 | benmanes_versions_plugin_version=<%= GRADLE_BENMANES_VERSIONS_PLUGIN_VERSION %> 7 | spotless_version=<%= GRADLE_SPOTLESS_PLUGIN_VERSION %> 8 | 9 | jacoco_min_coverage_required=<%= JACOCO_MIN_COVERAGE_REQUIRED %> 10 | 11 | spring_boot_version=<%= SPRING_BOOT_VERSION %> 12 | spring_cloud_version=<%= SPRING_CLOUD_VERSION %> 13 | spring_cloud_aws_version=<%= SPRING_CLOUD_AWS_VERSION %> 14 | spring_dependency_management_version=<%= SPRING_DEP_MNGMNT_VERSION %> 15 | logstash_logback_encoder_version=<%= LOGSTASH_LOGBACK_ENCODER %> 16 | commons_io_version=<%= COMMONS_IO_VERSION %> 17 | springdoc_openapi_version=<%= SPRINGDOC_OPENAPI_VERSION %> 18 | -------------------------------------------------------------------------------- /generators/server/templates/gradle/owasp.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'org.owasp.dependencycheck' 3 | 4 | dependencyCheck { 5 | autoUpdate=true 6 | cveValidForHours=1 7 | format='ALL' 8 | } 9 | 10 | check.dependsOn dependencyCheckAnalyze 11 | -------------------------------------------------------------------------------- /generators/server/templates/gradle/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | plugins { 6 | id "org.springframework.boot" version "${spring_boot_version}" 7 | id "io.spring.dependency-management" version "${spring_dependency_management_version}" 8 | id "com.gorylenko.gradle-git-properties" version "${git_properties_plugin_version}" 9 | id "org.sonarqube" version "${sonar_plugin_version}" 10 | id "com.github.ben-manes.versions" version "${benmanes_versions_plugin_version}" 11 | id "com.diffplug.spotless" version "${spotless_version}" 12 | } 13 | } 14 | 15 | rootProject.name = "<%= appName %>" 16 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "Generator Spring Boot" 2 | site_description: "The Yeoman generator for generating Spring Boot microservices" 3 | site_author: "K Siva Prasad Reddy" 4 | site_url: "https://sivaprasadreddy.github.io/generator-springboot" 5 | 6 | # Repository 7 | repo_name: "sivaprasadreddy/generator-springboot" 8 | repo_url: "https://github.com/sivaprasadreddy/generator-springboot" 9 | 10 | # Copyright 11 | copyright: "Copyright © 2023 K Siva Prasad Reddy" 12 | 13 | theme: 14 | name: material 15 | nav: 16 | - Home: index.md 17 | 18 | # Customization 19 | extra: 20 | manifest: "manifest.webmanifest" 21 | social: 22 | - icon: fontawesome/brands/github-alt 23 | link: "https://github.com/sivaprasadreddy" 24 | - icon: fontawesome/brands/twitter 25 | link: "https://twitter.com/sivalabs" 26 | - icon: fontawesome/brands/linkedin 27 | link: "https://www.linkedin.com/in/siva-prasad-reddy-katamreddy/" 28 | 29 | # Extensions 30 | markdown_extensions: 31 | - codehilite 32 | - tables 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-springboot", 3 | "version": "0.1.6", 4 | "description": "A Yeoman generator for generating SpringBoot microservices", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "java", 8 | "spring", 9 | "spring-boot", 10 | "microservice" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/sivaprasadreddy/generator-springboot" 15 | }, 16 | "author": "K Siva Prasad Reddy ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/sivaprasadreddy/generator-springboot/issues" 20 | }, 21 | "homepage": "https://github.com/sivaprasadreddy/generator-springboot", 22 | "files": [ 23 | "generators" 24 | ], 25 | "main": "generators/app/index.js", 26 | "scripts": { 27 | "test": "npm run test:unit -- test/**/*.spec.js test/*.spec.js --no-insight", 28 | "test:unit": "mocha --timeout 120000 --slow 0 --reporter spec" 29 | }, 30 | "dependencies": { 31 | "chalk": "4.1.2", 32 | "lodash": "4.17.21", 33 | "shelljs": "0.9.2", 34 | "yeoman-environment": "4.4.3", 35 | "yeoman-generator": "7.5.1" 36 | }, 37 | "devDependencies": { 38 | "@types/fs-extra": "11.0.4", 39 | "@types/lodash": "4.14.200", 40 | "@types/mocha": "10.0.7", 41 | "@types/node": "20.8.9", 42 | "@types/shelljs": "0.8.12", 43 | "@types/yeoman-assert": "3.1.4", 44 | "@types/yeoman-generator": "5.2.14", 45 | "chai": "5.2.0", 46 | "fs-extra": "11.3.0", 47 | "mocha": "10.8.2", 48 | "sinon": "17.0.1", 49 | "yeoman-assert": "3.1.1", 50 | "yeoman-test": "10.1.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "schedule:earlyMondays" 6 | ], 7 | "forkProcessing": "enabled", 8 | "baseBranches": [ 9 | "v0.2.0" 10 | ], 11 | "labels": [ 12 | "dependencies" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/server.spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const assert = require('yeoman-assert'); 3 | const { createHelpers } = require('yeoman-test'); 4 | 5 | describe('SpringBoot Generator', () => { 6 | // Helper function to test server generator with different configurations 7 | const testServerGenerator = async (testName, prompts, expectedFiles, additionalChecks) => { 8 | it(testName, async () => { 9 | const helpers = createHelpers(); 10 | await helpers 11 | .create(path.join(__dirname, '../generators/server')) 12 | .withPrompts(prompts) 13 | .run(); 14 | 15 | // Check expected files exist 16 | expectedFiles.forEach(file => assert.file(file)); 17 | 18 | // Run additional checks if provided 19 | if (additionalChecks) { 20 | additionalChecks(); 21 | } 22 | }); 23 | }; 24 | 25 | // Maven based generation 26 | describe('Generate minimal microservice using Maven', () => { 27 | testServerGenerator( 28 | 'creates expected default files for minimal microservice with maven', 29 | { 30 | "appName": "myservice", 31 | "packageName": "com.mycompany.myservice", 32 | "packageFolder": "com/mycompany/myservice", 33 | "buildTool": "maven", 34 | "features": [] 35 | }, 36 | ['myservice/pom.xml'] 37 | ); 38 | }); 39 | 40 | describe('Generate basic microservice using Maven with Flyway', () => { 41 | testServerGenerator( 42 | 'creates expected default files for basic microservice with maven', 43 | { 44 | "appName": "myservice", 45 | "packageName": "com.mycompany.myservice", 46 | "packageFolder": "com/mycompany/myservice", 47 | "databaseType": "postgresql", 48 | "dbMigrationTool": "flywaydb", 49 | "buildTool": "maven", 50 | "features": [] 51 | }, 52 | ['myservice/pom.xml'] 53 | ); 54 | }); 55 | 56 | describe('Generate basic microservice using Maven with Liquibase', () => { 57 | testServerGenerator( 58 | 'creates expected default files for basic microservice with maven', 59 | { 60 | "appName": "myservice", 61 | "packageName": "com.mycompany.myservice", 62 | "packageFolder": "com/mycompany/myservice", 63 | "databaseType": "postgresql", 64 | "dbMigrationTool": "liquibase", 65 | "buildTool": "maven", 66 | "features": [] 67 | }, 68 | ['myservice/pom.xml'] 69 | ); 70 | }); 71 | 72 | describe('Generate complete microservice using Maven', () => { 73 | testServerGenerator( 74 | 'creates expected default files for complete microservice with maven', 75 | { 76 | "appName": "myservice", 77 | "packageName": "com.mycompany.myservice", 78 | "packageFolder": "com/mycompany/myservice", 79 | "databaseType": "postgresql", 80 | "dbMigrationTool": "flywaydb", 81 | "buildTool": "maven", 82 | "features": ["elk", "monitoring"] 83 | }, 84 | [ 85 | 'myservice/pom.xml', 86 | 'myservice/docker/docker-compose.yml', 87 | 'myservice/docker/docker-compose-elk.yml', 88 | 'myservice/docker/docker-compose-monitoring.yml' 89 | ] 90 | ); 91 | }); 92 | 93 | // Gradle based generation 94 | describe('Generate minimal microservice using Gradle', () => { 95 | testServerGenerator( 96 | 'creates expected default files for minimal microservice with Gradle', 97 | { 98 | "appName": "myservice", 99 | "packageName": "com.mycompany.myservice", 100 | "packageFolder": "com/mycompany/myservice", 101 | "buildTool": "gradle", 102 | "features": [] 103 | }, 104 | ['myservice/build.gradle'] 105 | ); 106 | }); 107 | 108 | describe('Generate basic microservice using Gradle with Flyway', () => { 109 | testServerGenerator( 110 | 'creates expected default files for basic microservice with Gradle', 111 | { 112 | "appName": "myservice", 113 | "packageName": "com.mycompany.myservice", 114 | "packageFolder": "com/mycompany/myservice", 115 | "databaseType": "postgresql", 116 | "dbMigrationTool": "flywaydb", 117 | "buildTool": "gradle", 118 | "features": [] 119 | }, 120 | ['myservice/build.gradle'] 121 | ); 122 | }); 123 | 124 | describe('Generate basic microservice using Gradle with Liquibase', () => { 125 | testServerGenerator( 126 | 'creates expected default files for basic microservice with maven', 127 | { 128 | "appName": "myservice", 129 | "packageName": "com.mycompany.myservice", 130 | "packageFolder": "com/mycompany/myservice", 131 | "databaseType": "postgresql", 132 | "dbMigrationTool": "liquibase", 133 | "buildTool": "gradle", 134 | "features": [] 135 | }, 136 | ['myservice/build.gradle'] 137 | ); 138 | }); 139 | 140 | describe('Generate complete microservice using Gradle', () => { 141 | testServerGenerator( 142 | 'creates expected default files for complete microservice with Gradle', 143 | { 144 | "appName": "myservice", 145 | "packageName": "com.mycompany.myservice", 146 | "packageFolder": "com/mycompany/myservice", 147 | "databaseType": "postgresql", 148 | "dbMigrationTool": "flywaydb", 149 | "buildTool": "gradle", 150 | "features": ["elk", "monitoring"] 151 | }, 152 | [ 153 | 'myservice/build.gradle', 154 | 'myservice/docker/docker-compose.yml', 155 | 'myservice/docker/docker-compose-elk.yml', 156 | 'myservice/docker/docker-compose-monitoring.yml' 157 | ] 158 | ); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /test/templates/basic-microservice-flyway/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "myservice", 4 | "packageName": "com.mycompany.myservice", 5 | "packageFolder": "com/mycompany/myservice", 6 | "databaseType": "postgresql", 7 | "dbMigrationTool": "flywaydb", 8 | "features": [], 9 | "buildTool": "maven", 10 | "flywayMigrationCounter": 1 11 | } 12 | } -------------------------------------------------------------------------------- /test/templates/basic-microservice-liquibase/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "myservice", 4 | "packageName": "com.mycompany.myservice", 5 | "packageFolder": "com/mycompany/myservice", 6 | "databaseType": "postgresql", 7 | "dbMigrationTool": "liquibase", 8 | "dbMigrationFormat": "xml", 9 | "features": ["elk"], 10 | "buildTool": "maven", 11 | "liquibaseMigrationCounter": 1 12 | } 13 | } --------------------------------------------------------------------------------