From 9ea0f18b7f416d749623e329e7708610900afba3 Mon Sep 17 00:00:00 2001 From: Ivan Garcia Sainz-Aja Date: Sun, 28 Jan 2024 21:16:53 +0100 Subject: [PATCH] e2e: customer-address-relational.zdl --- .../TestCustomerAddressRelationalProject.java | 140 +++++++++++ .../java/io/zenwave360/sdk/e2e/TextUtils.java | 22 ++ .../customer-address-relational.zdl | 127 ++++++++++ .../customer-address-relational/pom.xml | 235 ++++++++++++++++++ .../src/main/docker/docker-compose.yml | 16 ++ .../io/zenwave360/example/Application.java | 13 + .../example/config/DatabaseConfiguration.java | 84 +++++++ .../example/config/SecurityConfiguration.java | 49 ++++ .../config/SpringSecurityAuditorAware.java | 44 ++++ .../src/main/resources/application-local.yml | 1 + .../src/main/resources/application.yml | 63 +++++ .../src/test/resources/application-test.yml | 1 + .../src/test/resources/karate-auth.js | 19 ++ .../src/test/resources/karate-config.js | 10 + .../models/customers.zdl | 1 + .../models/delivery.zdl | 1 + .../models/orders.zdl | 1 + .../models/restaurants.zdl | 1 + plugins/backend-application-default/README.md | 2 +- .../main/java/core/domain/jpa/Entity.java.hbs | 4 +- .../implementation/partials/jpaMethodBody.hbs | 2 +- plugins/zdl-to-asyncapi/README.md | 26 +- plugins/zdl-to-openapi/README.md | 6 +- .../sdk/plugins/PathsProcessor.java | 7 + .../ZDLToOpenAPI.yml.hbs | 5 + .../templating/CustomHandlebarsHelpers.java | 7 + 26 files changed, 856 insertions(+), 31 deletions(-) create mode 100644 e2e/src/test/java/io/zenwave360/sdk/e2e/TestCustomerAddressRelationalProject.java create mode 100644 e2e/src/test/java/io/zenwave360/sdk/e2e/TextUtils.java create mode 100644 e2e/src/test/resources/projects/customer-address-relational/customer-address-relational.zdl create mode 100644 e2e/src/test/resources/projects/customer-address-relational/pom.xml create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/docker/docker-compose.yml create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/Application.java create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application-local.yml create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application.yml create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/test/resources/application-test.yml create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-auth.js create mode 100644 e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-config.js diff --git a/e2e/src/test/java/io/zenwave360/sdk/e2e/TestCustomerAddressRelationalProject.java b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestCustomerAddressRelationalProject.java new file mode 100644 index 00000000..b8d1af31 --- /dev/null +++ b/e2e/src/test/java/io/zenwave360/sdk/e2e/TestCustomerAddressRelationalProject.java @@ -0,0 +1,140 @@ +package io.zenwave360.sdk.e2e; + +import java.io.File; + +import io.zenwave360.sdk.options.DatabaseType; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.*; + +import io.zenwave360.sdk.MainGenerator; +import io.zenwave360.sdk.Plugin; +import io.zenwave360.sdk.options.PersistenceType; +import io.zenwave360.sdk.options.ProgrammingStyle; +import io.zenwave360.sdk.plugins.BackendApplicationDefaultPlugin; +import io.zenwave360.sdk.plugins.OpenAPIControllersPlugin; +import io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin; +import io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin; +import io.zenwave360.sdk.testutils.MavenCompiler; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class TestCustomerAddressRelationalProject { + + private static String project = "customer-address-relational"; + private static String sourceFolder = "src/test/resources/projects/" + project + "/"; + private static String targetFolder = "target/projects/" + project + "/"; + private String basePackage = "io.zenwave360.example"; + private String zdlFile = targetFolder + "/customer-address-relational.zdl"; + + @BeforeAll + public static void beforeAll() throws Exception { + // copy whole dir from sourceFolder to targetFolder + FileUtils.deleteDirectory(new File(targetFolder)); + FileUtils.forceMkdir(new File(targetFolder)); + FileUtils.copyDirectory(new File(sourceFolder), new File(targetFolder)); + Assertions.assertTrue(new File(targetFolder).exists()); + } + +// @Test + public void test() throws Exception { + Plugin plugin = new BackendApplicationDefaultPlugin() + .withSpecFile(zdlFile) + .withTargetFolder(targetFolder) + .withOption("basePackage", basePackage) + .withOption("persistence", PersistenceType.jpa) + .withOption("databaseType", DatabaseType.mariadb) + .withOption("style", ProgrammingStyle.imperative) + .withOption("useLombok", true) + .withOption("includeEmitEventsImplementation", true) + .withOption("forceOverwrite", true) + .withOption("haltOnFailFormatting", false); + + + new MainGenerator().generate(plugin); + // int exitCode = MavenCompiler.compile(new File(targetFolder)); + // Assertions.assertEquals(0, exitCode); + } + + @Order(1) + @Test + public void generateApis() throws Exception { + Plugin plugin = null; + + plugin = new ZDLToOpenAPIPlugin() + .withSpecFile(zdlFile) + .withOption("idType", "integer") + .withOption("idTypeFormat", "int64") + .withOption("targetFile", "/src/main/resources/apis/openapi.yml") + .withTargetFolder(targetFolder); + new MainGenerator().generate(plugin); + + var replace = " - name: \"identifier\"\n" + + " in: path\n" + + " required: true\n" + + " schema:\n" + + " type: integer\n" + + " format: int64"; + var replacement = " - name: \"identifier\"\n" + + " in: path\n" + + " required: true\n" + + " schema:\n" + + " type: string"; + TextUtils.replaceInFile(new File(targetFolder + "/src/main/resources/apis/openapi.yml"), replace, replacement); + + plugin = new ZDLToAsyncAPIPlugin() + .withSpecFile(zdlFile) + .withOption("asyncapiVersion", "v3") + .withOption("idType", "integer") + .withOption("idTypeFormat", "int64") + .withOption("targetFile", "/src/main/resources/apis/asyncapi.yml") + .withTargetFolder(targetFolder); + new MainGenerator().generate(plugin); + } + + @Order(2) + @Test + public void generateSourceFromAPIs() throws Exception { + int exitCode = MavenCompiler.compile(new File(targetFolder)); + Assertions.assertEquals(0, exitCode); + } + + @Order(3) + @Test + public void generateModule() throws Exception { + Plugin plugin = null; + int exitCode = 0; + + plugin = new BackendApplicationDefaultPlugin() + .withSpecFile(zdlFile) + .withTargetFolder(targetFolder) + .withOption("basePackage", basePackage) + .withOption("persistence", PersistenceType.jpa) + .withOption("databaseType", DatabaseType.mariadb) + .withOption("style", ProgrammingStyle.imperative) + .withOption("useLombok", true) + .withOption("includeEmitEventsImplementation", true) + .withOption("forceOverwrite", true) + .withOption("haltOnFailFormatting", false); + + new MainGenerator().generate(plugin); + + exitCode = MavenCompiler.compile(new File(targetFolder)); + Assertions.assertEquals(0, exitCode); + + plugin = new OpenAPIControllersPlugin() + .withSpecFile(targetFolder + "/src/main/resources/apis/openapi.yml") + .withOption("zdlFile", zdlFile) + .withOption("basePackage", basePackage) + .withOption("controllersPackage", "{{basePackage}}.adapters.web") + .withOption("openApiApiPackage", "{{basePackage}}.adapters.web") + .withOption("openApiModelPackage", "{{basePackage}}.adapters.web.model") + .withOption("openApiModelNameSuffix", "DTO") + // .withOption("operationIds", List.of("addPet", "updatePet")) + .withOption("style", ProgrammingStyle.imperative) + .withTargetFolder(targetFolder); + + new MainGenerator().generate(plugin); + exitCode = MavenCompiler.compile(new File(targetFolder)); + Assertions.assertEquals(0, exitCode); + } + +} diff --git a/e2e/src/test/java/io/zenwave360/sdk/e2e/TextUtils.java b/e2e/src/test/java/io/zenwave360/sdk/e2e/TextUtils.java new file mode 100644 index 00000000..fed17e60 --- /dev/null +++ b/e2e/src/test/java/io/zenwave360/sdk/e2e/TextUtils.java @@ -0,0 +1,22 @@ +package io.zenwave360.sdk.e2e; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; + +public class TextUtils { + + public static void replaceInFile(File file, String regex, String replacement) throws IOException { + String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + content = Pattern.compile(fixMultilineRegex(regex), Pattern.MULTILINE).matcher(content).replaceAll(replacement); + FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8); + } + + public static String fixMultilineRegex(String text) { + return StringUtils.replace(text, "\r\n", "\\n").replace("\n", "\\r?\\n"); + } +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/customer-address-relational.zdl b/e2e/src/test/resources/projects/customer-address-relational/customer-address-relational.zdl new file mode 100644 index 00000000..369b8e00 --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/customer-address-relational.zdl @@ -0,0 +1,127 @@ +/** + * Simple Event-Driven CRUD for Customer/Addresses. + */ +config { + title "ZenWave Playground Customer-Address MariaDB" + basePackage "io.zenwave360.example" + persistence jpa + databaseType mariadb +// haltOnFailFormatting false + + plugins { + + ZDLToOpenAPIPlugin { + idType integer + idTypeFormat int64 + targetFile "src/main/resources/apis/openapi.yml" + } + + ZDLToAsyncAPIPlugin { + asyncapiVersion v3 + idType integer + idTypeFormat int64 + targetFile "src/main/resources/apis/asyncapi.yml" + } + + BackendApplicationDefaultPlugin { + useLombok true + includeEmitEventsImplementation true + // --force // overwite all files + } + + OpenAPIControllersPlugin { + formatter google // comments in one line are better for demos + specFile "src/main/resources/apis/openapi.yml" + zdlFile "customer-address-relational.zdl" + + // thse should match the values of openapi-generator-maven-plugin + openApiApiPackage "{{basePackage}}.adapters.web" + openApiModelPackage "{{basePackage}}.adapters.web.model" + openApiModelNameSuffix DTO + } + } +} + + +// == Entities ============================= +/** +* Customer javadoc comment +*/ +@aggregate +entity Customer { + username String required unique /** username javadoc comment */ + email String required unique /** email javadoc comment */ +} + +@aggregate +entity Address { + street String required /** street javadoc comment */ + city String /** city javadoc comment */ + state String /** state javadoc comment */ + zip String /** zip javadoc comment */ + type AddressType required /** address type is an enum */ +} + +enum AddressType { HOME(1) /** home description */, WORK(1) /** work description */ } + +relationship ManyToOne { + Address{customer} to Customer +} + + +// == Services ============================= + +@inline +input AddressInput { + identifier String required /** Description identifier for this Address */ + address Address +} + +/** + Service javadoc comment + */ +@rest("/customers") +service CustomerService for (Customer) { + /** + * Create customer javadoc comment + */ + @post + createCustomer(Customer) Customer withEvents CustomerEvent + + @put("/{customerId}") + updateCustomer(id, Customer) Customer? withEvents CustomerEvent + + /** Updates a the customer address identified by address.identifier */ + @put("/{customerId}/address/{identifier}") + updateCustomerAddress(id, AddressInput) Customer? withEvents CustomerEvent CustomerAddressUpdated + + @delete("/{customerId}") + deleteCustomer(id) withEvents CustomerEvent + + @get("/{customerId}") + getCustomer(id) Customer? + + @get({params: {search: "string"}}) + @paginated + listCustomers() Customer[] +} + +// == Events ============================= + +@skip // skip generating this domain enum, it will genereate by asyncapi code generator. +enum EventType { CREATED(1) /** created description */, UPDATED(1) /** updated description */, DELETED(1) /** deleted description */ } + +@asyncapi({channel: "CustomerEventsChannel", topic: "customer.events"}) +event CustomerEvent { + customerId String + eventType EventType + customer Customer +} + +@asyncapi({channel: "CustomerAddressEventsChannel", topic: "customer.address-events"}) +event CustomerAddressUpdated { + customerId String + addressDescription String + originalAddress Address + newAddress Address +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/pom.xml b/e2e/src/test/resources/projects/customer-address-relational/pom.xml new file mode 100644 index 00000000..5e4fcc23 --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/pom.xml @@ -0,0 +1,235 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.4 + + + io.zenwave360.example + zenwave-playground + 0.0.1-SNAPSHOT + ${project.groupId}:${project.artifactId} + ZenWave-SDK Examples Playground + + 17 + ${project.parent.version} + 2022.0.4 + + 1.4.0-SNAPSHOT + + 3.0.2 + 1.5.3.Final + 1.0.1 + 2.2.1.RELEASE + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.cloud + spring-cloud-starter-stream-kafka + + + org.springframework.cloud + spring-cloud-stream-schema + ${spring-cloud-stream-schema.version} + + + + org.projectlombok + lombok + true + + + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.apache.commons + commons-lang3 + + + + org.mariadb.jdbc + mariadb-java-client + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + com.tngtech.archunit + archunit-junit5-api + ${archunit-junit5.version} + test + + + + + com.tngtech.archunit + archunit-junit5-engine + ${archunit-junit5.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.openapitools + openapi-generator-maven-plugin + 6.6.0 + + + + generate + + generate-sources + + ${project.basedir}/src/main/resources/apis/openapi.yml + spring + io.zenwave360.example.adapters.web + io.zenwave360.example.adapters.web.model + DTO + true + + ApiUtil.java + + + Double=java.math.BigDecimal + + + true + none + false + true + true + true + false + + + + + + + io.github.zenwave360.zenwave-sdk + zenwave-sdk-maven-plugin + ${zenwave.version} + + ${pom.basedir}/src/main/resources/apis/asyncapi.yml + false + true + true + + + + + generate-asyncapi-dtos + generate-sources + + generate + + + jsonschema2pojo + + io.zenwave360.example.core.domain.events + + true + true + + + + + + generate-asyncapi + generate-sources + + generate + + + spring-cloud-streams3 + + provider + + + io.zenwave360.example.core.domain.events + io.zenwave360.example.core.outbound.events + io.zenwave360.example.adapters.commands + + + + + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-spring-cloud-streams3 + ${zenwave.version} + + + io.github.zenwave360.zenwave-sdk.plugins + asyncapi-jsonschema2pojo + ${zenwave.version} + + + + + + + diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/docker/docker-compose.yml b/e2e/src/test/resources/projects/customer-address-relational/src/main/docker/docker-compose.yml new file mode 100644 index 00000000..9fcc2a6e --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/docker/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3.8' +services: + mariadb: + image: mariadb:10.7.1 + # volumes: + # - ~/volumes/mysql/:/var/lib/mysql/ + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=zenwave-playground + ports: + - 127.0.0.1:3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp + kafka: + image: bashj79/kafka-kraft + ports: + - '9092:9092' diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/Application.java b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/Application.java new file mode 100644 index 00000000..9e6f9c94 --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/Application.java @@ -0,0 +1,13 @@ +package io.zenwave360.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java new file mode 100644 index 00000000..b76c0f8e --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/DatabaseConfiguration.java @@ -0,0 +1,84 @@ +package io.zenwave360.example.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.SQLException; + + +@Configuration +@EnableJpaRepositories({ "io.zenwave360.example.core.outbound.jpa" }) +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); + + private final Environment env; + + public DatabaseConfiguration(Environment env) { + this.env = env; + } + + /** + * Open the TCP port for the H2 database, so it is available remotely. + * + * @return the H2 database TCP server. + * @throws SQLException if the server failed to start. + */ + @Bean(initMethod = "start", destroyMethod = "stop") + @Profile("dev") + public Object h2TCPServer() throws SQLException { + String port = getValidPortForH2(); + log.debug("H2 database is available on port {}", port); + return createServer(port); + } + + private Object createServer(String port) throws SQLException { + try { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Class serverClass = Class.forName("org.h2.tools.Server", true, loader); + Method createServer = serverClass.getMethod("createTcpServer", String[].class); + return createServer.invoke(null, new Object[]{new String[]{"-tcp", "-tcpAllowOthers", "-tcpPort", port}}); + + } catch (ClassNotFoundException | LinkageError e) { + throw new RuntimeException("Failed to load and initialize org.h2.tools.Server", e); + + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException("Failed to get method org.h2.tools.Server.createTcpServer()", e); + + } catch (IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException("Failed to invoke org.h2.tools.Server.createTcpServer()", e); + + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (t instanceof SQLException) { + throw (SQLException) t; + } + throw new RuntimeException("Unchecked exception in org.h2.tools.Server.createTcpServer()", t); + } + } + + private String getValidPortForH2() { + int port = Integer.parseInt(env.getProperty("server.port")); + if (port < 10000) { + port = 10000 + port; + } else { + if (port < 63536) { + port = port + 2000; + } else { + port = port - 2000; + } + } + return String.valueOf(port); + } +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java new file mode 100644 index 00000000..107ef960 --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SecurityConfiguration.java @@ -0,0 +1,49 @@ +package io.zenwave360.example.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfiguration { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf(CsrfConfigurer::disable) + .authorizeHttpRequests(auth -> + auth.anyRequest().authenticated() + ) + .cors(Customizer.withDefaults()) + .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) + .httpBasic(Customizer.withDefaults()); + // @formatter:on + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowCredentials(true); + configuration.addAllowedHeader("Authorization"); + configuration.setAllowedOrigins(List.of("*")); + configuration.setAllowedMethods(List.of("*")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java new file mode 100644 index 00000000..fceb72f2 --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/java/io/zenwave360/example/config/SpringSecurityAuditorAware.java @@ -0,0 +1,44 @@ +package io.zenwave360.example.config; + +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link AuditorAware} based on Spring Security. + */ +@Component("springSecurityAuditorAware") +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.of(getCurrentUserLogin().orElse("system")); + } + + /** + * Get the login of the current user. + * + * @return the login of the current user. + */ + public static Optional getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); + } + + private static String extractPrincipal(Authentication authentication) { + if (authentication == null) { + return null; + } else if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } else if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + } + +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application-local.yml b/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application-local.yml new file mode 100644 index 00000000..8cd70bcd --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application-local.yml @@ -0,0 +1 @@ +MONGODB_URI: mongodb://localhost:27017/customers diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application.yml b/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application.yml new file mode 100644 index 00000000..ab7b07fa --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/main/resources/application.yml @@ -0,0 +1,63 @@ +logging: + level: + io.zenwave360.example: DEBUG + org.springframework.security: DEBUG +# org.apache.kafka: DEBUG +spring: + security: + user: + name: user + password: password + roles: USER + jpa.hibernate.ddl-auto: update + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mariadb://localhost:3306/zenwave-playground?useLegacyDatetimeCode=false&serverTimezone=UTC + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + kafka: + bootstrap-servers: localhost:9092 + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + cloud: + stream: + function: + definition: do-create-customer;on-create-customer-customer-event;on-create-customer-customer-created;on-create-customer-customer-created-failed;on-update-customer-customer-event;on-update-customer-customer-updated;on-delete-customer-customer-deleted + bindings: + on-create-customer-customer-event-in-0: + destination: customer.events + content-type: application/json + on-create-customer-customer-created-out-0: + destination: customer.events + content-type: application/json + on-create-customer-customer-created-failed-out-0: + destination: customer.events + content-type: application/json + on-update-customer-customer-event-out-0: + destination: customer.events + content-type: application/json + on-update-customer-customer-updated-out-0: + destination: customer.events + content-type: application/json + on-delete-customer-customer-deleted-out-0: + destination: customer.events + content-type: application/json + do-create-customer-in-0: + destination: customer.requests + content-type: application/json + dead-letter-queue-error-map: > + { + 'jakarta.validation.ValidationException': 'do-create-customer-validation-error-out-0', + 'java.lang.Exception': 'do-create-customer-error-out-0' + } + diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/application-test.yml b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/application-test.yml new file mode 100644 index 00000000..8cd70bcd --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/application-test.yml @@ -0,0 +1 @@ +MONGODB_URI: mongodb://localhost:27017/customers diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-auth.js b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-auth.js new file mode 100644 index 00000000..ccd4865b --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-auth.js @@ -0,0 +1,19 @@ +// use this function to return a dictionary of authentication/session headers +// you can implement any login workflow including calling other karate features +// take this as an example for Basic authentication: https://github.com/intuit/karate#http-basic-authentication-example +function fn(auth) { + const credentials = karate.merge(auth || {}); + credentials.authMode = credentials.authMode || 'basic'; + // if empty read password from 'credentials-.yml' files + credentials.password = credentials.password || karate.get('credentials', {})[credentials.username]; + if (credentials.authMode === 'basic' && credentials.username && credentials.password) { + const Base64 = Java.type('java.util.Base64'); + const encoded = Base64.getEncoder().encodeToString((credentials.username + ':' + credentials.password).toString().getBytes('utf-8')); + return { + Authorization: 'Basic ' + encoded, + }; + } + return { + // Authorization: 'Bearer ...' + }; +} diff --git a/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-config.js b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-config.js new file mode 100644 index 00000000..b024d90d --- /dev/null +++ b/e2e/src/test/resources/projects/customer-address-relational/src/test/resources/karate-config.js @@ -0,0 +1,10 @@ +function fn() { + return { + baseUrl: "http://localhost:8080/api", + auth: { + username: "user", + password: "password", + authMode: "basic", + }, + }; +} diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/customers.zdl b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/customers.zdl index 4ee853bb..39d4295c 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/customers.zdl +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/customers.zdl @@ -24,6 +24,7 @@ config { BackendApplicationDefaultPlugin { useLombok true + includeEmitEventsImplementation true // --force // overwite all files } diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/delivery.zdl b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/delivery.zdl index 828c6aee..d2471f40 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/delivery.zdl +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/delivery.zdl @@ -23,6 +23,7 @@ config { BackendApplicationDefaultPlugin { useLombok true + includeEmitEventsImplementation true // --force // overwite all files } diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/orders.zdl b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/orders.zdl index 7e35b468..1584c7a5 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/orders.zdl +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/orders.zdl @@ -23,6 +23,7 @@ config { BackendApplicationDefaultPlugin { useLombok true + includeEmitEventsImplementation true // --force // overwite all files } diff --git a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/restaurants.zdl b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/restaurants.zdl index 4b89454f..b386120e 100644 --- a/e2e/src/test/resources/projects/online-food-delivery-mongo/models/restaurants.zdl +++ b/e2e/src/test/resources/projects/online-food-delivery-mongo/models/restaurants.zdl @@ -24,6 +24,7 @@ config { BackendApplicationDefaultPlugin { useLombok true + includeEmitEventsImplementation true // --force // overwite all files } diff --git a/plugins/backend-application-default/README.md b/plugins/backend-application-default/README.md index e592471c..08eff75c 100644 --- a/plugins/backend-application-default/README.md +++ b/plugins/backend-application-default/README.md @@ -1,4 +1,4 @@ -# JDL 2 Backend Application Generator +# ZDL Backend Application Default Plugin > 👉 ZenWave360 Helps You Create Software Easy to Understand Generates a full backend application using a flexible hexagonal architecture. diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs index 5b106e8a..3d5a0be5 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/domain/jpa/Entity.java.hbs @@ -67,11 +67,11 @@ public class {{entity.className}} {{addExtends entity}} implements Serializable {{#each entity.relationships as |relationship|}} {{#if relationship.fieldName~}} - {{#if (addRelationshipById relationship entity=entity)}} + {{#if (addRelationshipById relationship entity=entity)}} {{#if relationship.required}}@NotNull{{/if}} @Column(name = "{{snakeCase relationship.fieldName}}_id") private {{idJavaType}} {{relationship.fieldName}}Id; - {{~/if}} + {{~/if}} {{> (partial 'partials/' relationship.type)}} {{#unless (addRelationshipById relationship entity=entity)}}{{#if relationship.required}}@NotNull{{/if}}{{/unless}} private {{{relationshipFieldType relationship}}} {{relationship.fieldName}} {{{relationshipFieldTypeInitializer relationship}}}; diff --git a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpaMethodBody.hbs b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpaMethodBody.hbs index c986e7e8..0d61b404 100644 --- a/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpaMethodBody.hbs +++ b/plugins/backend-application-default/src/main/resources/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/implementation/partials/jpaMethodBody.hbs @@ -9,8 +9,8 @@ {{~else if (isCrudMethod 'update' method=method entity=entity )}} log.debug("Request to update {{entity.className}}: {}", input); var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id); - {{entity.instanceName}} = {{entity.instanceName}}.map(existing{{entity.instanceName}} -> {{asInstanceName service.name}}Mapper.update(existing{{entity.instanceName}}, {{{mapperInputCallSignature method.parameter}}})); // saving is unnecessary: https://vladmihalcea.com/best-spring-data-jparepository/ + {{entity.instanceName}} = {{entity.instanceName}}.map(existing{{entity.instanceName}} -> {{asInstanceName service.name}}Mapper.update(existing{{entity.instanceName}}, {{{mapperInputCallSignature method.parameter}}})); {{~> (partial 'withEvents')}} return {{wrapWithMapper entity}}; {{~else if (isCrudMethod 'list' method=method entity=entity )}} diff --git a/plugins/zdl-to-asyncapi/README.md b/plugins/zdl-to-asyncapi/README.md index e0b3c28e..37fc2560 100644 --- a/plugins/zdl-to-asyncapi/README.md +++ b/plugins/zdl-to-asyncapi/README.md @@ -1,4 +1,4 @@ -# JDL To OpenAPI Generator +# ZDL To AsyncAPI Generator > 👉 ZenWave360 Helps You Create Software that's Easy to Understand Generate OpenAPI definition from JDL entities: @@ -8,7 +8,7 @@ Generate OpenAPI definition from JDL entities: ```shell jbang zw -p io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin \ - specFile=src/main/resources/model/orders-model.jdl \ + specFile=src/main/resources/model/orders-model.zdl \ idType=integer \ idTypeFormat=int64 \ targetFile=src/main/resources/model/openapi.yml @@ -37,25 +37,3 @@ jbang zw -p io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin \ ```shell jbang zw -p io.zenwave360.sdk.plugins.ZDLToAsyncAPIPlugin --help ``` - -# OpenAPI To JDL - -Generates JDL model from OpenAPI schemas - -## Options - -| **Option** | **Description** | **Type** | **Default** | **Values** | -|--------------------|----------------------------------------------------------------------------|----------|-------------------------|------------| -| `specFile` | API Specification File | URI | | | -| `targetFolder` | Target folder to generate code to. If left empty, it will print to stdout. | File | | | -| `entities` | Entities to generate code for | List | [] | | -| `targetFile` | Target file | String | entities.jdl | | -| `useRelationships` | Whether to use JDL relationships or plain field | boolean | true | | -| `basePackage` | Java Models package name | String | io.example.domain.model | | - - -## Getting Help - -```shell -jbang zw -p io.zenwave360.sdk.plugins.OpenAPIToJDLPlugin --help -``` diff --git a/plugins/zdl-to-openapi/README.md b/plugins/zdl-to-openapi/README.md index e0ff54b9..c6748941 100644 --- a/plugins/zdl-to-openapi/README.md +++ b/plugins/zdl-to-openapi/README.md @@ -1,14 +1,14 @@ -# JDL To OpenAPI Generator +# ZDL To OpenAPI Generator > 👉 ZenWave360 Helps You Create Software Easy to Understand -Generate OpenAPI definition from JDL entities: +Generate OpenAPI definition from ZDL Models: - Component Schemas for entities, plain and paginated lists - CRUD operations for entities ```shell jbang zw -p io.zenwave360.sdk.plugins.ZDLToOpenAPIPlugin \ - specFile=src/main/resources/model/orders-model.jdl \ + specFile=src/main/resources/model/orders-model.zdl \ idType=integer \ idTypeFormat=int64 \ targetFile=src/main/resources/model/openapi.yml diff --git a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java index 486a54c1..4e340915 100644 --- a/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java +++ b/plugins/zdl-to-openapi/src/main/java/io/zenwave360/sdk/plugins/PathsProcessor.java @@ -1,5 +1,6 @@ package io.zenwave360.sdk.plugins; +import io.zenwave360.sdk.doc.DocumentedOption; import io.zenwave360.sdk.processors.AbstractBaseProcessor; import io.zenwave360.sdk.processors.Processor; import io.zenwave360.sdk.utils.FluentMap; @@ -10,6 +11,12 @@ public class PathsProcessor extends AbstractBaseProcessor implements Processor { + @DocumentedOption(description = "JsonSchema type for id fields and parameters.") + public String idType = "string"; + + @DocumentedOption(description = "JsonSchema type format for id fields and parameters.") + public String idTypeFormat = null; + { targetProperty = "zdl"; } diff --git a/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs b/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs index 6991551e..9f8d6222 100644 --- a/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs +++ b/plugins/zdl-to-openapi/src/main/resources/io/zenwave360/sdk/plugins/ZDLToOpenAPIGenerator/ZDLToOpenAPI.yml.hbs @@ -33,7 +33,12 @@ paths: in: path required: true schema: + {{~#if (contains pathParam 'id' ignoreCase=true)}} + type: {{idType}} + {{#if idTypeFormat}}format: {{idTypeFormat}}{{/if}} + {{~else}} type: string + {{~/if}} {{~/each}} {{~#each path.params as |param|}} - name: "{{param}}" diff --git a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java index 2ee839da..c43c9679 100644 --- a/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java +++ b/zenwave-sdk-cli/src/main/java/io/zenwave360/sdk/templating/CustomHandlebarsHelpers.java @@ -107,6 +107,13 @@ public static boolean endsWith(String first, Options options) throws IOException return StringUtils.endsWith(first, second); } + public static boolean contains(String first, Options options) throws IOException { + String second = options.param(0); + boolean ignoreCase = options.hash("ignoreCase", false); + return ignoreCase? StringUtils.containsIgnoreCase(first, second) : StringUtils.contains(first, second); + } + + public static Object ifTruthy(final Object value, final Options options) throws IOException { if (isTruthy(value)) {