From 5e519b581b82bb1de1d35b34287707f89585cb38 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sat, 2 Aug 2025 21:44:20 +0530 Subject: [PATCH 01/11] feat: add reverse engg with example project --- .github/workflows/scaffold.yml | 5 +- .gitignore | 8 +- example/.docker/Dockerfile | 7 + example/.github/CODEOWNERS | 1 + example/.github/ISSUE_TEMPLATE/bug_report.md | 27 ++ .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + example/.github/PULL_REQUEST_TEMPLATE.md | 14 + example/.github/dependabot.yml | 10 + example/.github/workflows/build_workflow.yml | 29 ++ example/.mvn/jvm.config | 1 + example/README.md | 52 +++ example/acceptance-test/pom.xml | 144 ++++++++ .../packagename/cucumber/ExampleStepDef.java | 122 +++++++ .../cucumber/RunCucumberExampleTest.java | 21 ++ .../cucumber/SpringCucumberTestConfig.java | 13 + .../src/test/resources/application-test.yml | 5 + .../test/resources/features/example.feature | 24 ++ example/bootstrap/pom.xml | 87 +++++ .../packagename/boot/ExampleApplication.java | 14 + .../boot/config/BootstrapConfig.java | 19 + .../src/main/resources/application.yml | 9 + example/domain-api/pom.xml | 33 ++ .../exception/ExampleNotFoundException.java | 8 + .../packagename/domain/model/Example.java | 16 + .../domain/port/ObtainExample.java | 28 ++ .../domain/port/RequestExample.java | 12 + example/domain/pom.xml | 44 +++ .../packagename/domain/ExampleDomain.java | 32 ++ .../test/java/packagename/AcceptanceTest.java | 92 +++++ example/jpa-adapter/pom.xml | 88 +++++ .../repository/ExampleRepository.java | 29 ++ .../repository/config/JpaAdapterConfig.java | 23 ++ .../repository/dao/ExampleDao.java | 14 + .../entity/EnversRevisionEntity.java | 34 ++ .../repository/entity/ExampleEntity.java | 45 +++ .../src/main/resources/application.yaml | 27 ++ .../db/changelog/db.changelog-master.yaml | 5 + .../110720212155010-create-example.yaml | 77 ++++ .../240720211408009-create-revision.yaml | 34 ++ .../main/resources/preliquibase/default.sql | 3 + .../ExampleJpaAdapterApplication.java | 22 ++ .../repository/ExampleJpaTest.java | 80 ++++ .../src/test/resources/application-test.yml | 5 + .../src/test/resources/sql/data.sql | 2 + example/pom.xml | 345 ++++++++++++++++++ example/rest-adapter/pom.xml | 102 ++++++ .../packagename/rest/ExampleResource.java | 27 ++ .../exception/ExampleExceptionHandler.java | 30 ++ .../src/main/resources/open-api.yaml | 76 ++++ .../packagename/rest/ExampleResourceTest.java | 112 ++++++ .../rest/ExampleRestAdapterApplication.java | 14 + ...rename.sh => scaffold-code-from-example.sh | 21 +- .../.github/CONTRIBUTING.md | 84 ----- .../.github/dependabot.yml | 2 +- .../.github/workflows/build_workflow.yml | 21 +- {{cookiecutter.app_name}}/.mvn/jvm.config | 1 + {{cookiecutter.app_name}}/README.md | 30 +- .../acceptance-test/pom.xml | 28 ++ ...st{{cookiecutter.domain_capitalized}}.java | 2 +- {{cookiecutter.app_name}}/pom.xml | 117 +++++- .../rest-adapter/pom.xml | 2 +- ...tter.domain_capitalized}}ResourceTest.java | 3 +- ...n_capitalized}}RestAdapterApplication.java | 4 - 63 files changed, 2294 insertions(+), 112 deletions(-) create mode 100644 example/.docker/Dockerfile create mode 100644 example/.github/CODEOWNERS create mode 100644 example/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 example/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 example/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 example/.github/dependabot.yml create mode 100644 example/.github/workflows/build_workflow.yml create mode 100644 example/.mvn/jvm.config create mode 100644 example/README.md create mode 100644 example/acceptance-test/pom.xml create mode 100644 example/acceptance-test/src/test/java/packagename/cucumber/ExampleStepDef.java create mode 100644 example/acceptance-test/src/test/java/packagename/cucumber/RunCucumberExampleTest.java create mode 100644 example/acceptance-test/src/test/java/packagename/cucumber/SpringCucumberTestConfig.java create mode 100644 example/acceptance-test/src/test/resources/application-test.yml create mode 100644 example/acceptance-test/src/test/resources/features/example.feature create mode 100644 example/bootstrap/pom.xml create mode 100644 example/bootstrap/src/main/java/packagename/boot/ExampleApplication.java create mode 100644 example/bootstrap/src/main/java/packagename/boot/config/BootstrapConfig.java create mode 100644 example/bootstrap/src/main/resources/application.yml create mode 100644 example/domain-api/pom.xml create mode 100644 example/domain-api/src/main/java/packagename/domain/exception/ExampleNotFoundException.java create mode 100644 example/domain-api/src/main/java/packagename/domain/model/Example.java create mode 100644 example/domain-api/src/main/java/packagename/domain/port/ObtainExample.java create mode 100644 example/domain-api/src/main/java/packagename/domain/port/RequestExample.java create mode 100644 example/domain/pom.xml create mode 100644 example/domain/src/main/java/packagename/domain/ExampleDomain.java create mode 100644 example/domain/src/test/java/packagename/AcceptanceTest.java create mode 100644 example/jpa-adapter/pom.xml create mode 100644 example/jpa-adapter/src/main/java/packagename/repository/ExampleRepository.java create mode 100644 example/jpa-adapter/src/main/java/packagename/repository/config/JpaAdapterConfig.java create mode 100644 example/jpa-adapter/src/main/java/packagename/repository/dao/ExampleDao.java create mode 100644 example/jpa-adapter/src/main/java/packagename/repository/entity/EnversRevisionEntity.java create mode 100644 example/jpa-adapter/src/main/java/packagename/repository/entity/ExampleEntity.java create mode 100644 example/jpa-adapter/src/main/resources/application.yaml create mode 100644 example/jpa-adapter/src/main/resources/db/changelog/db.changelog-master.yaml create mode 100644 example/jpa-adapter/src/main/resources/db/changelog/includes/110720212155010-create-example.yaml create mode 100644 example/jpa-adapter/src/main/resources/db/changelog/includes/240720211408009-create-revision.yaml create mode 100644 example/jpa-adapter/src/main/resources/preliquibase/default.sql create mode 100644 example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaAdapterApplication.java create mode 100644 example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaTest.java create mode 100644 example/jpa-adapter/src/test/resources/application-test.yml create mode 100644 example/jpa-adapter/src/test/resources/sql/data.sql create mode 100644 example/pom.xml create mode 100644 example/rest-adapter/pom.xml create mode 100644 example/rest-adapter/src/main/java/packagename/rest/ExampleResource.java create mode 100644 example/rest-adapter/src/main/java/packagename/rest/exception/ExampleExceptionHandler.java create mode 100644 example/rest-adapter/src/main/resources/open-api.yaml create mode 100644 example/rest-adapter/src/test/java/packagename/rest/ExampleResourceTest.java create mode 100644 example/rest-adapter/src/test/java/packagename/rest/ExampleRestAdapterApplication.java rename misc/rename.sh => scaffold-code-from-example.sh (59%) delete mode 100644 {{cookiecutter.app_name}}/.github/CONTRIBUTING.md create mode 100644 {{cookiecutter.app_name}}/.mvn/jvm.config diff --git a/.github/workflows/scaffold.yml b/.github/workflows/scaffold.yml index a230aa1..d24f0ce 100644 --- a/.github/workflows/scaffold.yml +++ b/.github/workflows/scaffold.yml @@ -31,4 +31,7 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - name: Build with Maven - run: cd cart-service && mvn clean install -ntp \ No newline at end of file + run: | + cd cart-service + mvn git-code-format:format-code + mvn clean install -ntp \ No newline at end of file diff --git a/.gitignore b/.gitignore index 85cf01b..e14a06c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ -# IntelliJ project files +## IntelliJ project files .idea *.iml -# The default sample project +## Generated projects +# The generated project from scaffold-code-from-example.sh +example-tryout +# The generated project from cookiecutter cart-service +## Build artifacts target diff --git a/example/.docker/Dockerfile b/example/.docker/Dockerfile new file mode 100644 index 0000000..1fa6f27 --- /dev/null +++ b/example/.docker/Dockerfile @@ -0,0 +1,7 @@ +FROM gcr.io/distroless/java21-debian12 + +WORKDIR /app + +COPY ../../bootstrap/target/example-exec.jar . + +CMD ["example-exec.jar"] \ No newline at end of file diff --git a/example/.github/CODEOWNERS b/example/.github/CODEOWNERS new file mode 100644 index 0000000..078e607 --- /dev/null +++ b/example/.github/CODEOWNERS @@ -0,0 +1 @@ +* @devs-from-matrix/app-generator-team diff --git a/example/.github/ISSUE_TEMPLATE/bug_report.md b/example/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9568cbc --- /dev/null +++ b/example/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[Bug]" +labels: ":bug: bug" +assignees: 'paul58914080, anupbaranwal' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/example/.github/ISSUE_TEMPLATE/feature_request.md b/example/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..da9b0e5 --- /dev/null +++ b/example/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[New Feature]" +labels: ":rocket: enhancement" +assignees: 'paul58914080, anupbaranwal' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/example/.github/PULL_REQUEST_TEMPLATE.md b/example/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2b71227 --- /dev/null +++ b/example/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +# Description + + + +Closes : + +# Checklist: + +- [ ] My code follows the contribution guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] My commits follow [conventional commit message guidelines](https://www.conventionalcommits.org/en/v1.0.0/) diff --git a/example/.github/dependabot.yml b/example/.github/dependabot.yml new file mode 100644 index 0000000..c51d0f0 --- /dev/null +++ b/example/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: daily + time: "23:30" + open-pull-requests-limit: 10 + assignees: + - paul58914080 diff --git a/example/.github/workflows/build_workflow.yml b/example/.github/workflows/build_workflow.yml new file mode 100644 index 0000000..7ef2fa3 --- /dev/null +++ b/example/.github/workflows/build_workflow.yml @@ -0,0 +1,29 @@ +name: CI +on: + pull_request: + branches: [ main ] + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: mvn clean install -ntp + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/example/.mvn/jvm.config b/example/.mvn/jvm.config new file mode 100644 index 0000000..e2a50e0 --- /dev/null +++ b/example/.mvn/jvm.config @@ -0,0 +1 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ No newline at end of file diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..f5abc15 --- /dev/null +++ b/example/README.md @@ -0,0 +1,52 @@ +# {{cookiecutter.app_title}} + +## Pre-requisite + +- maven >= 3.8.6 +- open jdk 21 + +## How to build? + +``` +mvn clean install +``` + +### How to build a docker image? + +``` +cd bootstrap && mvn compile jib:dockerBuild +``` + +[More information](https://cloud.google.com/java/getting-started/jib) + +## How to start ? + +``` +cd bootstrap && mvn spring-boot:run +``` + +## Formatting + +This project uses [git-code-format-maven-plugin](https://github.com/Cosium/git-code-format-maven-plugin) for formatting +the code per [google style guide](https://google.github.io/styleguide/javaguide.html) + +### How to format ? + +`mvn git-code-format:format-code` + +## Validating + +This project +uses [githook-maven-plugin](https://mvnrepository.com/artifact/io.github.phillipuniverse/githook-maven-plugin) which is +a maven plugin to configure and install local git hooks by running set of commands during build. + +### Command to validate formatted code + +`mvn git-code-format:validate-code-format` + +## Contribution guidelines + +We are really glad you're reading this, because we need volunteer developers to help this project come to fruition. + +Request you to please read +our [contribution guidelines](https://devs-from-matrix.github.io/basic-template-repository/#/README?id=contribution-guidelines) diff --git a/example/acceptance-test/pom.xml b/example/acceptance-test/pom.xml new file mode 100644 index 0000000..2b8f16a --- /dev/null +++ b/example/acceptance-test/pom.xml @@ -0,0 +1,144 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + acceptance-test + + + group-id + bootstrap + + + + + group-id + rest-adapter + + + + group-id + domain + + + + group-id + jpa-adapter + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.h2database + h2 + + + + io.cucumber + cucumber-java8 + ${cucumber.version} + test + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + test + + + io.cucumber + cucumber-spring + ${cucumber.version} + test + + + + + + com.github.cukedoctor + cukedoctor-maven-plugin + ${cukedoctor-maven-plugin.version} + + target/cucumber + Example + Example - Living Documentation + ${project.version} + center + + + + + execute + + verify + + + + + org.jacoco + jacoco-maven-plugin + + + aggregate-jacoco-reports + verify + + report-aggregate + + + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoJunitAssertRuleTest + com.societegenerale.commons.plugin.rules.NoPowerMockRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreWithoutCommentRuleTest + + + + + + + + + + mutation-test + + + mutation.tests + + + + + + org.pitest + pitest-maven + ${pitest-maven-plugin.version} + + + aggregate-pitest-reports + verify + + report-aggregate + + + + + + + + + diff --git a/example/acceptance-test/src/test/java/packagename/cucumber/ExampleStepDef.java b/example/acceptance-test/src/test/java/packagename/cucumber/ExampleStepDef.java new file mode 100644 index 0000000..b147779 --- /dev/null +++ b/example/acceptance-test/src/test/java/packagename/cucumber/ExampleStepDef.java @@ -0,0 +1,122 @@ +package packagename.cucumber; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java8.En; +import io.cucumber.java8.HookNoArgsBody; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import packagename.domain.model.Example; +import packagename.repository.dao.ExampleDao; +import packagename.repository.entity.ExampleEntity; +import packagename.rest.generated.model.ExampleInfo; +import packagename.rest.generated.model.ProblemDetail; + +public class ExampleStepDef implements En { + + private static final String LOCALHOST = "http://localhost:"; + private static final String API_URI = "/api/v1/examples"; + @LocalServerPort private int port; + private ResponseEntity responseEntity; + + public ExampleStepDef(TestRestTemplate restTemplate, ExampleDao exampleDao) { + + DataTableType( + (Map row) -> + Example.builder() + .code(Long.parseLong(row.get("code"))) + .description(row.get("description")) + .build()); + DataTableType( + (Map row) -> + ExampleEntity.builder() + .code(Long.parseLong(row.get("code"))) + .description(row.get("description")) + .build()); + + Before((HookNoArgsBody) exampleDao::deleteAll); + After((HookNoArgsBody) exampleDao::deleteAll); + + Given( + "the following examples exists in the library", + (DataTable dataTable) -> { + List poems = dataTable.asList(ExampleEntity.class); + exampleDao.saveAll(poems); + }); + + When( + "user requests for all examples", + () -> { + String url = LOCALHOST + port + API_URI; + responseEntity = restTemplate.getForEntity(url, ExampleInfo.class); + }); + + When( + "user requests for examples by code {string}", + (String code) -> { + String url = LOCALHOST + port + API_URI + "/" + code; + responseEntity = restTemplate.getForEntity(url, Example.class); + }); + + When( + "user requests for examples by id {string} that does not exists", + (String code) -> { + String url = LOCALHOST + port + API_URI + "/" + code; + responseEntity = restTemplate.getForEntity(url, ProblemDetail.class); + }); + + Then( + "the user gets an exception {string}", + (String exception) -> { + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + var actualResponse = (ProblemDetail) responseEntity.getBody(); + var expectedProblemDetail = + ProblemDetail.builder() + .type("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404") + .status(HttpStatus.NOT_FOUND.value()) + .detail("Example with code 10000 does not exist") + .instance("/api/v1/examples/10000") + .title("Example not found") + .build(); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse) + .usingRecursiveComparison() + .ignoringFields("timestamp") + .isEqualTo(expectedProblemDetail); + assertThat(actualResponse.getTimestamp()) + .isCloseTo(LocalDateTime.now(), within(100L, ChronoUnit.SECONDS)); + }); + + Then( + "the user gets the following examples", + (DataTable dataTable) -> { + List expectedExamples = dataTable.asList(Example.class); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + Object body = responseEntity.getBody(); + assertThat(body).isNotNull(); + if (body instanceof ExampleInfo) { + assertThat(((ExampleInfo) body).getExamples()) + .isNotEmpty() + .extracting("description") + .containsAll( + expectedExamples.stream() + .map(Example::getDescription) + .collect(Collectors.toList())); + } else if (body instanceof Example) { + assertThat(body) + .isNotNull() + .extracting("description") + .isEqualTo(expectedExamples.get(0).getDescription()); + } + }); + } +} diff --git a/example/acceptance-test/src/test/java/packagename/cucumber/RunCucumberExampleTest.java b/example/acceptance-test/src/test/java/packagename/cucumber/RunCucumberExampleTest.java new file mode 100644 index 0000000..ef6435e --- /dev/null +++ b/example/acceptance-test/src/test/java/packagename/cucumber/RunCucumberExampleTest.java @@ -0,0 +1,21 @@ +package packagename.cucumber; + +import static io.cucumber.junit.platform.engine.Constants.*; + +import org.junit.platform.suite.api.ConfigurationParameter; +import org.junit.platform.suite.api.ConfigurationParameters; +import org.junit.platform.suite.api.IncludeEngines; +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; + +@Suite +@IncludeEngines("cucumber") +@SelectClasspathResource("features/example.feature") +@ConfigurationParameters({ + @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "packagename.cucumber"), + @ConfigurationParameter(key = FILTER_TAGS_PROPERTY_NAME, value = "@Example"), + @ConfigurationParameter(key = JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, value = "long"), + @ConfigurationParameter(key = PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true"), + @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "json:target/cucumber/cucumber.json") +}) +public class RunCucumberExampleTest {} diff --git a/example/acceptance-test/src/test/java/packagename/cucumber/SpringCucumberTestConfig.java b/example/acceptance-test/src/test/java/packagename/cucumber/SpringCucumberTestConfig.java new file mode 100644 index 0000000..76bbd96 --- /dev/null +++ b/example/acceptance-test/src/test/java/packagename/cucumber/SpringCucumberTestConfig.java @@ -0,0 +1,13 @@ +package packagename.cucumber; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import packagename.boot.ExampleApplication; + +@SpringBootTest(classes = ExampleApplication.class, webEnvironment = RANDOM_PORT) +@CucumberContextConfiguration +@ActiveProfiles("test") +public class SpringCucumberTestConfig {} diff --git a/example/acceptance-test/src/test/resources/application-test.yml b/example/acceptance-test/src/test/resources/application-test.yml new file mode 100644 index 0000000..5ed6901 --- /dev/null +++ b/example/acceptance-test/src/test/resources/application-test.yml @@ -0,0 +1,5 @@ +database: + url: jdbc:h2:mem:testdb;MODE=PostgreSQL; + driver-class-name: org.h2.Driver + username: '' + password: '' \ No newline at end of file diff --git a/example/acceptance-test/src/test/resources/features/example.feature b/example/acceptance-test/src/test/resources/features/example.feature new file mode 100644 index 0000000..148676b --- /dev/null +++ b/example/acceptance-test/src/test/resources/features/example.feature @@ -0,0 +1,24 @@ +@Example +Feature: User would like to get examples + Background: + Given the following examples exists in the library + | code | description | + | 1 | Twinkle twinkle little star | + | 2 | Johnny Johnny Yes Papa | + + Scenario: User should be able to get all examples + When user requests for all examples + Then the user gets the following examples + | code | description | + | 1 | Twinkle twinkle little star | + | 2 | Johnny Johnny Yes Papa | + + Scenario: User should be able to get examples by code + When user requests for examples by code "1" + Then the user gets the following examples + | code | description | + | 1 | Twinkle twinkle little star | + + Scenario: User should get an appropriate NOT FOUND message while trying get examples by an invalid code + When user requests for examples by id "10000" that does not exists + Then the user gets an exception "Example with code 10000 does not exist" \ No newline at end of file diff --git a/example/bootstrap/pom.xml b/example/bootstrap/pom.xml new file mode 100644 index 0000000..9b11193 --- /dev/null +++ b/example/bootstrap/pom.xml @@ -0,0 +1,87 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + bootstrap + + packagename.boot.ExampleApplication + + + + + group-id + rest-adapter + + + + group-id + domain + + + + group-id + jpa-adapter + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-devtools + + + + example + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + exec + + + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoStandardStreamRuleTest + com.societegenerale.commons.plugin.rules.NoJodaTimeRuleTest + com.societegenerale.commons.plugin.rules.NoJavaUtilDateRuleTest + com.societegenerale.commons.plugin.rules.NoPrefixForInterfacesRuleTest + com.societegenerale.commons.plugin.rules.NoPublicFieldRuleTest + com.societegenerale.commons.plugin.rules.NoInjectedFieldTest + com.societegenerale.commons.plugin.rules.NoAutowiredFieldTest + + + + + + com.google.cloud.tools + jib-maven-plugin + + + + diff --git a/example/bootstrap/src/main/java/packagename/boot/ExampleApplication.java b/example/bootstrap/src/main/java/packagename/boot/ExampleApplication.java new file mode 100644 index 0000000..5c02d17 --- /dev/null +++ b/example/bootstrap/src/main/java/packagename/boot/ExampleApplication.java @@ -0,0 +1,14 @@ +package packagename.boot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = "packagename") +public class ExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(ExampleApplication.class, args); + } +} diff --git a/example/bootstrap/src/main/java/packagename/boot/config/BootstrapConfig.java b/example/bootstrap/src/main/java/packagename/boot/config/BootstrapConfig.java new file mode 100644 index 0000000..66bc305 --- /dev/null +++ b/example/bootstrap/src/main/java/packagename/boot/config/BootstrapConfig.java @@ -0,0 +1,19 @@ +package packagename.boot.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import packagename.domain.ExampleDomain; +import packagename.domain.port.ObtainExample; +import packagename.domain.port.RequestExample; +import packagename.repository.config.JpaAdapterConfig; + +@Configuration +@Import(JpaAdapterConfig.class) +public class BootstrapConfig { + + @Bean + public RequestExample getRequestExample(ObtainExample obtainExample) { + return new ExampleDomain(obtainExample); + } +} diff --git a/example/bootstrap/src/main/resources/application.yml b/example/bootstrap/src/main/resources/application.yml new file mode 100644 index 0000000..94cd8b8 --- /dev/null +++ b/example/bootstrap/src/main/resources/application.yml @@ -0,0 +1,9 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb;MODE=PostgreSQL + jpa: + generate-ddl: false + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/example/domain-api/pom.xml b/example/domain-api/pom.xml new file mode 100644 index 0000000..308f237 --- /dev/null +++ b/example/domain-api/pom.xml @@ -0,0 +1,33 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + domain-api + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoStandardStreamRuleTest + com.societegenerale.commons.plugin.rules.NoJodaTimeRuleTest + com.societegenerale.commons.plugin.rules.NoJavaUtilDateRuleTest + com.societegenerale.commons.plugin.rules.NoPrefixForInterfacesRuleTest + com.societegenerale.commons.plugin.rules.NoPublicFieldRuleTest + com.societegenerale.commons.plugin.rules.NoInjectedFieldTest + com.societegenerale.commons.plugin.rules.NoAutowiredFieldTest + + + + + + + diff --git a/example/domain-api/src/main/java/packagename/domain/exception/ExampleNotFoundException.java b/example/domain-api/src/main/java/packagename/domain/exception/ExampleNotFoundException.java new file mode 100644 index 0000000..b5c724d --- /dev/null +++ b/example/domain-api/src/main/java/packagename/domain/exception/ExampleNotFoundException.java @@ -0,0 +1,8 @@ +package packagename.domain.exception; + +public class ExampleNotFoundException extends RuntimeException { + + public ExampleNotFoundException(Long id) { + super("Example with code " + id + " does not exist"); + } +} diff --git a/example/domain-api/src/main/java/packagename/domain/model/Example.java b/example/domain-api/src/main/java/packagename/domain/model/Example.java new file mode 100644 index 0000000..941e4a8 --- /dev/null +++ b/example/domain-api/src/main/java/packagename/domain/model/Example.java @@ -0,0 +1,16 @@ +package packagename.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Example { + + private Long code; + private String description; +} diff --git a/example/domain-api/src/main/java/packagename/domain/port/ObtainExample.java b/example/domain-api/src/main/java/packagename/domain/port/ObtainExample.java new file mode 100644 index 0000000..de07a92 --- /dev/null +++ b/example/domain-api/src/main/java/packagename/domain/port/ObtainExample.java @@ -0,0 +1,28 @@ +package packagename.domain.port; + +import java.util.List; +import java.util.Optional; +import lombok.NonNull; +import packagename.domain.model.Example; + +public interface ObtainExample { + + default List getAllExamples() { + Example example = + Example.builder() + .code(1L) + .description( + "If you could read a leaf or tree\r\nyoud have no need of books.\r\n-- Alistair Cockburn (1987)") + .build(); + return List.of(example); + } + + default Optional getExampleByCode(@NonNull Long code) { + return Optional.of( + Example.builder() + .code(1L) + .description( + "If you could read a leaf or tree\r\nyoud have no need of books.\r\n-- Alistair Cockburn (1987)") + .build()); + } +} diff --git a/example/domain-api/src/main/java/packagename/domain/port/RequestExample.java b/example/domain-api/src/main/java/packagename/domain/port/RequestExample.java new file mode 100644 index 0000000..a0af9a5 --- /dev/null +++ b/example/domain-api/src/main/java/packagename/domain/port/RequestExample.java @@ -0,0 +1,12 @@ +package packagename.domain.port; + +import java.util.List; +import lombok.NonNull; +import packagename.domain.model.Example; + +public interface RequestExample { + + List getExamples(); + + Example getExampleByCode(@NonNull Long code); +} diff --git a/example/domain/pom.xml b/example/domain/pom.xml new file mode 100644 index 0000000..6b07faa --- /dev/null +++ b/example/domain/pom.xml @@ -0,0 +1,44 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + domain + + + group-id + domain-api + + + org.assertj + assertj-core + test + + + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoStandardStreamRuleTest + com.societegenerale.commons.plugin.rules.NoJodaTimeRuleTest + com.societegenerale.commons.plugin.rules.NoJavaUtilDateRuleTest + com.societegenerale.commons.plugin.rules.NoPrefixForInterfacesRuleTest + com.societegenerale.commons.plugin.rules.NoPublicFieldRuleTest + com.societegenerale.commons.plugin.rules.NoInjectedFieldTest + com.societegenerale.commons.plugin.rules.NoAutowiredFieldTest + + + + + + + diff --git a/example/domain/src/main/java/packagename/domain/ExampleDomain.java b/example/domain/src/main/java/packagename/domain/ExampleDomain.java new file mode 100644 index 0000000..3a1f80a --- /dev/null +++ b/example/domain/src/main/java/packagename/domain/ExampleDomain.java @@ -0,0 +1,32 @@ +package packagename.domain; + +import java.util.List; +import lombok.NonNull; +import packagename.domain.exception.ExampleNotFoundException; +import packagename.domain.model.Example; +import packagename.domain.port.ObtainExample; +import packagename.domain.port.RequestExample; + +public class ExampleDomain implements RequestExample { + + private final ObtainExample obtainExample; + + public ExampleDomain() { + this(new ObtainExample() {}); + } + + public ExampleDomain(ObtainExample obtainExample) { + this.obtainExample = obtainExample; + } + + @Override + public List getExamples() { + return obtainExample.getAllExamples(); + } + + @Override + public Example getExampleByCode(@NonNull Long code) { + var example = obtainExample.getExampleByCode(code); + return example.orElseThrow(() -> new ExampleNotFoundException(code)); + } +} diff --git a/example/domain/src/test/java/packagename/AcceptanceTest.java b/example/domain/src/test/java/packagename/AcceptanceTest.java new file mode 100644 index 0000000..e0e5606 --- /dev/null +++ b/example/domain/src/test/java/packagename/AcceptanceTest.java @@ -0,0 +1,92 @@ +package packagename; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import packagename.domain.ExampleDomain; +import packagename.domain.exception.ExampleNotFoundException; +import packagename.domain.model.Example; +import packagename.domain.port.ObtainExample; + +@ExtendWith(MockitoExtension.class) +public class AcceptanceTest { + + @Test + @DisplayName("should be able to get examples when asked for examples from hard coded examples") + public void getExamplesFromHardCoded() { + /* + RequestExample - left side port + ExampleDomain - hexagon (domain) + ObtainExample - right side port + */ + var requestExample = new ExampleDomain(); // the example is hard coded + var examples = requestExample.getExamples(); + assertThat(examples) + .hasSize(1) + .extracting("description") + .contains( + "If you could read a leaf or tree\r\nyoud have no need of books.\r\n-- Alistair Cockburn (1987)"); + } + + @Test + @DisplayName("should be able to get examples when asked for examples from stub") + public void getExamplesFromMockedStub(@Mock ObtainExample obtainExample) { + // Stub + var example = + Example.builder() + .code(1L) + .description( + "I want to sleep\r\nSwat the flies\r\nSoftly, please.\r\n\r\n-- Masaoka Shiki (1867-1902)") + .build(); + Mockito.lenient().when(obtainExample.getAllExamples()).thenReturn(List.of(example)); + // hexagon + var requestExample = new ExampleDomain(obtainExample); + var examples = requestExample.getExamples(); + assertThat(examples) + .hasSize(1) + .extracting("description") + .contains( + "I want to sleep\r\nSwat the flies\r\nSoftly, please.\r\n\r\n-- Masaoka Shiki (1867-1902)"); + } + + @Test + @DisplayName("should be able to get example when asked for example by id from stub") + public void getExampleByIdFromMockedStub(@Mock ObtainExample obtainExample) { + // Given + // Stub + var code = 1L; + var description = + "I want to sleep\\r\\nSwat the flies\\r\\nSoftly, please.\\r\\n\\r\\n-- Masaoka Shiki (1867-1902)"; + var expectedExample = Example.builder().code(code).description(description).build(); + Mockito.lenient() + .when(obtainExample.getExampleByCode(code)) + .thenReturn(Optional.of(expectedExample)); + // When + var requestExample = new ExampleDomain(obtainExample); + var actualExample = requestExample.getExampleByCode(code); + assertThat(actualExample).isNotNull().isEqualTo(expectedExample); + } + + @Test + @DisplayName("should throw exception when asked for example by id that does not exists from stub") + public void getExceptionWhenAskedExampleByIdThatDoesNotExist(@Mock ObtainExample obtainExample) { + // Given + // Stub + var code = -1000L; + Mockito.lenient().when(obtainExample.getExampleByCode(code)).thenReturn(Optional.empty()); + // When + var requestExample = new ExampleDomain(obtainExample); + // Then + assertThatThrownBy(() -> requestExample.getExampleByCode(code)) + .isInstanceOf(ExampleNotFoundException.class) + .hasMessageContaining("Example with code " + code + " does not exist"); + } +} diff --git a/example/jpa-adapter/pom.xml b/example/jpa-adapter/pom.xml new file mode 100644 index 0000000..bd1e1f2 --- /dev/null +++ b/example/jpa-adapter/pom.xml @@ -0,0 +1,88 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + jpa-adapter + + + + group-id + domain-api + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.liquibase + liquibase-core + + + commons-text + org.apache.commons + + + + + commons-text + org.apache.commons + ${apache.commons.version} + + + org.springframework.data + spring-data-envers + + + org.postgresql + postgresql + + + net.lbruun.springboot + preliquibase-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.h2database + h2 + test + + + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoJunitAssertRuleTest + com.societegenerale.commons.plugin.rules.NoPowerMockRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreWithoutCommentRuleTest + + com.societegenerale.commons.plugin.rules.NoStandardStreamRuleTest + com.societegenerale.commons.plugin.rules.NoJodaTimeRuleTest + com.societegenerale.commons.plugin.rules.NoJavaUtilDateRuleTest + com.societegenerale.commons.plugin.rules.NoPrefixForInterfacesRuleTest + com.societegenerale.commons.plugin.rules.NoPublicFieldRuleTest + com.societegenerale.commons.plugin.rules.NoInjectedFieldTest + com.societegenerale.commons.plugin.rules.NoAutowiredFieldTest + + + + + + + diff --git a/example/jpa-adapter/src/main/java/packagename/repository/ExampleRepository.java b/example/jpa-adapter/src/main/java/packagename/repository/ExampleRepository.java new file mode 100644 index 0000000..e73d00a --- /dev/null +++ b/example/jpa-adapter/src/main/java/packagename/repository/ExampleRepository.java @@ -0,0 +1,29 @@ +package packagename.repository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import packagename.domain.model.Example; +import packagename.domain.port.ObtainExample; +import packagename.repository.dao.ExampleDao; +import packagename.repository.entity.ExampleEntity; + +public class ExampleRepository implements ObtainExample { + + private final ExampleDao exampleDao; + + public ExampleRepository(ExampleDao exampleDao) { + this.exampleDao = exampleDao; + } + + @Override + public List getAllExamples() { + return exampleDao.findAll().stream().map(ExampleEntity::toModel).collect(Collectors.toList()); + } + + @Override + public Optional getExampleByCode(Long code) { + var exampleEntity = exampleDao.findByCode(code); + return exampleEntity.map(ExampleEntity::toModel); + } +} diff --git a/example/jpa-adapter/src/main/java/packagename/repository/config/JpaAdapterConfig.java b/example/jpa-adapter/src/main/java/packagename/repository/config/JpaAdapterConfig.java new file mode 100644 index 0000000..877bcbb --- /dev/null +++ b/example/jpa-adapter/src/main/java/packagename/repository/config/JpaAdapterConfig.java @@ -0,0 +1,23 @@ +package packagename.repository.config; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import packagename.domain.port.ObtainExample; +import packagename.repository.ExampleRepository; +import packagename.repository.dao.ExampleDao; + +@Configuration +@EntityScan("packagename.repository.entity") +@EnableJpaRepositories( + basePackages = "packagename.repository.dao", + repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class) +public class JpaAdapterConfig { + + @Bean + public ObtainExample getExampleRepository(ExampleDao exampleDao) { + return new ExampleRepository(exampleDao); + } +} diff --git a/example/jpa-adapter/src/main/java/packagename/repository/dao/ExampleDao.java b/example/jpa-adapter/src/main/java/packagename/repository/dao/ExampleDao.java new file mode 100644 index 0000000..25e8d59 --- /dev/null +++ b/example/jpa-adapter/src/main/java/packagename/repository/dao/ExampleDao.java @@ -0,0 +1,14 @@ +package packagename.repository.dao; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.history.RevisionRepository; +import org.springframework.stereotype.Repository; +import packagename.repository.entity.ExampleEntity; + +@Repository +public interface ExampleDao + extends JpaRepository, RevisionRepository { + + Optional findByCode(Long code); +} diff --git a/example/jpa-adapter/src/main/java/packagename/repository/entity/EnversRevisionEntity.java b/example/jpa-adapter/src/main/java/packagename/repository/entity/EnversRevisionEntity.java new file mode 100644 index 0000000..7f6c0b5 --- /dev/null +++ b/example/jpa-adapter/src/main/java/packagename/repository/entity/EnversRevisionEntity.java @@ -0,0 +1,34 @@ +package packagename.repository.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Table(name = "REVISION_INFO", schema = "EXAMPLE_AUDIT") +@Entity +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@SequenceGenerator( + schema = "EXAMPLE_AUDIT", + name = "SEQ_REVISION_INFO", + sequenceName = "EXAMPLE_AUDIT.SEQ_REVISION_INFO", + allocationSize = 1) +public class EnversRevisionEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_REVISION_INFO") + @Column(name = "REV") + private Long rev; + + @Column(name = "TIMESTAMP") + private Long code; +} diff --git a/example/jpa-adapter/src/main/java/packagename/repository/entity/ExampleEntity.java b/example/jpa-adapter/src/main/java/packagename/repository/entity/ExampleEntity.java new file mode 100644 index 0000000..26bc81b --- /dev/null +++ b/example/jpa-adapter/src/main/java/packagename/repository/entity/ExampleEntity.java @@ -0,0 +1,45 @@ +package packagename.repository.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.envers.Audited; +import packagename.domain.model.Example; + +@Table(name = "T_EXAMPLE") +@Entity +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Audited +public class ExampleEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_EXAMPLE") + @SequenceGenerator( + name = "SEQ_T_EXAMPLE", + sequenceName = "SEQ_T_EXAMPLE", + allocationSize = 1, + initialValue = 1) + @Column(name = "TECH_ID") + private Long techId; + + @Column(name = "CODE") + private Long code; + + @Column(name = "DESCRIPTION") + private String description; + + public Example toModel() { + return Example.builder().code(code).description(description).build(); + } +} diff --git a/example/jpa-adapter/src/main/resources/application.yaml b/example/jpa-adapter/src/main/resources/application.yaml new file mode 100644 index 0000000..4210373 --- /dev/null +++ b/example/jpa-adapter/src/main/resources/application.yaml @@ -0,0 +1,27 @@ +spring: + datasource: + driver-class-name: ${database.driver-class-name:org.postgresql.Driver} + url: ${database.url} + username: ${database.username} + password: ${database.password} + jpa: + generate-ddl: false + hibernate: + ddl-auto: none + database-platform: org.hibernate.dialect.PostgreSQLDialect + show-sql: ${database.show_sql:false} + properties: + hibernate: + default_schema: ${database.default_schema:EXAMPLE} + show_sql: ${database.show_sql:false} + use_sql_comments: ${database.use_sql_comments:false} + format_sql: ${database.format_sql:false} + org: + hibernate: + envers: + default_schema: ${database.default_audit_schema:EXAMPLE_AUDIT} + store_data_at_delete: true + liquibase: + enabled: true + liquibase-schema: ${database.liquibase_schema:LIQUIBASE} + default-schema: ${database.default_schema:EXAMPLE} diff --git a/example/jpa-adapter/src/main/resources/db/changelog/db.changelog-master.yaml b/example/jpa-adapter/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000..1cad94e --- /dev/null +++ b/example/jpa-adapter/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,5 @@ +databaseChangeLog: + - include: + file: db/changelog/includes/240720211408009-create-revision.yaml + - include: + file: db/changelog/includes/110720212155010-create-example.yaml \ No newline at end of file diff --git a/example/jpa-adapter/src/main/resources/db/changelog/includes/110720212155010-create-example.yaml b/example/jpa-adapter/src/main/resources/db/changelog/includes/110720212155010-create-example.yaml new file mode 100644 index 0000000..f2c58ab --- /dev/null +++ b/example/jpa-adapter/src/main/resources/db/changelog/includes/110720212155010-create-example.yaml @@ -0,0 +1,77 @@ +databaseChangeLog: + - changeSet: + id: create-table-t_example + author: Paul WILLIAMS + changes: + - createTable: + tableName: T_EXAMPLE + columns: + - column: + name: TECH_ID + type: BIGINT + constraints: + primaryKey: true + nullable: false + - column: + name: CODE + type: BIGINT + constraints: + nullable: false + - column: + name: DESCRIPTION + type: VARCHAR(255) + constraints: + nullable: false + createSequence: + sequenceName: SEQ_T_EXAMPLE + startValue: 1 + incrementBy: 1 + rollback: + - dropSequence: + sequenceName: SEQ_T_EXAMPLE + - dropTable: + tableName: T_EXAMPLE + - changeSet: + id: create-table-t_example_aud + author: Paul WILLIAMS + changes: + - createTable: + schemaName: EXAMPLE_AUDIT + tableName: T_EXAMPLE_AUD + columns: + - column: + name: TECH_ID + type: BIGINT + constraints: + nullable: false + - column: + name: CODE + type: BIGINT + constraints: + nullable: false + - column: + name: DESCRIPTION + type: VARCHAR(255) + constraints: + nullable: false + - column: + name: REV + type: BIGINT + constraints: + nullable: false + foreignKeyName: FK_T_EXAMPLE_AUD_REV + references: EXAMPLE_AUDIT.REVINFO(REV) + - column: + name: REVTYPE + type: INTEGER + constraints: + nullable: false + - addPrimaryKey: + schemaName: EXAMPLE_AUDIT + tableName: T_EXAMPLE_AUD + columnNames: TECH_ID, REV + rollback: + - dropTable: + schemaName: EXAMPLE_AUDIT + tableName: T_EXAMPLE_AUD + cascadeConstraints: true diff --git a/example/jpa-adapter/src/main/resources/db/changelog/includes/240720211408009-create-revision.yaml b/example/jpa-adapter/src/main/resources/db/changelog/includes/240720211408009-create-revision.yaml new file mode 100644 index 0000000..6a96c07 --- /dev/null +++ b/example/jpa-adapter/src/main/resources/db/changelog/includes/240720211408009-create-revision.yaml @@ -0,0 +1,34 @@ +databaseChangeLog: + - changeSet: + id: create-table-t_example_audit + author: Anup Baranwal + changes: + - createTable: + schemaName: EXAMPLE_AUDIT + tableName: REVINFO + columns: + - column: + name: REV + type: BIGINT + constraints: + primaryKey: true + nullable: false + autoIncrement: true + - column: + name: REVTSTMP + type: BIGINT + constraints: + nullable: false + createSequence: + schemaName: EXAMPLE_AUDIT + sequenceName: SEQ_REVISION_INFO + startValue: 1 + incrementBy: 1 + rollback: + - dropTable: + schemaName: EXAMPLE_AUDIT + tableName: REVINFO + cascadeConstraints: true + - dropSequence: + schemaName: EXAMPLE_AUDIT + sequenceName: SEQ_REVISION_INFO \ No newline at end of file diff --git a/example/jpa-adapter/src/main/resources/preliquibase/default.sql b/example/jpa-adapter/src/main/resources/preliquibase/default.sql new file mode 100644 index 0000000..1805345 --- /dev/null +++ b/example/jpa-adapter/src/main/resources/preliquibase/default.sql @@ -0,0 +1,3 @@ +CREATE SCHEMA IF NOT EXISTS ${spring.liquibase.liquibase-schema:LIQUIBASE}; +CREATE SCHEMA IF NOT EXISTS ${spring.liquibase.default-schema:EXAMPLE}; +CREATE SCHEMA IF NOT EXISTS ${spring.jpa.properties.org.hibernate.envers:EXAMPLE_AUDIT}; \ No newline at end of file diff --git a/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaAdapterApplication.java b/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaAdapterApplication.java new file mode 100644 index 0000000..24c7bf0 --- /dev/null +++ b/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaAdapterApplication.java @@ -0,0 +1,22 @@ +package packagename.repository; + +import net.lbruun.springboot.preliquibase.PreLiquibaseAutoConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Import; +import packagename.repository.config.JpaAdapterConfig; + +@SpringBootApplication +public class ExampleJpaAdapterApplication { + + public static void main(String[] args) { + SpringApplication.run(ExampleJpaAdapterApplication.class, args); + } + + @TestConfiguration + @Import(JpaAdapterConfig.class) + @ImportAutoConfiguration({PreLiquibaseAutoConfiguration.class}) + static class ExampleJpaTestConfig {} +} diff --git a/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaTest.java b/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaTest.java new file mode 100644 index 0000000..df884f4 --- /dev/null +++ b/example/jpa-adapter/src/test/java/packagename/repository/ExampleJpaTest.java @@ -0,0 +1,80 @@ +package packagename.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import packagename.domain.model.Example; +import packagename.domain.port.ObtainExample; + +@ExtendWith(SpringExtension.class) +@DataJpaTest +@ActiveProfiles("test") +public class ExampleJpaTest { + + @Autowired private ObtainExample obtainExample; + + @Test + @DisplayName("should start the application") + public void startup() { + assertThat(Boolean.TRUE).isTrue(); + } + + @Sql(scripts = {"/sql/data.sql"}) + @Test + @DisplayName( + "given examples exist in database when asked should return all examples from database") + public void shouldGiveMeExamplesWhenAskedGivenExampleExistsInDatabase() { + // Given from @Sql + // When + var examples = obtainExample.getAllExamples(); + // Then + assertThat(examples) + .isNotNull() + .extracting("description") + .contains("Twinkle twinkle little star"); + } + + @Test + @DisplayName("given no examples exists in database when asked should return empty") + public void shouldGiveNoExampleWhenAskedGivenExamplesDoNotExistInDatabase() { + // When + var examples = obtainExample.getAllExamples(); + // Then + assertThat(examples).isNotNull().isEmpty(); + } + + @Sql(scripts = {"/sql/data.sql"}) + @Test + @DisplayName( + "given examples exists in database when asked for example by id should return the example") + public void shouldGiveTheExampleWhenAskedByIdGivenThatExampleByThatIdExistsInDatabase() { + // Given from @Sql + // When + var example = obtainExample.getExampleByCode(1L); + // Then + assertThat(example) + .isNotNull() + .isNotEmpty() + .get() + .isEqualTo(Example.builder().code(1L).description("Twinkle twinkle little star").build()); + } + + @Sql(scripts = {"/sql/data.sql"}) + @Test + @DisplayName( + "given examples exists in database when asked for example by id that does not exist should give empty") + public void shouldGiveNoExampleWhenAskedByIdGivenThatExampleByThatIdDoesNotExistInDatabase() { + // Given from @Sql + // When + var example = obtainExample.getExampleByCode(-1000L); + // Then + assertThat(example).isEmpty(); + } +} diff --git a/example/jpa-adapter/src/test/resources/application-test.yml b/example/jpa-adapter/src/test/resources/application-test.yml new file mode 100644 index 0000000..5ed6901 --- /dev/null +++ b/example/jpa-adapter/src/test/resources/application-test.yml @@ -0,0 +1,5 @@ +database: + url: jdbc:h2:mem:testdb;MODE=PostgreSQL; + driver-class-name: org.h2.Driver + username: '' + password: '' \ No newline at end of file diff --git a/example/jpa-adapter/src/test/resources/sql/data.sql b/example/jpa-adapter/src/test/resources/sql/data.sql new file mode 100644 index 0000000..e8eabff --- /dev/null +++ b/example/jpa-adapter/src/test/resources/sql/data.sql @@ -0,0 +1,2 @@ +INSERT INTO EXAMPLE.T_EXAMPLE(TECH_ID, CODE, DESCRIPTION) VALUES +(1000, 1, 'Twinkle twinkle little star'); \ No newline at end of file diff --git a/example/pom.xml b/example/pom.xml new file mode 100644 index 0000000..ad5cec8 --- /dev/null +++ b/example/pom.xml @@ -0,0 +1,345 @@ + + + 4.0.0 + group-id + artifactName-parent + pom + 1.0-SNAPSHOT + + UTF-8 + 21 + ${java.version} + ${java.version} + 5.13.4 + 3.5.4 + 7.27.0 + 2.8.9 + 1.6.1 + 1.1.1 + 2.4 + 2.3.232 + 1.14.0 + + 3.9.0 + 3.5.3 + 3.6.1 + 4.0.2 + 0.8.13 + 3.14.0 + 3.4.6 + 5.3 + 1.0.5 + 1.20.1 + 1.2.3 + + + domain-api + domain + jpa-adapter + rest-adapter + bootstrap + acceptance-test + + + + + + group-id + domain-api + ${project.version} + + + group-id + domain + ${project.version} + + + group-id + jpa-adapter + ${project.version} + + + group-id + rest-adapter + ${project.version} + + + group-id + bootstrap + ${project.version} + + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + com.opentable.components + otj-pg-embedded + ${otj-pg-embedded.version} + test + + + net.lbruun.springboot + preliquibase-spring-boot-starter + ${pre-liquibase.version} + + + io.github.classgraph + classgraph + ${classgraph.version} + + + + + + + org.projectlombok + lombok + provided + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + + org.mockito + mockito-core + test + + + org.mockito + mockito-junit-jupiter + test + + + + ${project.artifactId} + target + target/classes + target/test-classes + src/main/java + src/test/java + + + src/main/resources + + + + + src/test/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + false + + + + org.jacoco + jacoco-maven-plugin + + + + prepare-agent + + + + report + test + + report + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin} + + + + + false + + ${project.groupId}:* + + + + true + + + + enforce-versions + + enforce + + validate + + + + + com.cosium.code + git-code-format-maven-plugin + ${git-code-format-maven-plugin.version} + + + + install-formatter-hook + + install-hooks + + + + + validate-code-format + + validate-code-format + + + + + + + com.cosium.code + google-java-format + ${git-code-format-maven-plugin.version} + + + + + io.github.phillipuniverse + githook-maven-plugin + ${githook-maven-plugin.version} + + + + install + + + + + echo "Validating..." + exec mvn test + echo "Formatting code..." + exec mvn git-code-format:format-code + echo "Validating format..." + exec mvn git-code-format:validate-code-format + + + + + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + com.societegenerale.commons + arch-unit-maven-plugin + ${arch-unit-maven-plugin.version} + + + test + + arch-test + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven.plugin.version} + + + + + + + mutation-test + + mutation.tests + + + + + org.pitest + pitest-maven + ${pitest-maven-plugin.version} + + + test + + mutationCoverage + + + + + + org.slf4j + + true + + XML + HTML + + + + + org.pitest + pitest-junit5-plugin + ${pitest-junit5-plugin.version} + + + + + + + + diff --git a/example/rest-adapter/pom.xml b/example/rest-adapter/pom.xml new file mode 100644 index 0000000..f07e16e --- /dev/null +++ b/example/rest-adapter/pom.xml @@ -0,0 +1,102 @@ + + + + group-id + artifactName-parent + 1.0-SNAPSHOT + + 4.0.0 + rest-adapter + + + + group-id + domain-api + + + + org.springframework.boot + spring-boot-starter-web + + + org.yaml + snakeyaml + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi-starter.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.openapitools + openapi-generator-maven-plugin + 7.14.0 + + + generate-open-api-source + + generate + + + ${project.basedir}/src/main/resources/open-api.yaml + spring + spring-boot + + Example=packagename.domain.model.Example + + + OffsetDateTime=java.time.LocalDateTime + + packagename.rest.generated.api + packagename.rest.generated.model + + false + false + true + true + java8 + true + false + @lombok.AllArgsConstructor @lombok.Builder @lombok.Data @lombok.NoArgsConstructor + + + + + + + + com.societegenerale.commons + arch-unit-maven-plugin + + + + com.societegenerale.commons.plugin.rules.NoJunitAssertRuleTest + com.societegenerale.commons.plugin.rules.NoPowerMockRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreRuleTest + com.societegenerale.commons.plugin.rules.NoTestIgnoreWithoutCommentRuleTest + + com.societegenerale.commons.plugin.rules.NoStandardStreamRuleTest + com.societegenerale.commons.plugin.rules.NoJodaTimeRuleTest + com.societegenerale.commons.plugin.rules.NoJavaUtilDateRuleTest + com.societegenerale.commons.plugin.rules.NoPrefixForInterfacesRuleTest + com.societegenerale.commons.plugin.rules.NoPublicFieldRuleTest + com.societegenerale.commons.plugin.rules.NoInjectedFieldTest + com.societegenerale.commons.plugin.rules.NoAutowiredFieldTest + + + + + + + diff --git a/example/rest-adapter/src/main/java/packagename/rest/ExampleResource.java b/example/rest-adapter/src/main/java/packagename/rest/ExampleResource.java new file mode 100644 index 0000000..ff8136b --- /dev/null +++ b/example/rest-adapter/src/main/java/packagename/rest/ExampleResource.java @@ -0,0 +1,27 @@ +package packagename.rest; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import packagename.domain.model.Example; +import packagename.domain.port.RequestExample; +import packagename.rest.generated.api.ExampleApi; +import packagename.rest.generated.model.ExampleInfo; + +@RestController +public class ExampleResource implements ExampleApi { + + private final RequestExample requestExample; + + public ExampleResource(RequestExample requestExample) { + this.requestExample = requestExample; + } + + public ResponseEntity getExamples() { + return ResponseEntity.ok(ExampleInfo.builder().examples(requestExample.getExamples()).build()); + } + + public ResponseEntity getExampleByCode(@PathVariable("code") Long code) { + return ResponseEntity.ok(requestExample.getExampleByCode(code)); + } +} diff --git a/example/rest-adapter/src/main/java/packagename/rest/exception/ExampleExceptionHandler.java b/example/rest-adapter/src/main/java/packagename/rest/exception/ExampleExceptionHandler.java new file mode 100644 index 0000000..147c97f --- /dev/null +++ b/example/rest-adapter/src/main/java/packagename/rest/exception/ExampleExceptionHandler.java @@ -0,0 +1,30 @@ +package packagename.rest.exception; + +import java.time.LocalDateTime; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import packagename.domain.exception.ExampleNotFoundException; +import packagename.rest.generated.model.ProblemDetail; + +@RestControllerAdvice(basePackages = {"packagename"}) +public class ExampleExceptionHandler { + + @ExceptionHandler(value = ExampleNotFoundException.class) + public final ResponseEntity handleExampleNotFoundException( + final Exception exception, final WebRequest request) { + var problem = + ProblemDetail.builder() + .type("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404") + .status(HttpStatus.NOT_FOUND.value()) + .title("Example not found") + .detail(exception.getMessage()) + .instance(((ServletWebRequest) request).getRequest().getRequestURI()) + .timestamp(LocalDateTime.now()) + .build(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(problem); + } +} diff --git a/example/rest-adapter/src/main/resources/open-api.yaml b/example/rest-adapter/src/main/resources/open-api.yaml new file mode 100644 index 0000000..de49f1c --- /dev/null +++ b/example/rest-adapter/src/main/resources/open-api.yaml @@ -0,0 +1,76 @@ +--- +openapi: 3.0.1 +info: + title: Example API Documentation + version: v1 +tags: + - name: Example + description: Resource to manage example +paths: + "/api/v1/examples": + get: + tags: + - Example + summary: Get all examples + operationId: getExamples + responses: + '200': + description: OK + content: + "*/*": + schema: + "$ref": "#/components/schemas/ExampleInfo" + "/api/v1/examples/{code}": + get: + tags: + - Example + summary: Get example by code + operationId: getExampleByCode + parameters: + - name: code + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: OK + content: + "*/*": + schema: + "$ref": "#/components/schemas/Example" +components: + schemas: + Example: + type: object + properties: + code: + type: integer + format: int64 + description: + type: string + ExampleInfo: + type: object + properties: + examples: + type: array + items: + "$ref": "#/components/schemas/Example" + ProblemDetail: + type: object + properties: + type: + type: string + title: + type: string + status: + type: integer + format: int32 + detail: + type: string + instance: + type: string + timestamp: + type: string + format: date-time diff --git a/example/rest-adapter/src/test/java/packagename/rest/ExampleResourceTest.java b/example/rest-adapter/src/test/java/packagename/rest/ExampleResourceTest.java new file mode 100644 index 0000000..22ef19f --- /dev/null +++ b/example/rest-adapter/src/test/java/packagename/rest/ExampleResourceTest.java @@ -0,0 +1,112 @@ +package packagename.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import packagename.domain.exception.ExampleNotFoundException; +import packagename.domain.model.Example; +import packagename.domain.port.RequestExample; +import packagename.rest.generated.model.ExampleInfo; +import packagename.rest.generated.model.ProblemDetail; + +@ExtendWith(MockitoExtension.class) +@SpringBootTest(classes = ExampleRestAdapterApplication.class, webEnvironment = RANDOM_PORT) +@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) +public class ExampleResourceTest { + + private static final String LOCALHOST = "http://localhost:"; + private static final String API_URI = "/api/v1/examples"; + @LocalServerPort private int port; + @Autowired private TestRestTemplate restTemplate; + @MockitoBean private RequestExample requestExample; + + @Test + @DisplayName("should start the rest adapter application") + public void startup() { + assertThat(Boolean.TRUE).isTrue(); + } + + @Test + @DisplayName("should give examples when asked for examples with the support of domain stub") + public void obtainExamplesFromDomainStub() { + // Given + var example = Example.builder().code(1L).description("Johnny Johnny Yes Papa !!").build(); + Mockito.lenient().when(requestExample.getExamples()).thenReturn(List.of(example)); + // When + var url = LOCALHOST + port + API_URI; + var responseEntity = restTemplate.getForEntity(url, ExampleInfo.class); + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isNotNull(); + assertThat(responseEntity.getBody().getExamples()) + .isNotEmpty() + .extracting("description") + .contains("Johnny Johnny Yes Papa !!"); + } + + @Test + @DisplayName( + "should give the example when asked for an example by code with the support of domain stub") + public void obtainExampleByCodeFromDomainStub() { + // Given + var code = 1L; + var description = "Johnny Johnny Yes Papa !!"; + var example = Example.builder().code(code).description(description).build(); + Mockito.lenient().when(requestExample.getExampleByCode(code)).thenReturn(example); + // When + var url = LOCALHOST + port + API_URI + "/" + code; + var responseEntity = restTemplate.getForEntity(url, Example.class); + // Then + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(responseEntity.getBody()).isNotNull(); + assertThat(responseEntity.getBody()).isEqualTo(example); + } + + @Test + @DisplayName( + "should give exception when asked for an example by code that does not exists with the support of domain stub") + public void shouldGiveExceptionWhenAskedForAnExampleByCodeFromDomainStub() { + // Given + var code = -1000L; + Mockito.lenient() + .when(requestExample.getExampleByCode(code)) + .thenThrow(new ExampleNotFoundException(code)); + // When + var url = LOCALHOST + port + API_URI + "/" + code; + var responseEntity = restTemplate.getForEntity(url, ProblemDetail.class); + // Then + var expectedProblemDetail = + ProblemDetail.builder() + .type("https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404") + .status(HttpStatus.NOT_FOUND.value()) + .detail("Example with code -1000 does not exist") + .instance("/api/v1/examples/-1000") + .title("Example not found") + .build(); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(responseEntity.getBody()).isNotNull(); + assertThat(responseEntity.getBody()) + .usingRecursiveComparison() + .ignoringFields("timestamp") + .isEqualTo(expectedProblemDetail); + assertThat(responseEntity.getBody().getTimestamp()) + .isCloseTo(LocalDateTime.now(), within(100L, ChronoUnit.SECONDS)); + } +} diff --git a/example/rest-adapter/src/test/java/packagename/rest/ExampleRestAdapterApplication.java b/example/rest-adapter/src/test/java/packagename/rest/ExampleRestAdapterApplication.java new file mode 100644 index 0000000..0155670 --- /dev/null +++ b/example/rest-adapter/src/test/java/packagename/rest/ExampleRestAdapterApplication.java @@ -0,0 +1,14 @@ +package packagename.rest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = "packagename") +public class ExampleRestAdapterApplication { + + public static void main(String[] args) { + SpringApplication.run(ExampleRestAdapterApplication.class, args); + } +} diff --git a/misc/rename.sh b/scaffold-code-from-example.sh similarity index 59% rename from misc/rename.sh rename to scaffold-code-from-example.sh index 3a0fddc..b20f2fb 100644 --- a/misc/rename.sh +++ b/scaffold-code-from-example.sh @@ -1,6 +1,22 @@ +#!/bin/bash +# This script is used to rename files and directories in the example project to that of the cookiecutter template. + +# Remove previous example-tryout directory if it exists +if [ -d "example-tryout" ]; then + rm -rf "example-tryout" +fi +# Create a new example-tryout directory and copy the example project into it +mkdir "example-tryout" +cp -r ./example/ ./example-tryout +cd ./example-tryout + +# sets the locale for all commands run in the current shell session to the "C" locale, which is the default POSIX locale. This makes programs like sed and find treat files as raw bytes, ignoring any character encoding issues. It helps avoid errors like illegal byte sequence when processing files with mixed or unknown encodings. +export LC_ALL=C + find . -type f -exec sed -e s/Examples/{{cookiecutter.domain_plural_capitalized}}/g -i '' '{}' ';' find . -depth -name '*Examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Examples/\1{{cookiecutter.domain_plural_capitalized}}/')"; done find . -type f -exec sed -e s/examples/{{cookiecutter.domain_plural}}/g -i '' '{}' ';' +find . -type f -exec sed -e s/examples/{{cookiecutter.domain_plural}}/g -i '' '{}' ';' find . -depth -name '*examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)examples/\1{{cookiecutter.domain_plural}}/')"; done find . -type f -exec sed -e s/Example/{{cookiecutter.domain_capitalized}}/g -i '' '{}' ';' find . -depth -name '*Example*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Example/\1{{cookiecutter.domain_capitalized}}/')"; done @@ -9,6 +25,7 @@ find . -depth -name '*example*' -print0|while IFS= read -rd '' f; do mv -i "$f" find . -type f -exec sed -e s/packagename/{{cookiecutter.package_name}}/g -i '' '{}' ';' find . -depth -name '*packagename*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)packagename/\1{{cookiecutter.package_name}}/')"; done find . -type f -exec sed -e s/artifactName/{{cookiecutter.artifact_id}}/g -i '' '{}' ';' -# For the following, we need to replace EXAMPLES and EXAMPLE with the domain name +find . -type f -exec sed -e s/group-id/{{cookiecutter.group_id}}/g -i '' '{}' ';' +## For the following, we need to replace EXAMPLES and EXAMPLE with the domain name find . -type f -exec sed -e s/EXAMPLES/{{cookiecutter.domain_plural_uppercase}}/g -i '' '{}' ';' -find . -type f -exec sed -e s/EXAMPLE/{{cookiecutter.domain_uppercase}}/g -i '' '{}' ';' \ No newline at end of file +find . -type f -exec sed -e s/EXAMPLE/{{cookiecutter.domain_uppercase}}/g -i '' '{}' ';' diff --git a/{{cookiecutter.app_name}}/.github/CONTRIBUTING.md b/{{cookiecutter.app_name}}/.github/CONTRIBUTING.md deleted file mode 100644 index 47a9b1a..0000000 --- a/{{cookiecutter.app_name}}/.github/CONTRIBUTING.md +++ /dev/null @@ -1,84 +0,0 @@ -# How to contribute - -We are really glad you're reading this, because we need volunteer developers to help this project come to fruition. - -When contributing to this repository, please first discuss the change you wish to make via issue, -email, slack or any other method with the owners of this repository before making a change. - -Please note we have a code of conduct, please follow it in all your interactions with the project. - -## Pull request process - -- Ensure you have performed a self-review of your changes -- Ensure the new changes generate no new warnings -- Ensure you have added tests that prove the fix is effective or that the feature works -- Ensure new and existing unit tests pass locally with my changes -- Ensure your commit message should follow [conventional commit message guidelines](https://www.conventionalcommits.org/en/v1.0.0/) - -## Code of Conduct - -### Our pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -### Our standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -### Our responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -### Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -### Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [INSERT EMAIL ADDRESS]. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - diff --git a/{{cookiecutter.app_name}}/.github/dependabot.yml b/{{cookiecutter.app_name}}/.github/dependabot.yml index 48ac98b..c51d0f0 100644 --- a/{{cookiecutter.app_name}}/.github/dependabot.yml +++ b/{{cookiecutter.app_name}}/.github/dependabot.yml @@ -6,5 +6,5 @@ updates: interval: daily time: "23:30" open-pull-requests-limit: 10 - reviewers: + assignees: - paul58914080 diff --git a/{{cookiecutter.app_name}}/.github/workflows/build_workflow.yml b/{{cookiecutter.app_name}}/.github/workflows/build_workflow.yml index 5c70b70..7ef2fa3 100644 --- a/{{cookiecutter.app_name}}/.github/workflows/build_workflow.yml +++ b/{{cookiecutter.app_name}}/.github/workflows/build_workflow.yml @@ -7,18 +7,23 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: temurin java-version: 21 - - name: Cache Maven packages - uses: actions/cache@v2 + - name: Cache Maven dependencies + uses: actions/cache@v3 with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- - name: Build with Maven run: mvn clean install -ntp - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/{{cookiecutter.app_name}}/.mvn/jvm.config b/{{cookiecutter.app_name}}/.mvn/jvm.config new file mode 100644 index 0000000..e2a50e0 --- /dev/null +++ b/{{cookiecutter.app_name}}/.mvn/jvm.config @@ -0,0 +1 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ No newline at end of file diff --git a/{{cookiecutter.app_name}}/README.md b/{{cookiecutter.app_name}}/README.md index 1bae51e..f5abc15 100644 --- a/{{cookiecutter.app_name}}/README.md +++ b/{{cookiecutter.app_name}}/README.md @@ -5,13 +5,13 @@ - maven >= 3.8.6 - open jdk 21 -## How to build ? +## How to build? ``` mvn clean install ``` -### How to build a docker image ? +### How to build a docker image? ``` cd bootstrap && mvn compile jib:dockerBuild @@ -24,3 +24,29 @@ cd bootstrap && mvn compile jib:dockerBuild ``` cd bootstrap && mvn spring-boot:run ``` + +## Formatting + +This project uses [git-code-format-maven-plugin](https://github.com/Cosium/git-code-format-maven-plugin) for formatting +the code per [google style guide](https://google.github.io/styleguide/javaguide.html) + +### How to format ? + +`mvn git-code-format:format-code` + +## Validating + +This project +uses [githook-maven-plugin](https://mvnrepository.com/artifact/io.github.phillipuniverse/githook-maven-plugin) which is +a maven plugin to configure and install local git hooks by running set of commands during build. + +### Command to validate formatted code + +`mvn git-code-format:validate-code-format` + +## Contribution guidelines + +We are really glad you're reading this, because we need volunteer developers to help this project come to fruition. + +Request you to please read +our [contribution guidelines](https://devs-from-matrix.github.io/basic-template-repository/#/README?id=contribution-guidelines) diff --git a/{{cookiecutter.app_name}}/acceptance-test/pom.xml b/{{cookiecutter.app_name}}/acceptance-test/pom.xml index 48cb596..2a66933 100644 --- a/{{cookiecutter.app_name}}/acceptance-test/pom.xml +++ b/{{cookiecutter.app_name}}/acceptance-test/pom.xml @@ -113,4 +113,32 @@ + + + mutation-test + + + mutation.tests + + + + + + org.pitest + pitest-maven + ${pitest-maven-plugin.version} + + + aggregate-pitest-reports + verify + + report-aggregate + + + + + + + + diff --git a/{{cookiecutter.app_name}}/domain-api/src/main/java/{{cookiecutter.package_name}}/domain/port/Request{{cookiecutter.domain_capitalized}}.java b/{{cookiecutter.app_name}}/domain-api/src/main/java/{{cookiecutter.package_name}}/domain/port/Request{{cookiecutter.domain_capitalized}}.java index 04c051f..3c385e0 100644 --- a/{{cookiecutter.app_name}}/domain-api/src/main/java/{{cookiecutter.package_name}}/domain/port/Request{{cookiecutter.domain_capitalized}}.java +++ b/{{cookiecutter.app_name}}/domain-api/src/main/java/{{cookiecutter.package_name}}/domain/port/Request{{cookiecutter.domain_capitalized}}.java @@ -6,7 +6,7 @@ public interface Request{{cookiecutter.domain_capitalized}} { - List<{{cookiecutter.domain_capitalized}}> get{{cookiecutter.domain_capitalized}}s(); + List<{{cookiecutter.domain_capitalized}}> get{{cookiecutter.domain_plural_capitalized}}(); {{cookiecutter.domain_capitalized}} get{{cookiecutter.domain_capitalized}}ByCode(@NonNull Long code); } diff --git a/{{cookiecutter.app_name}}/pom.xml b/{{cookiecutter.app_name}}/pom.xml index d75f32a..8fda6fc 100644 --- a/{{cookiecutter.app_name}}/pom.xml +++ b/{{cookiecutter.app_name}}/pom.xml @@ -71,11 +71,6 @@ ${project.version} - - net.lbruun.springboot - preliquibase-spring-boot-starter - ${pre-liquibase.version} - org.junit junit-bom @@ -90,6 +85,22 @@ pom import + + com.opentable.components + otj-pg-embedded + ${otj-pg-embedded.version} + test + + + net.lbruun.springboot + preliquibase-spring-boot-starter + ${pre-liquibase.version} + + + io.github.classgraph + classgraph + ${classgraph.version} + @@ -201,6 +212,60 @@ + + com.cosium.code + git-code-format-maven-plugin + ${git-code-format-maven-plugin.version} + + + + install-formatter-hook + + install-hooks + + + + + validate-code-format + + validate-code-format + + + + + + + com.cosium.code + google-java-format + ${git-code-format-maven-plugin.version} + + + + + io.github.phillipuniverse + githook-maven-plugin + ${githook-maven-plugin.version} + + + + install + + + + + echo "Validating..." + exec mvn test + echo "Formatting code..." + exec mvn git-code-format:format-code + echo "Validating format..." + exec mvn git-code-format:validate-code-format + + + + + + @@ -235,4 +300,46 @@ + + + mutation-test + + mutation.tests + + + + + org.pitest + pitest-maven + ${pitest-maven-plugin.version} + + + test + + mutationCoverage + + + + + + org.slf4j + + true + + XML + HTML + + + + + org.pitest + pitest-junit5-plugin + ${pitest-junit5-plugin.version} + + + + + + + diff --git a/{{cookiecutter.app_name}}/rest-adapter/pom.xml b/{{cookiecutter.app_name}}/rest-adapter/pom.xml index 02bd4b8..6ec1f11 100644 --- a/{{cookiecutter.app_name}}/rest-adapter/pom.xml +++ b/{{cookiecutter.app_name}}/rest-adapter/pom.xml @@ -41,7 +41,7 @@ org.openapitools openapi-generator-maven-plugin - 7.9.0 + 7.14.0 generate-open-api-source diff --git a/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}ResourceTest.java b/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}ResourceTest.java index 65d7a06..729a5c9 100644 --- a/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}ResourceTest.java +++ b/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}ResourceTest.java @@ -19,6 +19,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import {{cookiecutter.package_name}}.domain.exception.{{cookiecutter.domain_capitalized}}NotFoundException; import {{cookiecutter.package_name}}.domain.model.{{cookiecutter.domain_capitalized}}; import {{cookiecutter.package_name}}.domain.port.Request{{cookiecutter.domain_capitalized}}; @@ -34,7 +35,7 @@ public class {{cookiecutter.domain_capitalized}}ResourceTest { private static final String API_URI = "/api/v1/{{cookiecutter.domain_plural}}"; @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; - @Autowired private Request{{cookiecutter.domain_capitalized}} request{{cookiecutter.domain_capitalized}}; + @MockitoBean private Request{{cookiecutter.domain_capitalized}} request{{cookiecutter.domain_capitalized}}; @Test @DisplayName("should start the rest adapter application") diff --git a/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}RestAdapterApplication.java b/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}RestAdapterApplication.java index 6528c9b..5735ff2 100644 --- a/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}RestAdapterApplication.java +++ b/{{cookiecutter.app_name}}/rest-adapter/src/test/java/{{cookiecutter.package_name}}/rest/{{cookiecutter.domain_capitalized}}RestAdapterApplication.java @@ -2,16 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.ComponentScan; -import {{cookiecutter.package_name}}.domain.port.Request{{cookiecutter.domain_capitalized}}; @SpringBootApplication @ComponentScan(basePackages = "{{cookiecutter.package_name}}") public class {{cookiecutter.domain_capitalized}}RestAdapterApplication { - @MockBean private Request{{cookiecutter.domain_capitalized}} request{{cookiecutter.domain_capitalized}}; - public static void main(String[] args) { SpringApplication.run({{cookiecutter.domain_capitalized}}RestAdapterApplication.class, args); } From 26d441727b8c8ff39455d47d972a800de03e8e0c Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:15:48 +0530 Subject: [PATCH 02/11] ci: add generate-from-example-cookiecutter-template workflow --- ...ecutter-template-from-example-project.yaml | 26 +++++++++++++++++++ ...kiecutter-template-from-example-project.sh | 0 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/generate-cookiecutter-template-from-example-project.yaml rename scaffold-code-from-example.sh => generate-cookiecutter-template-from-example-project.sh (100%) diff --git a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml new file mode 100644 index 0000000..2b5ce73 --- /dev/null +++ b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml @@ -0,0 +1,26 @@ +name: Generate cookiecutter template from example project +on: + pull_request: + branches: + - main + paths: + - 'example' + workflow_dispatch: +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Make script executable + run: chmod +x ./generate-cookiecutter-template-from-example-project.sh + - name: Generate cookiecutter template + run: ./generate-cookiecutter-template-from-example-project.sh + - name: Is there is difference in the generated template? + run: | + set -e + DIFF_OUTPUT=$(diff -qr example-tryout {{cookiecutter.app_name}} --exclude='.DS_Store' || true) + if [ -n "$DIFF_OUTPUT" ]; then + echo "$DIFF_OUTPUT" + echo "$DIFF_OUTPUT" > diff-output.txt + exit 1 + fi diff --git a/scaffold-code-from-example.sh b/generate-cookiecutter-template-from-example-project.sh similarity index 100% rename from scaffold-code-from-example.sh rename to generate-cookiecutter-template-from-example-project.sh From 7a42db5e4617b4102d47ba3d034e144f7fc91544 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:17:00 +0530 Subject: [PATCH 03/11] ci(scaffold-from-template-target-project): update name n add path --- ...d.yml => scaffold-from-template-target-project.yml} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename .github/workflows/{scaffold.yml => scaffold-from-template-target-project.yml} (87%) diff --git a/.github/workflows/scaffold.yml b/.github/workflows/scaffold-from-template-target-project.yml similarity index 87% rename from .github/workflows/scaffold.yml rename to .github/workflows/scaffold-from-template-target-project.yml index d24f0ce..f16988c 100644 --- a/.github/workflows/scaffold.yml +++ b/.github/workflows/scaffold-from-template-target-project.yml @@ -1,9 +1,13 @@ -name: Generate +name: Scaffold code from template on: pull_request: - branches: [ main ] + branches: + - main + paths: + - '{{cookiecutter.app_name}}' + workflow_dispatch: jobs: - generate: + scaffold: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 67331a3dc2b84a6acab58eee4763aec9e3d8f552 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:20:33 +0530 Subject: [PATCH 04/11] ci: update path for trigger --- .../generate-cookiecutter-template-from-example-project.yaml | 2 +- .github/workflows/scaffold-from-template-target-project.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml index 2b5ce73..da2f166 100644 --- a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml +++ b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml @@ -4,7 +4,7 @@ on: branches: - main paths: - - 'example' + - 'example/**' workflow_dispatch: jobs: generate: diff --git a/.github/workflows/scaffold-from-template-target-project.yml b/.github/workflows/scaffold-from-template-target-project.yml index f16988c..866b4c3 100644 --- a/.github/workflows/scaffold-from-template-target-project.yml +++ b/.github/workflows/scaffold-from-template-target-project.yml @@ -4,7 +4,7 @@ on: branches: - main paths: - - '{{cookiecutter.app_name}}' + - '{{cookiecutter.app_name}}/**' workflow_dispatch: jobs: scaffold: From 7a4c9d94c772afc661ba41700793f29f795845f4 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:27:01 +0530 Subject: [PATCH 05/11] ci: show only diff --- ...nerate-cookiecutter-template-from-example-project.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml index da2f166..9134376 100644 --- a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml +++ b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml @@ -17,10 +17,4 @@ jobs: run: ./generate-cookiecutter-template-from-example-project.sh - name: Is there is difference in the generated template? run: | - set -e - DIFF_OUTPUT=$(diff -qr example-tryout {{cookiecutter.app_name}} --exclude='.DS_Store' || true) - if [ -n "$DIFF_OUTPUT" ]; then - echo "$DIFF_OUTPUT" - echo "$DIFF_OUTPUT" > diff-output.txt - exit 1 - fi + diff -qr example-tryout {{cookiecutter.app_name}} --exclude='.DS_Store' From 30142b8c4b18b73188158191de847951c54f3340 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:43:27 +0530 Subject: [PATCH 06/11] ci(generate-cookiecutter-template-from-example-project): enlist files --- generate-cookiecutter-template-from-example-project.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generate-cookiecutter-template-from-example-project.sh b/generate-cookiecutter-template-from-example-project.sh index b20f2fb..239dd80 100644 --- a/generate-cookiecutter-template-from-example-project.sh +++ b/generate-cookiecutter-template-from-example-project.sh @@ -9,7 +9,8 @@ fi mkdir "example-tryout" cp -r ./example/ ./example-tryout cd ./example-tryout - +pwd +ls -lrt # sets the locale for all commands run in the current shell session to the "C" locale, which is the default POSIX locale. This makes programs like sed and find treat files as raw bytes, ignoring any character encoding issues. It helps avoid errors like illegal byte sequence when processing files with mixed or unknown encodings. export LC_ALL=C From 496802ffd014d7c9e3ce2b23a95aae9f345cd82c Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:48:09 +0530 Subject: [PATCH 07/11] ci(generate-cookiecutter-template-from-example-project): change from cp to rsync --- generate-cookiecutter-template-from-example-project.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate-cookiecutter-template-from-example-project.sh b/generate-cookiecutter-template-from-example-project.sh index 239dd80..147f364 100644 --- a/generate-cookiecutter-template-from-example-project.sh +++ b/generate-cookiecutter-template-from-example-project.sh @@ -7,7 +7,7 @@ if [ -d "example-tryout" ]; then fi # Create a new example-tryout directory and copy the example project into it mkdir "example-tryout" -cp -r ./example/ ./example-tryout +rsync -a ./example/ ./example-tryout/ cd ./example-tryout pwd ls -lrt From 626009cd2f2a0b3a3b19a8d599e7febbcd2bff71 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:49:37 +0530 Subject: [PATCH 08/11] ci(generate-cookiecutter-template-from-example-project): fail the build if there is a diff --- ...nerate-cookiecutter-template-from-example-project.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml index 9134376..da2f166 100644 --- a/.github/workflows/generate-cookiecutter-template-from-example-project.yaml +++ b/.github/workflows/generate-cookiecutter-template-from-example-project.yaml @@ -17,4 +17,10 @@ jobs: run: ./generate-cookiecutter-template-from-example-project.sh - name: Is there is difference in the generated template? run: | - diff -qr example-tryout {{cookiecutter.app_name}} --exclude='.DS_Store' + set -e + DIFF_OUTPUT=$(diff -qr example-tryout {{cookiecutter.app_name}} --exclude='.DS_Store' || true) + if [ -n "$DIFF_OUTPUT" ]; then + echo "$DIFF_OUTPUT" + echo "$DIFF_OUTPUT" > diff-output.txt + exit 1 + fi From 2278b81350f836effa9f5116bfeb00ddd4264e5c Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 11:59:46 +0530 Subject: [PATCH 09/11] ci(generate-cookiecutter-template-from-example-project): remove redundant code --- generate-cookiecutter-template-from-example-project.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/generate-cookiecutter-template-from-example-project.sh b/generate-cookiecutter-template-from-example-project.sh index 147f364..d7fd0cf 100644 --- a/generate-cookiecutter-template-from-example-project.sh +++ b/generate-cookiecutter-template-from-example-project.sh @@ -17,7 +17,6 @@ export LC_ALL=C find . -type f -exec sed -e s/Examples/{{cookiecutter.domain_plural_capitalized}}/g -i '' '{}' ';' find . -depth -name '*Examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Examples/\1{{cookiecutter.domain_plural_capitalized}}/')"; done find . -type f -exec sed -e s/examples/{{cookiecutter.domain_plural}}/g -i '' '{}' ';' -find . -type f -exec sed -e s/examples/{{cookiecutter.domain_plural}}/g -i '' '{}' ';' find . -depth -name '*examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)examples/\1{{cookiecutter.domain_plural}}/')"; done find . -type f -exec sed -e s/Example/{{cookiecutter.domain_capitalized}}/g -i '' '{}' ';' find . -depth -name '*Example*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Example/\1{{cookiecutter.domain_capitalized}}/')"; done From 9f7867a704d2eeae58fbe2bc0c9b85f014ed3ec5 Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 12:00:01 +0530 Subject: [PATCH 10/11] build: remove unwanted dependency --- example/pom.xml | 5 ----- {{cookiecutter.app_name}}/pom.xml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/example/pom.xml b/example/pom.xml index ad5cec8..67535c3 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -96,11 +96,6 @@ preliquibase-spring-boot-starter ${pre-liquibase.version} - - io.github.classgraph - classgraph - ${classgraph.version} - diff --git a/{{cookiecutter.app_name}}/pom.xml b/{{cookiecutter.app_name}}/pom.xml index 8fda6fc..6ac0e8d 100644 --- a/{{cookiecutter.app_name}}/pom.xml +++ b/{{cookiecutter.app_name}}/pom.xml @@ -96,11 +96,6 @@ preliquibase-spring-boot-starter ${pre-liquibase.version} - - io.github.classgraph - classgraph - ${classgraph.version} - From 429043396c2326a3d6f48ad5eb2bfbd9b70cecdd Mon Sep 17 00:00:00 2001 From: Paul Williams Date: Sun, 3 Aug 2025 12:10:41 +0530 Subject: [PATCH 11/11] ci(generate-cookiecutter-template-from-example-project): portable sed command --- ...kiecutter-template-from-example-project.sh | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/generate-cookiecutter-template-from-example-project.sh b/generate-cookiecutter-template-from-example-project.sh index d7fd0cf..51538f3 100644 --- a/generate-cookiecutter-template-from-example-project.sh +++ b/generate-cookiecutter-template-from-example-project.sh @@ -9,23 +9,29 @@ fi mkdir "example-tryout" rsync -a ./example/ ./example-tryout/ cd ./example-tryout -pwd -ls -lrt + # sets the locale for all commands run in the current shell session to the "C" locale, which is the default POSIX locale. This makes programs like sed and find treat files as raw bytes, ignoring any character encoding issues. It helps avoid errors like illegal byte sequence when processing files with mixed or unknown encodings. export LC_ALL=C -find . -type f -exec sed -e s/Examples/{{cookiecutter.domain_plural_capitalized}}/g -i '' '{}' ';' +# Detect OS and set sed inline flag +if [[ "$(uname)" == "Darwin" ]]; then + SED_INPLACE=(-i '') +else + SED_INPLACE=(-i) +fi + +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/Examples/{{cookiecutter.domain_plural_capitalized}}/g '{}' ';' find . -depth -name '*Examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Examples/\1{{cookiecutter.domain_plural_capitalized}}/')"; done -find . -type f -exec sed -e s/examples/{{cookiecutter.domain_plural}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/examples/{{cookiecutter.domain_plural}}/g '{}' ';' find . -depth -name '*examples*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)examples/\1{{cookiecutter.domain_plural}}/')"; done -find . -type f -exec sed -e s/Example/{{cookiecutter.domain_capitalized}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/Example/{{cookiecutter.domain_capitalized}}/g '{}' ';' find . -depth -name '*Example*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)Example/\1{{cookiecutter.domain_capitalized}}/')"; done -find . -type f -exec sed -e s/example/{{cookiecutter.domain}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/example/{{cookiecutter.domain}}/g '{}' ';' find . -depth -name '*example*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)example/\1{{cookiecutter.domain}}/')"; done -find . -type f -exec sed -e s/packagename/{{cookiecutter.package_name}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/packagename/{{cookiecutter.package_name}}/g '{}' ';' find . -depth -name '*packagename*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E 's/(.*)packagename/\1{{cookiecutter.package_name}}/')"; done -find . -type f -exec sed -e s/artifactName/{{cookiecutter.artifact_id}}/g -i '' '{}' ';' -find . -type f -exec sed -e s/group-id/{{cookiecutter.group_id}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/artifactName/{{cookiecutter.artifact_id}}/g '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/group-id/{{cookiecutter.group_id}}/g '{}' ';' ## For the following, we need to replace EXAMPLES and EXAMPLE with the domain name -find . -type f -exec sed -e s/EXAMPLES/{{cookiecutter.domain_plural_uppercase}}/g -i '' '{}' ';' -find . -type f -exec sed -e s/EXAMPLE/{{cookiecutter.domain_uppercase}}/g -i '' '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/EXAMPLES/{{cookiecutter.domain_plural_uppercase}}/g '{}' ';' +find . -type f -exec sed "${SED_INPLACE[@]}" -e s/EXAMPLE/{{cookiecutter.domain_uppercase}}/g '{}' ';'