├── src ├── test │ ├── resources │ │ ├── application-test.properties │ │ ├── logback-test.xml │ │ ├── test-orders-data.sql │ │ └── test-products-data.sql │ └── java │ │ └── com │ │ └── sivalabs │ │ └── bookstore │ │ ├── TestBookStoreApplication.java │ │ ├── BookStoreApplicationTests.java │ │ ├── AbstractIT.java │ │ ├── ModularityTests.java │ │ ├── ContainersConfig.java │ │ ├── catalog │ │ ├── events │ │ │ └── ProductPriceChangedEventHandlerTests.java │ │ └── api │ │ │ └── ProductControllerTest.java │ │ └── orders │ │ └── api │ │ └── OrderControllerTests.java └── main │ ├── java │ └── com │ │ └── sivalabs │ │ └── bookstore │ │ ├── orders │ │ ├── CreateOrderResponse.java │ │ ├── OrderStatus.java │ │ ├── InvalidOrderException.java │ │ ├── OrdersAPI.java │ │ ├── events │ │ │ ├── OrderCreatedEvent.java │ │ │ └── OrderEventPublisher.java │ │ ├── OrderDTO.java │ │ ├── CreateOrderRequest.java │ │ ├── Customer.java │ │ ├── OrderNotFoundException.java │ │ ├── OrderItem.java │ │ ├── Address.java │ │ ├── api │ │ │ └── OrderController.java │ │ ├── config │ │ │ └── OrdersExceptionHandler.java │ │ ├── domain │ │ │ ├── OrderService.java │ │ │ └── OrderRepository.java │ │ └── jobs │ │ │ └── OrderEventPublishingJob.java │ │ ├── catalog │ │ ├── FindProductsQuery.java │ │ ├── events │ │ │ ├── ProductPriceChangedEvent.java │ │ │ └── ProductPriceChangedEventHandler.java │ │ ├── Product.java │ │ ├── CreateProductCommand.java │ │ ├── ProductNotFoundException.java │ │ ├── CatalogAPI.java │ │ ├── config │ │ │ └── CatalogExceptionHandler.java │ │ ├── domain │ │ │ ├── CatalogService.java │ │ │ └── ProductRepository.java │ │ └── api │ │ │ └── ProductController.java │ │ ├── common │ │ ├── package-info.java │ │ └── models │ │ │ └── PagedResult.java │ │ ├── BookStoreApplication.java │ │ ├── config │ │ ├── WebMvcConfig.java │ │ ├── OpenAPIConfig.java │ │ └── GlobalExceptionHandler.java │ │ └── ApplicationProperties.java │ ├── jooq │ └── com │ │ └── sivalabs │ │ └── bookstore │ │ └── jooq │ │ ├── package-info.java │ │ └── models │ │ ├── Tables.java │ │ ├── Sequences.java │ │ ├── DefaultCatalog.java │ │ ├── Public.java │ │ ├── Keys.java │ │ └── tables │ │ ├── Products.java │ │ ├── OrderItems.java │ │ ├── records │ │ ├── ProductsRecord.java │ │ ├── OrderItemsRecord.java │ │ └── OrdersRecord.java │ │ └── Orders.java │ └── resources │ ├── db │ └── migration │ │ ├── V1__catalog_tables.sql │ │ ├── V2__orders_tables.sql │ │ └── V3__insert_products.sql │ └── application.properties ├── .gitignore ├── .github └── workflows │ └── maven.yml ├── .mvn ├── jvm.config └── wrapper │ └── maven-wrapper.properties ├── README.md ├── compose.yaml ├── mvnw.cmd ├── mvnw ├── LICENSE └── pom.xml /src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.kafka.consumer.auto-offset-reset=earliest 2 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/CreateOrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | public record CreateOrderResponse(String orderNumber) {} 4 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/FindProductsQuery.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | public record FindProductsQuery(int pageNo, int pageSize) {} 4 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/package-info.java: -------------------------------------------------------------------------------- 1 | @ApplicationModule(type = ApplicationModule.Type.OPEN) 2 | package com.sivalabs.bookstore.jooq; 3 | 4 | import org.springframework.modulith.ApplicationModule; 5 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/common/package-info.java: -------------------------------------------------------------------------------- 1 | @ApplicationModule(type = ApplicationModule.Type.OPEN) 2 | package com.sivalabs.bookstore.common; 3 | 4 | import org.springframework.modulith.ApplicationModule; 5 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | public enum OrderStatus { 4 | NEW, 5 | IN_PROCESS, 6 | DELIVERED, 7 | CANCELLED, 8 | ERROR 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/events/ProductPriceChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.events; 2 | 3 | import java.math.BigDecimal; 4 | 5 | record ProductPriceChangedEvent(String code, BigDecimal price) {} 6 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record Product(Long id, String code, String name, String description, String imageUrl, BigDecimal price) {} 6 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/CreateProductCommand.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record CreateProductCommand(String code, String name, String description, String imageUrl, BigDecimal price) {} 6 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/InvalidOrderException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | public class InvalidOrderException extends RuntimeException { 4 | 5 | public InvalidOrderException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/OrdersAPI.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public interface OrdersAPI { 7 | Optional findOrder(String orderNumber); 8 | 9 | List findOrdersByStatus(OrderStatus status); 10 | 11 | CreateOrderResponse createOrder(CreateOrderRequest request); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/common/models/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.common.models; 2 | 3 | import java.util.List; 4 | 5 | public record PagedResult( 6 | List data, 7 | long totalElements, 8 | int pageNumber, 9 | int totalPages, 10 | boolean isFirst, 11 | boolean isLast, 12 | boolean hasNext, 13 | boolean hasPrevious) {} 14 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/events/OrderCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.events; 2 | 3 | import com.sivalabs.bookstore.orders.Address; 4 | import com.sivalabs.bookstore.orders.Customer; 5 | import com.sivalabs.bookstore.orders.OrderItem; 6 | import java.util.Set; 7 | 8 | public record OrderCreatedEvent(String orderNumber, Set items, Customer customer, Address deliveryAddress) {} 9 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/TestBookStoreApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | 5 | public class TestBookStoreApplication { 6 | 7 | public static void main(String[] args) { 8 | SpringApplication.from(BookStoreApplication::main) 9 | .with(ContainersConfig.class) 10 | .run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | 6 | public record OrderDTO( 7 | String orderNumber, 8 | List items, 9 | Customer customer, 10 | Address deliveryAddress, 11 | OrderStatus status, 12 | String comments, 13 | LocalDateTime createdAt) {} 14 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import java.util.Set; 6 | 7 | public record CreateOrderRequest( 8 | @Valid @NotEmpty(message = "Items cannot be empty") Set items, 9 | @Valid Customer customer, 10 | @Valid Address deliveryAddress) {} 11 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1__catalog_tables.sql: -------------------------------------------------------------------------------- 1 | create sequence product_id_seq start with 100 increment by 50; 2 | 3 | create table products 4 | ( 5 | id bigint not null default nextval('product_id_seq'), 6 | code varchar not null unique, 7 | name varchar not null, 8 | description varchar, 9 | image_url varchar, 10 | price numeric not null, 11 | primary key (id) 12 | ); 13 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | 5 | public ProductNotFoundException(String msg) { 6 | super(msg); 7 | } 8 | 9 | public static ProductNotFoundException withCode(String code) { 10 | return new ProductNotFoundException("Product with code: " + code + " not found"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record Customer( 7 | @NotBlank(message = "Customer Name is required") String name, 8 | @NotBlank(message = "Customer email is required") @Email String email, 9 | @NotBlank(message = "Customer Phone number is required") String phone) {} 10 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | public class OrderNotFoundException extends RuntimeException { 4 | public OrderNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static OrderNotFoundException forOrderNumber(String orderNumber) { 9 | return new OrderNotFoundException("Order with Number " + orderNumber + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/BookStoreApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(webEnvironment = RANDOM_PORT) 9 | class BookStoreApplicationTests extends AbstractIT { 10 | 11 | @Test 12 | void contextLoads() {} 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/CatalogAPI.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | import com.sivalabs.bookstore.common.models.PagedResult; 4 | import java.math.BigDecimal; 5 | import java.util.Optional; 6 | 7 | public interface CatalogAPI { 8 | 9 | Long createProduct(CreateProductCommand cmd); 10 | 11 | PagedResult findProducts(FindProductsQuery query); 12 | 13 | Optional findProductByCode(String code); 14 | 15 | void updatePrice(String code, BigDecimal price); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/BookStoreApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class BookStoreApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(BookStoreApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import java.math.BigDecimal; 7 | 8 | public record OrderItem( 9 | @NotBlank(message = "Code is required") String code, 10 | @NotBlank(message = "Name is required") String name, 11 | @NotNull(message = "Price is required") BigDecimal price, 12 | @NotNull @Min(1) Integer quantity) {} 13 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/Address.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Address( 6 | @NotBlank(message = "AddressLine1 is required") String addressLine1, 7 | String addressLine2, 8 | @NotBlank(message = "City is required") String city, 9 | @NotBlank(message = "State is required") String state, 10 | @NotBlank(message = "ZipCode is required") String zipCode, 11 | @NotBlank(message = "Country is required") String country) {} 12 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/AbstractIT.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import io.restassured.RestAssured; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.springframework.boot.test.web.server.LocalServerPort; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.test.context.ActiveProfiles; 8 | 9 | @Import(ContainersConfig.class) 10 | @ActiveProfiles("test") 11 | public abstract class AbstractIT { 12 | @LocalServerPort 13 | int port; 14 | 15 | @BeforeEach 16 | void setUp() { 17 | RestAssured.port = port; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | build: 12 | name: Maven Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Setup Java 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '21' 21 | distribution: 'temurin' 22 | cache: 'maven' 23 | 24 | - name: Make Maven wrapper executable 25 | run: chmod +x mvnw 26 | 27 | - name: Build with Maven 28 | run: ./mvnw -ntp verify 29 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED 2 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED 3 | --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED 4 | --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED 5 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED 6 | --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED 7 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED 8 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 9 | --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED 10 | --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/events/ProductPriceChangedEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.events; 2 | 3 | import com.sivalabs.bookstore.catalog.CatalogAPI; 4 | import org.springframework.kafka.annotation.KafkaListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | class ProductPriceChangedEventHandler { 9 | private final CatalogAPI catalogAPI; 10 | 11 | ProductPriceChangedEventHandler(CatalogAPI catalogAPI) { 12 | this.catalogAPI = catalogAPI; 13 | } 14 | 15 | @KafkaListener(topics = "${app.product-price-updates-topic}", groupId = "catalog") 16 | public void handle(ProductPriceChangedEvent event) { 17 | catalogAPI.updatePrice(event.code(), event.price()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/ModularityTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.modulith.core.ApplicationModules; 5 | import org.springframework.modulith.docs.Documenter; 6 | 7 | class ModularityTests { 8 | private final ApplicationModules modules = ApplicationModules.of(BookStoreApplication.class); 9 | 10 | @Test 11 | void verifiesModularStructure() { 12 | System.out.println("==============Modules================"); 13 | System.out.println(modules); 14 | System.out.println("====================================="); 15 | modules.verify(); 16 | } 17 | 18 | @Test 19 | void createModuleDocumentation() { 20 | new Documenter(modules).writeDocumentation(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/ContainersConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | import org.testcontainers.containers.KafkaContainer; 7 | import org.testcontainers.containers.PostgreSQLContainer; 8 | import org.testcontainers.utility.DockerImageName; 9 | 10 | @TestConfiguration(proxyBeanMethods = false) 11 | public class ContainersConfig { 12 | @Bean 13 | @ServiceConnection 14 | PostgreSQLContainer postgresContainer() { 15 | return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16")); 16 | } 17 | 18 | @Bean 19 | @ServiceConnection 20 | KafkaContainer kafkaContainer() { 21 | return new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.1")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/Tables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.OrderItems; 8 | import com.sivalabs.bookstore.jooq.models.tables.Orders; 9 | import com.sivalabs.bookstore.jooq.models.tables.Products; 10 | 11 | 12 | /** 13 | * Convenience access to all tables in public. 14 | */ 15 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 16 | public class Tables { 17 | 18 | /** 19 | * The table public.order_items. 20 | */ 21 | public static final OrderItems ORDER_ITEMS = OrderItems.ORDER_ITEMS; 22 | 23 | /** 24 | * The table public.orders. 25 | */ 26 | public static final Orders ORDERS = Orders.ORDERS; 27 | 28 | /** 29 | * The table public.products. 30 | */ 31 | public static final Products PRODUCTS = Products.PRODUCTS; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.config; 2 | 3 | import com.sivalabs.bookstore.ApplicationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | class WebMvcConfig implements WebMvcConfigurer { 10 | private final ApplicationProperties properties; 11 | 12 | WebMvcConfig(ApplicationProperties properties) { 13 | this.properties = properties; 14 | } 15 | 16 | @Override 17 | public void addCorsMappings(CorsRegistry registry) { 18 | registry.addMapping(properties.cors().pathPattern()) 19 | .allowedOriginPatterns(properties.cors().allowedOrigins()) 20 | .allowedMethods(properties.cors().allowedMethods()) 21 | .allowedHeaders(properties.cors().allowedHeaders()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/test-orders-data.sql: -------------------------------------------------------------------------------- 1 | truncate table orders cascade; 2 | alter sequence order_id_seq restart with 100; 3 | alter sequence order_item_id_seq restart with 100; 4 | 5 | insert into orders (id,order_number, 6 | customer_name,customer_email,customer_phone, 7 | delivery_address_line1,delivery_address_line2,delivery_address_city, 8 | delivery_address_state,delivery_address_zip_code,delivery_address_country, 9 | status,comments) values 10 | (1, 'order-123', 'Siva', 'siva@gmail.com', '11111111', '123 Main St', 'Apt 1', 'Dallas', 'TX', '75001', 'USA', 'NEW', null), 11 | (2, 'order-456', 'Prasad', 'prasad@gmail.com', '2222222', '123 Main St', 'Apt 1', 'Hyderabad', 'TS', '500072', 'India', 'NEW', null) 12 | ; 13 | 14 | insert into order_items(order_id, code, name, price, quantity) values 15 | (1, 'P100', 'The Hunger Games', 34.0, 2), 16 | (1, 'P101', 'To Kill a Mockingbird', 45.40, 1), 17 | (2, 'P102', 'The Chronicles of Narnia', 44.50, 1) 18 | ; 19 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/config/OpenAPIConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.config; 2 | 3 | import com.sivalabs.bookstore.ApplicationProperties; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Contact; 6 | import io.swagger.v3.oas.models.info.Info; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | class OpenAPIConfig { 12 | 13 | @Bean 14 | OpenAPI openApi(ApplicationProperties properties) { 15 | var openapi = properties.openapi(); 16 | Contact contact = new Contact() 17 | .name(openapi.contact().name()) 18 | .email(openapi.contact().email()); 19 | Info info = new Info() 20 | .title(openapi.title()) 21 | .description(openapi.description()) 22 | .version(openapi.version()) 23 | .contact(contact); 24 | return new OpenAPI().info(info); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/events/OrderEventPublisher.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.events; 2 | 3 | import com.sivalabs.bookstore.ApplicationProperties; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.kafka.core.KafkaTemplate; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class OrderEventPublisher { 11 | private static final Logger log = LoggerFactory.getLogger(OrderEventPublisher.class); 12 | private final KafkaTemplate kafkaTemplate; 13 | private final ApplicationProperties properties; 14 | 15 | public OrderEventPublisher(KafkaTemplate kafkaTemplate, ApplicationProperties properties) { 16 | this.kafkaTemplate = kafkaTemplate; 17 | this.properties = properties; 18 | } 19 | 20 | public void send(OrderCreatedEvent event) { 21 | this.send(properties.newOrdersTopic(), event); 22 | } 23 | 24 | private void send(String topic, Object payload) { 25 | kafkaTemplate.send(topic, payload).thenRun(() -> log.info("Sent event {} to topic {}", payload, topic)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/config/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.config; 2 | 3 | import java.net.URI; 4 | import java.time.Instant; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ProblemDetail; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 10 | 11 | @RestControllerAdvice 12 | class GlobalExceptionHandler extends ResponseEntityExceptionHandler { 13 | 14 | @ExceptionHandler(Exception.class) 15 | ProblemDetail handleGenericException(Exception e) { 16 | ProblemDetail problemDetail = 17 | ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); 18 | problemDetail.setTitle("Internal Server Error"); 19 | problemDetail.setType(URI.create("https://api.bookstore.com/errors/server-error")); 20 | problemDetail.setProperty("errorCategory", "Generic"); 21 | problemDetail.setProperty("timestamp", Instant.now()); 22 | return problemDetail; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/Sequences.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models; 5 | 6 | 7 | import org.jooq.Sequence; 8 | import org.jooq.impl.Internal; 9 | import org.jooq.impl.SQLDataType; 10 | 11 | 12 | /** 13 | * Convenience access to all sequences in public. 14 | */ 15 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 16 | public class Sequences { 17 | 18 | /** 19 | * The sequence public.order_id_seq 20 | */ 21 | public static final Sequence ORDER_ID_SEQ = Internal.createSequence("order_id_seq", Public.PUBLIC, SQLDataType.BIGINT.nullable(false), 100, 50, null, null, false, null); 22 | 23 | /** 24 | * The sequence public.order_item_id_seq 25 | */ 26 | public static final Sequence ORDER_ITEM_ID_SEQ = Internal.createSequence("order_item_id_seq", Public.PUBLIC, SQLDataType.BIGINT.nullable(false), 100, 50, null, null, false, null); 27 | 28 | /** 29 | * The sequence public.product_id_seq 30 | */ 31 | public static final Sequence PRODUCT_ID_SEQ = Internal.createSequence("product_id_seq", Public.PUBLIC, SQLDataType.BIGINT.nullable(false), 100, 50, null, null, false, null); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.bind.DefaultValue; 6 | 7 | @ConfigurationProperties(prefix = "app") 8 | public record ApplicationProperties( 9 | @DefaultValue("10") @Min(1) int pageSize, 10 | String productPriceUpdatesTopic, 11 | String newOrdersTopic, 12 | OpenAPIProperties openapi, 13 | CorsProperties cors) { 14 | 15 | public record OpenAPIProperties( 16 | @DefaultValue("MRS APIs") String title, 17 | @DefaultValue("MRS REST API Swagger Documentation") String description, 18 | @DefaultValue("v1.0.0") String version, 19 | Contact contact) { 20 | 21 | public record Contact( 22 | @DefaultValue("SivaLabs") String name, @DefaultValue("support@sivalabs.in") String email) {} 23 | } 24 | 25 | public record CorsProperties( 26 | @DefaultValue("/api/**") String pathPattern, 27 | @DefaultValue("*") String allowedOrigins, 28 | @DefaultValue("*") String allowedMethods, 29 | @DefaultValue("*") String allowedHeaders) {} 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2__orders_tables.sql: -------------------------------------------------------------------------------- 1 | create sequence order_id_seq start with 100 increment by 50; 2 | create sequence order_item_id_seq start with 100 increment by 50; 3 | 4 | create table orders 5 | ( 6 | id bigint not null default nextval('order_id_seq'), 7 | order_number varchar not null, 8 | customer_name varchar not null, 9 | customer_email varchar not null, 10 | customer_phone varchar not null, 11 | delivery_address_line1 varchar not null, 12 | delivery_address_line2 varchar, 13 | delivery_address_city varchar not null, 14 | delivery_address_state varchar not null, 15 | delivery_address_zip_code varchar not null, 16 | delivery_address_country varchar not null, 17 | status varchar not null, 18 | comments text, 19 | created_at timestamp, 20 | updated_at timestamp, 21 | primary key (id) 22 | ); 23 | 24 | create table order_items 25 | ( 26 | id bigint not null default nextval('order_item_id_seq'), 27 | code varchar not null, 28 | name varchar not null, 29 | price numeric not null, 30 | quantity integer not null, 31 | order_id bigint not null references orders (id), 32 | primary key (id) 33 | ); 34 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/config/CatalogExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.config; 2 | 3 | import com.sivalabs.bookstore.catalog.ProductNotFoundException; 4 | import java.net.URI; 5 | import java.time.Instant; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ProblemDetail; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.RestControllerAdvice; 12 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 13 | 14 | @RestControllerAdvice 15 | @Order(Ordered.HIGHEST_PRECEDENCE) 16 | class CatalogExceptionHandler extends ResponseEntityExceptionHandler { 17 | 18 | @ExceptionHandler(ProductNotFoundException.class) 19 | ProblemDetail handleProductNotFoundException(ProductNotFoundException e) { 20 | ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage()); 21 | problemDetail.setTitle("Product Not Found"); 22 | problemDetail.setType(URI.create("https://api.bookstore.com/errors/not-found")); 23 | problemDetail.setProperty("errorCategory", "Generic"); 24 | problemDetail.setProperty("timestamp", Instant.now()); 25 | return problemDetail; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tomato-architecture-spring-boot-demo 2 | A sample Spring Boot application following [Tomato Architecture](https://github.com/sivaprasadreddy/tomato-architecture) 3 | 4 | ## Prerequisites 5 | * JDK 21 6 | * Docker Compose 7 | 8 | ## TechStack 9 | * Java 21 10 | * Spring Boot 11 | * Spring Modulith 12 | * jOOQ 13 | * PostgreSQL 14 | * Kafka 15 | * Testcontainers 16 | * Docker Compose 17 | 18 | ## How to run? 19 | The application is configured to use Docker Compose to start the dependent services (Postgres, Kafka). 20 | You can simply run `BookStoreApplication.java` from your IDE to start the application. 21 | 22 | **NOTE:** To work with Kafka transparently from both local and container, add `127.0.0.1 broker` entry in `/etc/hosts` file. 23 | 24 | To know more about Spring Boot Docker Compose Support, you can watch the following video. 25 | 26 | [![Spring Boot Docker Compose Support](https://img.youtube.com/vi/PZt5EJTLH4o/0.jpg)](https://www.youtube.com/watch?v=PZt5EJTLH4o) 27 | 28 | You can also start the application from commandline as follows: 29 | 30 | ```shell 31 | $ ./mvnw spring-boot:run 32 | ``` 33 | 34 | ## Run tests 35 | You can run the tests as follows: 36 | 37 | ```shell 38 | $ ./mvnw test 39 | ``` 40 | 41 | The application is using Spring Modulith to verify the module boundaries. 42 | To know more about Spring Modulith, you can watch the following video. 43 | 44 | [![Spring Modulith Crash Course : Building Modular Monoliths using Spring Boot](https://img.youtube.com/vi/FkP2aZiBrhg/0.jpg)](https://www.youtube.com/watch?v=FkP2aZiBrhg) 45 | 46 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: 'postgres:16-alpine' 4 | environment: 5 | - 'POSTGRES_DB=postgres' 6 | - 'POSTGRES_PASSWORD=postgres' 7 | - 'POSTGRES_USER=postgres' 8 | ports: 9 | - '5432:5432' 10 | 11 | broker: 12 | image: confluentinc/cp-kafka:7.6.1 13 | hostname: broker 14 | container_name: broker 15 | ports: 16 | - "9092:9092" 17 | - "9101:9101" 18 | environment: 19 | KAFKA_NODE_ID: 1 20 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' 21 | KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092' 22 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 23 | KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 24 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 25 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 26 | KAFKA_JMX_PORT: 9101 27 | KAFKA_JMX_HOSTNAME: localhost 28 | KAFKA_PROCESS_ROLES: 'broker,controller' 29 | KAFKA_CONTROLLER_QUORUM_VOTERS: '1@broker:29093' 30 | KAFKA_LISTENERS: 'PLAINTEXT://broker:29092,CONTROLLER://broker:29093,PLAINTEXT_HOST://0.0.0.0:9092' 31 | KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' 32 | KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' 33 | KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs' 34 | # Replace CLUSTER_ID with a unique base64 UUID using "bin/kafka-storage.sh random-uuid" 35 | # See https://docs.confluent.io/kafka/operations-tools/kafka-tools.html#kafka-storage-sh 36 | CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' 37 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/DefaultCatalog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models; 5 | 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import org.jooq.Constants; 11 | import org.jooq.Schema; 12 | import org.jooq.impl.CatalogImpl; 13 | 14 | 15 | /** 16 | * This class is generated by jOOQ. 17 | */ 18 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 19 | public class DefaultCatalog extends CatalogImpl { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * The reference instance of DEFAULT_CATALOG 25 | */ 26 | public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog(); 27 | 28 | /** 29 | * The schema public. 30 | */ 31 | public final Public PUBLIC = Public.PUBLIC; 32 | 33 | /** 34 | * No further instances allowed 35 | */ 36 | private DefaultCatalog() { 37 | super(""); 38 | } 39 | 40 | @Override 41 | public final List getSchemas() { 42 | return Arrays.asList( 43 | Public.PUBLIC 44 | ); 45 | } 46 | 47 | /** 48 | * A reference to the 3.18 minor release of the code generator. If this 49 | * doesn't compile, it's because the runtime library uses an older minor 50 | * release, namely: 3.18. You can turn off the generation of this reference 51 | * by specifying /configuration/generator/generate/jooqVersionReference 52 | */ 53 | private static final String REQUIRE_RUNTIME_JOOQ_VERSION = Constants.VERSION_3_18; 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/domain/CatalogService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import com.sivalabs.bookstore.catalog.CatalogAPI; 4 | import com.sivalabs.bookstore.catalog.CreateProductCommand; 5 | import com.sivalabs.bookstore.catalog.FindProductsQuery; 6 | import com.sivalabs.bookstore.catalog.Product; 7 | import com.sivalabs.bookstore.common.models.PagedResult; 8 | import java.math.BigDecimal; 9 | import java.util.Optional; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @Service 14 | @Transactional(readOnly = true) 15 | class CatalogService implements CatalogAPI { 16 | private final ProductRepository productRepository; 17 | 18 | CatalogService(ProductRepository productRepository) { 19 | this.productRepository = productRepository; 20 | } 21 | 22 | @Override 23 | @Transactional 24 | public Long createProduct(CreateProductCommand cmd) { 25 | Product product = new Product(null, cmd.code(), cmd.name(), cmd.description(), cmd.imageUrl(), cmd.price()); 26 | return productRepository.saveProduct(product); 27 | } 28 | 29 | @Override 30 | public PagedResult findProducts(FindProductsQuery query) { 31 | return productRepository.getProducts(query.pageNo(), query.pageSize()); 32 | } 33 | 34 | @Override 35 | public Optional findProductByCode(String code) { 36 | return productRepository.findByCode(code); 37 | } 38 | 39 | @Override 40 | @Transactional 41 | public void updatePrice(String code, BigDecimal price) { 42 | productRepository.updatePrice(code, price); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/api/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.api; 2 | 3 | import com.sivalabs.bookstore.orders.CreateOrderRequest; 4 | import com.sivalabs.bookstore.orders.CreateOrderResponse; 5 | import com.sivalabs.bookstore.orders.OrderDTO; 6 | import com.sivalabs.bookstore.orders.OrderNotFoundException; 7 | import com.sivalabs.bookstore.orders.OrdersAPI; 8 | import jakarta.validation.Valid; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.ResponseStatus; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | @RequestMapping("/api/orders") 22 | class OrderController { 23 | private static final Logger log = LoggerFactory.getLogger(OrderController.class); 24 | 25 | private final OrdersAPI ordersAPI; 26 | 27 | OrderController(OrdersAPI ordersAPI) { 28 | this.ordersAPI = ordersAPI; 29 | } 30 | 31 | @PostMapping 32 | @ResponseStatus(HttpStatus.CREATED) 33 | CreateOrderResponse createOrder(@Valid @RequestBody CreateOrderRequest request) { 34 | return ordersAPI.createOrder(request); 35 | } 36 | 37 | @GetMapping(value = "/{orderNumber}") 38 | OrderDTO getOrder(@PathVariable(value = "orderNumber") String orderNumber) { 39 | log.info("Fetching order by orderNumber: {}", orderNumber); 40 | return ordersAPI.findOrder(orderNumber).orElseThrow(() -> OrderNotFoundException.forOrderNumber(orderNumber)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/config/OrdersExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.config; 2 | 3 | import com.sivalabs.bookstore.orders.InvalidOrderException; 4 | import com.sivalabs.bookstore.orders.OrderNotFoundException; 5 | import java.net.URI; 6 | import java.time.Instant; 7 | import org.springframework.core.Ordered; 8 | import org.springframework.core.annotation.Order; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ProblemDetail; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.RestControllerAdvice; 13 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 14 | 15 | @RestControllerAdvice 16 | @Order(Ordered.HIGHEST_PRECEDENCE) 17 | class OrdersExceptionHandler extends ResponseEntityExceptionHandler { 18 | 19 | @ExceptionHandler(OrderNotFoundException.class) 20 | ProblemDetail handle(OrderNotFoundException e) { 21 | ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage()); 22 | problemDetail.setTitle("Order Not Found"); 23 | problemDetail.setType(URI.create("https://api.bookstore.com/errors/not-found")); 24 | problemDetail.setProperty("errorCategory", "Generic"); 25 | problemDetail.setProperty("timestamp", Instant.now()); 26 | return problemDetail; 27 | } 28 | 29 | @ExceptionHandler(InvalidOrderException.class) 30 | ProblemDetail handle(InvalidOrderException e) { 31 | ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage()); 32 | problemDetail.setTitle("Invalid Order Creation Request"); 33 | problemDetail.setType(URI.create("https://api.bookstore.com/errors/bad-request")); 34 | problemDetail.setProperty("errorCategory", "Generic"); 35 | problemDetail.setProperty("timestamp", Instant.now()); 36 | return problemDetail; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/api/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.api; 2 | 3 | import com.sivalabs.bookstore.ApplicationProperties; 4 | import com.sivalabs.bookstore.catalog.CatalogAPI; 5 | import com.sivalabs.bookstore.catalog.FindProductsQuery; 6 | import com.sivalabs.bookstore.catalog.Product; 7 | import com.sivalabs.bookstore.catalog.ProductNotFoundException; 8 | import com.sivalabs.bookstore.common.models.PagedResult; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | @RestController 19 | @RequestMapping("/api/products") 20 | class ProductController { 21 | private static final Logger log = LoggerFactory.getLogger(ProductController.class); 22 | 23 | private final CatalogAPI catalogAPI; 24 | private final ApplicationProperties properties; 25 | 26 | ProductController(CatalogAPI catalogAPI, ApplicationProperties properties) { 27 | this.catalogAPI = catalogAPI; 28 | this.properties = properties; 29 | } 30 | 31 | @GetMapping 32 | PagedResult getProducts(@RequestParam(name = "page", defaultValue = "1") int pageNo) { 33 | log.info("Fetching products for page: {}", pageNo); 34 | var query = new FindProductsQuery(pageNo, properties.pageSize()); 35 | return catalogAPI.findProducts(query); 36 | } 37 | 38 | @GetMapping("/{code}") 39 | ResponseEntity getProductByCode(@PathVariable String code) { 40 | log.info("Fetching product by code: {}", code); 41 | return catalogAPI 42 | .findProductByCode(code) 43 | .map(ResponseEntity::ok) 44 | .orElseThrow(() -> ProductNotFoundException.withCode(code)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/catalog/events/ProductPriceChangedEventHandlerTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.events; 2 | 3 | import static java.util.concurrent.TimeUnit.SECONDS; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.awaitility.Awaitility.await; 6 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 7 | 8 | import com.sivalabs.bookstore.AbstractIT; 9 | import com.sivalabs.bookstore.ApplicationProperties; 10 | import com.sivalabs.bookstore.catalog.CatalogAPI; 11 | import com.sivalabs.bookstore.catalog.Product; 12 | import java.math.BigDecimal; 13 | import java.time.Duration; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.kafka.core.KafkaTemplate; 17 | import org.springframework.modulith.test.ApplicationModuleTest; 18 | import org.springframework.test.context.jdbc.Sql; 19 | 20 | @ApplicationModuleTest(webEnvironment = RANDOM_PORT) 21 | @Sql("/test-products-data.sql") 22 | class ProductPriceChangedEventHandlerTests extends AbstractIT { 23 | 24 | @Autowired 25 | KafkaTemplate kafkaTemplate; 26 | 27 | @Autowired 28 | ApplicationProperties properties; 29 | 30 | @Autowired 31 | CatalogAPI catalogAPI; 32 | 33 | @Test 34 | void shouldConsumeAndProcessEventSuccessfully() { 35 | String code = "P100"; 36 | Product product = catalogAPI.findProductByCode(code).orElseThrow(); 37 | assertThat(product.price().compareTo(new BigDecimal("34.0"))).isEqualTo(0); 38 | var event = new ProductPriceChangedEvent(code, new BigDecimal("100.00")); 39 | kafkaTemplate.send(properties.productPriceUpdatesTopic(), event); 40 | 41 | await().pollInterval(Duration.ofSeconds(3)).atMost(10, SECONDS).untilAsserted(() -> { 42 | var updatedProduct = catalogAPI.findProductByCode(code).orElseThrow(); 43 | assertThat(updatedProduct.price().compareTo(new BigDecimal("100.0"))) 44 | .isEqualTo(0); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/Public.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.OrderItems; 8 | import com.sivalabs.bookstore.jooq.models.tables.Orders; 9 | import com.sivalabs.bookstore.jooq.models.tables.Products; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import org.jooq.Catalog; 15 | import org.jooq.Sequence; 16 | import org.jooq.Table; 17 | import org.jooq.impl.SchemaImpl; 18 | 19 | 20 | /** 21 | * This class is generated by jOOQ. 22 | */ 23 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 24 | public class Public extends SchemaImpl { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 29 | * The reference instance of public 30 | */ 31 | public static final Public PUBLIC = new Public(); 32 | 33 | /** 34 | * The table public.order_items. 35 | */ 36 | public final OrderItems ORDER_ITEMS = OrderItems.ORDER_ITEMS; 37 | 38 | /** 39 | * The table public.orders. 40 | */ 41 | public final Orders ORDERS = Orders.ORDERS; 42 | 43 | /** 44 | * The table public.products. 45 | */ 46 | public final Products PRODUCTS = Products.PRODUCTS; 47 | 48 | /** 49 | * No further instances allowed 50 | */ 51 | private Public() { 52 | super("public", null); 53 | } 54 | 55 | 56 | @Override 57 | public Catalog getCatalog() { 58 | return DefaultCatalog.DEFAULT_CATALOG; 59 | } 60 | 61 | @Override 62 | public final List> getSequences() { 63 | return Arrays.asList( 64 | Sequences.ORDER_ID_SEQ, 65 | Sequences.ORDER_ITEM_ID_SEQ, 66 | Sequences.PRODUCT_ID_SEQ 67 | ); 68 | } 69 | 70 | @Override 71 | public final List> getTables() { 72 | return Arrays.asList( 73 | OrderItems.ORDER_ITEMS, 74 | Orders.ORDERS, 75 | Products.PRODUCTS 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/domain/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import com.sivalabs.bookstore.catalog.CatalogAPI; 4 | import com.sivalabs.bookstore.catalog.Product; 5 | import com.sivalabs.bookstore.orders.CreateOrderRequest; 6 | import com.sivalabs.bookstore.orders.CreateOrderResponse; 7 | import com.sivalabs.bookstore.orders.InvalidOrderException; 8 | import com.sivalabs.bookstore.orders.OrderDTO; 9 | import com.sivalabs.bookstore.orders.OrderStatus; 10 | import com.sivalabs.bookstore.orders.OrdersAPI; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | @Service 17 | @Transactional(readOnly = true) 18 | class OrderService implements OrdersAPI { 19 | private final OrderRepository orderRepository; 20 | private final CatalogAPI catalogAPI; 21 | 22 | OrderService(OrderRepository orderRepository, CatalogAPI catalogAPI) { 23 | this.orderRepository = orderRepository; 24 | this.catalogAPI = catalogAPI; 25 | } 26 | 27 | @Override 28 | public Optional findOrder(String orderNumber) { 29 | return this.orderRepository.findByOrderNumber(orderNumber); 30 | } 31 | 32 | @Override 33 | public List findOrdersByStatus(OrderStatus status) { 34 | return orderRepository.findByStatus(status); 35 | } 36 | 37 | @Transactional 38 | @Override 39 | public CreateOrderResponse createOrder(CreateOrderRequest request) { 40 | this.validateOrderRequest(request); 41 | return orderRepository.createOrder(request); 42 | } 43 | 44 | private void validateOrderRequest(CreateOrderRequest req) { 45 | req.items().forEach(item -> { 46 | Product product = catalogAPI 47 | .findProductByCode(item.code()) 48 | .orElseThrow(() -> new InvalidOrderException("Invalid Product Code: " + item.code())); 49 | if (product.price().compareTo(item.price()) != 0) { 50 | throw new InvalidOrderException("Product price mismatch for code: " + item.code()); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/jobs/OrderEventPublishingJob.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.jobs; 2 | 3 | import com.sivalabs.bookstore.orders.OrderDTO; 4 | import com.sivalabs.bookstore.orders.OrderItem; 5 | import com.sivalabs.bookstore.orders.OrderStatus; 6 | import com.sivalabs.bookstore.orders.OrdersAPI; 7 | import com.sivalabs.bookstore.orders.events.OrderCreatedEvent; 8 | import com.sivalabs.bookstore.orders.events.OrderEventPublisher; 9 | import java.time.Instant; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.scheduling.annotation.Scheduled; 15 | import org.springframework.stereotype.Component; 16 | 17 | @Component 18 | class OrderEventPublishingJob { 19 | private static final Logger log = LoggerFactory.getLogger(OrderEventPublishingJob.class); 20 | private final OrdersAPI ordersAPI; 21 | private final OrderEventPublisher orderEventPublisher; 22 | 23 | OrderEventPublishingJob(OrdersAPI ordersAPI, OrderEventPublisher orderEventPublisher) { 24 | this.ordersAPI = ordersAPI; 25 | this.orderEventPublisher = orderEventPublisher; 26 | } 27 | 28 | @Scheduled(cron = "${app.new-orders-job-cron}") 29 | void processNewOrders() { 30 | log.info("Processing new orders job started at {}", Instant.now()); 31 | List newOrders = ordersAPI.findOrdersByStatus(OrderStatus.NEW); 32 | for (OrderDTO order : newOrders) { 33 | var orderCreatedEvent = this.buildOrderCreatedEvent(order); 34 | orderEventPublisher.send(orderCreatedEvent); 35 | log.info("Published OrderCreatedEvent for orderNumber:{}", order.orderNumber()); 36 | } 37 | log.info("Processing new orders job completed at {}", Instant.now()); 38 | } 39 | 40 | private OrderCreatedEvent buildOrderCreatedEvent(OrderDTO order) { 41 | var orderItems = order.items().stream() 42 | .map(item -> new OrderItem(item.code(), item.name(), item.price(), item.quantity())) 43 | .collect(Collectors.toSet()); 44 | return new OrderCreatedEvent(order.orderNumber(), orderItems, order.customer(), order.deliveryAddress()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=bookstore 2 | spring.threads.virtual.enabled=true 3 | 4 | logging.level.com.sivalabs=DEBUG 5 | #logging.level.org.jooq.tools.LoggerListener=DEBUG 6 | 7 | ######## App Configuration ######### 8 | app.product-price-updates-topic=product-price-updates-topic 9 | app.new-orders-topic=new-orders-topic 10 | app.new-orders-job-cron=*/10 * * * * * 11 | 12 | app.cors.path-pattern=/api/** 13 | app.cors.allowed-origins=* 14 | app.cors.allowed-methods=* 15 | app.cors.allowed-headers=* 16 | 17 | app.openapi.title=BookStore API 18 | app.openapi.description=OpenAPI Docs for BookStore API 19 | app.openapi.version=v1.0.0 20 | app.openapi.contact.name=SivaLabs 21 | app.openapi.contact.email=support@sivalabs.in 22 | 23 | ######## Compose Configuration ######### 24 | spring.docker.compose.lifecycle-management=start_only 25 | 26 | ######## Database Configuration ######### 27 | spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_DATABASE:postgres} 28 | spring.datasource.username=${DB_USERNAME:postgres} 29 | spring.datasource.password=${DB_PASSWORD:postgres} 30 | spring.jpa.show-sql=true 31 | spring.jpa.open-in-view=false 32 | #spring.flyway.clean-disabled=false 33 | #spring.flyway.clean-on-validation-error=true 34 | 35 | ######## Kafka Configuration ######### 36 | KAFKA_BROKER=broker:9092 37 | spring.kafka.bootstrap-servers=${KAFKA_BROKER} 38 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 39 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 40 | 41 | spring.kafka.consumer.group-id=${spring.application.name} 42 | spring.kafka.consumer.auto-offset-reset=latest 43 | #spring.kafka.consumer.auto-offset-reset=earliest 44 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 45 | #spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 46 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.ErrorHandlingDeserializer 47 | spring.kafka.consumer.properties.spring.deserializer.value.delegate.class=org.springframework.kafka.support.serializer.JsonDeserializer 48 | spring.kafka.producer.properties.spring.json.add.type.headers=true 49 | spring.kafka.consumer.properties.spring.json.trusted.packages=* 50 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/Keys.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.OrderItems; 8 | import com.sivalabs.bookstore.jooq.models.tables.Orders; 9 | import com.sivalabs.bookstore.jooq.models.tables.Products; 10 | import com.sivalabs.bookstore.jooq.models.tables.records.OrderItemsRecord; 11 | import com.sivalabs.bookstore.jooq.models.tables.records.OrdersRecord; 12 | import com.sivalabs.bookstore.jooq.models.tables.records.ProductsRecord; 13 | 14 | import org.jooq.ForeignKey; 15 | import org.jooq.TableField; 16 | import org.jooq.UniqueKey; 17 | import org.jooq.impl.DSL; 18 | import org.jooq.impl.Internal; 19 | 20 | 21 | /** 22 | * A class modelling foreign key relationships and constraints of tables in 23 | * public. 24 | */ 25 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 26 | public class Keys { 27 | 28 | // ------------------------------------------------------------------------- 29 | // UNIQUE and PRIMARY KEY definitions 30 | // ------------------------------------------------------------------------- 31 | 32 | public static final UniqueKey ORDER_ITEMS_PKEY = Internal.createUniqueKey(OrderItems.ORDER_ITEMS, DSL.name("order_items_pkey"), new TableField[] { OrderItems.ORDER_ITEMS.ID }, true); 33 | public static final UniqueKey ORDERS_PKEY = Internal.createUniqueKey(Orders.ORDERS, DSL.name("orders_pkey"), new TableField[] { Orders.ORDERS.ID }, true); 34 | public static final UniqueKey PRODUCTS_CODE_KEY = Internal.createUniqueKey(Products.PRODUCTS, DSL.name("products_code_key"), new TableField[] { Products.PRODUCTS.CODE }, true); 35 | public static final UniqueKey PRODUCTS_PKEY = Internal.createUniqueKey(Products.PRODUCTS, DSL.name("products_pkey"), new TableField[] { Products.PRODUCTS.ID }, true); 36 | 37 | // ------------------------------------------------------------------------- 38 | // FOREIGN KEY definitions 39 | // ------------------------------------------------------------------------- 40 | 41 | public static final ForeignKey ORDER_ITEMS__ORDER_ITEMS_ORDER_ID_FKEY = Internal.createForeignKey(OrderItems.ORDER_ITEMS, DSL.name("order_items_order_id_fkey"), new TableField[] { OrderItems.ORDER_ITEMS.ORDER_ID }, Keys.ORDERS_PKEY, new TableField[] { Orders.ORDERS.ID }, true); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/catalog/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import static com.sivalabs.bookstore.jooq.models.Tables.PRODUCTS; 4 | 5 | import com.sivalabs.bookstore.catalog.Product; 6 | import com.sivalabs.bookstore.common.models.PagedResult; 7 | import java.math.BigDecimal; 8 | import java.util.Optional; 9 | import org.jooq.DSLContext; 10 | import org.springframework.stereotype.Repository; 11 | 12 | @Repository 13 | class ProductRepository { 14 | private final DSLContext dsl; 15 | 16 | ProductRepository(DSLContext dsl) { 17 | this.dsl = dsl; 18 | } 19 | 20 | Optional findByCode(String code) { 21 | return dsl.selectFrom(PRODUCTS) 22 | .where(PRODUCTS.CODE.equalIgnoreCase(code)) 23 | .fetchOptional(p -> new Product( 24 | p.getId(), p.getCode(), p.getName(), p.getDescription(), p.getImageUrl(), p.getPrice())); 25 | } 26 | 27 | PagedResult getProducts(int pageNo, int pageSize) { 28 | long totalElements = dsl.fetchCount(PRODUCTS); 29 | int totalPages = (int) Math.ceil((double) totalElements / (double) pageSize); 30 | 31 | var products = dsl.selectFrom(PRODUCTS) 32 | .orderBy(PRODUCTS.NAME) 33 | .limit(pageSize) 34 | .offset((pageNo - 1) * pageSize) 35 | .fetch() 36 | .map(p -> new Product( 37 | p.getId(), p.getCode(), p.getName(), p.getDescription(), p.getImageUrl(), p.getPrice())); 38 | 39 | return new PagedResult<>( 40 | products, 41 | totalElements, 42 | pageNo, 43 | totalPages, 44 | pageNo == 1, 45 | totalPages == pageNo, 46 | totalPages > pageNo, 47 | pageNo > 1); 48 | } 49 | 50 | Long saveProduct(Product product) { 51 | return dsl.insertInto(PRODUCTS) 52 | .set(PRODUCTS.CODE, product.code()) 53 | .set(PRODUCTS.NAME, product.name()) 54 | .set(PRODUCTS.DESCRIPTION, product.description()) 55 | .set(PRODUCTS.IMAGE_URL, product.imageUrl()) 56 | .set(PRODUCTS.PRICE, product.price()) 57 | .returning(PRODUCTS.ID) 58 | .fetchOne(PRODUCTS.ID); 59 | } 60 | 61 | public void updatePrice(String code, BigDecimal price) { 62 | dsl.update(PRODUCTS) 63 | .set(PRODUCTS.PRICE, price) 64 | .where(PRODUCTS.CODE.equalIgnoreCase(code)) 65 | .execute(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/catalog/api/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.api; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.hamcrest.Matchers.hasSize; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 8 | 9 | import com.sivalabs.bookstore.AbstractIT; 10 | import com.sivalabs.bookstore.catalog.Product; 11 | import io.restassured.http.ContentType; 12 | import java.math.BigDecimal; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.modulith.test.ApplicationModuleTest; 15 | import org.springframework.test.context.jdbc.Sql; 16 | 17 | @ApplicationModuleTest(webEnvironment = RANDOM_PORT) 18 | @Sql("/test-products-data.sql") 19 | class ProductControllerTest extends AbstractIT { 20 | 21 | @Test 22 | void shouldGetAllProducts() { 23 | given().contentType(ContentType.JSON) 24 | .when() 25 | .get("/api/products") 26 | .then() 27 | .statusCode(200) 28 | .body("data", hasSize(10)) 29 | .body("totalElements", is(15)) 30 | .body("pageNumber", is(1)) 31 | .body("totalPages", is(2)) 32 | .body("isFirst", is(true)) 33 | .body("isLast", is(false)) 34 | .body("hasNext", is(true)) 35 | .body("hasPrevious", is(false)); 36 | } 37 | 38 | @Test 39 | void shouldGetProductByCode() { 40 | Product product = given().contentType(ContentType.JSON) 41 | .when() 42 | .get("/api/products/{code}", "P100") 43 | .then() 44 | .statusCode(200) 45 | .assertThat() 46 | .extract() 47 | .body() 48 | .as(Product.class); 49 | 50 | assertThat(product.code()).isEqualTo("P100"); 51 | assertThat(product.name()).isEqualTo("The Hunger Games"); 52 | assertThat(product.description()).isEqualTo("Winning will make you famous. Losing means certain death..."); 53 | assertThat(product.price()).isEqualTo(new BigDecimal("34.0")); 54 | } 55 | 56 | @Test 57 | void shouldReturnNotFoundWhenProductCodeNotExists() { 58 | String code = "invalid_product_code"; 59 | given().contentType(ContentType.JSON) 60 | .when() 61 | .get("/api/products/{code}", code) 62 | .then() 63 | .statusCode(404) 64 | .body("status", is(404)) 65 | .body("title", is("Product Not Found")) 66 | .body("detail", is("Product with code: " + code + " not found")); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V3__insert_products.sql: -------------------------------------------------------------------------------- 1 | insert into products(code, name, description, image_url, price) values 2 | ('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), 3 | ('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), 4 | ('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), 5 | ('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), 6 | ('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), 7 | ('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), 8 | ('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), 9 | ('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), 10 | ('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), 11 | ('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), 12 | ('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), 13 | ('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), 14 | ('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), 15 | ('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), 16 | ('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) 17 | ; -------------------------------------------------------------------------------- /src/test/resources/test-products-data.sql: -------------------------------------------------------------------------------- 1 | truncate table products; 2 | 3 | insert into products(code, name, description, image_url, price) values 4 | ('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), 5 | ('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), 6 | ('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), 7 | ('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), 8 | ('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), 9 | ('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), 10 | ('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), 11 | ('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), 12 | ('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), 13 | ('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), 14 | ('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), 15 | ('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), 16 | ('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), 17 | ('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), 18 | ('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) 19 | ; -------------------------------------------------------------------------------- /src/test/java/com/sivalabs/bookstore/orders/api/OrderControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.api; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static org.hamcrest.CoreMatchers.notNullValue; 6 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 7 | 8 | import com.sivalabs.bookstore.AbstractIT; 9 | import com.sivalabs.bookstore.catalog.CatalogAPI; 10 | import com.sivalabs.bookstore.catalog.Product; 11 | import com.sivalabs.bookstore.orders.CreateOrderResponse; 12 | import com.sivalabs.bookstore.orders.OrderDTO; 13 | import com.sivalabs.bookstore.orders.OrdersAPI; 14 | import io.restassured.http.ContentType; 15 | import java.math.BigDecimal; 16 | import java.util.Optional; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | import org.mockito.BDDMockito; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.test.mock.mockito.MockBean; 22 | import org.springframework.modulith.test.ApplicationModuleTest; 23 | import org.springframework.test.context.jdbc.Sql; 24 | 25 | @ApplicationModuleTest(webEnvironment = RANDOM_PORT) 26 | @Sql({"/test-orders-data.sql"}) 27 | class OrderControllerTests extends AbstractIT { 28 | @MockBean 29 | private CatalogAPI catalogAPI; 30 | 31 | @Autowired 32 | private OrdersAPI ordersAPI; 33 | 34 | String productCode = "P100"; 35 | String orderNumber = "order-123"; 36 | 37 | @BeforeEach 38 | void setUpMock() { 39 | 40 | BDDMockito.given(catalogAPI.findProductByCode(productCode)) 41 | .willReturn(Optional.of(new Product(1L, productCode, "", "", "", new BigDecimal("34.0")))); 42 | } 43 | 44 | @Test 45 | void shouldCreateOrderSuccessfully() { 46 | CreateOrderResponse createOrderResponse = given().contentType(ContentType.JSON) 47 | .body( 48 | """ 49 | 50 | { 51 | "customer" : { 52 | "name": "Siva", 53 | "email": "siva@gmail.com", 54 | "phone": "999999999" 55 | }, 56 | "deliveryAddress" : { 57 | "addressLine1": "KPHB", 58 | "addressLine2": "", 59 | "city": "Hyderabad", 60 | "state": "TS", 61 | "zipCode": "500072", 62 | "country": "India" 63 | }, 64 | "items": [ 65 | { 66 | "code": "%s", 67 | "name": "The Hunger Games", 68 | "price": 34.0, 69 | "quantity": 1 70 | } 71 | ] 72 | } 73 | """ 74 | .formatted(productCode)) 75 | .when() 76 | .post("/api/orders") 77 | .then() 78 | .statusCode(201) 79 | .body("orderNumber", notNullValue()) 80 | .extract() 81 | .body() 82 | .as(CreateOrderResponse.class); 83 | 84 | Optional orderOptional = ordersAPI.findOrder(createOrderResponse.orderNumber()); 85 | assertThat(orderOptional).isPresent(); 86 | // Add more assertions - Exercise for the reader :-) 87 | } 88 | 89 | @Test 90 | // TODO; Make it a parameterized test and pass different invalid Order payloads 91 | void shouldReturnBadRequestWhenMandatoryDataIsMissing() { 92 | given().contentType(ContentType.JSON) 93 | .body( 94 | """ 95 | { 96 | "customer" : { 97 | "name": "Siva", 98 | "email": "siva@gmail.com", 99 | "phone": "" 100 | }, 101 | "deliveryAddress" : { 102 | "addressLine1": "KPHB", 103 | "addressLine2": "", 104 | "city": "Hyderabad", 105 | "state": "TS", 106 | "zipCode": "500072", 107 | "country": "India" 108 | }, 109 | "items": [ 110 | { 111 | "code": "P100", 112 | "name": "The Hunger Games", 113 | "price": 34.00, 114 | "quantity": 1 115 | } 116 | ] 117 | } 118 | """) 119 | .when() 120 | .post("/api/orders") 121 | .then() 122 | .statusCode(400); 123 | } 124 | 125 | @Test 126 | void shouldGetOrderSuccessfully() { 127 | OrderDTO orderDTO = given().when() 128 | .get("/api/orders/{orderNumber}", orderNumber) 129 | .then() 130 | .statusCode(200) 131 | .extract() 132 | .body() 133 | .as(OrderDTO.class); 134 | 135 | assertThat(orderDTO.orderNumber()).isEqualTo(orderNumber); 136 | assertThat(orderDTO.items()).hasSize(2); 137 | // Add more assertions - Exercise for the reader :-) 138 | } 139 | 140 | @Test 141 | void shouldReturnNotFoundWhenOrderNumberNotExist() { 142 | given().when() 143 | .get("/api/orders/{orderNumber}", "non-existing-order-number") 144 | .then() 145 | .statusCode(404); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/Products.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.Keys; 8 | import com.sivalabs.bookstore.jooq.models.Public; 9 | import com.sivalabs.bookstore.jooq.models.tables.records.ProductsRecord; 10 | 11 | import java.math.BigDecimal; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.function.Function; 15 | 16 | import org.jooq.Field; 17 | import org.jooq.ForeignKey; 18 | import org.jooq.Function6; 19 | import org.jooq.Identity; 20 | import org.jooq.Name; 21 | import org.jooq.Record; 22 | import org.jooq.Records; 23 | import org.jooq.Row6; 24 | import org.jooq.Schema; 25 | import org.jooq.SelectField; 26 | import org.jooq.Table; 27 | import org.jooq.TableField; 28 | import org.jooq.TableOptions; 29 | import org.jooq.UniqueKey; 30 | import org.jooq.impl.DSL; 31 | import org.jooq.impl.SQLDataType; 32 | import org.jooq.impl.TableImpl; 33 | 34 | 35 | /** 36 | * This class is generated by jOOQ. 37 | */ 38 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 39 | public class Products extends TableImpl { 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | /** 44 | * The reference instance of public.products 45 | */ 46 | public static final Products PRODUCTS = new Products(); 47 | 48 | /** 49 | * The class holding records for this type 50 | */ 51 | @Override 52 | public Class getRecordType() { 53 | return ProductsRecord.class; 54 | } 55 | 56 | /** 57 | * The column public.products.id. 58 | */ 59 | public final TableField ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false).identity(true), this, ""); 60 | 61 | /** 62 | * The column public.products.code. 63 | */ 64 | public final TableField CODE = createField(DSL.name("code"), SQLDataType.VARCHAR.nullable(false), this, ""); 65 | 66 | /** 67 | * The column public.products.name. 68 | */ 69 | public final TableField NAME = createField(DSL.name("name"), SQLDataType.VARCHAR.nullable(false), this, ""); 70 | 71 | /** 72 | * The column public.products.description. 73 | */ 74 | public final TableField DESCRIPTION = createField(DSL.name("description"), SQLDataType.VARCHAR, this, ""); 75 | 76 | /** 77 | * The column public.products.image_url. 78 | */ 79 | public final TableField IMAGE_URL = createField(DSL.name("image_url"), SQLDataType.VARCHAR, this, ""); 80 | 81 | /** 82 | * The column public.products.price. 83 | */ 84 | public final TableField PRICE = createField(DSL.name("price"), SQLDataType.NUMERIC.nullable(false), this, ""); 85 | 86 | private Products(Name alias, Table aliased) { 87 | this(alias, aliased, null); 88 | } 89 | 90 | private Products(Name alias, Table aliased, Field[] parameters) { 91 | super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); 92 | } 93 | 94 | /** 95 | * Create an aliased public.products table reference 96 | */ 97 | public Products(String alias) { 98 | this(DSL.name(alias), PRODUCTS); 99 | } 100 | 101 | /** 102 | * Create an aliased public.products table reference 103 | */ 104 | public Products(Name alias) { 105 | this(alias, PRODUCTS); 106 | } 107 | 108 | /** 109 | * Create a public.products table reference 110 | */ 111 | public Products() { 112 | this(DSL.name("products"), null); 113 | } 114 | 115 | public Products(Table child, ForeignKey key) { 116 | super(child, key, PRODUCTS); 117 | } 118 | 119 | @Override 120 | public Schema getSchema() { 121 | return aliased() ? null : Public.PUBLIC; 122 | } 123 | 124 | @Override 125 | public Identity getIdentity() { 126 | return (Identity) super.getIdentity(); 127 | } 128 | 129 | @Override 130 | public UniqueKey getPrimaryKey() { 131 | return Keys.PRODUCTS_PKEY; 132 | } 133 | 134 | @Override 135 | public List> getUniqueKeys() { 136 | return Arrays.asList(Keys.PRODUCTS_CODE_KEY); 137 | } 138 | 139 | @Override 140 | public Products as(String alias) { 141 | return new Products(DSL.name(alias), this); 142 | } 143 | 144 | @Override 145 | public Products as(Name alias) { 146 | return new Products(alias, this); 147 | } 148 | 149 | @Override 150 | public Products as(Table alias) { 151 | return new Products(alias.getQualifiedName(), this); 152 | } 153 | 154 | /** 155 | * Rename this table 156 | */ 157 | @Override 158 | public Products rename(String name) { 159 | return new Products(DSL.name(name), null); 160 | } 161 | 162 | /** 163 | * Rename this table 164 | */ 165 | @Override 166 | public Products rename(Name name) { 167 | return new Products(name, null); 168 | } 169 | 170 | /** 171 | * Rename this table 172 | */ 173 | @Override 174 | public Products rename(Table name) { 175 | return new Products(name.getQualifiedName(), null); 176 | } 177 | 178 | // ------------------------------------------------------------------------- 179 | // Row6 type methods 180 | // ------------------------------------------------------------------------- 181 | 182 | @Override 183 | public Row6 fieldsRow() { 184 | return (Row6) super.fieldsRow(); 185 | } 186 | 187 | /** 188 | * Convenience mapping calling {@link SelectField#convertFrom(Function)}. 189 | */ 190 | public SelectField mapping(Function6 from) { 191 | return convertFrom(Records.mapping(from)); 192 | } 193 | 194 | /** 195 | * Convenience mapping calling {@link SelectField#convertFrom(Class, 196 | * Function)}. 197 | */ 198 | public SelectField mapping(Class toType, Function6 from) { 199 | return convertFrom(toType, Records.mapping(from)); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/OrderItems.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.Keys; 8 | import com.sivalabs.bookstore.jooq.models.Public; 9 | import com.sivalabs.bookstore.jooq.models.tables.records.OrderItemsRecord; 10 | 11 | import java.math.BigDecimal; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.function.Function; 15 | 16 | import org.jooq.Field; 17 | import org.jooq.ForeignKey; 18 | import org.jooq.Function6; 19 | import org.jooq.Identity; 20 | import org.jooq.Name; 21 | import org.jooq.Record; 22 | import org.jooq.Records; 23 | import org.jooq.Row6; 24 | import org.jooq.Schema; 25 | import org.jooq.SelectField; 26 | import org.jooq.Table; 27 | import org.jooq.TableField; 28 | import org.jooq.TableOptions; 29 | import org.jooq.UniqueKey; 30 | import org.jooq.impl.DSL; 31 | import org.jooq.impl.SQLDataType; 32 | import org.jooq.impl.TableImpl; 33 | 34 | 35 | /** 36 | * This class is generated by jOOQ. 37 | */ 38 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 39 | public class OrderItems extends TableImpl { 40 | 41 | private static final long serialVersionUID = 1L; 42 | 43 | /** 44 | * The reference instance of public.order_items 45 | */ 46 | public static final OrderItems ORDER_ITEMS = new OrderItems(); 47 | 48 | /** 49 | * The class holding records for this type 50 | */ 51 | @Override 52 | public Class getRecordType() { 53 | return OrderItemsRecord.class; 54 | } 55 | 56 | /** 57 | * The column public.order_items.id. 58 | */ 59 | public final TableField ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false).identity(true), this, ""); 60 | 61 | /** 62 | * The column public.order_items.code. 63 | */ 64 | public final TableField CODE = createField(DSL.name("code"), SQLDataType.VARCHAR.nullable(false), this, ""); 65 | 66 | /** 67 | * The column public.order_items.name. 68 | */ 69 | public final TableField NAME = createField(DSL.name("name"), SQLDataType.VARCHAR.nullable(false), this, ""); 70 | 71 | /** 72 | * The column public.order_items.price. 73 | */ 74 | public final TableField PRICE = createField(DSL.name("price"), SQLDataType.NUMERIC.nullable(false), this, ""); 75 | 76 | /** 77 | * The column public.order_items.quantity. 78 | */ 79 | public final TableField QUANTITY = createField(DSL.name("quantity"), SQLDataType.INTEGER.nullable(false), this, ""); 80 | 81 | /** 82 | * The column public.order_items.order_id. 83 | */ 84 | public final TableField ORDER_ID = createField(DSL.name("order_id"), SQLDataType.BIGINT.nullable(false), this, ""); 85 | 86 | private OrderItems(Name alias, Table aliased) { 87 | this(alias, aliased, null); 88 | } 89 | 90 | private OrderItems(Name alias, Table aliased, Field[] parameters) { 91 | super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); 92 | } 93 | 94 | /** 95 | * Create an aliased public.order_items table reference 96 | */ 97 | public OrderItems(String alias) { 98 | this(DSL.name(alias), ORDER_ITEMS); 99 | } 100 | 101 | /** 102 | * Create an aliased public.order_items table reference 103 | */ 104 | public OrderItems(Name alias) { 105 | this(alias, ORDER_ITEMS); 106 | } 107 | 108 | /** 109 | * Create a public.order_items table reference 110 | */ 111 | public OrderItems() { 112 | this(DSL.name("order_items"), null); 113 | } 114 | 115 | public OrderItems(Table child, ForeignKey key) { 116 | super(child, key, ORDER_ITEMS); 117 | } 118 | 119 | @Override 120 | public Schema getSchema() { 121 | return aliased() ? null : Public.PUBLIC; 122 | } 123 | 124 | @Override 125 | public Identity getIdentity() { 126 | return (Identity) super.getIdentity(); 127 | } 128 | 129 | @Override 130 | public UniqueKey getPrimaryKey() { 131 | return Keys.ORDER_ITEMS_PKEY; 132 | } 133 | 134 | @Override 135 | public List> getReferences() { 136 | return Arrays.asList(Keys.ORDER_ITEMS__ORDER_ITEMS_ORDER_ID_FKEY); 137 | } 138 | 139 | private transient Orders _orders; 140 | 141 | /** 142 | * Get the implicit join path to the public.orders table. 143 | */ 144 | public Orders orders() { 145 | if (_orders == null) 146 | _orders = new Orders(this, Keys.ORDER_ITEMS__ORDER_ITEMS_ORDER_ID_FKEY); 147 | 148 | return _orders; 149 | } 150 | 151 | @Override 152 | public OrderItems as(String alias) { 153 | return new OrderItems(DSL.name(alias), this); 154 | } 155 | 156 | @Override 157 | public OrderItems as(Name alias) { 158 | return new OrderItems(alias, this); 159 | } 160 | 161 | @Override 162 | public OrderItems as(Table alias) { 163 | return new OrderItems(alias.getQualifiedName(), this); 164 | } 165 | 166 | /** 167 | * Rename this table 168 | */ 169 | @Override 170 | public OrderItems rename(String name) { 171 | return new OrderItems(DSL.name(name), null); 172 | } 173 | 174 | /** 175 | * Rename this table 176 | */ 177 | @Override 178 | public OrderItems rename(Name name) { 179 | return new OrderItems(name, null); 180 | } 181 | 182 | /** 183 | * Rename this table 184 | */ 185 | @Override 186 | public OrderItems rename(Table name) { 187 | return new OrderItems(name.getQualifiedName(), null); 188 | } 189 | 190 | // ------------------------------------------------------------------------- 191 | // Row6 type methods 192 | // ------------------------------------------------------------------------- 193 | 194 | @Override 195 | public Row6 fieldsRow() { 196 | return (Row6) super.fieldsRow(); 197 | } 198 | 199 | /** 200 | * Convenience mapping calling {@link SelectField#convertFrom(Function)}. 201 | */ 202 | public SelectField mapping(Function6 from) { 203 | return convertFrom(Records.mapping(from)); 204 | } 205 | 206 | /** 207 | * Convenience mapping calling {@link SelectField#convertFrom(Class, 208 | * Function)}. 209 | */ 210 | public SelectField mapping(Class toType, Function6 from) { 211 | return convertFrom(toType, Records.mapping(from)); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /src/main/java/com/sivalabs/bookstore/orders/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import static com.sivalabs.bookstore.jooq.models.Tables.ORDERS; 4 | import static com.sivalabs.bookstore.jooq.models.Tables.ORDER_ITEMS; 5 | import static org.jooq.impl.DSL.multiset; 6 | import static org.jooq.impl.DSL.select; 7 | 8 | import com.sivalabs.bookstore.orders.Address; 9 | import com.sivalabs.bookstore.orders.CreateOrderRequest; 10 | import com.sivalabs.bookstore.orders.CreateOrderResponse; 11 | import com.sivalabs.bookstore.orders.Customer; 12 | import com.sivalabs.bookstore.orders.OrderDTO; 13 | import com.sivalabs.bookstore.orders.OrderItem; 14 | import com.sivalabs.bookstore.orders.OrderStatus; 15 | import java.time.LocalDateTime; 16 | import java.time.ZoneId; 17 | import java.util.List; 18 | import java.util.Optional; 19 | import java.util.UUID; 20 | import org.jooq.DSLContext; 21 | import org.jooq.Record14; 22 | import org.jooq.SelectJoinStep; 23 | import org.springframework.stereotype.Repository; 24 | 25 | @Repository 26 | class OrderRepository { 27 | private final DSLContext dsl; 28 | 29 | OrderRepository(DSLContext dsl) { 30 | this.dsl = dsl; 31 | } 32 | 33 | public CreateOrderResponse createOrder(CreateOrderRequest request) { 34 | var orderNumber = UUID.randomUUID().toString(); 35 | var orderId = dsl.insertInto(ORDERS) 36 | .set(ORDERS.ORDER_NUMBER, orderNumber) 37 | .set(ORDERS.CUSTOMER_NAME, request.customer().name()) 38 | .set(ORDERS.CUSTOMER_EMAIL, request.customer().email()) 39 | .set(ORDERS.CUSTOMER_PHONE, request.customer().phone()) 40 | .set(ORDERS.DELIVERY_ADDRESS_LINE1, request.deliveryAddress().addressLine1()) 41 | .set(ORDERS.DELIVERY_ADDRESS_LINE2, request.deliveryAddress().addressLine2()) 42 | .set(ORDERS.DELIVERY_ADDRESS_CITY, request.deliveryAddress().city()) 43 | .set(ORDERS.DELIVERY_ADDRESS_STATE, request.deliveryAddress().state()) 44 | .set(ORDERS.DELIVERY_ADDRESS_ZIP_CODE, request.deliveryAddress().zipCode()) 45 | .set(ORDERS.DELIVERY_ADDRESS_COUNTRY, request.deliveryAddress().country()) 46 | .set(ORDERS.STATUS, OrderStatus.NEW.name()) 47 | .set(ORDERS.CREATED_AT, LocalDateTime.now(ZoneId.systemDefault())) 48 | .returning(ORDERS.ID) 49 | .fetchOne(ORDERS.ID); 50 | 51 | request.items().forEach(item -> dsl.insertInto(ORDER_ITEMS) 52 | .set(ORDER_ITEMS.ORDER_ID, orderId) 53 | .set(ORDER_ITEMS.CODE, item.code()) 54 | .set(ORDER_ITEMS.NAME, item.name()) 55 | .set(ORDER_ITEMS.PRICE, item.price()) 56 | .set(ORDER_ITEMS.QUANTITY, item.quantity()) 57 | .execute()); 58 | 59 | return new CreateOrderResponse(orderNumber); 60 | } 61 | 62 | public void updateOrderStatus(String orderNumber, OrderStatus status, String comments) { 63 | dsl.update(ORDERS) 64 | .set(ORDERS.STATUS, status.name()) 65 | .set(ORDERS.COMMENTS, comments) 66 | .where(ORDERS.ORDER_NUMBER.eq(orderNumber)) 67 | .execute(); 68 | } 69 | 70 | public Optional findByOrderNumber(String orderNumber) { 71 | return selectOrderClause() 72 | .where(ORDERS.ORDER_NUMBER.eq(orderNumber)) 73 | .fetchOptional() 74 | .map(this::mapToOrderDTO); 75 | } 76 | 77 | public List findByStatus(OrderStatus status) { 78 | return selectOrderClause() 79 | .where(ORDERS.STATUS.eq(status.name())) 80 | .fetch() 81 | .map(this::mapToOrderDTO); 82 | } 83 | 84 | private SelectJoinStep< 85 | Record14< 86 | String, 87 | String, 88 | String, 89 | String, 90 | String, 91 | String, 92 | String, 93 | String, 94 | String, 95 | String, 96 | String, 97 | String, 98 | LocalDateTime, 99 | List>> 100 | selectOrderClause() { 101 | return dsl.select( 102 | ORDERS.ORDER_NUMBER, 103 | ORDERS.CUSTOMER_NAME, 104 | ORDERS.CUSTOMER_EMAIL, 105 | ORDERS.CUSTOMER_PHONE, 106 | ORDERS.DELIVERY_ADDRESS_LINE1, 107 | ORDERS.DELIVERY_ADDRESS_LINE2, 108 | ORDERS.DELIVERY_ADDRESS_CITY, 109 | ORDERS.DELIVERY_ADDRESS_STATE, 110 | ORDERS.DELIVERY_ADDRESS_ZIP_CODE, 111 | ORDERS.DELIVERY_ADDRESS_COUNTRY, 112 | ORDERS.STATUS, 113 | ORDERS.COMMENTS, 114 | ORDERS.CREATED_AT, 115 | multiset(select(ORDER_ITEMS.CODE, ORDER_ITEMS.NAME, ORDER_ITEMS.PRICE, ORDER_ITEMS.QUANTITY) 116 | .from(ORDER_ITEMS) 117 | .where(ORDER_ITEMS.ORDER_ID.eq(ORDERS.ID))) 118 | .as("items") 119 | .convertFrom(rs -> rs.map(it -> new OrderItem( 120 | it.get(ORDER_ITEMS.CODE), 121 | it.get(ORDER_ITEMS.NAME), 122 | it.get(ORDER_ITEMS.PRICE), 123 | it.get(ORDER_ITEMS.QUANTITY))))) 124 | .from(ORDERS); 125 | } 126 | 127 | private OrderDTO mapToOrderDTO( 128 | Record14< 129 | String, 130 | String, 131 | String, 132 | String, 133 | String, 134 | String, 135 | String, 136 | String, 137 | String, 138 | String, 139 | String, 140 | String, 141 | LocalDateTime, 142 | List> 143 | r) { 144 | return new OrderDTO( 145 | r.get(ORDERS.ORDER_NUMBER), 146 | r.value14(), 147 | new Customer(r.get(ORDERS.CUSTOMER_NAME), r.get(ORDERS.CUSTOMER_EMAIL), r.get(ORDERS.CUSTOMER_PHONE)), 148 | new Address( 149 | r.get(ORDERS.DELIVERY_ADDRESS_LINE1), 150 | r.get(ORDERS.DELIVERY_ADDRESS_LINE2), 151 | r.get(ORDERS.DELIVERY_ADDRESS_CITY), 152 | r.get(ORDERS.DELIVERY_ADDRESS_STATE), 153 | r.get(ORDERS.DELIVERY_ADDRESS_ZIP_CODE), 154 | r.get(ORDERS.DELIVERY_ADDRESS_COUNTRY)), 155 | OrderStatus.valueOf(r.get(ORDERS.STATUS)), 156 | r.get(ORDERS.COMMENTS), 157 | r.get(ORDERS.CREATED_AT)); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/records/ProductsRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables.records; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.Products; 8 | 9 | import java.math.BigDecimal; 10 | 11 | import org.jooq.Field; 12 | import org.jooq.Record1; 13 | import org.jooq.Record6; 14 | import org.jooq.Row6; 15 | import org.jooq.impl.UpdatableRecordImpl; 16 | 17 | 18 | /** 19 | * This class is generated by jOOQ. 20 | */ 21 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 22 | public class ProductsRecord extends UpdatableRecordImpl implements Record6 { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | /** 27 | * Setter for public.products.id. 28 | */ 29 | public void setId(Long value) { 30 | set(0, value); 31 | } 32 | 33 | /** 34 | * Getter for public.products.id. 35 | */ 36 | public Long getId() { 37 | return (Long) get(0); 38 | } 39 | 40 | /** 41 | * Setter for public.products.code. 42 | */ 43 | public void setCode(String value) { 44 | set(1, value); 45 | } 46 | 47 | /** 48 | * Getter for public.products.code. 49 | */ 50 | public String getCode() { 51 | return (String) get(1); 52 | } 53 | 54 | /** 55 | * Setter for public.products.name. 56 | */ 57 | public void setName(String value) { 58 | set(2, value); 59 | } 60 | 61 | /** 62 | * Getter for public.products.name. 63 | */ 64 | public String getName() { 65 | return (String) get(2); 66 | } 67 | 68 | /** 69 | * Setter for public.products.description. 70 | */ 71 | public void setDescription(String value) { 72 | set(3, value); 73 | } 74 | 75 | /** 76 | * Getter for public.products.description. 77 | */ 78 | public String getDescription() { 79 | return (String) get(3); 80 | } 81 | 82 | /** 83 | * Setter for public.products.image_url. 84 | */ 85 | public void setImageUrl(String value) { 86 | set(4, value); 87 | } 88 | 89 | /** 90 | * Getter for public.products.image_url. 91 | */ 92 | public String getImageUrl() { 93 | return (String) get(4); 94 | } 95 | 96 | /** 97 | * Setter for public.products.price. 98 | */ 99 | public void setPrice(BigDecimal value) { 100 | set(5, value); 101 | } 102 | 103 | /** 104 | * Getter for public.products.price. 105 | */ 106 | public BigDecimal getPrice() { 107 | return (BigDecimal) get(5); 108 | } 109 | 110 | // ------------------------------------------------------------------------- 111 | // Primary key information 112 | // ------------------------------------------------------------------------- 113 | 114 | @Override 115 | public Record1 key() { 116 | return (Record1) super.key(); 117 | } 118 | 119 | // ------------------------------------------------------------------------- 120 | // Record6 type implementation 121 | // ------------------------------------------------------------------------- 122 | 123 | @Override 124 | public Row6 fieldsRow() { 125 | return (Row6) super.fieldsRow(); 126 | } 127 | 128 | @Override 129 | public Row6 valuesRow() { 130 | return (Row6) super.valuesRow(); 131 | } 132 | 133 | @Override 134 | public Field field1() { 135 | return Products.PRODUCTS.ID; 136 | } 137 | 138 | @Override 139 | public Field field2() { 140 | return Products.PRODUCTS.CODE; 141 | } 142 | 143 | @Override 144 | public Field field3() { 145 | return Products.PRODUCTS.NAME; 146 | } 147 | 148 | @Override 149 | public Field field4() { 150 | return Products.PRODUCTS.DESCRIPTION; 151 | } 152 | 153 | @Override 154 | public Field field5() { 155 | return Products.PRODUCTS.IMAGE_URL; 156 | } 157 | 158 | @Override 159 | public Field field6() { 160 | return Products.PRODUCTS.PRICE; 161 | } 162 | 163 | @Override 164 | public Long component1() { 165 | return getId(); 166 | } 167 | 168 | @Override 169 | public String component2() { 170 | return getCode(); 171 | } 172 | 173 | @Override 174 | public String component3() { 175 | return getName(); 176 | } 177 | 178 | @Override 179 | public String component4() { 180 | return getDescription(); 181 | } 182 | 183 | @Override 184 | public String component5() { 185 | return getImageUrl(); 186 | } 187 | 188 | @Override 189 | public BigDecimal component6() { 190 | return getPrice(); 191 | } 192 | 193 | @Override 194 | public Long value1() { 195 | return getId(); 196 | } 197 | 198 | @Override 199 | public String value2() { 200 | return getCode(); 201 | } 202 | 203 | @Override 204 | public String value3() { 205 | return getName(); 206 | } 207 | 208 | @Override 209 | public String value4() { 210 | return getDescription(); 211 | } 212 | 213 | @Override 214 | public String value5() { 215 | return getImageUrl(); 216 | } 217 | 218 | @Override 219 | public BigDecimal value6() { 220 | return getPrice(); 221 | } 222 | 223 | @Override 224 | public ProductsRecord value1(Long value) { 225 | setId(value); 226 | return this; 227 | } 228 | 229 | @Override 230 | public ProductsRecord value2(String value) { 231 | setCode(value); 232 | return this; 233 | } 234 | 235 | @Override 236 | public ProductsRecord value3(String value) { 237 | setName(value); 238 | return this; 239 | } 240 | 241 | @Override 242 | public ProductsRecord value4(String value) { 243 | setDescription(value); 244 | return this; 245 | } 246 | 247 | @Override 248 | public ProductsRecord value5(String value) { 249 | setImageUrl(value); 250 | return this; 251 | } 252 | 253 | @Override 254 | public ProductsRecord value6(BigDecimal value) { 255 | setPrice(value); 256 | return this; 257 | } 258 | 259 | @Override 260 | public ProductsRecord values(Long value1, String value2, String value3, String value4, String value5, BigDecimal value6) { 261 | value1(value1); 262 | value2(value2); 263 | value3(value3); 264 | value4(value4); 265 | value5(value5); 266 | value6(value6); 267 | return this; 268 | } 269 | 270 | // ------------------------------------------------------------------------- 271 | // Constructors 272 | // ------------------------------------------------------------------------- 273 | 274 | /** 275 | * Create a detached ProductsRecord 276 | */ 277 | public ProductsRecord() { 278 | super(Products.PRODUCTS); 279 | } 280 | 281 | /** 282 | * Create a detached, initialised ProductsRecord 283 | */ 284 | public ProductsRecord(Long id, String code, String name, String description, String imageUrl, BigDecimal price) { 285 | super(Products.PRODUCTS); 286 | 287 | setId(id); 288 | setCode(code); 289 | setName(name); 290 | setDescription(description); 291 | setImageUrl(imageUrl); 292 | setPrice(price); 293 | resetChangedOnNotNull(); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/records/OrderItemsRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables.records; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.OrderItems; 8 | 9 | import java.math.BigDecimal; 10 | 11 | import org.jooq.Field; 12 | import org.jooq.Record1; 13 | import org.jooq.Record6; 14 | import org.jooq.Row6; 15 | import org.jooq.impl.UpdatableRecordImpl; 16 | 17 | 18 | /** 19 | * This class is generated by jOOQ. 20 | */ 21 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 22 | public class OrderItemsRecord extends UpdatableRecordImpl implements Record6 { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | /** 27 | * Setter for public.order_items.id. 28 | */ 29 | public void setId(Long value) { 30 | set(0, value); 31 | } 32 | 33 | /** 34 | * Getter for public.order_items.id. 35 | */ 36 | public Long getId() { 37 | return (Long) get(0); 38 | } 39 | 40 | /** 41 | * Setter for public.order_items.code. 42 | */ 43 | public void setCode(String value) { 44 | set(1, value); 45 | } 46 | 47 | /** 48 | * Getter for public.order_items.code. 49 | */ 50 | public String getCode() { 51 | return (String) get(1); 52 | } 53 | 54 | /** 55 | * Setter for public.order_items.name. 56 | */ 57 | public void setName(String value) { 58 | set(2, value); 59 | } 60 | 61 | /** 62 | * Getter for public.order_items.name. 63 | */ 64 | public String getName() { 65 | return (String) get(2); 66 | } 67 | 68 | /** 69 | * Setter for public.order_items.price. 70 | */ 71 | public void setPrice(BigDecimal value) { 72 | set(3, value); 73 | } 74 | 75 | /** 76 | * Getter for public.order_items.price. 77 | */ 78 | public BigDecimal getPrice() { 79 | return (BigDecimal) get(3); 80 | } 81 | 82 | /** 83 | * Setter for public.order_items.quantity. 84 | */ 85 | public void setQuantity(Integer value) { 86 | set(4, value); 87 | } 88 | 89 | /** 90 | * Getter for public.order_items.quantity. 91 | */ 92 | public Integer getQuantity() { 93 | return (Integer) get(4); 94 | } 95 | 96 | /** 97 | * Setter for public.order_items.order_id. 98 | */ 99 | public void setOrderId(Long value) { 100 | set(5, value); 101 | } 102 | 103 | /** 104 | * Getter for public.order_items.order_id. 105 | */ 106 | public Long getOrderId() { 107 | return (Long) get(5); 108 | } 109 | 110 | // ------------------------------------------------------------------------- 111 | // Primary key information 112 | // ------------------------------------------------------------------------- 113 | 114 | @Override 115 | public Record1 key() { 116 | return (Record1) super.key(); 117 | } 118 | 119 | // ------------------------------------------------------------------------- 120 | // Record6 type implementation 121 | // ------------------------------------------------------------------------- 122 | 123 | @Override 124 | public Row6 fieldsRow() { 125 | return (Row6) super.fieldsRow(); 126 | } 127 | 128 | @Override 129 | public Row6 valuesRow() { 130 | return (Row6) super.valuesRow(); 131 | } 132 | 133 | @Override 134 | public Field field1() { 135 | return OrderItems.ORDER_ITEMS.ID; 136 | } 137 | 138 | @Override 139 | public Field field2() { 140 | return OrderItems.ORDER_ITEMS.CODE; 141 | } 142 | 143 | @Override 144 | public Field field3() { 145 | return OrderItems.ORDER_ITEMS.NAME; 146 | } 147 | 148 | @Override 149 | public Field field4() { 150 | return OrderItems.ORDER_ITEMS.PRICE; 151 | } 152 | 153 | @Override 154 | public Field field5() { 155 | return OrderItems.ORDER_ITEMS.QUANTITY; 156 | } 157 | 158 | @Override 159 | public Field field6() { 160 | return OrderItems.ORDER_ITEMS.ORDER_ID; 161 | } 162 | 163 | @Override 164 | public Long component1() { 165 | return getId(); 166 | } 167 | 168 | @Override 169 | public String component2() { 170 | return getCode(); 171 | } 172 | 173 | @Override 174 | public String component3() { 175 | return getName(); 176 | } 177 | 178 | @Override 179 | public BigDecimal component4() { 180 | return getPrice(); 181 | } 182 | 183 | @Override 184 | public Integer component5() { 185 | return getQuantity(); 186 | } 187 | 188 | @Override 189 | public Long component6() { 190 | return getOrderId(); 191 | } 192 | 193 | @Override 194 | public Long value1() { 195 | return getId(); 196 | } 197 | 198 | @Override 199 | public String value2() { 200 | return getCode(); 201 | } 202 | 203 | @Override 204 | public String value3() { 205 | return getName(); 206 | } 207 | 208 | @Override 209 | public BigDecimal value4() { 210 | return getPrice(); 211 | } 212 | 213 | @Override 214 | public Integer value5() { 215 | return getQuantity(); 216 | } 217 | 218 | @Override 219 | public Long value6() { 220 | return getOrderId(); 221 | } 222 | 223 | @Override 224 | public OrderItemsRecord value1(Long value) { 225 | setId(value); 226 | return this; 227 | } 228 | 229 | @Override 230 | public OrderItemsRecord value2(String value) { 231 | setCode(value); 232 | return this; 233 | } 234 | 235 | @Override 236 | public OrderItemsRecord value3(String value) { 237 | setName(value); 238 | return this; 239 | } 240 | 241 | @Override 242 | public OrderItemsRecord value4(BigDecimal value) { 243 | setPrice(value); 244 | return this; 245 | } 246 | 247 | @Override 248 | public OrderItemsRecord value5(Integer value) { 249 | setQuantity(value); 250 | return this; 251 | } 252 | 253 | @Override 254 | public OrderItemsRecord value6(Long value) { 255 | setOrderId(value); 256 | return this; 257 | } 258 | 259 | @Override 260 | public OrderItemsRecord values(Long value1, String value2, String value3, BigDecimal value4, Integer value5, Long value6) { 261 | value1(value1); 262 | value2(value2); 263 | value3(value3); 264 | value4(value4); 265 | value5(value5); 266 | value6(value6); 267 | return this; 268 | } 269 | 270 | // ------------------------------------------------------------------------- 271 | // Constructors 272 | // ------------------------------------------------------------------------- 273 | 274 | /** 275 | * Create a detached OrderItemsRecord 276 | */ 277 | public OrderItemsRecord() { 278 | super(OrderItems.ORDER_ITEMS); 279 | } 280 | 281 | /** 282 | * Create a detached, initialised OrderItemsRecord 283 | */ 284 | public OrderItemsRecord(Long id, String code, String name, BigDecimal price, Integer quantity, Long orderId) { 285 | super(OrderItems.ORDER_ITEMS); 286 | 287 | setId(id); 288 | setCode(code); 289 | setName(name); 290 | setPrice(price); 291 | setQuantity(quantity); 292 | setOrderId(orderId); 293 | resetChangedOnNotNull(); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/Orders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.Keys; 8 | import com.sivalabs.bookstore.jooq.models.Public; 9 | import com.sivalabs.bookstore.jooq.models.tables.records.OrdersRecord; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.function.Function; 13 | 14 | import org.jooq.Field; 15 | import org.jooq.ForeignKey; 16 | import org.jooq.Function15; 17 | import org.jooq.Identity; 18 | import org.jooq.Name; 19 | import org.jooq.Record; 20 | import org.jooq.Records; 21 | import org.jooq.Row15; 22 | import org.jooq.Schema; 23 | import org.jooq.SelectField; 24 | import org.jooq.Table; 25 | import org.jooq.TableField; 26 | import org.jooq.TableOptions; 27 | import org.jooq.UniqueKey; 28 | import org.jooq.impl.DSL; 29 | import org.jooq.impl.SQLDataType; 30 | import org.jooq.impl.TableImpl; 31 | 32 | 33 | /** 34 | * This class is generated by jOOQ. 35 | */ 36 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 37 | public class Orders extends TableImpl { 38 | 39 | private static final long serialVersionUID = 1L; 40 | 41 | /** 42 | * The reference instance of public.orders 43 | */ 44 | public static final Orders ORDERS = new Orders(); 45 | 46 | /** 47 | * The class holding records for this type 48 | */ 49 | @Override 50 | public Class getRecordType() { 51 | return OrdersRecord.class; 52 | } 53 | 54 | /** 55 | * The column public.orders.id. 56 | */ 57 | public final TableField ID = createField(DSL.name("id"), SQLDataType.BIGINT.nullable(false).identity(true), this, ""); 58 | 59 | /** 60 | * The column public.orders.order_number. 61 | */ 62 | public final TableField ORDER_NUMBER = createField(DSL.name("order_number"), SQLDataType.VARCHAR.nullable(false), this, ""); 63 | 64 | /** 65 | * The column public.orders.customer_name. 66 | */ 67 | public final TableField CUSTOMER_NAME = createField(DSL.name("customer_name"), SQLDataType.VARCHAR.nullable(false), this, ""); 68 | 69 | /** 70 | * The column public.orders.customer_email. 71 | */ 72 | public final TableField CUSTOMER_EMAIL = createField(DSL.name("customer_email"), SQLDataType.VARCHAR.nullable(false), this, ""); 73 | 74 | /** 75 | * The column public.orders.customer_phone. 76 | */ 77 | public final TableField CUSTOMER_PHONE = createField(DSL.name("customer_phone"), SQLDataType.VARCHAR.nullable(false), this, ""); 78 | 79 | /** 80 | * The column public.orders.delivery_address_line1. 81 | */ 82 | public final TableField DELIVERY_ADDRESS_LINE1 = createField(DSL.name("delivery_address_line1"), SQLDataType.VARCHAR.nullable(false), this, ""); 83 | 84 | /** 85 | * The column public.orders.delivery_address_line2. 86 | */ 87 | public final TableField DELIVERY_ADDRESS_LINE2 = createField(DSL.name("delivery_address_line2"), SQLDataType.VARCHAR, this, ""); 88 | 89 | /** 90 | * The column public.orders.delivery_address_city. 91 | */ 92 | public final TableField DELIVERY_ADDRESS_CITY = createField(DSL.name("delivery_address_city"), SQLDataType.VARCHAR.nullable(false), this, ""); 93 | 94 | /** 95 | * The column public.orders.delivery_address_state. 96 | */ 97 | public final TableField DELIVERY_ADDRESS_STATE = createField(DSL.name("delivery_address_state"), SQLDataType.VARCHAR.nullable(false), this, ""); 98 | 99 | /** 100 | * The column public.orders.delivery_address_zip_code. 101 | */ 102 | public final TableField DELIVERY_ADDRESS_ZIP_CODE = createField(DSL.name("delivery_address_zip_code"), SQLDataType.VARCHAR.nullable(false), this, ""); 103 | 104 | /** 105 | * The column public.orders.delivery_address_country. 106 | */ 107 | public final TableField DELIVERY_ADDRESS_COUNTRY = createField(DSL.name("delivery_address_country"), SQLDataType.VARCHAR.nullable(false), this, ""); 108 | 109 | /** 110 | * The column public.orders.status. 111 | */ 112 | public final TableField STATUS = createField(DSL.name("status"), SQLDataType.VARCHAR.nullable(false), this, ""); 113 | 114 | /** 115 | * The column public.orders.comments. 116 | */ 117 | public final TableField COMMENTS = createField(DSL.name("comments"), SQLDataType.CLOB, this, ""); 118 | 119 | /** 120 | * The column public.orders.created_at. 121 | */ 122 | public final TableField CREATED_AT = createField(DSL.name("created_at"), SQLDataType.LOCALDATETIME(6), this, ""); 123 | 124 | /** 125 | * The column public.orders.updated_at. 126 | */ 127 | public final TableField UPDATED_AT = createField(DSL.name("updated_at"), SQLDataType.LOCALDATETIME(6), this, ""); 128 | 129 | private Orders(Name alias, Table aliased) { 130 | this(alias, aliased, null); 131 | } 132 | 133 | private Orders(Name alias, Table aliased, Field[] parameters) { 134 | super(alias, null, aliased, parameters, DSL.comment(""), TableOptions.table()); 135 | } 136 | 137 | /** 138 | * Create an aliased public.orders table reference 139 | */ 140 | public Orders(String alias) { 141 | this(DSL.name(alias), ORDERS); 142 | } 143 | 144 | /** 145 | * Create an aliased public.orders table reference 146 | */ 147 | public Orders(Name alias) { 148 | this(alias, ORDERS); 149 | } 150 | 151 | /** 152 | * Create a public.orders table reference 153 | */ 154 | public Orders() { 155 | this(DSL.name("orders"), null); 156 | } 157 | 158 | public Orders(Table child, ForeignKey key) { 159 | super(child, key, ORDERS); 160 | } 161 | 162 | @Override 163 | public Schema getSchema() { 164 | return aliased() ? null : Public.PUBLIC; 165 | } 166 | 167 | @Override 168 | public Identity getIdentity() { 169 | return (Identity) super.getIdentity(); 170 | } 171 | 172 | @Override 173 | public UniqueKey getPrimaryKey() { 174 | return Keys.ORDERS_PKEY; 175 | } 176 | 177 | @Override 178 | public Orders as(String alias) { 179 | return new Orders(DSL.name(alias), this); 180 | } 181 | 182 | @Override 183 | public Orders as(Name alias) { 184 | return new Orders(alias, this); 185 | } 186 | 187 | @Override 188 | public Orders as(Table alias) { 189 | return new Orders(alias.getQualifiedName(), this); 190 | } 191 | 192 | /** 193 | * Rename this table 194 | */ 195 | @Override 196 | public Orders rename(String name) { 197 | return new Orders(DSL.name(name), null); 198 | } 199 | 200 | /** 201 | * Rename this table 202 | */ 203 | @Override 204 | public Orders rename(Name name) { 205 | return new Orders(name, null); 206 | } 207 | 208 | /** 209 | * Rename this table 210 | */ 211 | @Override 212 | public Orders rename(Table name) { 213 | return new Orders(name.getQualifiedName(), null); 214 | } 215 | 216 | // ------------------------------------------------------------------------- 217 | // Row15 type methods 218 | // ------------------------------------------------------------------------- 219 | 220 | @Override 221 | public Row15 fieldsRow() { 222 | return (Row15) super.fieldsRow(); 223 | } 224 | 225 | /** 226 | * Convenience mapping calling {@link SelectField#convertFrom(Function)}. 227 | */ 228 | public SelectField mapping(Function15 from) { 229 | return convertFrom(Records.mapping(from)); 230 | } 231 | 232 | /** 233 | * Convenience mapping calling {@link SelectField#convertFrom(Class, 234 | * Function)}. 235 | */ 236 | public SelectField mapping(Class toType, Function15 from) { 237 | return convertFrom(toType, Records.mapping(from)); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.3.1 11 | 12 | 13 | com.sivalabs 14 | tomato-architecture-spring-boot-demo 15 | 0.0.1-SNAPSHOT 16 | tomato-architecture-spring-boot-demo 17 | 18 | 19 | UTF-8 20 | UTF-8 21 | 21 22 | 1.2.0 23 | 2.5.0 24 | 2.43.0 25 | 0.0.4 26 | sivaprasadreddy/${project.artifactId} 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-validation 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-jooq 40 | 41 | 42 | org.postgresql 43 | postgresql 44 | runtime 45 | 46 | 47 | org.flywaydb 48 | flyway-core 49 | 50 | 51 | org.flywaydb 52 | flyway-database-postgresql 53 | 54 | 55 | org.springframework.kafka 56 | spring-kafka 57 | 58 | 59 | org.springdoc 60 | springdoc-openapi-starter-webmvc-ui 61 | ${springdoc-openapi.version} 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-actuator 66 | 67 | 68 | org.springframework.modulith 69 | spring-modulith-starter-core 70 | 71 | 72 | org.springframework.modulith 73 | spring-modulith-actuator 74 | runtime 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-devtools 79 | runtime 80 | true 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-docker-compose 85 | runtime 86 | true 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-configuration-processor 91 | true 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-test 96 | test 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-testcontainers 101 | test 102 | 103 | 104 | org.springframework.kafka 105 | spring-kafka-test 106 | test 107 | 108 | 109 | org.springframework.modulith 110 | spring-modulith-starter-test 111 | test 112 | 113 | 114 | org.testcontainers 115 | junit-jupiter 116 | test 117 | 118 | 119 | org.testcontainers 120 | kafka 121 | test 122 | 123 | 124 | org.testcontainers 125 | postgresql 126 | test 127 | 128 | 129 | io.rest-assured 130 | rest-assured 131 | test 132 | 133 | 134 | org.awaitility 135 | awaitility 136 | test 137 | 138 | 139 | 140 | 141 | 142 | 143 | org.springframework.modulith 144 | spring-modulith-bom 145 | ${spring-modulith.version} 146 | pom 147 | import 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | org.springframework.boot 156 | spring-boot-maven-plugin 157 | 158 | 159 | dashaun/builder:tiny 160 | ${dockerImageName} 161 | 162 | 163 | 164 | 165 | 166 | build-info 167 | 168 | 169 | 170 | 171 | 172 | org.testcontainers 173 | testcontainers-jooq-codegen-maven-plugin 174 | ${tc-jooq-codegen-plugin.version} 175 | 176 | 177 | org.testcontainers 178 | postgresql 179 | ${testcontainers.version} 180 | 181 | 182 | org.postgresql 183 | postgresql 184 | ${postgresql.version} 185 | 186 | 187 | 188 | 189 | generate-jooq-sources 190 | 191 | generate 192 | 193 | generate-sources 194 | 195 | 196 | POSTGRES 197 | postgres:16-alpine 198 | 199 | 200 | 201 | filesystem:${project.basedir}/src/main/resources/db/migration 202 | 203 | 204 | 205 | 206 | 207 | 211 | true 212 | false 213 | 214 | 215 | public 216 | .* 217 | flyway_schema_history 218 | 230 | 231 | 232 | 248 | 249 | true 250 | com.sivalabs.bookstore.jooq.models 251 | src/main/jooq 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | org.codehaus.mojo 261 | build-helper-maven-plugin 262 | 263 | 264 | 265 | add-source 266 | 267 | generate-sources 268 | 269 | 270 | src/main/jooq 271 | 272 | 273 | 274 | 275 | 276 | 277 | com.diffplug.spotless 278 | spotless-maven-plugin 279 | ${spotless.version} 280 | 281 | 282 | 283 | 284 | 285 | 2.39.0 286 | 287 | 288 | 289 | 290 | 291 | 292 | compile 293 | 294 | check 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /src/main/jooq/com/sivalabs/bookstore/jooq/models/tables/records/OrdersRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is generated by jOOQ. 3 | */ 4 | package com.sivalabs.bookstore.jooq.models.tables.records; 5 | 6 | 7 | import com.sivalabs.bookstore.jooq.models.tables.Orders; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | import org.jooq.Field; 12 | import org.jooq.Record1; 13 | import org.jooq.Record15; 14 | import org.jooq.Row15; 15 | import org.jooq.impl.UpdatableRecordImpl; 16 | 17 | 18 | /** 19 | * This class is generated by jOOQ. 20 | */ 21 | @SuppressWarnings({ "all", "unchecked", "rawtypes" }) 22 | public class OrdersRecord extends UpdatableRecordImpl implements Record15 { 23 | 24 | private static final long serialVersionUID = 1L; 25 | 26 | /** 27 | * Setter for public.orders.id. 28 | */ 29 | public void setId(Long value) { 30 | set(0, value); 31 | } 32 | 33 | /** 34 | * Getter for public.orders.id. 35 | */ 36 | public Long getId() { 37 | return (Long) get(0); 38 | } 39 | 40 | /** 41 | * Setter for public.orders.order_number. 42 | */ 43 | public void setOrderNumber(String value) { 44 | set(1, value); 45 | } 46 | 47 | /** 48 | * Getter for public.orders.order_number. 49 | */ 50 | public String getOrderNumber() { 51 | return (String) get(1); 52 | } 53 | 54 | /** 55 | * Setter for public.orders.customer_name. 56 | */ 57 | public void setCustomerName(String value) { 58 | set(2, value); 59 | } 60 | 61 | /** 62 | * Getter for public.orders.customer_name. 63 | */ 64 | public String getCustomerName() { 65 | return (String) get(2); 66 | } 67 | 68 | /** 69 | * Setter for public.orders.customer_email. 70 | */ 71 | public void setCustomerEmail(String value) { 72 | set(3, value); 73 | } 74 | 75 | /** 76 | * Getter for public.orders.customer_email. 77 | */ 78 | public String getCustomerEmail() { 79 | return (String) get(3); 80 | } 81 | 82 | /** 83 | * Setter for public.orders.customer_phone. 84 | */ 85 | public void setCustomerPhone(String value) { 86 | set(4, value); 87 | } 88 | 89 | /** 90 | * Getter for public.orders.customer_phone. 91 | */ 92 | public String getCustomerPhone() { 93 | return (String) get(4); 94 | } 95 | 96 | /** 97 | * Setter for public.orders.delivery_address_line1. 98 | */ 99 | public void setDeliveryAddressLine1(String value) { 100 | set(5, value); 101 | } 102 | 103 | /** 104 | * Getter for public.orders.delivery_address_line1. 105 | */ 106 | public String getDeliveryAddressLine1() { 107 | return (String) get(5); 108 | } 109 | 110 | /** 111 | * Setter for public.orders.delivery_address_line2. 112 | */ 113 | public void setDeliveryAddressLine2(String value) { 114 | set(6, value); 115 | } 116 | 117 | /** 118 | * Getter for public.orders.delivery_address_line2. 119 | */ 120 | public String getDeliveryAddressLine2() { 121 | return (String) get(6); 122 | } 123 | 124 | /** 125 | * Setter for public.orders.delivery_address_city. 126 | */ 127 | public void setDeliveryAddressCity(String value) { 128 | set(7, value); 129 | } 130 | 131 | /** 132 | * Getter for public.orders.delivery_address_city. 133 | */ 134 | public String getDeliveryAddressCity() { 135 | return (String) get(7); 136 | } 137 | 138 | /** 139 | * Setter for public.orders.delivery_address_state. 140 | */ 141 | public void setDeliveryAddressState(String value) { 142 | set(8, value); 143 | } 144 | 145 | /** 146 | * Getter for public.orders.delivery_address_state. 147 | */ 148 | public String getDeliveryAddressState() { 149 | return (String) get(8); 150 | } 151 | 152 | /** 153 | * Setter for public.orders.delivery_address_zip_code. 154 | */ 155 | public void setDeliveryAddressZipCode(String value) { 156 | set(9, value); 157 | } 158 | 159 | /** 160 | * Getter for public.orders.delivery_address_zip_code. 161 | */ 162 | public String getDeliveryAddressZipCode() { 163 | return (String) get(9); 164 | } 165 | 166 | /** 167 | * Setter for public.orders.delivery_address_country. 168 | */ 169 | public void setDeliveryAddressCountry(String value) { 170 | set(10, value); 171 | } 172 | 173 | /** 174 | * Getter for public.orders.delivery_address_country. 175 | */ 176 | public String getDeliveryAddressCountry() { 177 | return (String) get(10); 178 | } 179 | 180 | /** 181 | * Setter for public.orders.status. 182 | */ 183 | public void setStatus(String value) { 184 | set(11, value); 185 | } 186 | 187 | /** 188 | * Getter for public.orders.status. 189 | */ 190 | public String getStatus() { 191 | return (String) get(11); 192 | } 193 | 194 | /** 195 | * Setter for public.orders.comments. 196 | */ 197 | public void setComments(String value) { 198 | set(12, value); 199 | } 200 | 201 | /** 202 | * Getter for public.orders.comments. 203 | */ 204 | public String getComments() { 205 | return (String) get(12); 206 | } 207 | 208 | /** 209 | * Setter for public.orders.created_at. 210 | */ 211 | public void setCreatedAt(LocalDateTime value) { 212 | set(13, value); 213 | } 214 | 215 | /** 216 | * Getter for public.orders.created_at. 217 | */ 218 | public LocalDateTime getCreatedAt() { 219 | return (LocalDateTime) get(13); 220 | } 221 | 222 | /** 223 | * Setter for public.orders.updated_at. 224 | */ 225 | public void setUpdatedAt(LocalDateTime value) { 226 | set(14, value); 227 | } 228 | 229 | /** 230 | * Getter for public.orders.updated_at. 231 | */ 232 | public LocalDateTime getUpdatedAt() { 233 | return (LocalDateTime) get(14); 234 | } 235 | 236 | // ------------------------------------------------------------------------- 237 | // Primary key information 238 | // ------------------------------------------------------------------------- 239 | 240 | @Override 241 | public Record1 key() { 242 | return (Record1) super.key(); 243 | } 244 | 245 | // ------------------------------------------------------------------------- 246 | // Record15 type implementation 247 | // ------------------------------------------------------------------------- 248 | 249 | @Override 250 | public Row15 fieldsRow() { 251 | return (Row15) super.fieldsRow(); 252 | } 253 | 254 | @Override 255 | public Row15 valuesRow() { 256 | return (Row15) super.valuesRow(); 257 | } 258 | 259 | @Override 260 | public Field field1() { 261 | return Orders.ORDERS.ID; 262 | } 263 | 264 | @Override 265 | public Field field2() { 266 | return Orders.ORDERS.ORDER_NUMBER; 267 | } 268 | 269 | @Override 270 | public Field field3() { 271 | return Orders.ORDERS.CUSTOMER_NAME; 272 | } 273 | 274 | @Override 275 | public Field field4() { 276 | return Orders.ORDERS.CUSTOMER_EMAIL; 277 | } 278 | 279 | @Override 280 | public Field field5() { 281 | return Orders.ORDERS.CUSTOMER_PHONE; 282 | } 283 | 284 | @Override 285 | public Field field6() { 286 | return Orders.ORDERS.DELIVERY_ADDRESS_LINE1; 287 | } 288 | 289 | @Override 290 | public Field field7() { 291 | return Orders.ORDERS.DELIVERY_ADDRESS_LINE2; 292 | } 293 | 294 | @Override 295 | public Field field8() { 296 | return Orders.ORDERS.DELIVERY_ADDRESS_CITY; 297 | } 298 | 299 | @Override 300 | public Field field9() { 301 | return Orders.ORDERS.DELIVERY_ADDRESS_STATE; 302 | } 303 | 304 | @Override 305 | public Field field10() { 306 | return Orders.ORDERS.DELIVERY_ADDRESS_ZIP_CODE; 307 | } 308 | 309 | @Override 310 | public Field field11() { 311 | return Orders.ORDERS.DELIVERY_ADDRESS_COUNTRY; 312 | } 313 | 314 | @Override 315 | public Field field12() { 316 | return Orders.ORDERS.STATUS; 317 | } 318 | 319 | @Override 320 | public Field field13() { 321 | return Orders.ORDERS.COMMENTS; 322 | } 323 | 324 | @Override 325 | public Field field14() { 326 | return Orders.ORDERS.CREATED_AT; 327 | } 328 | 329 | @Override 330 | public Field field15() { 331 | return Orders.ORDERS.UPDATED_AT; 332 | } 333 | 334 | @Override 335 | public Long component1() { 336 | return getId(); 337 | } 338 | 339 | @Override 340 | public String component2() { 341 | return getOrderNumber(); 342 | } 343 | 344 | @Override 345 | public String component3() { 346 | return getCustomerName(); 347 | } 348 | 349 | @Override 350 | public String component4() { 351 | return getCustomerEmail(); 352 | } 353 | 354 | @Override 355 | public String component5() { 356 | return getCustomerPhone(); 357 | } 358 | 359 | @Override 360 | public String component6() { 361 | return getDeliveryAddressLine1(); 362 | } 363 | 364 | @Override 365 | public String component7() { 366 | return getDeliveryAddressLine2(); 367 | } 368 | 369 | @Override 370 | public String component8() { 371 | return getDeliveryAddressCity(); 372 | } 373 | 374 | @Override 375 | public String component9() { 376 | return getDeliveryAddressState(); 377 | } 378 | 379 | @Override 380 | public String component10() { 381 | return getDeliveryAddressZipCode(); 382 | } 383 | 384 | @Override 385 | public String component11() { 386 | return getDeliveryAddressCountry(); 387 | } 388 | 389 | @Override 390 | public String component12() { 391 | return getStatus(); 392 | } 393 | 394 | @Override 395 | public String component13() { 396 | return getComments(); 397 | } 398 | 399 | @Override 400 | public LocalDateTime component14() { 401 | return getCreatedAt(); 402 | } 403 | 404 | @Override 405 | public LocalDateTime component15() { 406 | return getUpdatedAt(); 407 | } 408 | 409 | @Override 410 | public Long value1() { 411 | return getId(); 412 | } 413 | 414 | @Override 415 | public String value2() { 416 | return getOrderNumber(); 417 | } 418 | 419 | @Override 420 | public String value3() { 421 | return getCustomerName(); 422 | } 423 | 424 | @Override 425 | public String value4() { 426 | return getCustomerEmail(); 427 | } 428 | 429 | @Override 430 | public String value5() { 431 | return getCustomerPhone(); 432 | } 433 | 434 | @Override 435 | public String value6() { 436 | return getDeliveryAddressLine1(); 437 | } 438 | 439 | @Override 440 | public String value7() { 441 | return getDeliveryAddressLine2(); 442 | } 443 | 444 | @Override 445 | public String value8() { 446 | return getDeliveryAddressCity(); 447 | } 448 | 449 | @Override 450 | public String value9() { 451 | return getDeliveryAddressState(); 452 | } 453 | 454 | @Override 455 | public String value10() { 456 | return getDeliveryAddressZipCode(); 457 | } 458 | 459 | @Override 460 | public String value11() { 461 | return getDeliveryAddressCountry(); 462 | } 463 | 464 | @Override 465 | public String value12() { 466 | return getStatus(); 467 | } 468 | 469 | @Override 470 | public String value13() { 471 | return getComments(); 472 | } 473 | 474 | @Override 475 | public LocalDateTime value14() { 476 | return getCreatedAt(); 477 | } 478 | 479 | @Override 480 | public LocalDateTime value15() { 481 | return getUpdatedAt(); 482 | } 483 | 484 | @Override 485 | public OrdersRecord value1(Long value) { 486 | setId(value); 487 | return this; 488 | } 489 | 490 | @Override 491 | public OrdersRecord value2(String value) { 492 | setOrderNumber(value); 493 | return this; 494 | } 495 | 496 | @Override 497 | public OrdersRecord value3(String value) { 498 | setCustomerName(value); 499 | return this; 500 | } 501 | 502 | @Override 503 | public OrdersRecord value4(String value) { 504 | setCustomerEmail(value); 505 | return this; 506 | } 507 | 508 | @Override 509 | public OrdersRecord value5(String value) { 510 | setCustomerPhone(value); 511 | return this; 512 | } 513 | 514 | @Override 515 | public OrdersRecord value6(String value) { 516 | setDeliveryAddressLine1(value); 517 | return this; 518 | } 519 | 520 | @Override 521 | public OrdersRecord value7(String value) { 522 | setDeliveryAddressLine2(value); 523 | return this; 524 | } 525 | 526 | @Override 527 | public OrdersRecord value8(String value) { 528 | setDeliveryAddressCity(value); 529 | return this; 530 | } 531 | 532 | @Override 533 | public OrdersRecord value9(String value) { 534 | setDeliveryAddressState(value); 535 | return this; 536 | } 537 | 538 | @Override 539 | public OrdersRecord value10(String value) { 540 | setDeliveryAddressZipCode(value); 541 | return this; 542 | } 543 | 544 | @Override 545 | public OrdersRecord value11(String value) { 546 | setDeliveryAddressCountry(value); 547 | return this; 548 | } 549 | 550 | @Override 551 | public OrdersRecord value12(String value) { 552 | setStatus(value); 553 | return this; 554 | } 555 | 556 | @Override 557 | public OrdersRecord value13(String value) { 558 | setComments(value); 559 | return this; 560 | } 561 | 562 | @Override 563 | public OrdersRecord value14(LocalDateTime value) { 564 | setCreatedAt(value); 565 | return this; 566 | } 567 | 568 | @Override 569 | public OrdersRecord value15(LocalDateTime value) { 570 | setUpdatedAt(value); 571 | return this; 572 | } 573 | 574 | @Override 575 | public OrdersRecord values(Long value1, String value2, String value3, String value4, String value5, String value6, String value7, String value8, String value9, String value10, String value11, String value12, String value13, LocalDateTime value14, LocalDateTime value15) { 576 | value1(value1); 577 | value2(value2); 578 | value3(value3); 579 | value4(value4); 580 | value5(value5); 581 | value6(value6); 582 | value7(value7); 583 | value8(value8); 584 | value9(value9); 585 | value10(value10); 586 | value11(value11); 587 | value12(value12); 588 | value13(value13); 589 | value14(value14); 590 | value15(value15); 591 | return this; 592 | } 593 | 594 | // ------------------------------------------------------------------------- 595 | // Constructors 596 | // ------------------------------------------------------------------------- 597 | 598 | /** 599 | * Create a detached OrdersRecord 600 | */ 601 | public OrdersRecord() { 602 | super(Orders.ORDERS); 603 | } 604 | 605 | /** 606 | * Create a detached, initialised OrdersRecord 607 | */ 608 | public OrdersRecord(Long id, String orderNumber, String customerName, String customerEmail, String customerPhone, String deliveryAddressLine1, String deliveryAddressLine2, String deliveryAddressCity, String deliveryAddressState, String deliveryAddressZipCode, String deliveryAddressCountry, String status, String comments, LocalDateTime createdAt, LocalDateTime updatedAt) { 609 | super(Orders.ORDERS); 610 | 611 | setId(id); 612 | setOrderNumber(orderNumber); 613 | setCustomerName(customerName); 614 | setCustomerEmail(customerEmail); 615 | setCustomerPhone(customerPhone); 616 | setDeliveryAddressLine1(deliveryAddressLine1); 617 | setDeliveryAddressLine2(deliveryAddressLine2); 618 | setDeliveryAddressCity(deliveryAddressCity); 619 | setDeliveryAddressState(deliveryAddressState); 620 | setDeliveryAddressZipCode(deliveryAddressZipCode); 621 | setDeliveryAddressCountry(deliveryAddressCountry); 622 | setStatus(status); 623 | setComments(comments); 624 | setCreatedAt(createdAt); 625 | setUpdatedAt(updatedAt); 626 | resetChangedOnNotNull(); 627 | } 628 | } 629 | --------------------------------------------------------------------------------