├── media ├── orders.png ├── customer.png ├── products.png ├── categories.png ├── db-diagram.png └── sushi-uml.png ├── src ├── main │ ├── java │ │ └── com │ │ │ └── sushi │ │ │ └── api │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── login │ │ │ │ │ ├── LoginResponseDTO.java │ │ │ │ │ ├── RegisterResponseDTO.java │ │ │ │ │ ├── LoginRequestDTO.java │ │ │ │ │ └── RegisterRequestDTO.java │ │ │ │ ├── phone │ │ │ │ │ └── PhoneDTO.java │ │ │ │ ├── category │ │ │ │ │ ├── CategoryRequestDTO.java │ │ │ │ │ └── CategoryUpdateDTO.java │ │ │ │ ├── order_item │ │ │ │ │ ├── OrderItemRequestDTO.java │ │ │ │ │ └── OrderItemUpdateDTO.java │ │ │ │ ├── employee │ │ │ │ │ ├── EmployeeRequestDTO.java │ │ │ │ │ └── EmployeeUpdateDTO.java │ │ │ │ ├── order │ │ │ │ │ ├── OrderUpdateDTO.java │ │ │ │ │ └── OrderRequestDTO.java │ │ │ │ ├── address │ │ │ │ │ └── AddressDTO.java │ │ │ │ ├── customer │ │ │ │ │ ├── CustomerRequestDTO.java │ │ │ │ │ └── CustomerUpdateDTO.java │ │ │ │ └── product │ │ │ │ │ ├── ProductRequestDTO.java │ │ │ │ │ └── ProductUpdateDTO.java │ │ │ ├── Phone.java │ │ │ ├── Employee.java │ │ │ ├── Category.java │ │ │ ├── OrderItem.java │ │ │ ├── Customer.java │ │ │ ├── Address.java │ │ │ ├── Order.java │ │ │ └── Product.java │ │ │ ├── repositories │ │ │ ├── OrderRepository.java │ │ │ ├── OrderItemRepository.java │ │ │ ├── AddressRepository.java │ │ │ ├── EmployeeRepository.java │ │ │ ├── ProductRepository.java │ │ │ ├── CategoryRepository.java │ │ │ └── CustomerRepository.java │ │ │ ├── exceptions │ │ │ ├── BadRequestException.java │ │ │ ├── ResourceNotFoundException.java │ │ │ └── handler │ │ │ │ ├── ValidationExceptionDetails.java │ │ │ │ ├── ExceptionResponse.java │ │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── Application.java │ │ │ ├── config │ │ │ └── WebConfig.java │ │ │ ├── security │ │ │ ├── SecurityFilter.java │ │ │ ├── TokenService.java │ │ │ └── SecurityConfig.java │ │ │ ├── services │ │ │ ├── EmployeeService.java │ │ │ ├── CategoryService.java │ │ │ ├── ProductService.java │ │ │ ├── AuthService.java │ │ │ ├── OrderService.java │ │ │ └── CustomerService.java │ │ │ └── controllers │ │ │ ├── AuthController.java │ │ │ ├── OrderController.java │ │ │ ├── ProductController.java │ │ │ ├── EmployeeController.java │ │ │ ├── CustomerController.java │ │ │ └── CategoryController.java │ └── resources │ │ ├── application.properties │ │ ├── db │ │ └── migration │ │ │ └── V1__create_tables.sql │ │ └── data.sql └── test │ └── java │ └── com │ └── sushi │ └── api │ ├── common │ ├── AuthConstants.java │ ├── ProductConstants.java │ ├── CategoryConstants.java │ ├── CustomerControllerConstants.java │ ├── EmployeeConstants.java │ ├── OrderConstants.java │ └── CustomerConstants.java │ ├── controllers │ ├── AuthControllerTest.java │ ├── OrderControllerTest.java │ ├── ProductControllerTest.java │ ├── EmployeeControllerTest.java │ └── CategoryControllerTest.java │ └── services │ ├── OrderServiceTest.java │ ├── CategoryServiceTest.java │ └── EmployeeServiceTest.java ├── Dockerfile ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── README.md ├── pom.xml └── mvnw.cmd /media/orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/orders.png -------------------------------------------------------------------------------- /media/customer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/customer.png -------------------------------------------------------------------------------- /media/products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/products.png -------------------------------------------------------------------------------- /media/categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/categories.png -------------------------------------------------------------------------------- /media/db-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/db-diagram.png -------------------------------------------------------------------------------- /media/sushi-uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isinhah/api-pedidos-sushi/HEAD/media/sushi-uml.png -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/login/LoginResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.login; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | 5 | @Schema(name = "Login Response DTO", description = "Response DTO for user login") 6 | public record LoginResponseDTO(String name, String token, String expiresAt) { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/login/RegisterResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.login; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | 5 | @Schema(name = "Register Response DTO", description = "Response DTO for user registration") 6 | public record RegisterResponseDTO(String name, String token, String expiresAt) { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Order; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface OrderRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest AS build 2 | 3 | RUN apt-get update 4 | RUN apt-get install openjdk-17-jdk -y 5 | COPY . . 6 | 7 | RUN apt-get install maven -y 8 | RUN mvn clean install 9 | 10 | FROM openjdk:17-jdk-slim 11 | 12 | EXPOSE 8080 13 | 14 | COPY --from=build /target/api-0.0.1-SNAPSHOT.jar app.jar 15 | 16 | ENTRYPOINT ["java", "-jar", "app.jar"] -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/OrderItemRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.OrderItem; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface OrderItemRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/AddressRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Address; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.UUID; 8 | 9 | @Repository 10 | public interface AddressRepository extends JpaRepository { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Employee; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface EmployeeRepository extends JpaRepository { 10 | Optional findByEmail(String email); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/exceptions/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | public class BadRequestException extends RuntimeException { 8 | public BadRequestException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/exceptions/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | public ResourceNotFoundException(String message) { 9 | super(message); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Product; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface ProductRepository extends JpaRepository { 11 | List findByNameContainingIgnoreCase(String name); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/phone/PhoneDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.phone; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | @Schema(name = "Phone DTO", description = "DTO for phone number") 7 | public record PhoneDTO( 8 | @Schema(description = "Phone number", example = "1234567890") 9 | @NotBlank(message = "Phone number cannot be null") 10 | String number 11 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Category; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | 10 | @Repository 11 | public interface CategoryRepository extends JpaRepository { 12 | List findByNameContainingIgnoreCase(String name); 13 | } -------------------------------------------------------------------------------- /.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/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Database Configuration 2 | spring.datasource.driver-class-name=org.postgresql.Driver 3 | spring.datasource.url=${DATABASE_URL:jdbc:postgresql://localhost:5432/sushi_api} 4 | spring.datasource.username=${DATABASE_USERNAME:postgres} 5 | spring.datasource.password=${DATABASE_PASSWORD:admin} 6 | 7 | # Schema Initialization 8 | spring.jpa.hibernate.ddl-auto=none 9 | 10 | # JWT 11 | api.security.token.secret=my-secret-key 12 | 13 | # CORS 14 | cors.allowed.origins=http://localhost:8080,https://sushi-ordering-system.onrender.com/ -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/AuthConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.dto.login.LoginRequestDTO; 4 | import com.sushi.api.model.dto.login.RegisterRequestDTO; 5 | 6 | import static com.sushi.api.common.CustomerConstants.EMAIL; 7 | import static com.sushi.api.common.CustomerConstants.PASSWORD; 8 | 9 | public class AuthConstants { 10 | public static final LoginRequestDTO LOGIN_REQUEST_DTO = new LoginRequestDTO(EMAIL, PASSWORD); 11 | public static final RegisterRequestDTO REGISTER_REQUEST_DTO = new RegisterRequestDTO("Mario", "mario@gmail.com", PASSWORD); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/login/LoginRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.login; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | @Schema(name = "Login Request DTO", description = "DTO for user login request") 7 | public record LoginRequestDTO( 8 | @Schema(description = "The user's email", example = "ana@example.com") 9 | @NotBlank(message = "Email is mandatory") 10 | String email, 11 | 12 | @Schema(description = "The user's password", example = "123") 13 | @NotBlank(message = "Password is mandatory") 14 | String password 15 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/Application.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.info.Info; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | @OpenAPIDefinition(info = @Info( 10 | title = "Sushi Ordering System", 11 | description = "API REST for managing sushi orders, customers, employees, food categories, products and menu items")) 12 | public class Application { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/category/CategoryRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.category; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | @Schema(name = "Category Request DTO", description = "DTO for creating a category") 7 | public record CategoryRequestDTO( 8 | @Schema(description = "The category name", example = "Japanese desserts") 9 | @NotBlank(message = "Name cannot be blank") 10 | String name, 11 | @Schema(description = "The category description", example = "Various types of desserts") 12 | @NotBlank(message = "Description cannot be blank") 13 | String description 14 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/repositories/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.repositories; 2 | 3 | import com.sushi.api.model.Customer; 4 | import com.sushi.api.model.Phone; 5 | import com.sushi.api.model.dto.phone.PhoneDTO; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.data.repository.query.Param; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.UUID; 14 | 15 | @Repository 16 | public interface CustomerRepository extends JpaRepository { 17 | List findByNameContainingIgnoreCase(String name); 18 | Optional findByEmail(String email); 19 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/login/RegisterRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.login; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | @Schema(name = "Register Request DTO", description = "DTO for register a user") 7 | public record RegisterRequestDTO( 8 | @Schema(description = "The user's name", example = "Clara") 9 | @NotBlank(message = "Name is mandatory") String name, 10 | 11 | @Schema(description = "The user's email", example = "clara@example.com") 12 | @NotBlank(message = "Email is mandatory") String email, 13 | 14 | @Schema(description = "The user's password", example = "123") 15 | @NotBlank(message = "Password is mandatory") String password) { 16 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/order_item/OrderItemRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.order_item; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Positive; 6 | 7 | @Schema(name = "Order Item Request DTO", description = "request order item DTO") 8 | public record OrderItemRequestDTO( 9 | @Schema(description = "The unique identifier of the product", example = "1") 10 | @NotNull(message = "Product ID cannot be null") 11 | Long productId, 12 | 13 | @Schema(description = "The quantity of the product", example = "2") 14 | @NotNull(message = "Quantity cannot be null") 15 | @Positive(message = "Quantity must be greater than zero") 16 | Integer quantity 17 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/exceptions/handler/ValidationExceptionDetails.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.exceptions.handler; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public class ValidationExceptionDetails extends ExceptionResponse { 6 | private final String fields; 7 | private final String fieldsMessage; 8 | 9 | public ValidationExceptionDetails(String title, int status, String details, String developerMessage, LocalDateTime timestamp, String fields, String fieldsMessage) { 10 | super(title, status, details, developerMessage, timestamp); 11 | this.fields = fields; 12 | this.fieldsMessage = fieldsMessage; 13 | } 14 | 15 | public String getFields() { 16 | return fields; 17 | } 18 | 19 | public String getFieldsMessage() { 20 | return fieldsMessage; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/employee/EmployeeRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.employee; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | @Schema(name = "Employee Request DTO", description = "DTO for creating an employee") 7 | public record EmployeeRequestDTO( 8 | @Schema(description = "The employee's name", example = "Laura") 9 | @NotBlank(message = "Name cannot be blank") 10 | String name, 11 | @Schema(description = "The employee's email", example = "laura@example.com") 12 | @NotBlank(message = "Email cannot be blank") 13 | String email, 14 | @Schema(description = "The employee's password", example = "123") 15 | @NotBlank(message = "Password cannot be blank") 16 | String password 17 | ) { 18 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/category/CategoryUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.category; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | @Schema(name = "Category Update DTO", description = "DTO for updating a category") 8 | public record CategoryUpdateDTO( 9 | @Schema(description = "The unique identifier of the category", example = "1") 10 | @NotNull(message = "ID cannot be null") 11 | Long id, 12 | @Schema(description = "The category name", example = "Japanese desserts") 13 | @NotBlank(message = "Name cannot be blank") 14 | String name, 15 | @Schema(description = "The category description", example = "Various types of desserts") 16 | @NotBlank(message = "Description cannot be blank") 17 | String description 18 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/order_item/OrderItemUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.order_item; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Positive; 6 | 7 | @Schema(name = "Order Item Update DTO", description = "update order item DTO") 8 | public record OrderItemUpdateDTO( 9 | @Schema(description = "The unique identifier of the order item", example = "1") 10 | Long id, 11 | @Schema(description = "The unique identifier of the product", example = "1") 12 | @NotNull(message = "Product ID cannot be null") 13 | Long productId, 14 | 15 | @Schema(description = "The quantity of the product", example = "2") 16 | @NotNull(message = "Quantity cannot be null") 17 | @Positive(message = "Quantity must be greater than zero") 18 | Integer quantity 19 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/order/OrderUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.order; 2 | 3 | import com.sushi.api.model.dto.order_item.OrderItemUpdateDTO; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.Valid; 6 | import jakarta.validation.constraints.NotNull; 7 | 8 | import java.util.List; 9 | 10 | @Schema(name = "Order Update DTO", description = "DTO for updating a order") 11 | public record OrderUpdateDTO( 12 | @Schema(description = "The unique identifier of the order", example = "1") 13 | @NotNull(message = "Order ID cannot be null") 14 | Long id, 15 | 16 | @Schema(description = "ID of the delivery address", example = "1") 17 | @NotNull(message = "Delivery Address ID cannot be null") 18 | Long deliveryAddressId, 19 | 20 | @Schema(description = "Order items") 21 | @NotNull(message = "Order items cannot be null") 22 | List<@Valid OrderItemUpdateDTO> items 23 | ) {} -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/ProductConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Product; 4 | 5 | import java.util.List; 6 | 7 | import static com.sushi.api.common.CategoryConstants.CATEGORIES_FOR_PRODUCTS; 8 | 9 | public class ProductConstants { 10 | public static final Product PRODUCT = new Product(1L, "California Roll", 11 | "A delicious roll made with crab meat, avocado, and cucumber.", 12 | 8.99, 13 | 8, 14 | "pieces", 15 | "http://example.com/images/california_roll.jpg", CATEGORIES_FOR_PRODUCTS); 16 | public static final Product PRODUCT2 = new Product(2L, "Spicy Tuna Roll", 17 | "A flavorful roll made with spicy tuna, cucumber, and a hint of sriracha.", 18 | 10.49, 19 | 6, 20 | "pieces", 21 | "http://example.com/images/spicy_tuna_roll.jpg", CATEGORIES_FOR_PRODUCTS); 22 | public static final List PRODUCTS = List.of(PRODUCT, PRODUCT2); 23 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/address/AddressDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.address; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | 7 | @Schema(name = "Address DTO", description = "DTO for Address") 8 | public record AddressDTO( 9 | @Schema(description = "The address number", example = "123") 10 | @NotBlank(message = "Address number cannot be blank") 11 | String number, 12 | 13 | @Schema(description = "The street name", example = "Avenida São João") 14 | @NotBlank(message = "Street cannot be blank") 15 | @Size(max = 255, message = "Street must be less than 255 characters") 16 | String street, 17 | 18 | @Schema(description = "The neighborhood name", example = "Centro") 19 | @NotBlank(message = "Neighborhood cannot be blank") 20 | @Size(max = 255, message = "Neighborhood must be less than 255 characters") 21 | String neighborhood 22 | ) {} -------------------------------------------------------------------------------- /.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 | # https://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/sushi/api/model/dto/order/OrderRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.order; 2 | 3 | import com.sushi.api.model.dto.order_item.OrderItemRequestDTO; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import jakarta.validation.Valid; 6 | import jakarta.validation.constraints.NotNull; 7 | 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | @Schema(name = "Order Request DTO", description = "DTO for creating a order") 12 | public record OrderRequestDTO( 13 | @Schema(description = "ID of the customer placing the order", example = "d13c75d3-e7ed-4b6a-8f89-cc7a1d4a2c76") 14 | @NotNull(message = "Customer ID cannot be null") 15 | UUID customerId, 16 | 17 | @Schema(description = "ID of the delivery address", example = "123") 18 | @NotNull(message = "Delivery Address ID cannot be null") 19 | Long deliveryAddressId, 20 | 21 | @Schema(description = "Order items") 22 | @NotNull(message = "Order items cannot be null") 23 | List<@Valid OrderItemRequestDTO> items 24 | ) {} -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/CategoryConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Category; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | public class CategoryConstants { 9 | public static final Category CATEGORY = new Category(1L, "Sushi Tradicional", "Uma seleção dos sushis tradicionais mais populares, incluindo nigiri, maki e sashimi, preparados com ingredientes frescos e autênticos."); 10 | public static final Category CATEGORY2 = new Category(2L, "Comida chinesa", "Uma seleção dos pratos chineses tradicionais mais populares, incluindo Pato de Pequim, Frango Kung Pao e Carne com Brócolis, preparados com receitas autênticas."); 11 | public static final Category CATEGORY3 = new Category(3L, "Sushi Fusion", "Inovadores e deliciosos sushis de fusão, combinando sabores tradicionais japoneses com ingredientes modernos e internacionais para uma experiência única."); 12 | public static final List CATEGORIES = List.of(CATEGORY, CATEGORY2, CATEGORY3); 13 | public static final Set CATEGORIES_FOR_PRODUCTS = Set.of(CATEGORY, CATEGORY2); 14 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/employee/EmployeeUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.employee; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.util.UUID; 8 | 9 | @Schema(name = "Employee Update DTO", description = "DTO for updating an employee") 10 | public record EmployeeUpdateDTO( 11 | @Schema(description = "The unique identifier of the employee", example = "550e7200-e29b-41d4-a716-446655440000") 12 | @NotNull(message = "ID cannot be null") 13 | UUID id, 14 | @Schema(description = "The employee's name", example = "Laura Silva") 15 | @NotBlank(message = "Name cannot be blank") 16 | String name, 17 | @Schema(description = "The employee's email", example = "laura@example.com") 18 | @NotBlank(message = "Email cannot be blank") 19 | String email, 20 | @Schema(description = "The employee's password", example = "123") 21 | @NotBlank(message = "Password cannot be blank") 22 | String password 23 | ) { 24 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/CustomerControllerConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Customer; 4 | import com.sushi.api.model.dto.customer.CustomerRequestDTO; 5 | import com.sushi.api.model.dto.customer.CustomerUpdateDTO; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.UUID; 10 | 11 | import static com.sushi.api.common.CustomerConstants.*; 12 | 13 | public class CustomerControllerConstants { 14 | public static final Customer CUSTOMER_WITH_ADDRESS = new Customer(UUID.randomUUID(), "isabel", "isabel@gmail.com", "1234", PHONE); 15 | public static final List CUSTOMERS_WITH_ADDRESS = List.of(CUSTOMER_WITH_ADDRESS); 16 | static { 17 | CUSTOMER_WITH_ADDRESS.setAddresses(Set.of((ADDRESS))); 18 | } 19 | 20 | public static final CustomerRequestDTO CUSTOMER_REQUEST_DTO = new CustomerRequestDTO("isabel", "isabel@gmail.com", "1234", PHONE_DTO, Set.of(ADDRESS_DTO)); 21 | public static final CustomerUpdateDTO CUSTOMER_UPDATE_DTO = new CustomerUpdateDTO(UUID.randomUUID(), "isabel", "isabel@gmail.com", "1234", PHONE_DTO, Set.of(ADDRESS_DTO)); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/customer/CustomerRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.customer; 2 | 3 | import com.sushi.api.model.dto.address.AddressDTO; 4 | import com.sushi.api.model.dto.phone.PhoneDTO; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.Valid; 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.NotNull; 9 | 10 | import java.util.Set; 11 | 12 | @Schema(name = "Customer Request DTO", description = "DTO for creating a customer") 13 | public record CustomerRequestDTO( 14 | @Schema(description = "The customer's name", example = "Alice") 15 | @NotBlank(message = "Name cannot be blank") 16 | String name, 17 | @Schema(description = "The customer's email", example = "alice@example.com") 18 | @NotBlank(message = "Email cannot be blank") 19 | String email, 20 | @Schema(description = "The customer's password", example = "123") 21 | @NotBlank(message = "Password cannot be blank") 22 | String password, 23 | @Schema(description = "The customer's phone number", example = "123456789") 24 | @NotNull(message = "Phone cannot be null") 25 | @Valid 26 | PhoneDTO phone, 27 | @Schema(description = "The customer's address") 28 | @NotNull(message = "Address cannot be null") 29 | Set<@Valid AddressDTO> addresses 30 | ) {} -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/EmployeeConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Employee; 4 | import com.sushi.api.model.dto.employee.EmployeeRequestDTO; 5 | import com.sushi.api.model.dto.employee.EmployeeUpdateDTO; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | 10 | import static com.sushi.api.common.CustomerConstants.EMAIL; 11 | import static com.sushi.api.common.CustomerConstants.PASSWORD; 12 | 13 | public class EmployeeConstants { 14 | public static final Employee EMPLOYEE_LOGIN = new Employee(UUID.randomUUID(), "ana", EMAIL, PASSWORD); 15 | public static final Employee EMPLOYEE = new Employee(UUID.randomUUID(), "isabel", "isabel@gmail.com", "1234"); 16 | public static final Employee EMPLOYEE2 = new Employee(UUID.randomUUID(), "joao", "joao@gmail.com", "1234"); 17 | public static final Employee EMPLOYEE3 = new Employee(UUID.randomUUID(), "maria", "maria@gmail.com", "1234"); 18 | public static final Employee EMPLOYEE4 = new Employee(UUID.randomUUID(), "mariana", "mariana@gmail.com", "1234"); 19 | public static final List EMPLOYEES = List.of(EMPLOYEE2, EMPLOYEE3, EMPLOYEE4); 20 | public static final EmployeeRequestDTO EMPLOYEE_REQUEST_DTO = new EmployeeRequestDTO("isabel", "isabel@gmail.com", "1234"); 21 | public static final EmployeeUpdateDTO EMPLOYEE_UPDATE_DTO = new EmployeeUpdateDTO(UUID.randomUUID(), "isabel", "isabel@gmail.com", "1234"); 22 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; 7 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | public class WebConfig implements WebMvcConfigurer { 12 | 13 | @Value("${cors.allowed.origins}") 14 | private String[] allowedOrigins; 15 | 16 | @Override 17 | public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 18 | configurer.favorParameter(true) 19 | .parameterName("mediaType") 20 | .ignoreAcceptHeader(true) 21 | .useRegisteredExtensionsOnly(false) 22 | .defaultContentType(MediaType.APPLICATION_JSON) 23 | .mediaType("json", MediaType.APPLICATION_JSON) 24 | .mediaType("xml", MediaType.APPLICATION_XML); 25 | } 26 | 27 | @Override 28 | public void addCorsMappings(CorsRegistry registry) { 29 | registry.addMapping("/**") 30 | .allowedOrigins(allowedOrigins) 31 | .allowedMethods("*") 32 | .allowCredentials(true); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/OrderConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Order; 4 | import com.sushi.api.model.OrderItem; 5 | import com.sushi.api.model.dto.order.OrderRequestDTO; 6 | import com.sushi.api.model.dto.order.OrderUpdateDTO; 7 | import com.sushi.api.model.dto.order_item.OrderItemRequestDTO; 8 | import com.sushi.api.model.dto.order_item.OrderItemUpdateDTO; 9 | 10 | import java.util.List; 11 | 12 | import static com.sushi.api.common.CustomerConstants.ADDRESS; 13 | import static com.sushi.api.common.CustomerConstants.CUSTOMER; 14 | import static com.sushi.api.common.ProductConstants.PRODUCT; 15 | 16 | public class OrderConstants { 17 | public static final List ITEMS = List.of(new OrderItem(1L, 2, 8.99)); 18 | public static final OrderItem ORDER_ITEM = new OrderItem(1L, 1, 10.00); 19 | 20 | public static final Order ORDER = new Order(1L, CUSTOMER, ADDRESS, ITEMS); 21 | public static final List ORDERS = List.of(ORDER); 22 | 23 | public static final OrderItemRequestDTO ORDER_ITEM_REQUEST_DTO = new OrderItemRequestDTO(PRODUCT.getId(), 2); 24 | public static final OrderItemUpdateDTO ORDER_ITEM_UPDATE_DTO = new OrderItemUpdateDTO(1L, PRODUCT.getId(), 2); 25 | public static final OrderRequestDTO ORDER_REQUEST_DTO = new OrderRequestDTO(CUSTOMER.getId(), ADDRESS.getId(), List.of(ORDER_ITEM_REQUEST_DTO)); 26 | public static final OrderUpdateDTO ORDER_UPDATE_DTO = new OrderUpdateDTO(ORDER.getId(), ADDRESS.getId(), List.of(ORDER_ITEM_UPDATE_DTO)); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/customer/CustomerUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.customer; 2 | 3 | import com.sushi.api.model.dto.address.AddressDTO; 4 | import com.sushi.api.model.dto.phone.PhoneDTO; 5 | import io.swagger.v3.oas.annotations.media.Schema; 6 | import jakarta.validation.Valid; 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.NotNull; 9 | 10 | import java.util.Set; 11 | import java.util.UUID; 12 | 13 | @Schema(name = "Customer Update DTO", description = "DTO for updating a customer") 14 | public record CustomerUpdateDTO( 15 | @Schema(description = "The unique identifier of the customer", example = "550e8400-e29b-41d4-a716-446655440000") 16 | @NotNull(message = "ID cannot be null") 17 | UUID id, 18 | @Schema(description = "The customer's name", example = "Alice Silva") 19 | @NotBlank(message = "Name cannot be blank") 20 | String name, 21 | @Schema(description = "The customer's email", example = "alice@example.com") 22 | @NotBlank(message = "Email cannot be blank") 23 | String email, 24 | @Schema(description = "The customer's password", example = "123") 25 | @NotBlank(message = "Password cannot be blank") 26 | String password, 27 | @Schema(description = "The customer's phone number", example = "1234567890") 28 | @NotNull(message = "Phone cannot be null") 29 | @Valid 30 | PhoneDTO phone, 31 | @Schema(description = "The customer's address") 32 | @NotNull(message = "Address cannot be null") 33 | Set<@Valid AddressDTO> addresses 34 | ) {} -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Phone.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | 6 | import java.io.Serializable; 7 | import java.util.Objects; 8 | 9 | @Entity 10 | @Table(name = "phone") 11 | public class Phone implements Serializable { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String number; 21 | 22 | @JsonIgnore 23 | @OneToOne(fetch = FetchType.LAZY) 24 | @JoinColumn(name = "customer_id", nullable = false) 25 | private Customer customer; 26 | 27 | public Phone() {} 28 | 29 | public Phone(String number) { 30 | this.number = number; 31 | } 32 | 33 | public Long getId() { 34 | return id; 35 | } 36 | 37 | public void setId(Long id) { 38 | this.id = id; 39 | } 40 | 41 | public String getNumber() { 42 | return number; 43 | } 44 | 45 | public void setNumber(String number) { 46 | this.number = number; 47 | } 48 | 49 | public Customer getCustomer() { 50 | return customer; 51 | } 52 | 53 | public void setCustomer(Customer customer) { 54 | this.customer = customer; 55 | } 56 | 57 | @Override 58 | public boolean equals(Object object) { 59 | if (this == object) return true; 60 | if (object == null || getClass() != object.getClass()) return false; 61 | Phone phone = (Phone) object; 62 | return Objects.equals(id, phone.id); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/exceptions/handler/ExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.exceptions.handler; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | public class ExceptionResponse { 8 | protected String title; 9 | protected int status; 10 | protected String details; 11 | protected String developerMessage; 12 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 13 | protected LocalDateTime timestamp; 14 | 15 | public ExceptionResponse(String title, int status, String details, String developerMessage, LocalDateTime timestamp) { 16 | this.title = title; 17 | this.status = status; 18 | this.details = details; 19 | this.developerMessage = developerMessage; 20 | this.timestamp = timestamp; 21 | } 22 | 23 | public String getTitle() { 24 | return title; 25 | } 26 | 27 | public void setTitle(String title) { 28 | this.title = title; 29 | } 30 | 31 | public int getStatus() { 32 | return status; 33 | } 34 | 35 | public void setStatus(int status) { 36 | this.status = status; 37 | } 38 | 39 | public String getDetails() { 40 | return details; 41 | } 42 | 43 | public void setDetails(String details) { 44 | this.details = details; 45 | } 46 | 47 | public String getDeveloperMessage() { 48 | return developerMessage; 49 | } 50 | 51 | public void setDeveloperMessage(String developerMessage) { 52 | this.developerMessage = developerMessage; 53 | } 54 | 55 | public LocalDateTime getTimestamp() { 56 | return timestamp; 57 | } 58 | 59 | public void setTimestamp(LocalDateTime timestamp) { 60 | this.timestamp = timestamp; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/product/ProductRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.product; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Positive; 7 | 8 | import java.util.Set; 9 | 10 | @Schema(name = "Product Request DTO", description = "DTO for creating a product") 11 | public record ProductRequestDTO( 12 | @Schema(description = "The name of the product", example = "California Roll") 13 | @NotBlank(message = "Name cannot be blank") 14 | String name, 15 | 16 | @Schema(description = "A description of the product", example = "A delicious roll made with crab meat, avocado, and cucumber") 17 | @NotBlank(message = "Description cannot be blank") 18 | String description, 19 | 20 | @Schema(description = "The price of the product", example = "8.99") 21 | @NotNull(message = "Price cannot be null") 22 | @Positive(message = "Price must be greater than zero") 23 | Double price, 24 | 25 | @Schema(description = "The quantity of portions in the product", example = "20") 26 | @NotNull(message = "Quantity of portions cannot be null") 27 | Integer portionQuantity, 28 | 29 | @Schema(description = "The unit of measurement for portions", example = "pieces") 30 | @NotBlank(message = "Units of portions cannot be blank") 31 | String portionUnit, 32 | 33 | @Schema(description = "The URL of the product's image", example = "http://example.com/images/california_roll.jpg") 34 | @NotBlank(message = "URL Image cannot be blank") 35 | String urlImage, 36 | 37 | @Schema(description = "A set of category IDs associated with the product (it can be null)", example = "[]") 38 | Set categoriesId 39 | ) { 40 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/common/CustomerConstants.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.common; 2 | 3 | import com.sushi.api.model.Address; 4 | import com.sushi.api.model.Customer; 5 | import com.sushi.api.model.Phone; 6 | import com.sushi.api.model.dto.address.AddressDTO; 7 | import com.sushi.api.model.dto.phone.PhoneDTO; 8 | 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.UUID; 12 | 13 | public class CustomerConstants { 14 | public static final String EMAIL = "ana@gmail.com"; 15 | public static final String PASSWORD = "senha123"; 16 | public static final String ENCODED_PASSWORD = "senhacodificada"; 17 | public static final String TOKEN = "token"; 18 | 19 | public static final Phone PHONE = new Phone("111111111"); 20 | public static final PhoneDTO PHONE_DTO = new PhoneDTO("222222222"); 21 | public static final Address ADDRESS = new Address(1L, "123", "Main St", "Downtown"); 22 | public static final AddressDTO ADDRESS_DTO = new AddressDTO("123", "Main St", "Downtown"); 23 | 24 | public static final Customer CUSTOMER_LOGIN = new Customer(UUID.randomUUID(), "ana", EMAIL, PASSWORD, PHONE); 25 | public static final Customer CUSTOMER_ADDRESS = new Customer(UUID.randomUUID(), "pedro", "pedro@gmail.com", "1234", PHONE); 26 | public static final Customer CUSTOMER = new Customer(UUID.randomUUID(), "isabel", "isabel@gmail.com", "1234", PHONE); 27 | public static final Customer CUSTOMER2 = new Customer(UUID.randomUUID(), "joao", "joao@gmail.com", "1234", PHONE); 28 | public static final Customer CUSTOMER3 = new Customer(UUID.randomUUID(), "maria", "maria@gmail.com", "1234", PHONE); 29 | public static final Customer CUSTOMER4 = new Customer(UUID.randomUUID(), "mariana", "mariana@gmail.com", "1234", PHONE); 30 | public static final List CUSTOMERS = List.of(CUSTOMER2, CUSTOMER3, CUSTOMER4); 31 | static { 32 | CUSTOMER_ADDRESS.setAddresses(Set.of((ADDRESS))); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Employee.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | 6 | import java.util.Objects; 7 | import java.util.UUID; 8 | 9 | @Entity 10 | @Table(name = "employees") 11 | public class Employee { 12 | @Id 13 | @GeneratedValue(strategy = GenerationType.AUTO) 14 | private UUID id; 15 | 16 | @Column(nullable = false) 17 | private String name; 18 | @Column(nullable = false, unique = true) 19 | private String email; 20 | @JsonIgnore 21 | @Column(nullable = false) 22 | private String password; 23 | 24 | public Employee() { 25 | } 26 | 27 | public Employee(UUID id, String name, String email, String password) { 28 | this.id = id; 29 | this.name = name; 30 | this.email = email; 31 | this.password = password; 32 | } 33 | 34 | public UUID getId() { 35 | return id; 36 | } 37 | 38 | public void setId(UUID id) { 39 | this.id = id; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public String getEmail() { 51 | return email; 52 | } 53 | 54 | public void setEmail(String email) { 55 | this.email = email; 56 | } 57 | 58 | public String getPassword() { 59 | return password; 60 | } 61 | 62 | public void setPassword(String password) { 63 | this.password = password; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object object) { 68 | if (this == object) return true; 69 | if (object == null || getClass() != object.getClass()) return false; 70 | Employee employee = (Employee) object; 71 | return Objects.equals(id, employee.id); 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return Objects.hash(id); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/dto/product/ProductUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model.dto.product; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Positive; 7 | 8 | import java.util.Set; 9 | 10 | @Schema(name = "Product Update DTO", description = "DTO for updating a product") 11 | public record ProductUpdateDTO( 12 | @Schema(description = "The unique identifier of the product", example = "1") 13 | @NotNull(message = "ID cannot be null") 14 | Long id, 15 | 16 | @Schema(description = "The name of the product", example = "California Roll") 17 | @NotBlank(message = "Name cannot be blank") 18 | String name, 19 | 20 | @Schema(description = "A description of the product", example = "A delicious roll made with crab meat, avocado, and cucumber") 21 | @NotBlank(message = "Description cannot be blank") 22 | String description, 23 | 24 | @Schema(description = "The price of the product", example = "8.99") 25 | @NotNull(message = "Price cannot be null") 26 | @Positive(message = "Price must be greater than zero") 27 | Double price, 28 | 29 | @Schema(description = "The quantity of portions in the product", example = "20") 30 | @NotNull(message = "Quantity of portions cannot be null") 31 | Integer portionQuantity, 32 | 33 | @Schema(description = "The unit of measurement for portions", example = "pieces") 34 | @NotBlank(message = "Units of portions cannot be blank") 35 | String portionUnit, 36 | 37 | @Schema(description = "The URL of the product's image", example = "http://example.com/images/california_roll.jpg") 38 | @NotBlank(message = "URL Image cannot be blank") 39 | String urlImage, 40 | 41 | @Schema(description = "A set of category IDs associated with the product (it can be null)", example = "[]") 42 | Set categoriesId 43 | ) { 44 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/security/SecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.security; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.filter.OncePerRequestFilter; 13 | 14 | import java.io.IOException; 15 | import java.util.Collections; 16 | 17 | @Component 18 | public class SecurityFilter extends OncePerRequestFilter { 19 | @Autowired 20 | private TokenService tokenService; 21 | 22 | @Override 23 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 24 | String token = getTokenFromRequest(request); 25 | if (token != null && tokenService.validateToken(token)) { 26 | String userEmail = tokenService.getEmailFromToken(token); 27 | String role = tokenService.getRoleFromToken(token); 28 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( 29 | userEmail, null, Collections.singletonList(() -> role) 30 | ); 31 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 32 | SecurityContextHolder.getContext().setAuthentication(authentication); 33 | } 34 | filterChain.doFilter(request, response); 35 | } 36 | 37 | private String getTokenFromRequest(HttpServletRequest request) { 38 | String bearerToken = request.getHeader("Authorization"); 39 | if (bearerToken != null && bearerToken.startsWith("Bearer ")) { 40 | return bearerToken.substring(7); 41 | } 42 | return null; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Category.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonManagedReference; 4 | import jakarta.persistence.*; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashSet; 8 | import java.util.Objects; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Table(name = "categories") 13 | public class Category implements Serializable { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String name; 21 | private String description; 22 | 23 | @JsonManagedReference 24 | @ManyToMany(mappedBy = "categories", fetch = FetchType.EAGER) 25 | private Set products = new HashSet<>(); 26 | 27 | public Category() {} 28 | 29 | public Category(Long id, String name, String description) { 30 | this.id = id; 31 | this.name = name; 32 | this.description = description; 33 | } 34 | 35 | public Category(String name, String description) { 36 | this.name = name; 37 | this.description = description; 38 | } 39 | 40 | public Long getId() { 41 | return id; 42 | } 43 | 44 | public void setId(Long id) { 45 | this.id = id; 46 | } 47 | 48 | public String getName() { 49 | return name; 50 | } 51 | 52 | public void setName(String name) { 53 | this.name = name; 54 | } 55 | 56 | public String getDescription() { 57 | return description; 58 | } 59 | 60 | public void setDescription(String description) { 61 | this.description = description; 62 | } 63 | 64 | public Set getProducts() { 65 | return products; 66 | } 67 | 68 | public void setProducts(Set products) { 69 | this.products = products; 70 | } 71 | 72 | @Override 73 | public boolean equals(Object object) { 74 | if (this == object) return true; 75 | if (object == null || getClass() != object.getClass()) return false; 76 | Category category = (Category) object; 77 | return Objects.equals(id, category.id); 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return Objects.hash(id); 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1__create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE customers ( 2 | id UUID PRIMARY KEY, 3 | name VARCHAR(255) NOT NULL, 4 | email VARCHAR(255) NOT NULL UNIQUE, 5 | password VARCHAR(255) NOT NULL 6 | ); 7 | 8 | CREATE TABLE employees ( 9 | id UUID PRIMARY KEY, 10 | name VARCHAR(255) NOT NULL, 11 | email VARCHAR(255) NOT NULL UNIQUE, 12 | password VARCHAR(255) NOT NULL 13 | ); 14 | 15 | CREATE TABLE phone ( 16 | id SERIAL PRIMARY KEY, 17 | number VARCHAR(255) NOT NULL, 18 | customer_id UUID, 19 | FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE 20 | ); 21 | 22 | CREATE TABLE addresses ( 23 | id SERIAL PRIMARY KEY, 24 | number VARCHAR(255) NOT NULL, 25 | street VARCHAR(255) NOT NULL, 26 | neighborhood VARCHAR(255) NOT NULL, 27 | customer_id UUID, 28 | FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE 29 | ); 30 | 31 | CREATE TABLE categories ( 32 | id SERIAL PRIMARY KEY, 33 | name VARCHAR(255) NOT NULL UNIQUE, 34 | description TEXT 35 | ); 36 | 37 | CREATE TABLE products ( 38 | id SERIAL PRIMARY KEY, 39 | name VARCHAR(255) NOT NULL UNIQUE, 40 | description TEXT NOT NULL, 41 | price DOUBLE PRECISION NOT NULL, 42 | portion_quantity INTEGER NOT NULL, 43 | portion_unit VARCHAR(255) NOT NULL, 44 | url_image VARCHAR(255) NOT NULL 45 | ); 46 | 47 | CREATE TABLE orders ( 48 | id SERIAL PRIMARY KEY, 49 | order_date TIMESTAMP NOT NULL, 50 | customer_id UUID NOT NULL, 51 | delivery_address_id INTEGER NOT NULL, 52 | total_amount DOUBLE PRECISION NOT NULL, 53 | FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE, 54 | FOREIGN KEY (delivery_address_id) REFERENCES addresses(id) ON DELETE CASCADE 55 | ); 56 | 57 | CREATE TABLE order_item ( 58 | id SERIAL PRIMARY KEY, 59 | order_id INTEGER NOT NULL, 60 | product_id INTEGER NOT NULL, 61 | quantity INTEGER NOT NULL, 62 | price DOUBLE PRECISION NOT NULL, 63 | total_price DOUBLE PRECISION NOT NULL, 64 | FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, 65 | FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE 66 | ); 67 | 68 | CREATE TABLE category_product ( 69 | category_id BIGINT NOT NULL, 70 | product_id BIGINT NOT NULL, 71 | PRIMARY KEY (category_id, product_id), 72 | FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE, 73 | FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE 74 | ); -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/EmployeeService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Employee; 5 | import com.sushi.api.model.dto.employee.EmployeeRequestDTO; 6 | import com.sushi.api.model.dto.employee.EmployeeUpdateDTO; 7 | import com.sushi.api.repositories.EmployeeRepository; 8 | import jakarta.transaction.Transactional; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.dao.DataIntegrityViolationException; 11 | import org.springframework.data.domain.Page; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.List; 17 | import java.util.UUID; 18 | 19 | @Service 20 | public class EmployeeService { 21 | @Autowired 22 | private EmployeeRepository employeeRepository; 23 | @Autowired 24 | private PasswordEncoder passwordEncoder; 25 | 26 | public Page listAllPageable(Pageable pageable) { 27 | return employeeRepository.findAll(pageable); 28 | } 29 | 30 | public List listAllNonPageable() { 31 | return employeeRepository.findAll(); 32 | } 33 | 34 | public Employee findEmployeeById(UUID id) { 35 | return employeeRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Employee not found with this id.")); 36 | } 37 | 38 | public Employee findEmployeeByEmail(String email) { 39 | return employeeRepository.findByEmail(email) 40 | .orElseThrow(() -> new ResourceNotFoundException("Employee not found with this id.")); 41 | } 42 | 43 | @Transactional 44 | public Employee createEmployee(EmployeeRequestDTO dto) { 45 | if (employeeRepository.findByEmail(dto.email()).isPresent()) { 46 | throw new DataIntegrityViolationException("Data integrity violation error occurred."); 47 | } 48 | 49 | Employee employee = new Employee(); 50 | employee.setName(dto.name()); 51 | employee.setEmail(dto.email()); 52 | employee.setPassword(passwordEncoder.encode(dto.password())); 53 | 54 | return employeeRepository.save(employee); 55 | } 56 | 57 | @Transactional 58 | public void replaceEmployee(EmployeeUpdateDTO dto) { 59 | Employee savedEmployee = findEmployeeById(dto.id()); 60 | savedEmployee.setName(dto.name()); 61 | savedEmployee.setEmail(dto.email()); 62 | savedEmployee.setPassword(passwordEncoder.encode(dto.password())); 63 | employeeRepository.save(savedEmployee); 64 | } 65 | 66 | @Transactional 67 | public void deleteEmployee(UUID id) { 68 | employeeRepository.delete(findEmployeeById(id)); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Category; 5 | import com.sushi.api.model.dto.category.CategoryRequestDTO; 6 | import com.sushi.api.model.dto.category.CategoryUpdateDTO; 7 | import com.sushi.api.repositories.CategoryRepository; 8 | import com.sushi.api.repositories.ProductRepository; 9 | import jakarta.transaction.Transactional; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.dao.DataIntegrityViolationException; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.List; 17 | 18 | @Service 19 | public class CategoryService { 20 | @Autowired 21 | private CategoryRepository categoryRepository; 22 | 23 | @Autowired 24 | private ProductRepository productRepository; 25 | 26 | public List listAllNonPageable() { 27 | return categoryRepository.findAll(); 28 | } 29 | 30 | public Page listAllPageable(Pageable pageable) { 31 | return categoryRepository.findAll(pageable); 32 | } 33 | 34 | public Category findCategoryById(Long id) { 35 | return categoryRepository.findById(id) 36 | .orElseThrow(() -> new ResourceNotFoundException("Category not found with this id.")); 37 | } 38 | 39 | public List findCategoryByName(String name) { 40 | List categories = categoryRepository.findByNameContainingIgnoreCase(name); 41 | if (categories.isEmpty()) { 42 | throw new ResourceNotFoundException("No categories found with this name."); 43 | } 44 | return categories; 45 | } 46 | 47 | @Transactional 48 | public Category createCategory(CategoryRequestDTO dto) { 49 | List existingCategories = categoryRepository.findByNameContainingIgnoreCase(dto.name()); 50 | 51 | if (!existingCategories.isEmpty()) { 52 | throw new DataIntegrityViolationException("Data integrity violation error occurred."); 53 | } 54 | 55 | Category category = new Category(); 56 | category.setName(dto.name()); 57 | category.setDescription(dto.description()); 58 | 59 | return categoryRepository.save(category); 60 | } 61 | 62 | @Transactional 63 | public void replaceCategory(CategoryUpdateDTO dto) { 64 | Category category = findCategoryById(dto.id()); 65 | 66 | category.setName(dto.name()); 67 | category.setDescription(dto.description()); 68 | 69 | categoryRepository.save(category); 70 | } 71 | 72 | @Transactional 73 | public void deleteCategory(Long id) { 74 | categoryRepository.delete(findCategoryById(id)); 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | 6 | import java.io.Serializable; 7 | import java.util.Objects; 8 | 9 | @Entity 10 | @Table(name = "order_item") 11 | public class OrderItem implements Serializable { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Long id; 16 | 17 | @Column(nullable = false) 18 | private Integer quantity; 19 | @Column(nullable = false) 20 | private Double price; 21 | @Column(nullable = false) 22 | private Double totalPrice; 23 | 24 | @JsonIgnore 25 | @ManyToOne 26 | @JoinColumn(name = "product_id", nullable = false) 27 | private Product product; 28 | 29 | @JsonIgnore 30 | @ManyToOne 31 | @JoinColumn(name = "order_id", nullable = false) 32 | private Order order; 33 | 34 | public OrderItem() {} 35 | 36 | public OrderItem(Long id, Integer quantity, Double price) { 37 | this.id = id; 38 | this.quantity = quantity; 39 | this.price = price; 40 | } 41 | 42 | public void calculateTotalPrice() { 43 | if (price != null && quantity != null) { 44 | this.totalPrice = price * quantity; 45 | } 46 | } 47 | 48 | public Long getId() { 49 | return id; 50 | } 51 | 52 | public void setId(Long id) { 53 | this.id = id; 54 | } 55 | 56 | public Integer getQuantity() { 57 | return quantity; 58 | } 59 | 60 | public void setQuantity(Integer quantity) { 61 | this.quantity = quantity; 62 | } 63 | 64 | public Double getPrice() { 65 | return price; 66 | } 67 | 68 | public void setPrice(Double price) { 69 | if (price == null) { 70 | throw new IllegalArgumentException("Price cannot be null"); 71 | } 72 | this.price = price; 73 | } 74 | 75 | public Product getProduct() { 76 | return product; 77 | } 78 | 79 | public void setProduct(Product product) { 80 | this.product = product; 81 | } 82 | 83 | public Order getOrder() { 84 | return order; 85 | } 86 | 87 | public void setOrder(Order order) { 88 | this.order = order; 89 | } 90 | 91 | public Double getTotalPrice() { 92 | return totalPrice; 93 | } 94 | 95 | public void setTotalPrice(Double totalPrice) { 96 | this.totalPrice = totalPrice; 97 | } 98 | 99 | @Override 100 | public boolean equals(Object object) { 101 | if (this == object) return true; 102 | if (object == null || getClass() != object.getClass()) return false; 103 | OrderItem orderItem = (OrderItem) object; 104 | return Objects.equals(id, orderItem.id); 105 | } 106 | 107 | @Override 108 | public int hashCode() { 109 | return Objects.hash(id); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/security/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.security; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.auth0.jwt.exceptions.JWTCreationException; 6 | import com.auth0.jwt.exceptions.JWTVerificationException; 7 | import com.sushi.api.model.Customer; 8 | import com.sushi.api.model.Employee; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.time.Instant; 13 | import java.time.LocalDateTime; 14 | import java.time.ZoneOffset; 15 | 16 | @Service 17 | public class TokenService { 18 | @Value("${api.security.token.secret}") 19 | private String secret; 20 | 21 | private Algorithm getAlgorithm() { 22 | return Algorithm.HMAC256(secret); 23 | } 24 | 25 | private String createToken(String subject, String role) { 26 | try { 27 | return JWT.create() 28 | .withIssuer("login-auth-api") 29 | .withSubject(subject) 30 | .withClaim("role", role) 31 | .withExpiresAt(generateExpirationDate()) 32 | .sign(getAlgorithm()); 33 | } catch (JWTCreationException exception) { 34 | throw new RuntimeException("Error while creating token", exception); 35 | } 36 | } 37 | 38 | public String generateCustomerToken(Customer customer) { 39 | return createToken(customer.getEmail(), "USER"); 40 | } 41 | 42 | public String generateEmployeeToken(Employee employee) { 43 | return createToken(employee.getEmail(), "ADMIN"); 44 | } 45 | 46 | public boolean validateToken(String token) { 47 | try { 48 | JWT.require(getAlgorithm()) 49 | .withIssuer("login-auth-api") 50 | .build() 51 | .verify(token); 52 | return true; 53 | } catch (JWTVerificationException exception) { 54 | return false; 55 | } 56 | } 57 | 58 | public String getEmailFromToken(String token) { 59 | return JWT.require(getAlgorithm()) 60 | .withIssuer("login-auth-api") 61 | .build() 62 | .verify(token) 63 | .getSubject(); 64 | } 65 | 66 | public String getRoleFromToken(String token) { 67 | return JWT.require(getAlgorithm()) 68 | .withIssuer("login-auth-api") 69 | .build() 70 | .verify(token) 71 | .getClaim("role").asString(); 72 | } 73 | 74 | public Instant getExpirationDateFromToken(String token) { 75 | return JWT.require(getAlgorithm()) 76 | .withIssuer("login-auth-api") 77 | .build() 78 | .verify(token) 79 | .getExpiresAt() 80 | .toInstant(); 81 | } 82 | 83 | public Instant generateExpirationDate() { 84 | return LocalDateTime.now().plusHours(1).toInstant(ZoneOffset.of("-03:00")); 85 | } 86 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍣 Sistema de Pedidos de Sushi 2 | 3 | --- 4 | 5 | ## 📄 Descrição 6 | 7 | Esse projeto foi desenvolvido para os clientes realizarem pedidos de sushi online. Com esta API, os clientes visualizam o cardápio, os produtos dentro de cada categoria e realizam os pedidos. 8 | 9 | Para os funcionários, a API oferece ferramentas para gerenciar o cardápio e produtos, processar pedidos e administrar clientes. 10 | 11 | --- 12 | 13 | ## ⚙️ Funcionalidades 14 | 15 | - **Visualização do cardápio**: Clientes podem navegar por categorias e produtos disponíveis. 16 | - **Realização de pedidos**: Clientes podem criar e finalizar pedidos. 17 | - **Gestão de cardápio**: Funcionários podem gerenciar categorias e produtos. 18 | - **Processamento de pedidos**: Funcionários podem acompanhar e processar pedidos. 19 | - **Administração de clientes**: Funcionários podem gerenciar informações dos clientes. 20 | 21 | --- 22 | 23 | ## 🗂️ Imagens do Projeto 24 | 25 |
26 |     Categorias 27 |     Categorias 28 |
29 |
30 |     Produtos 31 |     Produtos 32 |
33 |
34 |     Pedidos 35 |     Pedidos 36 |
37 |
38 |     Cliente 39 |     Cliente 40 |
41 | 42 | --- 43 | 44 | ## 🛠️ Tecnologias 45 | 46 | - **Linguagem**: Java 47 | - **Framework**: Spring Boot 48 | - **Gerenciador de Dependências**: Maven 49 | - **Banco de Dados**: PostgreSQL 50 | - **Migração de Banco**: Flyway Migrations 51 | - **Segurança**: Java JWT 52 | - **Testes**: JUnit, Mockito 53 | - **Validação**: Spring Validation 54 | - **Documentação da API**: SwaggerUI 55 | 56 | --- 57 | 58 | ## 📝 Endpoints 59 | 60 | - **Documentação online**: https://sushi-ordering-system.onrender.com/swagger-ui/index.html 61 | - **Documentação local**: http://localhost:8080/swagger-ui/index.html#/ 62 | - **Coleção com requisições HTTP**: [Collection](media/sushi_ordering_system_collection.json) 63 | 64 | --- 65 | 66 | ## 📈 Diagramas 67 | 68 |
69 |     Diagrama de Classes 70 |     Diagrama de Classes 71 |
72 |
73 |     Diagrama de Entidade e Relacionamento 74 |     Diagrama de Entidade e Relacionamento 75 |
76 | 77 | --- 78 | 79 | ## ⚙️ Configuração e Execução 80 | 81 | **Pré-requisitos**: 82 | 83 | - Java 17 84 | - Maven 85 | - PostgreSQL 86 | 87 | **Passos para Configuração**: 88 | 89 | 1. Clone o repositório 90 | 2. Acesse o diretório do projeto 91 | 3. Configure o banco de dados no arquivo `application.properties` (URL, usuário, senha) 92 | 93 | ```bash 94 | # Execute a aplicação 95 | mvn spring-boot:run 96 | 97 | # Pressione (CTRL + C) para encerrar a aplicação 98 | ``` 99 | 100 | --- 101 | 102 | ## 🙋‍♀️ Autor 103 | 104 | 👩‍💻 Projeto desenvolvido por [Isabel Henrique](https://www.linkedin.com/in/isabel-henrique/) 105 | 106 | 🤝 Fique à vontade para contribuir! 107 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonManagedReference; 5 | import jakarta.persistence.*; 6 | 7 | import java.io.Serializable; 8 | import java.util.*; 9 | 10 | @Entity 11 | @Table(name = "customers") 12 | public class Customer implements Serializable { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.AUTO) 18 | private UUID id; 19 | 20 | @Column(nullable = false) 21 | private String name; 22 | @Column(nullable = false, unique = true) 23 | private String email; 24 | @JsonIgnore 25 | @Column(nullable = false) 26 | private String password; 27 | 28 | @OneToOne(mappedBy = "customer", cascade = CascadeType.ALL) 29 | @JsonManagedReference 30 | private Phone phone; 31 | @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 32 | @JsonManagedReference 33 | private Set
addresses = new HashSet<>(); 34 | @JsonIgnore 35 | @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 36 | private List orders = new ArrayList<>(); 37 | 38 | public Customer() { 39 | } 40 | 41 | public Customer(UUID id, String name, String email, String password, Phone phone) { 42 | this.id = id; 43 | this.name = name; 44 | this.email = email; 45 | this.password = password; 46 | this.phone = phone; 47 | } 48 | 49 | public UUID getId() { 50 | return id; 51 | } 52 | 53 | public void setId(UUID id) { 54 | this.id = id; 55 | } 56 | 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public String getEmail() { 66 | return email; 67 | } 68 | 69 | public void setEmail(String email) { 70 | this.email = email; 71 | } 72 | 73 | public Phone getPhone() { 74 | return phone; 75 | } 76 | 77 | public void setPhone(Phone phone) { 78 | this.phone = phone; 79 | } 80 | 81 | public Set
getAddresses() { 82 | return addresses; 83 | } 84 | 85 | public void setAddresses(Set
addresses) { 86 | this.addresses = addresses; 87 | } 88 | 89 | public List getOrders() { 90 | return orders; 91 | } 92 | 93 | public void setOrders(List orders) { 94 | this.orders = orders; 95 | } 96 | 97 | public String getPassword() { 98 | return password; 99 | } 100 | 101 | public void setPassword(String password) { 102 | this.password = password; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object object) { 107 | if (this == object) return true; 108 | if (object == null || getClass() != object.getClass()) return false; 109 | Customer customer = (Customer) object; 110 | return Objects.equals(id, customer.id); 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | return Objects.hash(id); 116 | } 117 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Address.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | 6 | import java.io.Serializable; 7 | import java.util.*; 8 | 9 | @Entity 10 | @Table(name = "addresses") 11 | public class Address implements Serializable { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String number; 21 | @Column(nullable = false) 22 | private String street; 23 | @Column(nullable = false) 24 | private String neighborhood; 25 | 26 | @JsonIgnore 27 | @ManyToOne 28 | @JoinColumn(name = "customer_id") 29 | private Customer customer; 30 | @JsonIgnore 31 | @OneToMany(mappedBy = "deliveryAddress", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 32 | private List orders = new ArrayList<>(); 33 | 34 | public Address() { 35 | } 36 | 37 | public Address(String number, String street, String neighborhood) { 38 | this.number = number; 39 | this.street = street; 40 | this.neighborhood = neighborhood; 41 | } 42 | 43 | public Address(Long id, String number, String street, String neighborhood) { 44 | this.id = id; 45 | this.number = number; 46 | this.street = street; 47 | this.neighborhood = neighborhood; 48 | } 49 | 50 | public Address(String number, String street, String neighborhood, Customer customer) { 51 | this.number = number; 52 | this.street = street; 53 | this.neighborhood = neighborhood; 54 | this.customer = customer; 55 | } 56 | 57 | public Long getId() { 58 | return id; 59 | } 60 | 61 | public void setId(Long id) { 62 | this.id = id; 63 | } 64 | 65 | public String getNumber() { 66 | return number; 67 | } 68 | 69 | public void setNumber(String number) { 70 | this.number = number; 71 | } 72 | 73 | public String getStreet() { 74 | return street; 75 | } 76 | 77 | public void setStreet(String street) { 78 | this.street = street; 79 | } 80 | 81 | public String getNeighborhood() { 82 | return neighborhood; 83 | } 84 | 85 | public void setNeighborhood(String neighborhood) { 86 | this.neighborhood = neighborhood; 87 | } 88 | 89 | public Customer getCustomer() { 90 | return customer; 91 | } 92 | 93 | public void setCustomer(Customer customer) { 94 | this.customer = customer; 95 | } 96 | 97 | public List getOrders() { 98 | return orders; 99 | } 100 | 101 | public void setOrders(List orders) { 102 | this.orders = orders; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object object) { 107 | if (this == object) return true; 108 | if (object == null || getClass() != object.getClass()) return false; 109 | Address address = (Address) object; 110 | return Objects.equals(id, address.id); 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | return Objects.hash(id); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.1 9 | 10 | 11 | com.sushi 12 | api 13 | 0.0.1-SNAPSHOT 14 | api 15 | Sushi Ordering System 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 17 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-validation 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-devtools 48 | runtime 49 | true 50 | 51 | 52 | org.postgresql 53 | postgresql 54 | 55 | 56 | org.flywaydb 57 | flyway-database-postgresql 58 | 59 | 60 | org.flywaydb 61 | flyway-core 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-test 66 | test 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-security 71 | 72 | 73 | org.springframework.security 74 | spring-security-test 75 | test 76 | 77 | 78 | org.springdoc 79 | springdoc-openapi-starter-webmvc-ui 80 | 2.6.0 81 | 82 | 83 | com.auth0 84 | java-jwt 85 | 4.4.0 86 | 87 | 88 | com.fasterxml.jackson.dataformat 89 | jackson-dataformat-xml 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-maven-plugin 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Order.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import jakarta.persistence.*; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | @Entity 15 | @Table(name = "orders") 16 | public class Order implements Serializable { 17 | 18 | @Serial 19 | private static final long serialVersionUID = 1L; 20 | 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private Long id; 24 | @JsonFormat(pattern = "dd/MM/yyyy hh:mm") 25 | @Column(name = "order_date", nullable = false) 26 | private LocalDateTime orderDate; 27 | @Column(name = "total_amount", nullable = false) 28 | private Double totalAmount; 29 | 30 | @JsonIgnore 31 | @ManyToOne 32 | @JoinColumn(name = "customer_id", nullable = false) 33 | private Customer customer; 34 | @ManyToOne 35 | @JoinColumn(name = "delivery_address_id", nullable = false) 36 | private Address deliveryAddress; 37 | @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER) 38 | private List items = new ArrayList<>(); 39 | 40 | public Order() {} 41 | 42 | public Order(Long id, Customer customer, Address deliveryAddress, List items) { 43 | this.id = id; 44 | this.customer = customer; 45 | this.deliveryAddress = deliveryAddress; 46 | this.items = items; 47 | } 48 | 49 | public void calculateTotalAmount() { 50 | this.totalAmount = this.items.stream() 51 | .mapToDouble(OrderItem::getTotalPrice) 52 | .sum(); 53 | } 54 | 55 | public Long getId() { 56 | return id; 57 | } 58 | 59 | public void setId(Long id) { 60 | this.id = id; 61 | } 62 | 63 | public LocalDateTime getOrderDate() { 64 | return orderDate; 65 | } 66 | 67 | public void setOrderDate(LocalDateTime orderDate) { 68 | this.orderDate = orderDate; 69 | } 70 | 71 | public Customer getCustomer() { 72 | return customer; 73 | } 74 | 75 | public void setCustomer(Customer customer) { 76 | this.customer = customer; 77 | } 78 | 79 | public Address getDeliveryAddress() { 80 | return deliveryAddress; 81 | } 82 | 83 | public void setDeliveryAddress(Address deliveryAddress) { 84 | this.deliveryAddress = deliveryAddress; 85 | } 86 | 87 | public List getItems() { 88 | return items; 89 | } 90 | 91 | public void setItems(List items) { 92 | this.items = items; 93 | } 94 | 95 | public Double getTotalAmount() { 96 | return totalAmount; 97 | } 98 | 99 | public void setTotalAmount(Double totalAmount) { 100 | this.totalAmount = totalAmount; 101 | } 102 | 103 | @Override 104 | public boolean equals(Object object) { 105 | if (this == object) return true; 106 | if (object == null || getClass() != object.getClass()) return false; 107 | Order order = (Order) object; 108 | return Objects.equals(id, order.id); 109 | } 110 | 111 | @Override 112 | public int hashCode() { 113 | return Objects.hash(id); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/controllers/AuthControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sushi.api.security.TokenService; 5 | import com.sushi.api.services.AuthService; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 10 | import org.springframework.boot.test.mock.mockito.MockBean; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.security.test.context.support.WithMockUser; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | import static com.sushi.api.common.AuthConstants.LOGIN_REQUEST_DTO; 16 | import static com.sushi.api.common.AuthConstants.REGISTER_REQUEST_DTO; 17 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @WebMvcTest(AuthController.class) 22 | public class AuthControllerTest { 23 | 24 | @Autowired 25 | private MockMvc mockMvc; 26 | @Autowired 27 | private ObjectMapper objectMapper; 28 | @MockBean 29 | private TokenService tokenService; 30 | @MockBean 31 | private AuthService authService; 32 | 33 | @Test 34 | @WithMockUser(roles = {"ADMIN", "USER"}) 35 | @DisplayName("Login customer with valid data should return token") 36 | public void loginCustomer_WithValidData_ReturnsToken() throws Exception { 37 | String customerJson = objectMapper.writeValueAsString(LOGIN_REQUEST_DTO); 38 | 39 | mockMvc 40 | .perform(post("/api/auth/customers/login") 41 | .contentType(MediaType.APPLICATION_JSON) 42 | .content(customerJson) 43 | .with(csrf())) 44 | .andExpect(status().isOk()); 45 | } 46 | 47 | @Test 48 | @WithMockUser(roles = {"ADMIN", "USER"}) 49 | @DisplayName("Register customer with valid data should return token") 50 | public void registerCustomer_WithValidData_ReturnsToken() throws Exception { 51 | String customerJson = objectMapper.writeValueAsString(REGISTER_REQUEST_DTO); 52 | 53 | mockMvc 54 | .perform(post("/api/auth/customers/register") 55 | .contentType(MediaType.APPLICATION_JSON) 56 | .content(customerJson) 57 | .with(csrf())) 58 | .andExpect(status().isOk()); 59 | } 60 | 61 | @Test 62 | @WithMockUser(roles = {"ADMIN", "USER"}) 63 | @DisplayName("Login employee with valid data should return token") 64 | public void loginEmployee_WithValidData_ReturnsToken() throws Exception { 65 | String customerJson = objectMapper.writeValueAsString(LOGIN_REQUEST_DTO); 66 | 67 | mockMvc 68 | .perform(post("/api/auth/employees/login") 69 | .contentType(MediaType.APPLICATION_JSON) 70 | .content(customerJson) 71 | .with(csrf())) 72 | .andExpect(status().isOk()); 73 | } 74 | 75 | @Test 76 | @WithMockUser(roles = {"ADMIN", "USER"}) 77 | @DisplayName("Register employee with valid data should return token") 78 | public void registerEmployee_WithValidData_ReturnsToken() throws Exception { 79 | String customerJson = objectMapper.writeValueAsString(REGISTER_REQUEST_DTO); 80 | 81 | mockMvc 82 | .perform(post("/api/auth/employees/register") 83 | .contentType(MediaType.APPLICATION_JSON) 84 | .content(customerJson) 85 | .with(csrf())) 86 | .andExpect(status().isOk()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Category; 5 | import com.sushi.api.model.Product; 6 | import com.sushi.api.model.dto.product.ProductRequestDTO; 7 | import com.sushi.api.model.dto.product.ProductUpdateDTO; 8 | import com.sushi.api.repositories.CategoryRepository; 9 | import com.sushi.api.repositories.ProductRepository; 10 | import jakarta.transaction.Transactional; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.dao.DataIntegrityViolationException; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.stream.Collectors; 20 | 21 | @Service 22 | public class ProductService { 23 | @Autowired 24 | private ProductRepository productRepository; 25 | 26 | @Autowired 27 | private CategoryRepository categoryRepository; 28 | 29 | public List listAllNonPageable() { 30 | return productRepository.findAll(); 31 | } 32 | 33 | public Page listAllPageable(Pageable pageable) { 34 | return productRepository.findAll(pageable); 35 | } 36 | 37 | public Product findProductById(Long id) { 38 | return productRepository.findById(id) 39 | .orElseThrow(() -> new ResourceNotFoundException("Product not found with this id.")); 40 | } 41 | 42 | public List findProductByName(String name) { 43 | List products = productRepository.findByNameContainingIgnoreCase(name); 44 | if (products.isEmpty()) { 45 | throw new ResourceNotFoundException("No products found with this name."); 46 | } 47 | return products; 48 | } 49 | 50 | @Transactional 51 | public Product createProduct(ProductRequestDTO dto) { 52 | List existingProducts = productRepository.findByNameContainingIgnoreCase(dto.name()); 53 | 54 | if (!existingProducts.isEmpty()) { 55 | throw new DataIntegrityViolationException("Data integrity violation error occurred."); 56 | } 57 | 58 | Product product = new Product(); 59 | 60 | product.setName(dto.name()); 61 | product.setDescription(dto.description()); 62 | product.setPrice(dto.price()); 63 | product.setPortionQuantity(dto.portionQuantity()); 64 | product.setPortionUnit(dto.portionUnit()); 65 | product.setUrlImage(dto.urlImage()); 66 | 67 | Set categories = dto.categoriesId().stream() 68 | .map(id -> categoryRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Category not found with this id."))) 69 | .collect(Collectors.toSet()); 70 | product.setCategories(categories); 71 | 72 | return productRepository.save(product); 73 | } 74 | 75 | @Transactional 76 | public void replaceProduct(ProductUpdateDTO dto) { 77 | Product product = findProductById(dto.id()); 78 | 79 | product.setName(dto.name()); 80 | product.setDescription(dto.description()); 81 | product.setPrice(dto.price()); 82 | product.setPortionQuantity(dto.portionQuantity()); 83 | product.setPortionUnit(dto.portionUnit()); 84 | product.setUrlImage(dto.urlImage()); 85 | 86 | Set categories = dto.categoriesId().stream() 87 | .map(id -> categoryRepository.findById(id) 88 | .orElseThrow(() -> new ResourceNotFoundException("Category not found with this id."))) 89 | .collect(Collectors.toSet()); 90 | product.setCategories(categories); 91 | 92 | productRepository.save(product); 93 | } 94 | 95 | @Transactional 96 | public void deleteProduct(Long id) { 97 | productRepository.delete(findProductById(id)); 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.http.SessionCreationPolicy; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.web.SecurityFilterChain; 15 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 16 | 17 | @Configuration 18 | @EnableWebSecurity 19 | public class SecurityConfig { 20 | @Autowired 21 | private SecurityFilter securityFilter; 22 | 23 | @Bean 24 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 25 | http 26 | .csrf(csrf -> csrf.disable()) 27 | .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 28 | .authorizeHttpRequests(authorize -> authorize 29 | .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() 30 | .requestMatchers("/api/auth/customers/login", "/api/auth/customers/register").permitAll() 31 | .requestMatchers("/api/auth/employees/login", "/api/auth/employees/register").permitAll() 32 | 33 | .requestMatchers(HttpMethod.GET, "/api/categories", "api/categories/list", "/api/categories/find/by-name").permitAll() 34 | .requestMatchers(HttpMethod.GET, "/api/products", "api/products/list", "/api/products/find/by-name").permitAll() 35 | 36 | .requestMatchers(HttpMethod.GET, "/api/categories/{id}", "/api/products/{id}", "/api/orders/{id}").hasAnyAuthority("USER", "ADMIN") 37 | .requestMatchers(HttpMethod.POST, "/api/customers", "/api/orders").hasAnyAuthority("USER", "ADMIN") 38 | .requestMatchers(HttpMethod.PUT, "/api/customers/{id}", "/api/orders/{id}").hasAnyAuthority("USER", "ADMIN") 39 | .requestMatchers(HttpMethod.DELETE, "/api/orders/{id}").hasAnyAuthority("USER", "ADMIN") 40 | 41 | .requestMatchers(HttpMethod.GET, "/api/employees", "/api/employees/list", "/api/employees/find/by-email").hasAuthority("ADMIN") 42 | .requestMatchers(HttpMethod.GET, "/api/customers", "/api/customers/{id}", "/api/customers/find/by-name", "/api/customers/find/by-email").hasAuthority("ADMIN") 43 | .requestMatchers(HttpMethod.GET, "/api/orders", "/api/orders/list").hasAuthority("ADMIN") 44 | .requestMatchers(HttpMethod.POST, "/api/categories", "/api/products", "/api/employees").hasAuthority("ADMIN") 45 | .requestMatchers(HttpMethod.PUT, "/api/categories", "/api/products", "/api/orders", "/api/employees", "/api/customers").hasAuthority("ADMIN") 46 | .requestMatchers(HttpMethod.DELETE, "/api/categories/{id}", "/api/products/{id}", "/api/employees/{id}").hasAuthority("ADMIN") 47 | 48 | .anyRequest().authenticated() 49 | ) 50 | .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class); 51 | return http.build(); 52 | } 53 | 54 | @Bean 55 | public PasswordEncoder passwordEncoder() { 56 | return new BCryptPasswordEncoder(); 57 | } 58 | 59 | @Bean 60 | public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { 61 | return authenticationConfiguration.getAuthenticationManager(); 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.dto.login.LoginRequestDTO; 4 | import com.sushi.api.model.dto.login.LoginResponseDTO; 5 | import com.sushi.api.model.dto.login.RegisterRequestDTO; 6 | import com.sushi.api.model.dto.login.RegisterResponseDTO; 7 | import com.sushi.api.services.AuthService; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 11 | import jakarta.validation.Valid; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.http.ResponseEntity; 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.RestController; 18 | 19 | @RestController 20 | @RequestMapping(value = "/api/auth", produces = {"application/json"}) 21 | public class AuthController { 22 | 23 | @Autowired 24 | private AuthService authService; 25 | 26 | @Operation(summary = "Authenticate customer", 27 | description = "Verifies customer credentials and returns an authentication token.") 28 | @ApiResponses(value = { 29 | @ApiResponse(responseCode = "200", description = "Successfully authenticated"), 30 | @ApiResponse(responseCode = "401", description = "Invalid credentials, authentication failed"), 31 | @ApiResponse(responseCode = "404", description = "Customer not found"), 32 | @ApiResponse(responseCode = "500", description = "Internal server error") 33 | }) 34 | @PostMapping("/customers/login") 35 | public ResponseEntity loginCustomer(@RequestBody @Valid LoginRequestDTO dto) { 36 | LoginResponseDTO response = authService.loginCustomer(dto); 37 | return ResponseEntity.ok(response); 38 | } 39 | 40 | @Operation( 41 | summary = "Register a new customer", 42 | description = "Registers a new customer and returns an authentication token." 43 | ) 44 | @ApiResponses(value = { 45 | @ApiResponse(responseCode = "200", description = "Successfully authenticated"), 46 | @ApiResponse(responseCode = "400", description = "Customer already exists"), 47 | @ApiResponse(responseCode = "500", description = "Internal server error") 48 | }) 49 | @PostMapping("/customers/register") 50 | public ResponseEntity registerCustomer(@RequestBody @Valid RegisterRequestDTO dto) { 51 | RegisterResponseDTO response = authService.registerCustomer(dto); 52 | return ResponseEntity.ok(response); 53 | } 54 | 55 | @Operation(summary = "Authenticate employee", 56 | description = "Verifies employee credentials and returns an authentication token.") 57 | @ApiResponses(value = { 58 | @ApiResponse(responseCode = "200", description = "Successfully authenticated"), 59 | @ApiResponse(responseCode = "401", description = "Invalid credentials, authentication failed"), 60 | @ApiResponse(responseCode = "404", description = "Employee not found"), 61 | @ApiResponse(responseCode = "500", description = "Internal server error") 62 | }) 63 | @PostMapping("/employees/login") 64 | public ResponseEntity loginEmployee(@RequestBody @Valid LoginRequestDTO dto) { 65 | LoginResponseDTO response = authService.loginEmployee(dto); 66 | return ResponseEntity.ok(response); 67 | } 68 | 69 | @Operation( 70 | summary = "Register a new employee", 71 | description = "Registers a new employee and returns an authentication token." 72 | ) 73 | @ApiResponses(value = { 74 | @ApiResponse(responseCode = "200", description = "Successfully registered"), 75 | @ApiResponse(responseCode = "400", description = "Employee already exists with the provided email"), 76 | @ApiResponse(responseCode = "500", description = "Internal server error") 77 | }) 78 | @PostMapping("/employees/register") 79 | public ResponseEntity registerEmployee(@RequestBody @Valid RegisterRequestDTO dto) { 80 | RegisterResponseDTO response = authService.registerEmployee(dto); 81 | return ResponseEntity.ok(response); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Customer; 5 | import com.sushi.api.model.Employee; 6 | import com.sushi.api.model.dto.login.LoginRequestDTO; 7 | import com.sushi.api.model.dto.login.LoginResponseDTO; 8 | import com.sushi.api.model.dto.login.RegisterRequestDTO; 9 | import com.sushi.api.model.dto.login.RegisterResponseDTO; 10 | import com.sushi.api.repositories.CustomerRepository; 11 | import com.sushi.api.repositories.EmployeeRepository; 12 | import com.sushi.api.security.TokenService; 13 | import jakarta.transaction.Transactional; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.time.Instant; 18 | import java.util.Optional; 19 | 20 | @Service 21 | public class AuthService { 22 | 23 | private final TokenService tokenService; 24 | private final PasswordEncoder passwordEncoder; 25 | private final CustomerRepository customerRepository; 26 | private final EmployeeRepository employeeRepository; 27 | 28 | public AuthService(TokenService tokenService, PasswordEncoder passwordEncoder, CustomerRepository customerRepository, EmployeeRepository employeeRepository) { 29 | this.tokenService = tokenService; 30 | this.passwordEncoder = passwordEncoder; 31 | this.customerRepository = customerRepository; 32 | this.employeeRepository = employeeRepository; 33 | } 34 | 35 | public LoginResponseDTO loginCustomer(LoginRequestDTO dto) { 36 | Customer customer = customerRepository.findByEmail(dto.email()) 37 | .orElseThrow(() -> new ResourceNotFoundException("Customer not found.")); 38 | 39 | if (passwordEncoder.matches(dto.password(), customer.getPassword())) { 40 | String token = tokenService.generateCustomerToken(customer); 41 | Instant expiresAt = tokenService.getExpirationDateFromToken(token); 42 | return new LoginResponseDTO(customer.getName(), token, expiresAt.toString()); 43 | } 44 | 45 | return new LoginResponseDTO("Invalid credentials", null, null); 46 | } 47 | 48 | @Transactional 49 | public RegisterResponseDTO registerCustomer(RegisterRequestDTO dto) { 50 | Optional existingCustomer = customerRepository.findByEmail(dto.email()); 51 | if (existingCustomer.isEmpty()) { 52 | Customer newCustomer = new Customer(); 53 | newCustomer.setPassword(passwordEncoder.encode(dto.password())); 54 | newCustomer.setEmail(dto.email()); 55 | newCustomer.setName(dto.name()); 56 | 57 | customerRepository.save(newCustomer); 58 | String token = tokenService.generateCustomerToken(newCustomer); 59 | Instant expiresAt = tokenService.getExpirationDateFromToken(token); 60 | return new RegisterResponseDTO(newCustomer.getName(), token, expiresAt.toString()); 61 | } 62 | 63 | return new RegisterResponseDTO("Customer already exists", null, null); 64 | } 65 | 66 | public LoginResponseDTO loginEmployee(LoginRequestDTO dto) { 67 | Employee employee = employeeRepository.findByEmail(dto.email()) 68 | .orElseThrow(() -> new ResourceNotFoundException("Employee not found.")); 69 | 70 | if (passwordEncoder.matches(dto.password(), employee.getPassword())) { 71 | String token = tokenService.generateEmployeeToken(employee); 72 | Instant expiresAt = tokenService.getExpirationDateFromToken(token); 73 | return new LoginResponseDTO(employee.getName(), token, expiresAt.toString()); 74 | } 75 | 76 | return new LoginResponseDTO("Invalid credentials", null, null); 77 | } 78 | 79 | @Transactional 80 | public RegisterResponseDTO registerEmployee(RegisterRequestDTO dto) { 81 | Optional existingEmployee = employeeRepository.findByEmail(dto.email()); 82 | if (existingEmployee.isEmpty()) { 83 | Employee newEmployee = new Employee(); 84 | newEmployee.setPassword(passwordEncoder.encode(dto.password())); 85 | newEmployee.setEmail(dto.email()); 86 | newEmployee.setName(dto.name()); 87 | employeeRepository.save(newEmployee); 88 | String token = tokenService.generateEmployeeToken(newEmployee); 89 | Instant expiresAt = tokenService.getExpirationDateFromToken(token); 90 | return new RegisterResponseDTO(newEmployee.getName(), token, expiresAt.toString()); 91 | } 92 | 93 | return new RegisterResponseDTO("Employee already exists", null, null); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | 6 | import java.io.Serializable; 7 | import java.util.HashSet; 8 | import java.util.Objects; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Table(name = "products") 13 | public class Product implements Serializable { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String name; 21 | @Column(nullable = false) 22 | private String description; 23 | @Column(nullable = false) 24 | private Double price; 25 | @Column(nullable = false) 26 | private Integer portionQuantity; 27 | @Column(nullable = false) 28 | private String portionUnit; 29 | @Column(name = "url_image", nullable = false) 30 | private String urlImage; 31 | 32 | @JsonIgnore 33 | @ManyToMany 34 | @JoinTable( 35 | name = "category_product", 36 | joinColumns = @JoinColumn(name = "product_id"), 37 | inverseJoinColumns = @JoinColumn(name = "category_id") 38 | ) 39 | private Set categories = new HashSet<>(); 40 | @JsonIgnore 41 | @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) 42 | private Set orderItems = new HashSet<>(); 43 | 44 | public Product() {} 45 | 46 | public Product(Long id, String name, String description) { 47 | this.id = id; 48 | this.name = name; 49 | this.description = description; 50 | } 51 | 52 | public Product(Long id, String name, String description, Double price, Integer portionQuantity, String portionUnit, String urlImage) { 53 | this.id = id; 54 | this.name = name; 55 | this.description = description; 56 | this.price = price; 57 | this.portionQuantity = portionQuantity; 58 | this.portionUnit = portionUnit; 59 | this.urlImage = urlImage; 60 | } 61 | 62 | public Product(Long id, String name, String description, Double price, Integer portionQuantity, String portionUnit, String urlImage, Set categories) { 63 | this.id = id; 64 | this.name = name; 65 | this.description = description; 66 | this.price = price; 67 | this.portionQuantity = portionQuantity; 68 | this.portionUnit = portionUnit; 69 | this.urlImage = urlImage; 70 | this.categories = categories; 71 | } 72 | 73 | public Long getId() { 74 | return id; 75 | } 76 | 77 | public void setId(Long id) { 78 | this.id = id; 79 | } 80 | 81 | public String getName() { 82 | return name; 83 | } 84 | 85 | public void setName(String name) { 86 | this.name = name; 87 | } 88 | 89 | public String getDescription() { 90 | return description; 91 | } 92 | 93 | public void setDescription(String description) { 94 | this.description = description; 95 | } 96 | 97 | public Double getPrice() { 98 | return price; 99 | } 100 | 101 | public void setPrice(Double price) { 102 | this.price = price; 103 | } 104 | 105 | public Integer getPortionQuantity() { 106 | return portionQuantity; 107 | } 108 | 109 | public void setPortionQuantity(Integer portionQuantity) { 110 | this.portionQuantity = portionQuantity; 111 | } 112 | 113 | public String getPortionUnit() { 114 | return portionUnit; 115 | } 116 | 117 | public void setPortionUnit(String portionUnit) { 118 | this.portionUnit = portionUnit; 119 | } 120 | 121 | public String getUrlImage() { 122 | return urlImage; 123 | } 124 | 125 | public void setUrlImage(String urlImage) { 126 | this.urlImage = urlImage; 127 | } 128 | 129 | public Set getCategories() { 130 | return categories; 131 | } 132 | 133 | public void setCategories(Set categories) { 134 | this.categories = categories; 135 | } 136 | 137 | public Set getOrderItems() { 138 | return orderItems; 139 | } 140 | 141 | public void setOrderItems(Set orderItems) { 142 | this.orderItems = orderItems; 143 | } 144 | 145 | @Override 146 | public boolean equals(Object object) { 147 | if (this == object) return true; 148 | if (object == null || getClass() != object.getClass()) return false; 149 | Product product = (Product) object; 150 | return Objects.equals(id, product.id); 151 | } 152 | 153 | @Override 154 | public int hashCode() { 155 | return Objects.hash(id); 156 | } 157 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.Order; 4 | import com.sushi.api.model.dto.order.OrderRequestDTO; 5 | import com.sushi.api.model.dto.order.OrderUpdateDTO; 6 | import com.sushi.api.services.OrderService; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 10 | import jakarta.validation.Valid; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import java.util.List; 19 | 20 | @RestController 21 | @RequestMapping(value = "/api/orders", produces = {"application/json"}) 22 | public class OrderController { 23 | @Autowired 24 | private OrderService orderService; 25 | 26 | @Operation(summary = "Get all orders (non-pageable)", 27 | description = "Returns a list of all orders without pagination.") 28 | @ApiResponses(value = { 29 | @ApiResponse(responseCode = "200", description = "Orders retrieved successfully"), 30 | @ApiResponse(responseCode = "500", description = "Internal server error") 31 | }) 32 | @GetMapping(value = "/list") 33 | public ResponseEntity> listAllNonPageable() { 34 | return new ResponseEntity<>(orderService.listAllNonPageable(), HttpStatus.OK); 35 | } 36 | 37 | @Operation(summary = "Get all orders (pageable)", 38 | description = "Returns a paginated list of orders.") 39 | @ApiResponses(value = { 40 | @ApiResponse(responseCode = "200", description = "Orders retrieved successfully"), 41 | @ApiResponse(responseCode = "500", description = "Internal server error") 42 | }) 43 | @GetMapping 44 | public ResponseEntity> listAllPageable(Pageable pageable) { 45 | return new ResponseEntity<>(orderService.listAllPageable(pageable).getContent(), HttpStatus.OK); 46 | } 47 | 48 | @Operation(summary = "Get order by ID", 49 | description = "Returns an order by its ID.") 50 | @ApiResponses(value = { 51 | @ApiResponse(responseCode = "200", description = "Order retrieved successfully"), 52 | @ApiResponse(responseCode = "404", description = "Order not found"), 53 | @ApiResponse(responseCode = "500", description = "Internal server error") 54 | }) 55 | @GetMapping(value = "/{id}") 56 | public ResponseEntity findOrderById(@PathVariable Long id) { 57 | Order order = orderService.findOrderById(id); 58 | return ResponseEntity.ok(order); 59 | } 60 | 61 | @Operation(summary = "Create a new order", 62 | description = "Create a new order with the provided details.") 63 | @ApiResponses(value = { 64 | @ApiResponse(responseCode = "201", description = "Order created successfully"), 65 | @ApiResponse(responseCode = "400", description = "Invalid input"), 66 | @ApiResponse(responseCode = "500", description = "Internal server error") 67 | }) 68 | @PostMapping 69 | public ResponseEntity createOrder(@Valid @RequestBody OrderRequestDTO dto) { 70 | return new ResponseEntity<>(orderService.createOrder(dto), HttpStatus.CREATED); 71 | } 72 | 73 | @Operation(summary = "Update an existing order", 74 | description = "Update an existing order with the provided details.") 75 | @ApiResponses(value = { 76 | @ApiResponse(responseCode = "200", description = "Order updated successfully"), 77 | @ApiResponse(responseCode = "400", description = "Invalid input"), 78 | @ApiResponse(responseCode = "404", description = "Order not found"), 79 | @ApiResponse(responseCode = "500", description = "Internal server error") 80 | }) 81 | @PutMapping 82 | public ResponseEntity replaceOrder(@Valid @RequestBody OrderUpdateDTO dto) { 83 | orderService.replaceOrder(dto); 84 | return ResponseEntity.ok().build(); 85 | } 86 | 87 | @Operation(summary = "Delete an order by ID", 88 | description = "Delete an order by its ID.") 89 | @ApiResponses(value = { 90 | @ApiResponse(responseCode = "204", description = "Order deleted successfully"), 91 | @ApiResponse(responseCode = "404", description = "Order not found"), 92 | @ApiResponse(responseCode = "500", description = "Internal server error") 93 | }) 94 | @DeleteMapping("/{id}") 95 | public ResponseEntity deleteOrder(@PathVariable Long id) { 96 | orderService.deleteOrder(id); 97 | return ResponseEntity.noContent().build(); 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.*; 5 | import com.sushi.api.model.dto.order.OrderRequestDTO; 6 | import com.sushi.api.model.dto.order.OrderUpdateDTO; 7 | import com.sushi.api.repositories.*; 8 | import jakarta.transaction.Transactional; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.time.LocalDateTime; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | @Service 18 | public class OrderService { 19 | private final OrderRepository orderRepository; 20 | private final CustomerRepository customerRepository; 21 | private final AddressRepository addressRepository; 22 | private final ProductRepository productRepository; 23 | private final OrderItemRepository orderItemRepository; 24 | 25 | public OrderService(OrderRepository orderRepository, CustomerRepository customerRepository, AddressRepository addressRepository, ProductRepository productRepository, OrderItemRepository orderItemRepository) { 26 | this.orderRepository = orderRepository; 27 | this.customerRepository = customerRepository; 28 | this.addressRepository = addressRepository; 29 | this.productRepository = productRepository; 30 | this.orderItemRepository = orderItemRepository; 31 | } 32 | 33 | public List listAllNonPageable() { 34 | return orderRepository.findAll(); 35 | } 36 | 37 | public Page listAllPageable(Pageable pageable) { 38 | return orderRepository.findAll(pageable); 39 | } 40 | 41 | public Order findOrderById(Long id) { 42 | return orderRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Order not found with this id.")); 43 | } 44 | 45 | @Transactional 46 | public Order createOrder(OrderRequestDTO dto) { 47 | Customer customer = customerRepository.findById(dto.customerId()) 48 | .orElseThrow(() -> new ResourceNotFoundException("Customer not found with this id.")); 49 | Address address = addressRepository.findById(dto.deliveryAddressId()) 50 | .orElseThrow(() -> new ResourceNotFoundException("Address not found with this id.")); 51 | 52 | Order order = new Order(); 53 | order.setOrderDate(LocalDateTime.now()); 54 | order.setCustomer(customer); 55 | order.setDeliveryAddress(address); 56 | 57 | List items = dto.items().stream().map(itemDto -> { 58 | Product product = productRepository.findById(itemDto.productId()) 59 | .orElseThrow(() -> new ResourceNotFoundException("Product not found with this id.")); 60 | OrderItem item = new OrderItem(); 61 | item.setProduct(product); 62 | item.setQuantity(itemDto.quantity()); 63 | item.setPrice(product.getPrice()); 64 | item.calculateTotalPrice(); 65 | item.setOrder(order); 66 | return item; 67 | }).collect(Collectors.toList()); 68 | 69 | order.setItems(items); 70 | order.calculateTotalAmount(); 71 | 72 | return orderRepository.save(order); 73 | } 74 | 75 | @Transactional 76 | public Order replaceOrder(OrderUpdateDTO dto) { 77 | Order order = orderRepository.findById(dto.id()) 78 | .orElseThrow(() -> new ResourceNotFoundException("Order not found with this id.")); 79 | 80 | Address address = addressRepository.findById(dto.deliveryAddressId()) 81 | .orElseThrow(() -> new ResourceNotFoundException("Address not found with this id.")); 82 | order.setDeliveryAddress(address); 83 | 84 | List items = dto.items().stream().map(itemDto -> { 85 | Product product = productRepository.findById(itemDto.productId()) 86 | .orElseThrow(() -> new ResourceNotFoundException("Product not found with this id.")); 87 | OrderItem item; 88 | if (itemDto.id() != null) { 89 | item = orderItemRepository.findById(itemDto.id()) 90 | .orElseThrow(() -> new ResourceNotFoundException("OrderItem not found with this id.")); 91 | } else { 92 | item = new OrderItem(); 93 | item.setOrder(order); 94 | } 95 | item.setProduct(product); 96 | item.setQuantity(itemDto.quantity()); 97 | item.setPrice(product.getPrice()); 98 | item.calculateTotalPrice(); 99 | return item; 100 | }).collect(Collectors.toList()); 101 | 102 | order.setItems(items); 103 | order.calculateTotalAmount(); 104 | 105 | return orderRepository.save(order); 106 | } 107 | 108 | 109 | @Transactional 110 | public void deleteOrder(Long id) { 111 | orderRepository.delete(findOrderById(id)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/services/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Address; 5 | import com.sushi.api.model.Customer; 6 | import com.sushi.api.model.Phone; 7 | import com.sushi.api.model.dto.customer.CustomerRequestDTO; 8 | import com.sushi.api.model.dto.customer.CustomerUpdateDTO; 9 | import com.sushi.api.repositories.CustomerRepository; 10 | import jakarta.transaction.Transactional; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.dao.DataIntegrityViolationException; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.util.List; 19 | import java.util.Set; 20 | import java.util.UUID; 21 | import java.util.stream.Collectors; 22 | 23 | @Service 24 | public class CustomerService { 25 | @Autowired 26 | private CustomerRepository customerRepository; 27 | @Autowired 28 | private PasswordEncoder passwordEncoder; 29 | 30 | public Page listAllPageable(Pageable pageable) { 31 | return customerRepository.findAll(pageable); 32 | } 33 | 34 | public List listAllNonPageable() { 35 | return customerRepository.findAll(); 36 | } 37 | 38 | public Customer findCustomerById(UUID id) { 39 | return customerRepository.findById(id) 40 | .orElseThrow(() -> new ResourceNotFoundException("Customer not found with this id.")); 41 | } 42 | 43 | public List findCustomerByName(String name) { 44 | List customers = customerRepository.findByNameContainingIgnoreCase(name); 45 | if (customers.isEmpty()) { 46 | throw new ResourceNotFoundException("No customers found with this name."); 47 | } 48 | return customers; 49 | } 50 | 51 | public Customer findCustomerByEmail(String email) { 52 | return customerRepository.findByEmail(email).orElseThrow(() -> new ResourceNotFoundException("Customer not found with this id.")); 53 | } 54 | 55 | @Transactional 56 | public Customer createCustomer(CustomerRequestDTO dto) { 57 | if (customerRepository.findByEmail(dto.email()).isPresent()) { 58 | throw new DataIntegrityViolationException("Data integrity violation error occurred."); 59 | } 60 | 61 | Customer customer = new Customer(); 62 | customer.setName(dto.name()); 63 | customer.setEmail(dto.email()); 64 | customer.setPassword(passwordEncoder.encode(dto.password())); 65 | 66 | Phone phone = new Phone(); 67 | phone.setNumber(dto.phone().number()); 68 | phone.setCustomer(customer); 69 | customer.setPhone(phone); 70 | 71 | Set
addresses = dto.addresses().stream() 72 | .map(addressDTO -> { 73 | Address address = new Address(); 74 | address.setNumber(addressDTO.number()); 75 | address.setStreet(addressDTO.street()); 76 | address.setNeighborhood(addressDTO.neighborhood()); 77 | address.setCustomer(customer); 78 | return address; 79 | }) 80 | .collect(Collectors.toSet()); 81 | 82 | customer.setAddresses(addresses); 83 | 84 | return customerRepository.save(customer); 85 | } 86 | 87 | @Transactional 88 | public void replaceCustomer(CustomerUpdateDTO dto) { 89 | Customer savedCustomer = findCustomerById(dto.id()); 90 | savedCustomer.setName(dto.name()); 91 | savedCustomer.setEmail(dto.email()); 92 | savedCustomer.setPassword(passwordEncoder.encode(dto.password())); 93 | 94 | Phone phone = savedCustomer.getPhone(); 95 | if (phone == null) { 96 | phone = new Phone(); 97 | phone.setCustomer(savedCustomer); 98 | } 99 | phone.setNumber(dto.phone().number()); 100 | savedCustomer.setPhone(phone); 101 | 102 | savedCustomer.getAddresses().clear(); 103 | if (dto.addresses() != null) { 104 | Set
updatedAddresses = dto.addresses().stream() 105 | .map(addressDTO -> { 106 | Address address = new Address(); 107 | address.setNumber(addressDTO.number()); 108 | address.setStreet(addressDTO.street()); 109 | address.setNeighborhood(addressDTO.neighborhood()); 110 | address.setCustomer(savedCustomer); 111 | return address; 112 | }) 113 | .collect(Collectors.toSet()); 114 | savedCustomer.getAddresses().addAll(updatedAddresses); 115 | } 116 | 117 | customerRepository.save(savedCustomer); 118 | } 119 | 120 | @Transactional 121 | public void deleteCustomer(UUID id) { 122 | customerRepository.delete(findCustomerById(id)); 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/exceptions/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.exceptions.handler; 2 | 3 | import com.sushi.api.exceptions.BadRequestException; 4 | import com.sushi.api.exceptions.ResourceNotFoundException; 5 | import org.springframework.dao.DataIntegrityViolationException; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.validation.FieldError; 9 | import org.springframework.web.bind.MethodArgumentNotValidException; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 13 | import org.springframework.web.servlet.resource.NoResourceFoundException; 14 | 15 | import java.time.LocalDateTime; 16 | import java.util.List; 17 | import java.util.stream.Collectors; 18 | 19 | @ControllerAdvice 20 | public class GlobalExceptionHandler { 21 | @ExceptionHandler(ResourceNotFoundException.class) 22 | public ResponseEntity handlerResourceNotFoundException(ResourceNotFoundException ex) { 23 | ExceptionResponse response = new ExceptionResponse( 24 | "Resource Not Found Exception", 25 | HttpStatus.NOT_FOUND.value(), 26 | ex.getMessage(), 27 | ex.getClass().getName(), 28 | LocalDateTime.now()); 29 | return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); 30 | } 31 | 32 | @ExceptionHandler(BadRequestException.class) 33 | public ResponseEntity handlerBadRequestException(BadRequestException ex) { 34 | ExceptionResponse response = new ExceptionResponse( 35 | "Bad Request Exception", 36 | HttpStatus.BAD_REQUEST.value(), 37 | ex.getMessage(), 38 | ex.getClass().getName(), 39 | LocalDateTime.now()); 40 | return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); 41 | } 42 | 43 | @ExceptionHandler(MethodArgumentTypeMismatchException.class) 44 | public ResponseEntity handlerMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { 45 | ExceptionResponse response = new ExceptionResponse( 46 | "Argument Type Mismatch", 47 | HttpStatus.BAD_REQUEST.value(), 48 | ex.getMessage(), 49 | ex.getClass().getName(), 50 | LocalDateTime.now()); 51 | return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); 52 | } 53 | 54 | @ExceptionHandler(MethodArgumentNotValidException.class) 55 | public ResponseEntity handlerMethodArgumentNotValidException(MethodArgumentNotValidException ex) { 56 | 57 | List fieldErrors = ex.getBindingResult().getFieldErrors(); 58 | 59 | String fields = fieldErrors.stream().map(FieldError::getField).collect(Collectors.joining(", ")); 60 | String fieldsMessage = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", ")); 61 | 62 | ValidationExceptionDetails response = new ValidationExceptionDetails( 63 | "Method Argument Not Valid", 64 | HttpStatus.BAD_REQUEST.value(), 65 | "Validation failed for one or more fields.", 66 | ex.getClass().getName(), 67 | LocalDateTime.now(), 68 | fields, 69 | fieldsMessage 70 | ); 71 | 72 | return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); 73 | } 74 | 75 | @ExceptionHandler(NullPointerException.class) 76 | public ResponseEntity handlerNullPointerException(NullPointerException ex) { 77 | ExceptionResponse response = new ExceptionResponse( 78 | "Null Pointer Exception", 79 | HttpStatus.INTERNAL_SERVER_ERROR.value(), 80 | ex.getMessage(), 81 | ex.getClass().getName(), 82 | LocalDateTime.now()); 83 | return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); 84 | } 85 | 86 | @ExceptionHandler(DataIntegrityViolationException.class) 87 | public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { 88 | ExceptionResponse response = new ExceptionResponse( 89 | "Data Integrity Violation", 90 | HttpStatus.INTERNAL_SERVER_ERROR.value(), 91 | "Data integrity violation error occurred.", 92 | ex.getClass().getName(), 93 | LocalDateTime.now() 94 | ); 95 | 96 | return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); 97 | } 98 | 99 | @ExceptionHandler(NoResourceFoundException.class) 100 | public ResponseEntity handleNoResourceFoundException(NoResourceFoundException ex) { 101 | ExceptionResponse response = new ExceptionResponse( 102 | "No Resource Found Exception", 103 | HttpStatus.NOT_FOUND.value(), 104 | "The requested resource was not found.", 105 | ex.getClass().getName(), 106 | LocalDateTime.now() 107 | ); 108 | 109 | return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.Customer; 4 | import com.sushi.api.model.Product; 5 | import com.sushi.api.model.dto.product.ProductRequestDTO; 6 | import com.sushi.api.model.dto.product.ProductUpdateDTO; 7 | import com.sushi.api.services.ProductService; 8 | import io.swagger.v3.oas.annotations.Operation; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 11 | import jakarta.validation.Valid; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.List; 20 | 21 | @RestController 22 | @RequestMapping(value = "/api/products", produces = {"application/json"}) 23 | public class ProductController { 24 | @Autowired 25 | private ProductService productService; 26 | 27 | @Operation(summary = "Get all products (non-pageable)", 28 | description = "Retrieve a list of all products without pagination.") 29 | @ApiResponses(value = { 30 | @ApiResponse(responseCode = "200", description = "Products retrieved successfully"), 31 | @ApiResponse(responseCode = "500", description = "Internal server error") 32 | }) 33 | @GetMapping(value = "/list") 34 | public ResponseEntity> listAllNonPageable() { 35 | return new ResponseEntity<>(productService.listAllNonPageable(), HttpStatus.OK); 36 | } 37 | 38 | @Operation(summary = "Get all products (pageable)", 39 | description = "Retrieve a paginated list of products.") 40 | @ApiResponses(value = { 41 | @ApiResponse(responseCode = "200", description = "Products retrieved successfully"), 42 | @ApiResponse(responseCode = "500", description = "Internal server error") 43 | }) 44 | @GetMapping 45 | public ResponseEntity> listAllPageable(Pageable pageable) { 46 | return new ResponseEntity<>(productService.listAllPageable(pageable).getContent(), HttpStatus.OK); 47 | } 48 | 49 | @Operation(summary = "Get product by ID", 50 | description = "Retrieve a product by its ID.") 51 | @ApiResponses(value = { 52 | @ApiResponse(responseCode = "200", description = "Product retrieved successfully"), 53 | @ApiResponse(responseCode = "404", description = "Product not found"), 54 | @ApiResponse(responseCode = "500", description = "Internal server error") 55 | }) 56 | @GetMapping(value = "/{id}") 57 | public ResponseEntity findProductById(@PathVariable Long id) { 58 | Product product = productService.findProductById(id); 59 | return ResponseEntity.ok(product); 60 | } 61 | 62 | @Operation(summary = "Get products by name", 63 | description = "Retrieve a list of products by name.") 64 | @ApiResponses(value = { 65 | @ApiResponse(responseCode = "200", description = "Products retrieved successfully"), 66 | @ApiResponse(responseCode = "500", description = "Internal server error") 67 | }) 68 | @GetMapping(value = "/find/by-name") 69 | public ResponseEntity> findProductByName(@RequestParam String name) { 70 | return ResponseEntity.ok(productService.findProductByName(name)); 71 | } 72 | 73 | @Operation(summary = "Create a new product", 74 | description = "Create a new product with the provided details.") 75 | @ApiResponses(value = { 76 | @ApiResponse(responseCode = "201", description = "Product created successfully"), 77 | @ApiResponse(responseCode = "400", description = "Invalid input"), 78 | @ApiResponse(responseCode = "500", description = "Internal server error") 79 | }) 80 | @PostMapping 81 | public ResponseEntity createProduct(@Valid @RequestBody ProductRequestDTO dto) { 82 | return new ResponseEntity<>(productService.createProduct(dto), HttpStatus.CREATED); 83 | } 84 | 85 | @Operation(summary = "Update an existing product", 86 | description = "Update an existing product with the provided details.") 87 | @ApiResponses(value = { 88 | @ApiResponse(responseCode = "200", description = "Product updated successfully"), 89 | @ApiResponse(responseCode = "400", description = "Invalid input"), 90 | @ApiResponse(responseCode = "404", description = "Product not found"), 91 | @ApiResponse(responseCode = "500", description = "Internal server error") 92 | }) 93 | @PutMapping 94 | public ResponseEntity replaceProduct(@Valid @RequestBody ProductUpdateDTO dto) { 95 | productService.replaceProduct(dto); 96 | return ResponseEntity.noContent().build(); 97 | } 98 | 99 | @Operation(summary = "Delete a product by ID", 100 | description = "Delete a product by its ID.") 101 | @ApiResponses(value = { 102 | @ApiResponse(responseCode = "204", description = "Product deleted successfully"), 103 | @ApiResponse(responseCode = "404", description = "Product not found"), 104 | @ApiResponse(responseCode = "500", description = "Internal server error") 105 | }) 106 | @DeleteMapping("/{id}") 107 | public ResponseEntity deleteProduct(@PathVariable Long id) { 108 | productService.deleteProduct(id); 109 | return ResponseEntity.noContent().build(); 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.Employee; 4 | import com.sushi.api.model.dto.employee.EmployeeRequestDTO; 5 | import com.sushi.api.model.dto.employee.EmployeeUpdateDTO; 6 | import com.sushi.api.services.EmployeeService; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 10 | import jakarta.validation.Valid; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | import java.util.List; 18 | import java.util.UUID; 19 | 20 | @RestController 21 | @RequestMapping(value = "/api/employees", produces = {"application/json"}) 22 | public class EmployeeController { 23 | 24 | @Autowired 25 | private EmployeeService employeeService; 26 | 27 | @Operation(summary = "Get all employees (pageable)", 28 | description = "Returns a paginated list of employees.") 29 | @ApiResponses(value = { 30 | @ApiResponse(responseCode = "200", description = "Employees retrieved successfully"), 31 | @ApiResponse(responseCode = "500", description = "Internal server error") 32 | }) 33 | @GetMapping 34 | public ResponseEntity> listAllPageable(Pageable pageable) { 35 | return new ResponseEntity<>(employeeService.listAllPageable(pageable).getContent(), HttpStatus.OK); 36 | } 37 | 38 | @Operation(summary = "Get all employees (non-pageable)", 39 | description = "Returns a list of all employees without pagination.") 40 | @ApiResponses(value = { 41 | @ApiResponse(responseCode = "200", description = "Employees retrieved successfully"), 42 | @ApiResponse(responseCode = "500", description = "Internal server error") 43 | }) 44 | @GetMapping(value = "/list") 45 | public ResponseEntity> listAllNonPageable() { 46 | return new ResponseEntity<>(employeeService.listAllNonPageable(), HttpStatus.OK); 47 | } 48 | 49 | @Operation(summary = "Get employee by ID", 50 | description = "Returns an employee by their ID.") 51 | @ApiResponses(value = { 52 | @ApiResponse(responseCode = "200", description = "Employee retrieved successfully"), 53 | @ApiResponse(responseCode = "404", description = "Employee not found"), 54 | @ApiResponse(responseCode = "500", description = "Internal server error") 55 | }) 56 | @GetMapping(value = "/{id}") 57 | public ResponseEntity findEmployeeById(@PathVariable UUID id) { 58 | Employee employee = employeeService.findEmployeeById(id); 59 | return ResponseEntity.ok(employee); 60 | } 61 | 62 | @Operation(summary = "Find employee by email", 63 | description = "Returns an employee by their email.") 64 | @ApiResponses(value = { 65 | @ApiResponse(responseCode = "200", description = "Employee retrieved successfully"), 66 | @ApiResponse(responseCode = "404", description = "Employee not found"), 67 | @ApiResponse(responseCode = "500", description = "Internal server error") 68 | }) 69 | @GetMapping(value = "/find/by-email") 70 | public ResponseEntity findEmployeeByEmail(@RequestParam String email) { 71 | return ResponseEntity.ok(employeeService.findEmployeeByEmail(email)); 72 | } 73 | 74 | @Operation(summary = "Create a new employee", 75 | description = "Create a new employee with the provided details.") 76 | @ApiResponses(value = { 77 | @ApiResponse(responseCode = "201", description = "Employee created successfully"), 78 | @ApiResponse(responseCode = "400", description = "Invalid input"), 79 | @ApiResponse(responseCode = "500", description = "Internal server error") 80 | }) 81 | @PostMapping 82 | public ResponseEntity createEmployee(@Valid @RequestBody EmployeeRequestDTO dto) { 83 | return new ResponseEntity<>(employeeService.createEmployee(dto), HttpStatus.CREATED); 84 | } 85 | 86 | @Operation(summary = "Update an existing employee", 87 | description = "Update an existing employee with the provided details.") 88 | @ApiResponses(value = { 89 | @ApiResponse(responseCode = "204", description = "Employee updated successfully"), 90 | @ApiResponse(responseCode = "400", description = "Invalid input"), 91 | @ApiResponse(responseCode = "404", description = "Employee not found"), 92 | @ApiResponse(responseCode = "500", description = "Internal server error") 93 | }) 94 | @PutMapping 95 | public ResponseEntity replaceEmployee(@Valid @RequestBody EmployeeUpdateDTO dto) { 96 | employeeService.replaceEmployee(dto); 97 | return ResponseEntity.noContent().build(); 98 | } 99 | 100 | @Operation(summary = "Delete an employee by ID", 101 | description = "Delete an employee by their ID.") 102 | @ApiResponses(value = { 103 | @ApiResponse(responseCode = "204", description = "Employee deleted successfully"), 104 | @ApiResponse(responseCode = "404", description = "Employee not found"), 105 | @ApiResponse(responseCode = "500", description = "Internal server error") 106 | }) 107 | @DeleteMapping("/{id}") 108 | public ResponseEntity deleteEmployee(@PathVariable UUID id) { 109 | employeeService.deleteEmployee(id); 110 | return ResponseEntity.noContent().build(); 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.Customer; 4 | import com.sushi.api.model.dto.customer.CustomerRequestDTO; 5 | import com.sushi.api.model.dto.customer.CustomerUpdateDTO; 6 | import com.sushi.api.services.CustomerService; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 10 | import jakarta.validation.Valid; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import java.util.List; 19 | import java.util.UUID; 20 | 21 | @RestController 22 | @RequestMapping(value = "/api/customers", produces = {"application/json"}) 23 | public class CustomerController { 24 | @Autowired 25 | private CustomerService customerService; 26 | 27 | @Operation(summary = "Get all customers (pageable)", 28 | description = "Returns a paginated list of customers.") 29 | @ApiResponses(value = { 30 | @ApiResponse(responseCode = "200", description = "Customers retrieved successfully"), 31 | @ApiResponse(responseCode = "500", description = "Internal server error") 32 | }) 33 | @GetMapping 34 | public ResponseEntity> listAllPageable(Pageable pageable) { 35 | return new ResponseEntity<>(customerService.listAllPageable(pageable).getContent(), HttpStatus.OK); 36 | } 37 | 38 | @Operation(summary = "Get all customers (non-pageable)", 39 | description = "Returns a list of all customers without pagination.") 40 | @ApiResponses(value = { 41 | @ApiResponse(responseCode = "200", description = "Customers retrieved successfully"), 42 | @ApiResponse(responseCode = "500", description = "Internal server error") 43 | }) 44 | @GetMapping(value = "/list") 45 | public ResponseEntity> listAllNonPageable() { 46 | return new ResponseEntity<>(customerService.listAllNonPageable(), HttpStatus.OK); 47 | } 48 | 49 | @Operation(summary = "Get customer by ID", 50 | description = "Returns a customer by its ID.") 51 | @ApiResponses(value = { 52 | @ApiResponse(responseCode = "200", description = "Customer retrieved successfully"), 53 | @ApiResponse(responseCode = "404", description = "Customer not found"), 54 | @ApiResponse(responseCode = "500", description = "Internal server error") 55 | }) 56 | @GetMapping(value = "/{id}") 57 | public ResponseEntity findCustomerById(@PathVariable UUID id) { 58 | Customer customer = customerService.findCustomerById(id); 59 | return ResponseEntity.ok(customer); 60 | } 61 | 62 | @Operation(summary = "Find customers by name", 63 | description = "Returns a list of customers matching the given name.") 64 | @ApiResponses(value = { 65 | @ApiResponse(responseCode = "200", description = "Customers retrieved successfully"), 66 | @ApiResponse(responseCode = "404", description = "No customers found with the given name"), 67 | @ApiResponse(responseCode = "500", description = "Internal server error") 68 | }) 69 | @GetMapping(value = "/find/by-name") 70 | public ResponseEntity> findCustomerByName(@RequestParam String name) { 71 | return ResponseEntity.ok(customerService.findCustomerByName(name)); 72 | } 73 | 74 | @Operation(summary = "Find customer by email", 75 | description = "Returns a customer by its email.") 76 | @ApiResponses(value = { 77 | @ApiResponse(responseCode = "200", description = "Customer retrieved successfully"), 78 | @ApiResponse(responseCode = "404", description = "Customer not found"), 79 | @ApiResponse(responseCode = "500", description = "Internal server error") 80 | }) 81 | @GetMapping(value = "/find/by-email") 82 | public ResponseEntity findCustomerByEmail(@RequestParam String email) { 83 | return ResponseEntity.ok(customerService.findCustomerByEmail(email)); 84 | } 85 | 86 | @Operation(summary = "Create a new customer", 87 | description = "Create a new customer with the provided details.") 88 | @ApiResponses(value = { 89 | @ApiResponse(responseCode = "201", description = "Customer created successfully"), 90 | @ApiResponse(responseCode = "400", description = "Invalid input"), 91 | @ApiResponse(responseCode = "500", description = "Internal server error") 92 | }) 93 | @PostMapping 94 | public ResponseEntity createCustomer(@Valid @RequestBody CustomerRequestDTO dto) { 95 | return new ResponseEntity<>(customerService.createCustomer(dto), HttpStatus.CREATED); 96 | } 97 | 98 | @Operation(summary = "Update an existing customer", 99 | description = "Update an existing customer with the provided details.") 100 | @ApiResponses(value = { 101 | @ApiResponse(responseCode = "204", description = "Customer updated successfully"), 102 | @ApiResponse(responseCode = "400", description = "Invalid input"), 103 | @ApiResponse(responseCode = "404", description = "Customer not found"), 104 | @ApiResponse(responseCode = "500", description = "Internal server error") 105 | }) 106 | @PutMapping 107 | public ResponseEntity replaceCustomer(@Valid @RequestBody CustomerUpdateDTO dto) { 108 | customerService.replaceCustomer(dto); 109 | return ResponseEntity.noContent().build(); 110 | } 111 | 112 | @Operation(summary = "Delete a customer by ID", 113 | description = "Delete an existing customer by ID.") 114 | @ApiResponses(value = { 115 | @ApiResponse(responseCode = "204", description = "Customer deleted successfully"), 116 | @ApiResponse(responseCode = "404", description = "Customer not found"), 117 | @ApiResponse(responseCode = "500", description = "Internal server error") 118 | }) 119 | @DeleteMapping("/{id}") 120 | public ResponseEntity deleteCustomer(@PathVariable UUID id) { 121 | customerService.deleteCustomer(id); 122 | return ResponseEntity.noContent().build(); 123 | } 124 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/controllers/OrderControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sushi.api.exceptions.ResourceNotFoundException; 5 | import com.sushi.api.model.Order; 6 | import com.sushi.api.security.TokenService; 7 | import com.sushi.api.services.OrderService; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageImpl; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import static com.sushi.api.common.OrderConstants.*; 22 | import static org.mockito.Mockito.when; 23 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 27 | 28 | @WebMvcTest(OrderController.class) 29 | public class OrderControllerTest { 30 | @Autowired 31 | private MockMvc mockMvc; 32 | @Autowired 33 | private ObjectMapper objectMapper; 34 | @MockBean 35 | private TokenService tokenService; 36 | @MockBean 37 | private OrderService orderService; 38 | 39 | @Test 40 | @WithMockUser(roles = {"ADMIN"}) 41 | @DisplayName("Should return a list of orders inside page object when successful") 42 | public void listAllPageable_ReturnsAllOrdersWithPagination() throws Exception { 43 | Pageable pageable = PageRequest.of(0, 10); 44 | Page page = new PageImpl<>(ORDERS, pageable, ORDERS.size()); 45 | 46 | when(orderService.listAllPageable(pageable)).thenReturn(page); 47 | 48 | String expectedJson = objectMapper.writeValueAsString(ORDERS); 49 | 50 | mockMvc 51 | .perform(get("/api/orders") 52 | .param("page", "0") 53 | .param("size", "10") 54 | .accept(MediaType.APPLICATION_JSON)) 55 | .andExpect(status().isOk()) 56 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 57 | .andExpect(content().json(expectedJson)); 58 | } 59 | 60 | @Test 61 | @WithMockUser(roles = {"ADMIN"}) 62 | @DisplayName("Should return a list of orders when successful") 63 | public void listAllNonPageable_ReturnsAllOrders() throws Exception { 64 | String expectedJson = objectMapper.writeValueAsString(ORDERS); 65 | 66 | when(orderService.listAllNonPageable()).thenReturn(ORDERS); 67 | 68 | mockMvc.perform(get("/api/orders/list") 69 | .accept(MediaType.APPLICATION_JSON)) 70 | .andExpect(status().isOk()) 71 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 72 | .andExpect(content().json(expectedJson)); 73 | } 74 | 75 | @Test 76 | @WithMockUser(roles = {"ADMIN", "USER"}) 77 | @DisplayName("Should return a order by id when successful") 78 | public void findOrderById_ReturnsOrderById() throws Exception { 79 | when(orderService.findOrderById(ORDER.getId())).thenReturn(ORDER); 80 | 81 | String expectedJson = objectMapper.writeValueAsString(ORDER); 82 | 83 | mockMvc.perform(get("/api/orders/{id}", ORDER.getId()) 84 | .accept(MediaType.APPLICATION_JSON)) 85 | .andExpect(status().isOk()) 86 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 87 | .andExpect(content().json(expectedJson)); 88 | } 89 | 90 | @Test 91 | @WithMockUser(roles = {"ADMIN", "USER"}) 92 | @DisplayName("Should return ResourceNotFoundException when trying to find a order by id that does not exist") 93 | public void findOrderById_ReturnsNotFound_WhenOrderDoesNotExist() throws Exception { 94 | when(orderService.findOrderById(5L)).thenThrow(new ResourceNotFoundException("Order not found")); 95 | 96 | mockMvc.perform(get("/api/orders/{id}", 5L) 97 | .accept(MediaType.APPLICATION_JSON)) 98 | .andExpect(status().isNotFound()) 99 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)); 100 | } 101 | 102 | @Test 103 | @WithMockUser(roles = {"ADMIN", "USER"}) 104 | @DisplayName("Should create a new order and returns Created") 105 | public void createOrder_WithValidData_ReturnsOrderCreated() throws Exception { 106 | String orderJson = objectMapper.writeValueAsString(ORDER_REQUEST_DTO); 107 | 108 | mockMvc 109 | .perform(post("/api/orders") 110 | .contentType(MediaType.APPLICATION_JSON) 111 | .content(orderJson) 112 | .with(csrf())) 113 | .andExpect(status().isCreated()); 114 | } 115 | 116 | @Test 117 | @WithMockUser(roles = {"ADMIN", "USER"}) 118 | @DisplayName("Should replace an existing order") 119 | public void replaceOrder_WithValidData() throws Exception { 120 | String orderJson = objectMapper.writeValueAsString(ORDER_UPDATE_DTO); 121 | 122 | mockMvc 123 | .perform(put("/api/orders") 124 | .contentType(MediaType.APPLICATION_JSON) 125 | .content(orderJson) 126 | .with(csrf())) 127 | .andExpect(status().isOk()); 128 | } 129 | 130 | @Test 131 | @WithMockUser(roles = {"ADMIN", "USER"}) 132 | @DisplayName("Should delete an order by id and returns No Content") 133 | public void deleteOrderById_ReturnsNoContent() throws Exception { 134 | when(orderService.findOrderById(ORDER.getId())).thenReturn(ORDER); 135 | 136 | mockMvc 137 | .perform(delete("/api/orders/{id}", ORDER.getId()) 138 | .accept(MediaType.APPLICATION_JSON) 139 | .with(csrf())) 140 | .andExpect(status().isNoContent()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/sushi/api/controllers/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.sushi.api.model.Category; 4 | import com.sushi.api.model.dto.category.CategoryRequestDTO; 5 | import com.sushi.api.model.dto.category.CategoryUpdateDTO; 6 | import com.sushi.api.services.CategoryService; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.media.Content; 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 11 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 12 | import jakarta.validation.Valid; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import java.util.List; 21 | 22 | @RestController 23 | @RequestMapping(value = "/api/categories", produces = {"application/json"}) 24 | public class CategoryController { 25 | @Autowired 26 | private CategoryService categoryService; 27 | 28 | @Operation(summary = "Get all categories (non-pageable)", 29 | description = "Returns a list of all categories without pagination.") 30 | @ApiResponses(value = { 31 | @ApiResponse(responseCode = "200", description = "Categories retrieved successfully", 32 | content = @Content(schema = @Schema(implementation = Category.class))), 33 | @ApiResponse(responseCode = "500", description = "Internal server error") 34 | }) 35 | @GetMapping(value = "/list", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) 36 | public ResponseEntity> listAllNonPageable() { 37 | return new ResponseEntity<>(categoryService.listAllNonPageable(), HttpStatus.OK); 38 | } 39 | 40 | @Operation(summary = "Get all categories (pageable)", 41 | description = "Returns a paginated list of categories.") 42 | @ApiResponses(value = { 43 | @ApiResponse(responseCode = "200", description = "Categories retrieved successfully", 44 | content = @Content(schema = @Schema(implementation = Category.class))), 45 | @ApiResponse(responseCode = "500", description = "Internal server error") 46 | }) 47 | @GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) 48 | public ResponseEntity> listAllPageable(Pageable pageable) { 49 | return new ResponseEntity<>(categoryService.listAllPageable(pageable).getContent(), HttpStatus.OK); 50 | } 51 | 52 | @Operation(summary = "Get category by ID", 53 | description = "Returns a single category by its ID.") 54 | @ApiResponses(value = { 55 | @ApiResponse(responseCode = "200", description = "Category retrieved successfully", 56 | content = @Content(schema = @Schema(implementation = Category.class))), 57 | @ApiResponse(responseCode = "404", description = "Category not found"), 58 | @ApiResponse(responseCode = "500", description = "Internal server error") 59 | }) 60 | @GetMapping(value = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) 61 | public ResponseEntity findCategoryById(@PathVariable Long id) { 62 | Category category = categoryService.findCategoryById(id); 63 | return ResponseEntity.ok(category); 64 | } 65 | 66 | @Operation(summary = "Find categories by name", 67 | description = "Returns a list of categories matching the given name.") 68 | @ApiResponses(value = { 69 | @ApiResponse(responseCode = "200", description = "Categories retrieved successfully", 70 | content = @Content(schema = @Schema(implementation = Category.class))), 71 | @ApiResponse(responseCode = "404", description = "No categories found with the given name"), 72 | @ApiResponse(responseCode = "500", description = "Internal server error") 73 | }) 74 | @GetMapping(value = "/find/by-name", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) 75 | public ResponseEntity> findCategoryByName(@RequestParam String name) { 76 | return ResponseEntity.ok(categoryService.findCategoryByName(name)); 77 | } 78 | 79 | @Operation(summary = "Create a new category", 80 | description = "Create a new category with the provided details.") 81 | @ApiResponses(value = { 82 | @ApiResponse(responseCode = "201", description = "Category created successfully", 83 | content = @Content(schema = @Schema(implementation = Category.class))), 84 | @ApiResponse(responseCode = "400", description = "Invalid input"), 85 | @ApiResponse(responseCode = "500", description = "Internal server error") 86 | }) 87 | @PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }, 88 | produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }) 89 | public ResponseEntity createCategory(@Valid @RequestBody CategoryRequestDTO dto) { 90 | return new ResponseEntity<>(categoryService.createCategory(dto), HttpStatus.CREATED); 91 | } 92 | 93 | @Operation(summary = "Update an existing category", 94 | description = "Update an existing category with the provided details.") 95 | @ApiResponses(value = { 96 | @ApiResponse(responseCode = "204", description = "Category updated successfully"), 97 | @ApiResponse(responseCode = "400", description = "Invalid input"), 98 | @ApiResponse(responseCode = "404", description = "Category not found"), 99 | @ApiResponse(responseCode = "500", description = "Internal server error") 100 | }) 101 | @PutMapping(consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }) 102 | public ResponseEntity replaceCategory(@Valid @RequestBody CategoryUpdateDTO dto) { 103 | categoryService.replaceCategory(dto); 104 | return ResponseEntity.noContent().build(); 105 | } 106 | 107 | @Operation(summary = "Delete a category by ID", 108 | description = "Delete an existing category by ID.") 109 | @ApiResponses(value = { 110 | @ApiResponse(responseCode = "204", description = "Category deleted successfully"), 111 | @ApiResponse(responseCode = "404", description = "Category not found"), 112 | @ApiResponse(responseCode = "500", description = "Internal server error") 113 | }) 114 | @DeleteMapping("/{id}") 115 | public ResponseEntity deleteCategory(@PathVariable Long id) { 116 | categoryService.deleteCategory(id); 117 | return ResponseEntity.noContent().build(); 118 | } 119 | } -------------------------------------------------------------------------------- /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 https://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/test/java/com/sushi/api/services/OrderServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Order; 5 | import com.sushi.api.model.dto.order.OrderRequestDTO; 6 | import com.sushi.api.model.dto.order.OrderUpdateDTO; 7 | import com.sushi.api.repositories.*; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageImpl; 15 | import org.springframework.data.domain.Pageable; 16 | import org.springframework.test.context.junit.jupiter.SpringExtension; 17 | 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Optional; 21 | 22 | import static com.sushi.api.common.CustomerConstants.*; 23 | import static com.sushi.api.common.CustomerConstants.CUSTOMER; 24 | import static com.sushi.api.common.OrderConstants.*; 25 | import static com.sushi.api.common.ProductConstants.PRODUCT; 26 | import static org.assertj.core.api.Assertions.assertThatCode; 27 | import static org.junit.jupiter.api.Assertions.*; 28 | import static org.mockito.Mockito.*; 29 | 30 | @ExtendWith(SpringExtension.class) 31 | public class OrderServiceTest { 32 | @InjectMocks 33 | private OrderService orderService; 34 | @Mock 35 | private OrderRepository orderRepository; 36 | @Mock 37 | private CustomerRepository customerRepository; 38 | @Mock 39 | private AddressRepository addressRepository; 40 | @Mock 41 | private ProductRepository productRepository; 42 | @Mock 43 | private OrderItemRepository orderItemRepository; 44 | 45 | @Test 46 | @DisplayName("Should return a list of orders inside page object when successful") 47 | void listAll_ReturnsListOfOrdersInsidePageObject_WhenSuccessful() { 48 | Page orderPage = mock(Page.class); 49 | Pageable pageable = mock(Pageable.class); 50 | 51 | when(orderRepository.findAll(pageable)).thenReturn(orderPage); 52 | Page result = orderService.listAllPageable(pageable); 53 | 54 | assertNotNull(result); 55 | assertEquals(orderPage, result); 56 | } 57 | 58 | @Test 59 | @DisplayName("Should return an empty list of orders inside page object when there are no orders") 60 | void listAllPageable_ReturnsEmptyListOfOrdersInsidePageObject_WhenThereAreNoOrders() { 61 | Page emptyOrderPage = new PageImpl<>(Collections.emptyList()); 62 | Pageable pageable = mock(Pageable.class); 63 | 64 | when(orderRepository.findAll(pageable)).thenReturn(emptyOrderPage); 65 | 66 | Page result = orderService.listAllPageable(pageable); 67 | 68 | assertNotNull(result); 69 | assertTrue(result.isEmpty()); 70 | } 71 | 72 | @Test 73 | @DisplayName("Should return a list of orders when successful") 74 | void listAllNonPageable_ReturnsListOfOrders_WhenSuccessful() { 75 | when(orderRepository.findAll()).thenReturn(ORDERS); 76 | 77 | List result = orderService.listAllNonPageable(); 78 | 79 | assertNotNull(result); 80 | assertEquals(ORDERS.size(), result.size()); 81 | } 82 | 83 | @Test 84 | @DisplayName("Should return an empty list of orders when there are no orders") 85 | void listAllNonPageable_ReturnsEmptyListOfOrders_WhenThereAreNoOrders() { 86 | when(orderRepository.findAll()).thenReturn(Collections.emptyList()); 87 | 88 | List result = orderService.listAllNonPageable(); 89 | 90 | assertEquals(0, result.size()); 91 | } 92 | 93 | @Test 94 | @DisplayName("Should return an order by id when successful") 95 | void findOrderById_ReturnsOrder_WhenSuccessful() { 96 | when(orderRepository.findById(ORDER.getId())).thenReturn(Optional.of(ORDER)); 97 | 98 | Order result = orderService.findOrderById(ORDER.getId()); 99 | 100 | assertNotNull(result); 101 | assertEquals(ORDER, result); 102 | } 103 | 104 | @Test 105 | @DisplayName("Should throw a ResourceNotFoundException when order id does not exist") 106 | void findOrderById_ThrowsResourceNotFoundException_WhenOrderIdDoesNotExist() { 107 | when(orderRepository.findById(ORDER.getId())).thenReturn(Optional.empty()); 108 | 109 | assertThrows(ResourceNotFoundException.class, () -> orderService.findOrderById(ORDER.getId())); 110 | } 111 | 112 | @Test 113 | @DisplayName("Should create a new order when provided with valid OrderRequestDTO") 114 | void createOrder_WithValidData_CreatesOrder() { 115 | OrderRequestDTO request = new OrderRequestDTO(CUSTOMER.getId(), ADDRESS.getId(), List.of(ORDER_ITEM_REQUEST_DTO)); 116 | 117 | when(customerRepository.findById(CUSTOMER.getId())).thenReturn(Optional.of(CUSTOMER_ADDRESS)); 118 | when(addressRepository.findById(ADDRESS.getId())).thenReturn(Optional.of(ADDRESS)); 119 | when(productRepository.findById(PRODUCT.getId())).thenReturn(Optional.of(PRODUCT)); 120 | when(orderRepository.save(any(Order.class))).thenReturn(ORDER); 121 | 122 | Order result = orderService.createOrder(request); 123 | 124 | assertNotNull(result); 125 | assertEquals(ORDER.getId(), result.getId()); 126 | assertEquals(ORDER.getDeliveryAddress(), result.getDeliveryAddress()); 127 | assertEquals(ORDER.getItems(), result.getItems()); 128 | verify(orderRepository, times(1)).save(any(Order.class)); 129 | } 130 | 131 | @Test 132 | @DisplayName("Should replace an existing order when provided with valid OrderUpdateDTO") 133 | void replaceOrder_WhenSuccessful() { 134 | OrderUpdateDTO updateDTO = new OrderUpdateDTO(PRODUCT.getId(), ADDRESS.getId(), List.of(ORDER_ITEM_UPDATE_DTO)); 135 | 136 | when(orderRepository.findById(ORDER.getId())).thenReturn(Optional.of(ORDER)); 137 | when(addressRepository.findById(ADDRESS.getId())).thenReturn(Optional.of(ADDRESS)); 138 | when(productRepository.findById(PRODUCT.getId())).thenReturn(Optional.of(PRODUCT)); 139 | when(orderItemRepository.findById(ORDER_ITEM.getId())).thenReturn(Optional.of(ORDER_ITEM)); 140 | when(orderRepository.save(any(Order.class))).thenReturn(ORDER); 141 | 142 | Order result = orderService.replaceOrder(updateDTO); 143 | 144 | assertNotNull(result); 145 | assertEquals(ORDER.getId(), result.getId()); 146 | 147 | verify(orderRepository).findById(ORDER.getId()); 148 | verify(orderRepository).save(any(Order.class)); 149 | } 150 | 151 | @Test 152 | @DisplayName("Should delete an order by id when successful") 153 | void deleteOrder_WithExistingId_WhenSuccessful() { 154 | when(orderRepository.findById(PRODUCT.getId())).thenReturn(Optional.of(ORDER)); 155 | 156 | assertThatCode(() -> orderService.deleteOrder(ORDER.getId())).doesNotThrowAnyException(); 157 | 158 | verify(orderRepository, times(1)).delete(ORDER); 159 | } 160 | 161 | @Test 162 | @DisplayName("Should throw a ResourceNotFoundException when order id does not exist") 163 | void deleteOrder_ThrowsResourceNotFoundException_WhenIdDoesNotExist() { 164 | when(orderRepository.findById(ORDER.getId())).thenReturn(Optional.empty()); 165 | 166 | assertThrows(ResourceNotFoundException.class, () -> orderService.deleteOrder(ORDER.getId())); 167 | } 168 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/controllers/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sushi.api.exceptions.ResourceNotFoundException; 5 | import com.sushi.api.model.Product; 6 | import com.sushi.api.security.TokenService; 7 | import com.sushi.api.services.ProductService; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageImpl; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import java.util.List; 22 | 23 | import static com.sushi.api.common.ProductConstants.*; 24 | import static org.mockito.Mockito.when; 25 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 26 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 | 30 | @WebMvcTest(ProductController.class) 31 | public class ProductControllerTest { 32 | @Autowired 33 | private MockMvc mockMvc; 34 | @Autowired 35 | private ObjectMapper objectMapper; 36 | @MockBean 37 | private TokenService tokenService; 38 | @MockBean 39 | private ProductService productService; 40 | 41 | @Test 42 | @WithMockUser(roles = {"ADMIN", "USER"}) 43 | @DisplayName("Should return a list of products inside page object when successful") 44 | public void listAllPageable_ReturnsAllProductsWithPagination() throws Exception { 45 | Pageable pageable = PageRequest.of(0, 10); 46 | Page page = new PageImpl<>(PRODUCTS, pageable, PRODUCTS.size()); 47 | 48 | when(productService.listAllPageable(pageable)).thenReturn(page); 49 | 50 | String expectedJson = objectMapper.writeValueAsString(PRODUCTS); 51 | 52 | mockMvc 53 | .perform(get("/api/products") 54 | .param("page", "0") 55 | .param("size", "10") 56 | .accept(MediaType.APPLICATION_JSON)) 57 | .andExpect(status().isOk()) 58 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 59 | .andExpect(content().json(expectedJson)); 60 | } 61 | 62 | @Test 63 | @WithMockUser(roles = {"ADMIN", "USER"}) 64 | @DisplayName("Should return a list of products when successful") 65 | public void listAllNonPageable_ReturnsAllProducts() throws Exception { 66 | String expectedJson = objectMapper.writeValueAsString(PRODUCTS); 67 | 68 | when(productService.listAllNonPageable()).thenReturn(PRODUCTS); 69 | 70 | mockMvc.perform(get("/api/products/list") 71 | .accept(MediaType.APPLICATION_JSON)) 72 | .andExpect(status().isOk()) 73 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 74 | .andExpect(content().json(expectedJson)); 75 | } 76 | 77 | @Test 78 | @WithMockUser(roles = {"ADMIN", "USER"}) 79 | @DisplayName("Should return a product by id when successful") 80 | public void findProductById_ReturnsProductById() throws Exception { 81 | when(productService.findProductById(PRODUCT.getId())).thenReturn(PRODUCT); 82 | 83 | String expectedJson = objectMapper.writeValueAsString(PRODUCT); 84 | 85 | mockMvc.perform(get("/api/products/{id}", PRODUCT.getId()) 86 | .accept(MediaType.APPLICATION_JSON)) 87 | .andExpect(status().isOk()) 88 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 89 | .andExpect(content().json(expectedJson)); 90 | } 91 | 92 | @Test 93 | @WithMockUser(roles = {"ADMIN", "USER"}) 94 | @DisplayName("Should return ResourceNotFoundException when trying to find a product by id that does not exist") 95 | public void findProductById_ReturnsNotFound_WhenProductDoesNotExist() throws Exception { 96 | when(productService.findProductById(5L)).thenThrow(new ResourceNotFoundException("Product not found")); 97 | 98 | mockMvc.perform(get("/api/products/{id}", 5L) 99 | .accept(MediaType.APPLICATION_JSON)) 100 | .andExpect(status().isNotFound()) 101 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)); 102 | } 103 | 104 | @Test 105 | @WithMockUser(roles = {"ADMIN", "USER"}) 106 | @DisplayName("Should return a product by name when successful") 107 | public void findProductByName_ReturnsProductByName() throws Exception { 108 | String name = "roll"; 109 | List products = List.of(PRODUCT, PRODUCT2); 110 | 111 | when(productService.findProductByName(name)).thenReturn(products); 112 | 113 | String expectedJson = objectMapper.writeValueAsString(products); 114 | 115 | mockMvc.perform(get("/api/products/find/by-name") 116 | .param("name", name) 117 | .accept(MediaType.APPLICATION_JSON)) 118 | .andExpect(status().isOk()) 119 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 120 | .andExpect(content().json(expectedJson)); 121 | } 122 | 123 | @Test 124 | @WithMockUser(roles = {"ADMIN"}) 125 | @DisplayName("Should create a new product when successful") 126 | public void createProduct_WithValidData_ReturnsProductCreated() throws Exception { 127 | String productJson = objectMapper.writeValueAsString(PRODUCT); 128 | 129 | mockMvc 130 | .perform(post("/api/products") 131 | .contentType(MediaType.APPLICATION_JSON) 132 | .content(productJson) 133 | .with(csrf())) 134 | .andExpect(status().isCreated()); 135 | } 136 | 137 | @Test 138 | @WithMockUser(roles = {"ADMIN"}) 139 | @DisplayName("Should replace a product by id when successful") 140 | public void replaceProduct_WithValidData_ReturnsNoContent() throws Exception { 141 | String productJson = objectMapper.writeValueAsString(PRODUCT); 142 | 143 | mockMvc 144 | .perform(put("/api/products") 145 | .contentType(MediaType.APPLICATION_JSON) 146 | .content(productJson) 147 | .with(csrf())) 148 | .andExpect(status().isNoContent()); 149 | } 150 | 151 | @Test 152 | @WithMockUser(roles = {"ADMIN"}) 153 | @DisplayName("Should delete a product by id and returns No Content") 154 | public void deleteProductById_ReturnsNoContent() throws Exception { 155 | when(productService.findProductById(PRODUCT.getId())).thenReturn(PRODUCT); 156 | 157 | mockMvc 158 | .perform(delete("/api/products/{id}", PRODUCT.getId()) 159 | .accept(MediaType.APPLICATION_JSON) 160 | .with(csrf())) 161 | .andExpect(status().isNoContent()); 162 | } 163 | } -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/controllers/EmployeeControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sushi.api.exceptions.ResourceNotFoundException; 5 | import com.sushi.api.model.Employee; 6 | import com.sushi.api.security.TokenService; 7 | import com.sushi.api.services.EmployeeService; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageImpl; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import java.util.UUID; 22 | 23 | import static com.sushi.api.common.EmployeeConstants.*; 24 | import static org.mockito.Mockito.when; 25 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 26 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 | 30 | @WebMvcTest(EmployeeController.class) 31 | public class EmployeeControllerTest { 32 | @Autowired 33 | private MockMvc mockMvc; 34 | @Autowired 35 | private ObjectMapper objectMapper; 36 | @MockBean 37 | private TokenService tokenService; 38 | @MockBean 39 | private EmployeeService employeeService; 40 | 41 | @Test 42 | @WithMockUser(roles = {"ADMIN"}) 43 | @DisplayName("Should return a list of employees inside page object when successful") 44 | public void listAllPageable_ReturnsAllEmployeesWithPagination() throws Exception { 45 | Pageable pageable = PageRequest.of(0, 10); 46 | Page page = new PageImpl<>(EMPLOYEES, pageable, EMPLOYEES.size()); 47 | 48 | when(employeeService.listAllPageable(pageable)).thenReturn(page); 49 | 50 | String expectedJson = objectMapper.writeValueAsString(EMPLOYEES); 51 | 52 | mockMvc 53 | .perform(get("/api/employees") 54 | .param("page", "0") 55 | .param("size", "10") 56 | .accept(MediaType.APPLICATION_JSON)) 57 | .andExpect(status().isOk()) 58 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 59 | .andExpect(content().json(expectedJson)); 60 | } 61 | 62 | @Test 63 | @WithMockUser(roles = {"ADMIN"}) 64 | @DisplayName("Should return a list of employees when successful") 65 | public void listAllNonPageable_ReturnsAllEmployees() throws Exception { 66 | String expectedJson = objectMapper.writeValueAsString(EMPLOYEES); 67 | 68 | when(employeeService.listAllNonPageable()).thenReturn(EMPLOYEES); 69 | 70 | mockMvc.perform(get("/api/employees/list") 71 | .accept(MediaType.APPLICATION_JSON)) 72 | .andExpect(status().isOk()) 73 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 74 | .andExpect(content().json(expectedJson)); 75 | } 76 | 77 | @Test 78 | @WithMockUser(roles = {"ADMIN"}) 79 | @DisplayName("Should return an employee by id when successful") 80 | public void findEmployeeById_ReturnsEmployeeById() throws Exception { 81 | when(employeeService.findEmployeeById(EMPLOYEE.getId())).thenReturn(EMPLOYEE); 82 | 83 | String expectedJson = objectMapper.writeValueAsString(EMPLOYEE); 84 | 85 | mockMvc.perform(get("/api/employees/{id}", EMPLOYEE.getId()) 86 | .accept(MediaType.APPLICATION_JSON)) 87 | .andExpect(status().isOk()) 88 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 89 | .andExpect(content().json(expectedJson)); 90 | } 91 | 92 | @Test 93 | @WithMockUser(roles = {"ADMIN"}) 94 | @DisplayName("Should return ResourceNotFoundException when trying to find a employee by id that does not exist") 95 | public void findEmployeeById_ReturnsNotFound_WhenEmployeeDoesNotExist() throws Exception { 96 | UUID nonExistentId = UUID.randomUUID(); 97 | when(employeeService.findEmployeeById(nonExistentId)).thenThrow(new ResourceNotFoundException("Employee not found")); 98 | 99 | mockMvc.perform(get("/api/employees/{id}", nonExistentId) 100 | .accept(MediaType.APPLICATION_JSON)) 101 | .andExpect(status().isNotFound()) 102 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)); 103 | } 104 | 105 | @Test 106 | @WithMockUser(roles = {"ADMIN"}) 107 | @DisplayName("Should return an employee by email when successful") 108 | public void findEmployeeByEmail_ReturnsEmployeeByEmail() throws Exception { 109 | String email = "isabel@gmail.com"; 110 | 111 | when(employeeService.findEmployeeByEmail(email)).thenReturn(EMPLOYEE); 112 | 113 | String expectedJson = objectMapper.writeValueAsString(EMPLOYEE); 114 | 115 | mockMvc.perform(get("/api/employees/find/by-email") 116 | .param("email", email) 117 | .accept(MediaType.APPLICATION_JSON)) 118 | .andExpect(status().isOk()) 119 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 120 | .andExpect(content().json(expectedJson)); 121 | } 122 | 123 | @Test 124 | @WithMockUser(roles = {"ADMIN"}) 125 | @DisplayName("Should create a new employee and returns Created") 126 | public void createEmployee_WithValidData_ReturnsEmployeeCreated() throws Exception { 127 | String employeeJson = objectMapper.writeValueAsString(EMPLOYEE_REQUEST_DTO); 128 | 129 | mockMvc 130 | .perform(post("/api/employees") 131 | .contentType(MediaType.APPLICATION_JSON) 132 | .content(employeeJson) 133 | .with(csrf())) 134 | .andExpect(status().isCreated()); 135 | } 136 | 137 | @Test 138 | @WithMockUser(roles = {"ADMIN"}) 139 | @DisplayName("Should replace an existing employee and returns No Content") 140 | public void replaceEmployee_WithValidData_ReturnsNoContent() throws Exception { 141 | String employeeJson = objectMapper.writeValueAsString(EMPLOYEE_UPDATE_DTO); 142 | 143 | mockMvc 144 | .perform(put("/api/employees") 145 | .contentType(MediaType.APPLICATION_JSON) 146 | .content(employeeJson) 147 | .with(csrf())) 148 | .andExpect(status().isNoContent()); 149 | } 150 | 151 | @Test 152 | @WithMockUser(roles = {"ADMIN"}) 153 | @DisplayName("Should delete a employee by id and returns No Content") 154 | public void deleteEmployeeById_ReturnsNoContent() throws Exception { 155 | when(employeeService.findEmployeeById(EMPLOYEE.getId())).thenReturn(EMPLOYEE); 156 | 157 | mockMvc 158 | .perform(delete("/api/employees/{id}", EMPLOYEE.getId()) 159 | .accept(MediaType.APPLICATION_JSON) 160 | .with(csrf())) 161 | .andExpect(status().isNoContent()); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/controllers/CategoryControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.sushi.api.exceptions.ResourceNotFoundException; 5 | import com.sushi.api.model.Category; 6 | import com.sushi.api.security.TokenService; 7 | import com.sushi.api.services.CategoryService; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageImpl; 15 | import org.springframework.data.domain.PageRequest; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import java.util.List; 22 | 23 | import static com.sushi.api.common.CategoryConstants.CATEGORIES; 24 | import static com.sushi.api.common.CategoryConstants.CATEGORY; 25 | import static org.mockito.Mockito.when; 26 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 29 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 30 | 31 | @WebMvcTest(CategoryController.class) 32 | public class CategoryControllerTest { 33 | @Autowired 34 | private MockMvc mockMvc; 35 | @Autowired 36 | private ObjectMapper objectMapper; 37 | @MockBean 38 | private TokenService tokenService; 39 | @MockBean 40 | private CategoryService categoryService; 41 | 42 | @Test 43 | @WithMockUser(roles = {"ADMIN", "USER"}) 44 | @DisplayName("Should return a list of categories inside page object when successful") 45 | public void listAllPageable_ReturnsAllCategoriesWithPagination() throws Exception { 46 | Pageable pageable = PageRequest.of(0, 10); 47 | Page page = new PageImpl<>(CATEGORIES, pageable, CATEGORIES.size()); 48 | 49 | when(categoryService.listAllPageable(pageable)).thenReturn(page); 50 | 51 | String expectedJson = objectMapper.writeValueAsString(CATEGORIES); 52 | 53 | mockMvc 54 | .perform(get("/api/categories") 55 | .param("page", "0") 56 | .param("size", "10") 57 | .accept(MediaType.APPLICATION_JSON)) 58 | .andExpect(status().isOk()) 59 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 60 | .andExpect(content().json(expectedJson)); 61 | } 62 | 63 | @Test 64 | @WithMockUser(roles = {"ADMIN", "USER"}) 65 | @DisplayName("Should return a list of categories when successful") 66 | public void listAllNonPageable_ReturnsAllCategories() throws Exception { 67 | String expectedJson = objectMapper.writeValueAsString(CATEGORIES); 68 | 69 | when(categoryService.listAllNonPageable()).thenReturn(CATEGORIES); 70 | 71 | mockMvc.perform(get("/api/categories/list") 72 | .accept(MediaType.APPLICATION_JSON)) 73 | .andExpect(status().isOk()) 74 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 75 | .andExpect(content().json(expectedJson)); 76 | } 77 | 78 | @Test 79 | @WithMockUser(roles = {"ADMIN", "USER"}) 80 | @DisplayName("Should return a category by id when successful") 81 | public void findCategoryById_ReturnsCategoryById() throws Exception { 82 | when(categoryService.findCategoryById(CATEGORY.getId())).thenReturn(CATEGORY); 83 | 84 | String expectedJson = objectMapper.writeValueAsString(CATEGORY); 85 | 86 | mockMvc.perform(get("/api/categories/{id}", CATEGORY.getId()) 87 | .accept(MediaType.APPLICATION_JSON)) 88 | .andExpect(status().isOk()) 89 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 90 | .andExpect(content().json(expectedJson)); 91 | } 92 | 93 | @Test 94 | @WithMockUser(roles = {"ADMIN", "USER"}) 95 | @DisplayName("Should return ResourceNotFoundException when trying to find a category by id that does not exist") 96 | public void findCategoryById_ReturnsNotFound_WhenCategoryDoesNotExist() throws Exception { 97 | when(categoryService.findCategoryById(5L)).thenThrow(new ResourceNotFoundException("Category not found")); 98 | 99 | mockMvc.perform(get("/api/categories/{id}", 5L) 100 | .accept(MediaType.APPLICATION_JSON)) 101 | .andExpect(status().isNotFound()) 102 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)); 103 | } 104 | 105 | @Test 106 | @WithMockUser(roles = {"ADMIN", "USER"}) 107 | @DisplayName("Should return a list of categories when successful") 108 | public void findCategoryByName_ReturnsListOfCategories_WhenSuccessful() throws Exception { 109 | String name = "sushi"; 110 | List categories = List.of(new Category("Sushi", "Delicious sushi")); 111 | 112 | when(categoryService.findCategoryByName(name)).thenReturn(categories); 113 | 114 | String expectedJson = objectMapper.writeValueAsString(categories); 115 | 116 | mockMvc.perform(get("/api/categories/find/by-name") 117 | .param("name", name) 118 | .accept(MediaType.APPLICATION_JSON)) 119 | .andExpect(status().isOk()) 120 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 121 | .andExpect(content().json(expectedJson)); 122 | } 123 | 124 | @Test 125 | @WithMockUser(roles = {"ADMIN"}) 126 | @DisplayName("Should create a new category and returns Created") 127 | public void createCategory_WithValidData_ReturnsCategoryCreated() throws Exception { 128 | String categoryJson = objectMapper.writeValueAsString(CATEGORY); 129 | 130 | mockMvc 131 | .perform(post("/api/categories") 132 | .contentType(MediaType.APPLICATION_JSON) 133 | .content(categoryJson) 134 | .with(csrf())) 135 | .andExpect(status().isCreated()); 136 | } 137 | 138 | @Test 139 | @WithMockUser(roles = {"ADMIN"}) 140 | @DisplayName("Should replace an existing category and returns No Content") 141 | public void replaceCategory_WithValidData_ReturnsNoContent() throws Exception { 142 | String categoryJson = objectMapper.writeValueAsString(CATEGORY); 143 | 144 | mockMvc 145 | .perform(put("/api/categories") 146 | .contentType(MediaType.APPLICATION_JSON) 147 | .content(categoryJson) 148 | .with(csrf())) 149 | .andExpect(status().isNoContent()); 150 | } 151 | 152 | @Test 153 | @WithMockUser(roles = {"ADMIN"}) 154 | @DisplayName("Should delete a category by id and returns No Content") 155 | public void deleteCustomerById_ReturnsNoContent() throws Exception { 156 | when(categoryService.findCategoryById(CATEGORY.getId())).thenReturn(CATEGORY); 157 | 158 | mockMvc 159 | .perform(delete("/api/categories/{id}", CATEGORY.getId()) 160 | .accept(MediaType.APPLICATION_JSON) 161 | .with(csrf())) 162 | .andExpect(status().isNoContent()); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | -- Habilitar a extensão uuid-ossp para gerar UUIDs 2 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 3 | 4 | -- Inserir customers. (Senha = 123) 5 | INSERT INTO customers (id, name, email, password) VALUES 6 | (uuid_generate_v4(), 'Ana', 'ana@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 7 | (uuid_generate_v4(), 'Carlos', 'carlos@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 8 | (uuid_generate_v4(), 'Fernanda', 'fernanda@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 9 | (uuid_generate_v4(), 'Gustavo', 'gustavo@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 10 | (uuid_generate_v4(), 'Juliana', 'juliana@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'); 11 | 12 | -- Inserir employees 13 | INSERT INTO employees (id, name, email, password) VALUES 14 | (uuid_generate_v4(), 'Isabel', 'isabel@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 15 | (uuid_generate_v4(), 'Giulia', 'giulia@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 16 | (uuid_generate_v4(), 'Maria', 'maria@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'), 17 | (uuid_generate_v4(), 'João', 'joao@gmail.com', '$2a$10$6f0rWHMXsne13lwsQ2V48.PhBkFzfCZzWkIJ/qWD0QLAshq88HOOW'); 18 | 19 | -- Inserir phones 20 | INSERT INTO phone (number, customer_id) VALUES 21 | ('123456789', (SELECT id FROM customers WHERE name = 'Ana')), 22 | ('987654321', (SELECT id FROM customers WHERE name = 'Carlos')), 23 | ('555555555', (SELECT id FROM customers WHERE name = 'Fernanda')), 24 | ('444444444', (SELECT id FROM customers WHERE name = 'Gustavo')), 25 | ('333333333', (SELECT id FROM customers WHERE name = 'Juliana')); 26 | 27 | -- Inserir addresses 28 | INSERT INTO addresses (number, street, neighborhood, customer_id) VALUES 29 | ('123', 'Rua das Flores', 'Centro', (SELECT id FROM customers WHERE name = 'Ana')), 30 | ('456', 'Avenida Brasil', 'Jardim', (SELECT id FROM customers WHERE name = 'Carlos')), 31 | ('789', 'Rua das Palmeiras', 'Vila Nova', (SELECT id FROM customers WHERE name = 'Fernanda')), 32 | ('101', 'Praça Central', 'Bela Vista', (SELECT id FROM customers WHERE name = 'Gustavo')), 33 | ('202', 'Rua do Comércio', 'Santa Cruz', (SELECT id FROM customers WHERE name = 'Juliana')); 34 | 35 | -- Inserir categories 36 | INSERT INTO categories (name, description) VALUES 37 | ('Sushi', 'Various types of sushi'), 38 | ('Japanese Food', 'Japanese cuisine dishes'), 39 | ('Chinese Food', 'Chinese cuisine dishes'), 40 | ('Beverages', 'Various types of drinks'); 41 | 42 | -- Inserir products 43 | INSERT INTO products (name, description, price, portion_quantity, portion_unit, url_image) VALUES 44 | ('California Roll', 'A delicious roll made with crab meat, avocado, and cucumber', 8.99, 8, 'pieces', 'http://example.com/images/california_roll.jpg'), 45 | ('Spicy Tuna Roll', 'Spicy tuna wrapped in rice and seaweed', 10.99, 8, 'pieces', 'http://example.com/images/spicy_tuna_roll.jpg'), 46 | ('Sushi Assortment', 'A variety of sushi pieces including nigiri and sashimi', 15.99, 10, 'pieces', 'http://example.com/images/sushi_assortment.jpg'), 47 | ('Tempura Shrimp', 'Crispy tempura shrimp served with dipping sauce', 12.99, 8, 'pieces', 'http://example.com/images/tempura_shrimp.jpg'), 48 | ('Miso Soup', 'Traditional Japanese miso soup', 5.99, 1, 'bowl', 'http://example.com/images/miso_soup.jpg'), 49 | ('Peking Duck', 'Crispy duck served with pancakes and hoisin sauce', 18.99, 1, 'whole', 'http://example.com/images/peking_duck.jpg'), 50 | ('Spring Rolls', 'Crispy spring rolls with vegetables', 7.99, 6, 'pieces', 'http://example.com/images/spring_rolls.jpg'), 51 | ('Kung Pao Chicken', 'Spicy stir-fried chicken with peanuts', 13.99, 1, 'plate', 'http://example.com/images/kung_pao_chicken.jpg'), 52 | ('Green Tea', 'Traditional Japanese green tea', 3.99, 1, 'cup', 'http://example.com/images/green_tea.jpg'), 53 | ('Sake', 'Japanese rice wine', 14.99, 1, 'bottle', 'http://example.com/images/sake.jpg'); 54 | 55 | -- Inserir relacionamento category_product 56 | INSERT INTO category_product (category_id, product_id) VALUES 57 | ((SELECT id FROM categories WHERE name = 'Sushi'), (SELECT id FROM products WHERE name = 'California Roll')), 58 | ((SELECT id FROM categories WHERE name = 'Sushi'), (SELECT id FROM products WHERE name = 'Spicy Tuna Roll')), 59 | ((SELECT id FROM categories WHERE name = 'Sushi'), (SELECT id FROM products WHERE name = 'Sushi Assortment')), 60 | ((SELECT id FROM categories WHERE name = 'Sushi'), (SELECT id FROM products WHERE name = 'Tempura Shrimp')), 61 | ((SELECT id FROM categories WHERE name = 'Japanese Food'), (SELECT id FROM products WHERE name = 'Miso Soup')), 62 | ((SELECT id FROM categories WHERE name = 'Chinese Food'), (SELECT id FROM products WHERE name = 'Peking Duck')), 63 | ((SELECT id FROM categories WHERE name = 'Chinese Food'), (SELECT id FROM products WHERE name = 'Spring Rolls')), 64 | ((SELECT id FROM categories WHERE name = 'Chinese Food'), (SELECT id FROM products WHERE name = 'Kung Pao Chicken')), 65 | ((SELECT id FROM categories WHERE name = 'Beverages'), (SELECT id FROM products WHERE name = 'Green Tea')), 66 | ((SELECT id FROM categories WHERE name = 'Beverages'), (SELECT id FROM products WHERE name = 'Sake')); 67 | 68 | -- Inserir orders 69 | INSERT INTO orders (order_date, customer_id, delivery_address_id, total_amount) VALUES 70 | (NOW(), (SELECT id FROM customers WHERE name = 'Ana'), (SELECT id FROM addresses WHERE customer_id = (SELECT id FROM customers WHERE name = 'Ana')), 50.00), 71 | (NOW(), (SELECT id FROM customers WHERE name = 'Carlos'), (SELECT id FROM addresses WHERE customer_id = (SELECT id FROM customers WHERE name = 'Carlos')), 35.00), 72 | (NOW(), (SELECT id FROM customers WHERE name = 'Fernanda'), (SELECT id FROM addresses WHERE customer_id = (SELECT id FROM customers WHERE name = 'Fernanda')), 40.00), 73 | (NOW(), (SELECT id FROM customers WHERE name = 'Gustavo'), (SELECT id FROM addresses WHERE customer_id = (SELECT id FROM customers WHERE name = 'Gustavo')), 55.00), 74 | (NOW(), (SELECT id FROM customers WHERE name = 'Juliana'), (SELECT id FROM addresses WHERE customer_id = (SELECT id FROM customers WHERE name = 'Juliana')), 30.00); 75 | 76 | -- Inserir order_item 77 | INSERT INTO order_item (order_id, product_id, quantity, price, total_price) VALUES 78 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Ana')), (SELECT id FROM products WHERE name = 'California Roll'), 2, 8.99, 17.98), 79 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Ana')), (SELECT id FROM products WHERE name = 'Spicy Tuna Roll'), 1, 10.99, 10.99), 80 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Carlos')), (SELECT id FROM products WHERE name = 'Tempura Shrimp'), 1, 12.99, 12.99), 81 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Carlos')), (SELECT id FROM products WHERE name = 'Green Tea'), 2, 3.99, 7.98), 82 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Fernanda')), (SELECT id FROM products WHERE name = 'Sushi Assortment'), 1, 15.99, 15.99), 83 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Fernanda')), (SELECT id FROM products WHERE name = 'Kung Pao Chicken'), 1, 13.99, 13.99), 84 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Gustavo')), (SELECT id FROM products WHERE name = 'Peking Duck'), 1, 18.99, 18.99), 85 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Gustavo')), (SELECT id FROM products WHERE name = 'Sake'), 1, 14.99, 14.99), 86 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Juliana')), (SELECT id FROM products WHERE name = 'Spring Rolls'), 1, 7.99, 7.99), 87 | ((SELECT id FROM orders WHERE customer_id = (SELECT id FROM customers WHERE name = 'Juliana')), (SELECT id FROM products WHERE name = 'Green Tea'), 1, 3.99, 3.99); -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/services/CategoryServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Category; 5 | import com.sushi.api.model.dto.category.CategoryRequestDTO; 6 | import com.sushi.api.model.dto.category.CategoryUpdateDTO; 7 | import com.sushi.api.repositories.CategoryRepository; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.springframework.dao.DataIntegrityViolationException; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageImpl; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.test.context.junit.jupiter.SpringExtension; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | import static com.sushi.api.common.CategoryConstants.*; 24 | import static org.assertj.core.api.Assertions.assertThatCode; 25 | import static org.junit.jupiter.api.Assertions.*; 26 | import static org.mockito.ArgumentMatchers.any; 27 | import static org.mockito.Mockito.*; 28 | 29 | @ExtendWith(SpringExtension.class) 30 | public class CategoryServiceTest { 31 | @InjectMocks 32 | private CategoryService categoryService; 33 | @Mock 34 | private CategoryRepository categoryRepository; 35 | 36 | @Test 37 | @DisplayName("Should return a list of categories inside page object when successful") 38 | void listAll_ReturnsListOfCategoriesInsidePageObject_WhenSuccessful() { 39 | Page categoryPage = mock(Page.class); 40 | Pageable pageable = mock(Pageable.class); 41 | 42 | when(categoryRepository.findAll(pageable)).thenReturn(categoryPage); 43 | Page result = categoryService.listAllPageable(pageable); 44 | 45 | assertNotNull(result); 46 | assertEquals(categoryPage, result); 47 | } 48 | 49 | @Test 50 | @DisplayName("Should return an empty list of categories inside page object when there are no categories") 51 | void listAllPageable_ReturnsEmptyListOfCategoriesInsidePageObject_WhenThereAreNoCategories() { 52 | Page emptyCategoryPage = new PageImpl<>(Collections.emptyList()); 53 | Pageable pageable = mock(Pageable.class); 54 | 55 | when(categoryRepository.findAll(pageable)).thenReturn(emptyCategoryPage); 56 | 57 | Page result = categoryService.listAllPageable(pageable); 58 | 59 | assertNotNull(result); 60 | assertTrue(result.isEmpty()); 61 | } 62 | 63 | @Test 64 | @DisplayName("Should return a list of categories when successful") 65 | void listAllNonPageable_ReturnsListOfCategories_WhenSuccessful() { 66 | when(categoryRepository.findAll()).thenReturn(CATEGORIES); 67 | 68 | List result = categoryService.listAllNonPageable(); 69 | 70 | assertNotNull(result); 71 | assertEquals(CATEGORIES.size(), result.size()); 72 | } 73 | 74 | @Test 75 | @DisplayName("Should return an empty list of categories when there are no categories") 76 | void listAllNonPageable_ReturnsEmptyListOfCategories_WhenThereAreNoCategories() { 77 | when(categoryRepository.findAll()).thenReturn(Collections.emptyList()); 78 | 79 | List result = categoryService.listAllNonPageable(); 80 | 81 | assertEquals(0, result.size()); 82 | } 83 | 84 | @Test 85 | @DisplayName("Should return a category by id when successful") 86 | void findCategoryById_ReturnsCategory_WhenSuccessful() { 87 | when(categoryRepository.findById(CATEGORY.getId())).thenReturn(Optional.of(CATEGORY)); 88 | 89 | Category result = categoryService.findCategoryById(CATEGORY.getId()); 90 | 91 | assertNotNull(result); 92 | assertEquals(CATEGORY, result); 93 | } 94 | 95 | @Test 96 | @DisplayName("Should throw a ResourceNotFoundException when category id does not exist") 97 | void findCategoryById_ThrowsResourceNotFoundException_WhenCategoryIdDoesNotExist() { 98 | when(categoryRepository.findById(CATEGORY.getId())).thenReturn(Optional.empty()); 99 | 100 | assertThrows(ResourceNotFoundException.class, () -> categoryService.findCategoryById(CATEGORY.getId())); 101 | } 102 | 103 | @Test 104 | @DisplayName("Should return a category by name when successful") 105 | void findCategoryByName_ReturnsCategory_WhenSuccessful() { 106 | String name = "sushi"; 107 | List categories = List.of(CATEGORY, CATEGORY3); 108 | 109 | when(categoryRepository.findByNameContainingIgnoreCase(name)).thenReturn(categories); 110 | 111 | List result = categoryService.findCategoryByName(name); 112 | 113 | assertNotNull(result); 114 | assertEquals(categories, result); 115 | } 116 | 117 | @Test 118 | @DisplayName("Should throw a ResourceNotFoundException when category name does not exist") 119 | void findCategoryByName_ThrowsResourceNotFoundException_WhenCategoryNameDoesNotExist() { 120 | String name = "doces"; 121 | 122 | when(categoryRepository.findByNameContainingIgnoreCase(name)).thenReturn(Collections.emptyList()); 123 | 124 | assertThrows(ResourceNotFoundException.class, () -> categoryService.findCategoryByName(CATEGORY.getName())); 125 | } 126 | 127 | @Test 128 | @DisplayName("Should create a new category when provided with valid CategoryRequestDTO") 129 | void createCategory_WithValidData_CreatesCategory() { 130 | CategoryRequestDTO request = new CategoryRequestDTO(CATEGORY.getName(), CATEGORY.getDescription()); 131 | 132 | when(categoryRepository.save(any(Category.class))).thenReturn(CATEGORY); 133 | 134 | Category result = categoryService.createCategory(request); 135 | 136 | assertNotNull(result); 137 | assertEquals(CATEGORY.getName(), result.getName()); 138 | assertEquals(CATEGORY.getDescription(), result.getDescription()); 139 | 140 | verify(categoryRepository, times(1)).save(any(Category.class)); 141 | } 142 | 143 | @Test 144 | @DisplayName("Should throw a DataIntegrityViolationException when name already exists") 145 | void createCategory_WithExistingName_ThrowsDataIntegrityViolationException() { 146 | CategoryRequestDTO request = new CategoryRequestDTO(CATEGORY.getName(), CATEGORY.getDescription()); 147 | Category existingCategory = new Category(CATEGORY.getName(), CATEGORY.getDescription()); 148 | 149 | when(categoryRepository.findByNameContainingIgnoreCase(request.name())).thenReturn(List.of(existingCategory)); 150 | 151 | assertThrows(DataIntegrityViolationException.class, () -> categoryService.createCategory(request)); 152 | } 153 | 154 | @Test 155 | @DisplayName("Should replace an existing category when provided with valid CategoryUpdateDTO") 156 | void replaceCategory_WhenSuccessful() { 157 | when(categoryRepository.findById(CATEGORY.getId())).thenReturn(Optional.of(CATEGORY)); 158 | 159 | CategoryUpdateDTO updateDTO = new CategoryUpdateDTO( 160 | CATEGORY.getId(), 161 | "newName", 162 | "newDescription" 163 | ); 164 | 165 | categoryService.replaceCategory(updateDTO); 166 | 167 | verify(categoryRepository).findById(CATEGORY.getId()); 168 | verify(categoryRepository).save(CATEGORY); 169 | } 170 | 171 | @Test 172 | @DisplayName("Should delete a category by id when successful") 173 | void deleteCategory_WithExistingId_WhenSuccessful() { 174 | when(categoryRepository.findById(CATEGORY.getId())).thenReturn(Optional.of(CATEGORY)); 175 | 176 | assertThatCode(() -> categoryService.deleteCategory(CATEGORY.getId())).doesNotThrowAnyException(); 177 | 178 | verify(categoryRepository, times(1)).delete(CATEGORY); 179 | } 180 | 181 | @Test 182 | @DisplayName("Should throw a ResourceNotFoundException when category id does not exist") 183 | void deleteCategory_ThrowsResourceNotFoundException_WhenIdDoesNotExist() { 184 | when(categoryRepository.findById(CATEGORY.getId())).thenReturn(Optional.empty()); 185 | 186 | assertThrows(ResourceNotFoundException.class, () -> categoryService.deleteCategory(CATEGORY.getId())); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/test/java/com/sushi/api/services/EmployeeServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sushi.api.services; 2 | 3 | import com.sushi.api.exceptions.ResourceNotFoundException; 4 | import com.sushi.api.model.Employee; 5 | import com.sushi.api.model.dto.employee.EmployeeRequestDTO; 6 | import com.sushi.api.model.dto.employee.EmployeeUpdateDTO; 7 | import com.sushi.api.repositories.EmployeeRepository; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockito.InjectMocks; 12 | import org.mockito.Mock; 13 | import org.springframework.dao.DataIntegrityViolationException; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageImpl; 16 | import org.springframework.data.domain.Pageable; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.test.context.junit.jupiter.SpringExtension; 19 | 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Optional; 23 | 24 | import static com.sushi.api.common.EmployeeConstants.*; 25 | import static org.assertj.core.api.Assertions.assertThatCode; 26 | import static org.junit.jupiter.api.Assertions.*; 27 | import static org.mockito.ArgumentMatchers.any; 28 | import static org.mockito.Mockito.*; 29 | 30 | @ExtendWith(SpringExtension.class) 31 | public class EmployeeServiceTest { 32 | @InjectMocks 33 | private EmployeeService employeeService; 34 | @Mock 35 | private EmployeeRepository employeeRepository; 36 | @Mock 37 | private PasswordEncoder passwordEncoder; 38 | 39 | @Test 40 | @DisplayName("Should return a list of employees inside page object when successful") 41 | void listAll_ReturnsListOfEmployeesInsidePageObject_WhenSuccessful() { 42 | Page employeePage = mock(Page.class); 43 | Pageable pageable = mock(Pageable.class); 44 | 45 | when(employeeRepository.findAll(pageable)).thenReturn(employeePage); 46 | Page result = employeeService.listAllPageable(pageable); 47 | 48 | assertNotNull(result); 49 | assertEquals(employeePage, result); 50 | } 51 | 52 | @Test 53 | @DisplayName("Should return an empty list of employees inside page object when there are no employees") 54 | void listAllPageable_ReturnsEmptyListOfEmployeesInsidePageObject_WhenThereAreNoEmployees() { 55 | Page emptyEmployeePage = new PageImpl<>(Collections.emptyList()); 56 | Pageable pageable = mock(Pageable.class); 57 | 58 | when(employeeRepository.findAll(pageable)).thenReturn(emptyEmployeePage); 59 | 60 | Page result = employeeService.listAllPageable(pageable); 61 | 62 | assertNotNull(result); 63 | assertTrue(result.isEmpty()); 64 | } 65 | 66 | @Test 67 | @DisplayName("Should return a list of employees when successful") 68 | void listAllNonPageable_ReturnsListOfEmployees_WhenSuccessful() { 69 | when(employeeRepository.findAll()).thenReturn(EMPLOYEES); 70 | 71 | List result = employeeService.listAllNonPageable(); 72 | 73 | assertNotNull(result); 74 | assertEquals(EMPLOYEES.size(), result.size()); 75 | } 76 | 77 | @Test 78 | @DisplayName("Should return an empty list of employees when there are no employees") 79 | void listAllNonPageable_ReturnsEmptyListOfEmployees_WhenThereAreNoEmployees() { 80 | when(employeeRepository.findAll()).thenReturn(Collections.emptyList()); 81 | 82 | List result = employeeService.listAllNonPageable(); 83 | 84 | assertEquals(0, result.size()); 85 | } 86 | 87 | @Test 88 | @DisplayName("Should return an employee by id when successful") 89 | void findEmployeeById_ReturnsEmployee_WhenSuccessful() { 90 | when(employeeRepository.findById(EMPLOYEE.getId())).thenReturn(Optional.of(EMPLOYEE)); 91 | 92 | Employee result = employeeService.findEmployeeById(EMPLOYEE.getId()); 93 | 94 | assertNotNull(result); 95 | assertEquals(EMPLOYEE, result); 96 | } 97 | 98 | @Test 99 | @DisplayName("Should throw a ResourceNotFoundException when employee id does not exist") 100 | void findEmployeeById_ThrowsResourceNotFoundException_WhenEmployeeIdDoesNotExist() { 101 | when(employeeRepository.findById(EMPLOYEE.getId())).thenReturn(Optional.empty()); 102 | 103 | assertThrows(ResourceNotFoundException.class, () -> employeeService.findEmployeeById(EMPLOYEE.getId())); 104 | } 105 | 106 | @Test 107 | @DisplayName("Should return an employee by email when successful") 108 | void findEmployeeByEmail_ReturnsEmployee_WhenSuccessful() { 109 | String email = "isabel@gmail.com"; 110 | 111 | when(employeeRepository.findByEmail(email)).thenReturn(Optional.of(EMPLOYEE)); 112 | 113 | Employee result = employeeService.findEmployeeByEmail(email); 114 | 115 | assertNotNull(result); 116 | assertEquals(email, result.getEmail()); 117 | } 118 | 119 | @Test 120 | @DisplayName("Should throw a ResourceNotFoundException when employee email does not exist") 121 | void findEmployeeByEmail_ThrowsResourceNotFoundException_WhenEmployeeEmailDoesNotExist() { 122 | String email = "joao@gmail.com"; 123 | 124 | when(employeeRepository.findByEmail(email)).thenReturn(Optional.of(EMPLOYEE2)); 125 | 126 | Employee result = employeeService.findEmployeeByEmail(email); 127 | 128 | assertNotNull(result); 129 | assertEquals(email, result.getEmail()); 130 | } 131 | 132 | @Test 133 | @DisplayName("Should create a new employee when provided with valid EmployeeRequestDTO") 134 | void createEmployee_WithValidData_CreatesEmployee() { 135 | EmployeeRequestDTO request = new EmployeeRequestDTO(EMPLOYEE.getName(), EMPLOYEE.getEmail(), EMPLOYEE.getPassword()); 136 | 137 | when(passwordEncoder.encode(request.password())).thenReturn("encodedPassword"); 138 | when(employeeRepository.save(any(Employee.class))).thenReturn(EMPLOYEE); 139 | 140 | Employee result = employeeService.createEmployee(request); 141 | 142 | assertNotNull(result); 143 | assertEquals(EMPLOYEE.getName(), result.getName()); 144 | assertEquals(EMPLOYEE.getEmail(), result.getEmail()); 145 | 146 | verify(employeeRepository, times(1)).save(any(Employee.class)); 147 | } 148 | 149 | @Test 150 | @DisplayName("Should throw a DataIntegrityViolationException when email already exists") 151 | void createEmployee_WithExistingEmail_ThrowsDataIntegrityViolationException() { 152 | EmployeeRequestDTO request = new EmployeeRequestDTO(EMPLOYEE.getName(), EMPLOYEE.getEmail(), EMPLOYEE.getPassword()); 153 | 154 | when(employeeRepository.findByEmail(request.email())).thenReturn(Optional.of(EMPLOYEE)); 155 | 156 | assertThrows(DataIntegrityViolationException.class, () -> employeeService.createEmployee(request)); 157 | } 158 | 159 | @Test 160 | @DisplayName("Should replace an existing employee when provided with valid EmployeeUpdateDTO") 161 | void replaceEmployee_WhenSuccessful() { 162 | when(employeeRepository.findById(EMPLOYEE.getId())).thenReturn(Optional.of(EMPLOYEE)); 163 | when(passwordEncoder.encode(EMPLOYEE.getPassword())).thenReturn("encodedPassword"); 164 | 165 | EmployeeUpdateDTO updateDTO = new EmployeeUpdateDTO( 166 | EMPLOYEE.getId(), 167 | "newName", 168 | "newEmail", 169 | EMPLOYEE.getPassword() 170 | ); 171 | 172 | employeeService.replaceEmployee(updateDTO); 173 | 174 | verify(employeeRepository).findById(EMPLOYEE.getId()); 175 | verify(employeeRepository).save(EMPLOYEE); 176 | } 177 | 178 | @Test 179 | @DisplayName("Should delete an employee by id when successful") 180 | void deleteEmployee_WithExistingId_WhenSuccessful() { 181 | when(employeeRepository.findById(EMPLOYEE.getId())).thenReturn(Optional.of(EMPLOYEE)); 182 | 183 | assertThatCode(() -> employeeService.deleteEmployee(EMPLOYEE.getId())).doesNotThrowAnyException(); 184 | 185 | verify(employeeRepository, times(1)).delete(EMPLOYEE); 186 | } 187 | 188 | @Test 189 | @DisplayName("Should throw a ResourceNotFoundException when employee id does not exist") 190 | void deleteEmployee_ThrowsResourceNotFoundException_WhenIdDoesNotExist() { 191 | when(employeeRepository.findById(EMPLOYEE.getId())).thenReturn(Optional.empty()); 192 | 193 | assertThrows(ResourceNotFoundException.class, () -> employeeService.deleteEmployee(EMPLOYEE.getId())); 194 | } 195 | } --------------------------------------------------------------------------------