diff --git a/build.gradle b/build.gradle index df2995b..d8e87d5 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,8 @@ group = 'com.cuoco' version = '0.0.1-SNAPSHOT' java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 toolchain { languageVersion = JavaLanguageVersion.of(17) } @@ -25,21 +27,27 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'com.google.cloud:google-cloud-vision:3.12.0' implementation 'com.google.protobuf:protobuf-java:4.28.2' implementation 'org.jetbrains.kotlinx:kotlinx-io-bytestring-jvm:0.5.4' implementation 'net.javacrumbs.hamcrest-logger:hamcrest-logger:0.0.1' implementation 'com.github.lolgab:snunit-autowire_native0.4.0-M2_2.11:0.0.4' - implementation 'com.github.lolgab:snunit-autowire_native0.4.0-M2_2.11:0.0.4' implementation 'jakarta.validation:jakarta.validation-api:3.0.2' + implementation 'com.mercadopago:sdk-java:2.5.0' + + // Swagger documentation + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + // Lombok configuration compileOnly 'org.projectlombok:lombok:1.18.28' annotationProcessor 'org.projectlombok:lombok:1.18.28' testCompileOnly 'org.projectlombok:lombok:1.18.28' testAnnotationProcessor 'org.projectlombok:lombok:1.18.28' + // Testing dependencies developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' @@ -51,6 +59,10 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +tasks.withType(JavaCompile) { + options.release = 17 +} + tasks.named('test') { useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/com/cuoco/CuocoApplication.java b/src/main/java/com/cuoco/CuocoApplication.java index 2eba52c..4eec870 100644 --- a/src/main/java/com/cuoco/CuocoApplication.java +++ b/src/main/java/com/cuoco/CuocoApplication.java @@ -2,9 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication +@EnableConfigurationProperties +@EnableJpaRepositories +@EnableAsync public class CuocoApplication { public static void main(String[] args) { diff --git a/src/main/java/com/cuoco/adapter/exception/ConflictException.java b/src/main/java/com/cuoco/adapter/exception/ConflictException.java new file mode 100644 index 0000000..805e2ca --- /dev/null +++ b/src/main/java/com/cuoco/adapter/exception/ConflictException.java @@ -0,0 +1,7 @@ +package com.cuoco.adapter.exception; + +public class ConflictException extends AdapterException { + public ConflictException(String description) { + super(description); + } +} diff --git a/src/main/java/com/cuoco/adapter/exception/ImageGenerationException.java b/src/main/java/com/cuoco/adapter/exception/ImageGenerationException.java new file mode 100644 index 0000000..72875e0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/exception/ImageGenerationException.java @@ -0,0 +1,14 @@ +package com.cuoco.adapter.exception; + +import com.cuoco.shared.model.ErrorDescription; + +public class ImageGenerationException extends AdapterException { + + public ImageGenerationException(String message) { + super(message); + } + + public ImageGenerationException(ErrorDescription errorDescription) { + super(errorDescription.getValue()); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/AllergyControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/AllergyControllerAdapter.java index 543911f..d015062 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/AllergyControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/AllergyControllerAdapter.java @@ -3,8 +3,15 @@ import com.cuoco.adapter.in.controller.model.ParametricResponse; import com.cuoco.application.port.in.GetAllAllergiesQuery; import com.cuoco.application.usecase.model.Allergy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,12 +19,11 @@ import java.util.List; +@Slf4j @RestController -@RequestMapping("/allergy") +@RequestMapping("/allergies") public class AllergyControllerAdapter { - static final Logger log = LoggerFactory.getLogger(AllergyControllerAdapter.class); - private final GetAllAllergiesQuery getAllAllergiesQuery; public AllergyControllerAdapter(GetAllAllergiesQuery getAllAllergiesQuery) { @@ -25,19 +31,32 @@ public AllergyControllerAdapter(GetAllAllergiesQuery getAllAllergiesQuery) { } @GetMapping - public ResponseEntity getAll() { - log.info("GET all allergies"); + @Tag(name = "Parametric Endpoints", description = "Parametric values of immutable resources") + @Operation(summary = "GET all the allergies") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent allergies", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all allergies"); List allergies = getAllAllergiesQuery.execute(); - List response = allergies.stream().map(this::buildParametricResponse).toList(); + List response = allergies.stream().map(ParametricResponse::fromDomain).toList(); log.info("All allergies are retrieved successfully"); return ResponseEntity.ok(response); } - - private ParametricResponse buildParametricResponse(Allergy allergy) { - return ParametricResponse.builder() - .id(allergy.getId()) - .description(allergy.getDescription()) - .build(); - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java index 397e7f8..b7f5b8b 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java @@ -1,51 +1,141 @@ package com.cuoco.adapter.in.controller; import com.cuoco.adapter.in.controller.model.AuthDataResponse; +import com.cuoco.adapter.in.controller.model.AuthOperationRequest; import com.cuoco.adapter.in.controller.model.AuthRequest; import com.cuoco.adapter.in.controller.model.AuthResponse; +import com.cuoco.adapter.in.controller.model.ChangePasswordRequest; import com.cuoco.adapter.in.controller.model.ParametricResponse; import com.cuoco.adapter.in.controller.model.UserPreferencesResponse; import com.cuoco.adapter.in.controller.model.UserRequest; import com.cuoco.adapter.in.controller.model.UserResponse; +import com.cuoco.application.port.in.ActivateUserCommand; +import com.cuoco.application.port.in.ChangeUserPasswordCommand; import com.cuoco.application.port.in.CreateUserCommand; +import com.cuoco.application.port.in.ResendUserActivationEmailCommand; +import com.cuoco.application.port.in.ResetUserPasswordConfirmationCommand; import com.cuoco.application.port.in.SignInUserCommand; import com.cuoco.application.usecase.model.Allergy; import com.cuoco.application.usecase.model.AuthenticatedUser; import com.cuoco.application.usecase.model.DietaryNeed; import com.cuoco.application.usecase.model.User; -import com.cuoco.application.usecase.model.UserPreferences; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +@Slf4j @RestController @RequestMapping("/auth") +@RequiredArgsConstructor +@Tag(name = "Authentication", description = "Operations related to authenticate users") public class AuthenticationControllerAdapter { - static final Logger log = LoggerFactory.getLogger(AuthenticationControllerAdapter.class); - - private final SignInUserCommand signInUserCommand; private final CreateUserCommand createUserCommand; + private final SignInUserCommand signInUserCommand; + private final ActivateUserCommand activateUserCommand; + private final ResendUserActivationEmailCommand resendUserActivationEmailCommand; + private final ResetUserPasswordConfirmationCommand resetUserPasswordConfirmationCommand; + private final ChangeUserPasswordCommand changeUserPasswordCommand; + + @PostMapping("/register") + @Operation(summary = "POST for user creation with basic data and preferences") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "Return created user", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = AuthResponse.class) + ) + ), + @ApiResponse( + responseCode = "400", + description = "Required parameter is not present or some parameter is not valid ", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "409", + description = "The user already exists", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity register(@RequestBody @Valid UserRequest request) { + log.info("Executing POST register with email {}", request.getEmail()); + User user = createUserCommand.execute(buildCreateCommand(request)); + UserResponse userResponse = buildUserResponse(user, null); - public AuthenticationControllerAdapter( - SignInUserCommand signInUserCommand, - CreateUserCommand createUserCommand - ) { - this.signInUserCommand = signInUserCommand; - this.createUserCommand = createUserCommand; + return ResponseEntity.status(HttpStatus.CREATED).body(userResponse); } @PostMapping("/login") - public ResponseEntity login(@RequestBody AuthRequest request) { + @Operation(summary = "POST for user authentication with email and password") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return user data with token", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = AuthResponse.class) + ) + ), + @ApiResponse( + responseCode = "403", + description = "Invalid credentials", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "422", + description = "The request can't be procesed due to an error", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity login(@RequestBody AuthRequest request) { log.info("Executing POST login for email {}", request.getEmail()); @@ -55,23 +145,70 @@ public ResponseEntity login(@RequestBody AuthRequest request) { return ResponseEntity.ok(response); } - private AuthResponse buildAuthResponse(AuthenticatedUser authenticatedUser) { - return AuthResponse.builder() - .data(AuthDataResponse.builder() - .user(buildUserResponse(authenticatedUser.getUser(), authenticatedUser.getToken())) - .build()) - .build(); + @GetMapping("/activate") + @Operation(summary = "GET for confirm user email") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Email confirmed successfully" + ), + @ApiResponse( + responseCode = "400", + description = "Invalid token or expired", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "404", + description = "User not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity confirmEmail(@RequestParam String token) { + log.info("Executing POST email confirmation for token {}", token); + + ActivateUserCommand.Command command = ActivateUserCommand.Command.builder().token(token).build(); + + activateUserCommand.execute(command); + + return ResponseEntity.ok().build(); } - @PostMapping("/register") - public ResponseEntity register(@RequestBody @Valid UserRequest request) { - log.info("Executing POST register with email {}", request.getEmail()); + @PostMapping("/activate/resend-email") + public ResponseEntity resendEmailConfirmation(@RequestBody @Valid AuthOperationRequest request) { + log.info("Executing POST for resend email confirmation"); - User user = createUserCommand.execute(buildCreateCommand(request)); + resendUserActivationEmailCommand.execute(request.getEmail()); - UserResponse userResponse = buildUserResponse(user, null); + return ResponseEntity.ok().build(); + } - return ResponseEntity.status(HttpStatus.CREATED).body(userResponse); + @PostMapping("/password/reset") + public ResponseEntity resetPasswordConfirmation(@RequestBody @Valid AuthOperationRequest request) { + log.info("Executing POST for require reset password"); + + resetUserPasswordConfirmationCommand.execute(request.getEmail()); + + return ResponseEntity.ok().build(); + } + + @PostMapping("/password") + public ResponseEntity resetPassword(@RequestParam String token, @RequestBody @Valid ChangePasswordRequest request) { + log.info("Executing POST for change password without authentication"); + + ChangeUserPasswordCommand.Command command = ChangeUserPasswordCommand.Command.builder() + .token(token) + .password(request.getPassword()) + .build(); + + changeUserPasswordCommand.execute(command); + + return ResponseEntity.ok().build(); } private SignInUserCommand.Command buildAuthenticationCommand(AuthRequest request) { @@ -94,52 +231,32 @@ private CreateUserCommand.Command buildCreateCommand(UserRequest request) { ); } + private AuthResponse buildAuthResponse(AuthenticatedUser authenticatedUser) { + return AuthResponse.builder() + .data(AuthDataResponse.builder() + .user(buildUserResponse(authenticatedUser.getUser(), authenticatedUser.getToken())) + .build()) + .build(); + } + private UserResponse buildUserResponse(User user, String token) { return UserResponse.builder() .id(user.getId()) .name(user.getName()) .email(user.getEmail()) .token(token) - .plan(ParametricResponse.builder() - .id(user.getPlan().getId()) - .description(user.getPlan().getDescription()) - .build()) - .preferences(buildUserPreferencesResponse(user.getPreferences())) + .plan(ParametricResponse.fromDomain(user.getPlan())) + .preferences(UserPreferencesResponse.fromDomain(user.getPreferences())) .dietaryNeeds(buildDietaryNeeds(user.getDietaryNeeds())) .allergies(buildAllergies(user.getAllergies())) .build(); } private List buildDietaryNeeds(List dietaryNeeds) { - if(dietaryNeeds != null && !dietaryNeeds.isEmpty()) { - return dietaryNeeds.stream().map(dietaryNeed -> ParametricResponse.builder() - .id(dietaryNeed.getId()) - .description(dietaryNeed.getDescription()) - .build()).toList(); - } else return null; - + return dietaryNeeds != null && !dietaryNeeds.isEmpty() ? dietaryNeeds.stream().map(ParametricResponse::fromDomain).toList() : null; } private List buildAllergies(List allergies) { - if(allergies != null && !allergies.isEmpty()) { - return allergies.stream().map(allergy -> ParametricResponse.builder() - .id(allergy.getId()) - .description(allergy.getDescription()) - .build() - ).toList(); - } else return null; - } - - private UserPreferencesResponse buildUserPreferencesResponse(UserPreferences preferences) { - return UserPreferencesResponse.builder() - .cookLevel(ParametricResponse.builder() - .id(preferences.getCookLevel().getId()) - .description(preferences.getCookLevel().getDescription()) - .build()) - .diet(ParametricResponse.builder() - .id(preferences.getDiet().getId()) - .description(preferences.getDiet().getDescription()) - .build()) - .build(); + return allergies != null && !allergies.isEmpty() ? allergies.stream().map(ParametricResponse::fromDomain).toList() : null; } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapter.java index ab2cbc2..83eaa09 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapter.java @@ -1,10 +1,17 @@ package com.cuoco.adapter.in.controller; import com.cuoco.adapter.in.controller.model.ParametricResponse; -import com.cuoco.application.port.in.GetCookLevelsQuery; +import com.cuoco.application.port.in.GetAllCookLevelsQuery; import com.cuoco.application.usecase.model.CookLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,32 +19,45 @@ import java.util.List; +@Slf4j @RestController -@RequestMapping("/cook-level") +@RequestMapping("/cook-levels") public class CookLevelControllerAdapter { - static final Logger log = LoggerFactory.getLogger(CookLevelControllerAdapter.class); + private final GetAllCookLevelsQuery getAllCookLevelsQuery; - private final GetCookLevelsQuery getCookLevelsQuery; - - public CookLevelControllerAdapter(GetCookLevelsQuery getCookLevelsQuery) { - this.getCookLevelsQuery = getCookLevelsQuery; + public CookLevelControllerAdapter(GetAllCookLevelsQuery getAllCookLevelsQuery) { + this.getAllCookLevelsQuery = getAllCookLevelsQuery; } @GetMapping + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the cook levels") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent cook levels", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) public ResponseEntity> getAll() { - log.info("GET all cook levels"); - List cookLevels = getCookLevelsQuery.execute(); - List response = cookLevels.stream().map(this::buildParametricResponse).toList(); + log.info("Executing GET all cook levels"); + + List cookLevels = getAllCookLevelsQuery.execute(); + List response = cookLevels.stream().map(ParametricResponse::fromDomain).toList(); log.info("All cook levels are retrieved successfully"); return ResponseEntity.ok(response); } - - private ParametricResponse buildParametricResponse(CookLevel cookLevel) { - return ParametricResponse.builder() - .id(cookLevel.getId()) - .description(cookLevel.getDescription()) - .build(); - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/DietControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/DietControllerAdapter.java index fe61af7..1717d83 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/DietControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/DietControllerAdapter.java @@ -1,10 +1,17 @@ package com.cuoco.adapter.in.controller; import com.cuoco.adapter.in.controller.model.ParametricResponse; -import com.cuoco.application.port.in.GetDietsQuery; +import com.cuoco.application.port.in.GetAllDietsQuery; import com.cuoco.application.usecase.model.Diet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,32 +19,44 @@ import java.util.List; +@Slf4j @RestController -@RequestMapping("/diet") +@RequestMapping("/diets") public class DietControllerAdapter { - static final Logger log = LoggerFactory.getLogger(DietControllerAdapter.class); + private final GetAllDietsQuery getAllDietsQuery; - private final GetDietsQuery getDietsQuery; - - public DietControllerAdapter(GetDietsQuery getDietsQuery) { - this.getDietsQuery = getDietsQuery; + public DietControllerAdapter(GetAllDietsQuery getAllDietsQuery) { + this.getAllDietsQuery = getAllDietsQuery; } @GetMapping - public ResponseEntity getAll() { - log.info("GET all diets"); - List diets = getDietsQuery.execute(); - List response = diets.stream().map(this::buildParametricResponse).toList(); + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the diets") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent diets", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all diets"); + List diets = getAllDietsQuery.execute(); + List response = diets.stream().map(ParametricResponse::fromDomain).toList(); log.info("All diets are retrieved successfully"); return ResponseEntity.ok(response); } - - private ParametricResponse buildParametricResponse(Diet diet) { - return ParametricResponse.builder() - .id(diet.getId()) - .description(diet.getDescription()) - .build(); - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapter.java index 8a3cde5..d60c1c7 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapter.java @@ -3,8 +3,15 @@ import com.cuoco.adapter.in.controller.model.ParametricResponse; import com.cuoco.application.port.in.GetAllDietaryNeedsQuery; import com.cuoco.application.usecase.model.DietaryNeed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,12 +19,11 @@ import java.util.List; +@Slf4j @RestController -@RequestMapping("/dietary-need") +@RequestMapping("/dietary-needs") public class DietaryNeedControllerAdapter { - static final Logger log = LoggerFactory.getLogger(DietaryNeedControllerAdapter.class); - private final GetAllDietaryNeedsQuery getAllDietaryNeedsQuery; public DietaryNeedControllerAdapter(GetAllDietaryNeedsQuery getAllDietaryNeedsQuery) { @@ -25,20 +31,33 @@ public DietaryNeedControllerAdapter(GetAllDietaryNeedsQuery getAllDietaryNeedsQu } @GetMapping - public ResponseEntity getAll() { - log.info("GET all dietary needs"); + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the dietary needs") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent dietary needs", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all dietary needs"); List dietaryNeeds = getAllDietaryNeedsQuery.execute(); - List response = dietaryNeeds.stream().map(this::buildParametricResponse).toList(); + List response = dietaryNeeds.stream().map(ParametricResponse::fromDomain).toList(); log.info("All dietary needs are retrieved successfully"); return ResponseEntity.ok(response); } - - private ParametricResponse buildParametricResponse(DietaryNeed dietaryNeed) { - return ParametricResponse.builder() - .id(dietaryNeed.getId()) - .description(dietaryNeed.getDescription()) - .build(); - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/IngredientControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/IngredientControllerAdapter.java new file mode 100644 index 0000000..bdc20e5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/IngredientControllerAdapter.java @@ -0,0 +1,171 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ImageIngredientsResponse; +import com.cuoco.adapter.in.controller.model.IngredientResponse; +import com.cuoco.adapter.in.controller.model.TextRequest; +import com.cuoco.adapter.in.controller.model.UnitResponse; +import com.cuoco.application.port.in.GetIngredientsFromAudioCommand; +import com.cuoco.application.port.in.GetIngredientsFromTextCommand; +import com.cuoco.application.port.in.GetIngredientsGroupedFromImagesCommand; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Validated +@RestController +@RequestMapping("/ingredients") +@Tag(name = "Ingredients", description = "Obtains ingredients from different sources") +public class IngredientControllerAdapter { + + private final GetIngredientsFromAudioCommand getIngredientsFromAudioCommand; + private final GetIngredientsGroupedFromImagesCommand getIngredientsGroupedFromImagesCommand; + private final GetIngredientsFromTextCommand getIngredientsFromTextCommand; + + public IngredientControllerAdapter( + GetIngredientsFromAudioCommand getIngredientsFromAudioCommand, + GetIngredientsGroupedFromImagesCommand getIngredientsGroupedFromImagesCommand, + GetIngredientsFromTextCommand getIngredientsFromTextCommand + ) { + this.getIngredientsFromAudioCommand = getIngredientsFromAudioCommand; + this.getIngredientsGroupedFromImagesCommand = getIngredientsGroupedFromImagesCommand; + this.getIngredientsFromTextCommand = getIngredientsFromTextCommand; + } + + @PostMapping("/audio") + @Operation(summary = "Obtains all the ingredients by audio") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the founded ingredients in the provided audio", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ImageIngredientsResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> analyzeVoice( + @RequestParam("audio") @NotNull MultipartFile audioFile, + @RequestParam(value = "language", defaultValue = "es-ES") String language + ) { + log.info("Executing POST for audio processing to get ingredients with file {} (size: {} bytes)", audioFile.getOriginalFilename(), audioFile.getSize()); + + List ingredients = getIngredientsFromAudioCommand.execute(buildAudioCommand(audioFile, language)); + List response = ingredients.stream().map(this::buildIngredientResponse).toList(); + + log.info("Successfully extracted {} ingredients from voice", ingredients.size()); + return ResponseEntity.ok(response); + } + + @PostMapping("/image") + @Operation(summary = "Obtains all the ingredients by image") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the founded ingredients in the provided image", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ImageIngredientsResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getIngredients(@RequestParam("image") @NotNull List images) { + log.info("Executing POST for image file processing to get ingredients, with {} images", images.size()); + + Map> ingredientsByImage = getIngredientsGroupedFromImagesCommand.execute(buildImageCommand(images)); + List response = buildImageIngredientsResponseList(ingredientsByImage); + + log.info("Successfully extracted ingredients from {} images with separation", ingredientsByImage.size()); + return ResponseEntity.ok(response); + } + + @PostMapping("/text") + public ResponseEntity processText(@RequestBody @NotNull TextRequest request) { + log.info("Processing text for ingredient extraction: {}", request); + + List ingredients = getIngredientsFromTextCommand.execute(buildTextCommand(request)); + List response = ingredients.stream().map(this::buildIngredientResponse).toList(); + + log.info("Successfully extracted {} ingredients from text", ingredients.size()); + return ResponseEntity.ok(response); + } + + private GetIngredientsFromAudioCommand.Command buildAudioCommand(MultipartFile audioFile, String language) { + return GetIngredientsFromAudioCommand.Command.builder() + .audioFile(audioFile) + .language(language) + .build(); + } + + private GetIngredientsGroupedFromImagesCommand.Command buildImageCommand(List images) { + return GetIngredientsGroupedFromImagesCommand.Command.builder() + .images(images) + .build(); + } + + private GetIngredientsFromTextCommand.Command buildTextCommand(TextRequest request) { + return GetIngredientsFromTextCommand.Command.builder() + .text(request.getText()) + .source(request.getSource()) + .build(); + } + + private List buildImageIngredientsResponseList(Map> ingredientsByImage) { + return ingredientsByImage.entrySet().stream() + .map(imageIngredients -> ImageIngredientsResponse.builder() + .filename(imageIngredients.getKey()) + .ingredients(imageIngredients.getValue().stream().map(this::buildIngredientResponse).toList()) + .build()) + .toList(); + } + + private IngredientResponse buildIngredientResponse(Ingredient ingredient) { + return IngredientResponse.builder() + .name(ingredient.getName()) + .quantity(ingredient.getQuantity()) + .unit(UnitResponse.builder() + .id(ingredient.getUnit().getId()) + .description(ingredient.getUnit().getDescription()) + .symbol(ingredient.getUnit().getSymbol()) + .build() + ) + .confirmed(ingredient.getConfirmed()) + .source(ingredient.getSource()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapter.java new file mode 100644 index 0000000..592c9c0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapter.java @@ -0,0 +1,169 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.IngredientRequest; +import com.cuoco.adapter.in.controller.model.IngredientResponse; +import com.cuoco.adapter.in.controller.model.MealPrepConfigurationRequest; +import com.cuoco.adapter.in.controller.model.MealPrepFilterRequest; +import com.cuoco.adapter.in.controller.model.MealPrepRequest; +import com.cuoco.adapter.in.controller.model.MealPrepResponse; +import com.cuoco.adapter.in.controller.model.RecipeResponse; +import com.cuoco.adapter.in.controller.model.StepResponse; +import com.cuoco.application.port.in.GetMealPrepByIdQuery; +import com.cuoco.application.port.in.GetMealPrepFromIngredientsCommand; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/meal-preps") +@Tag(name = "Meal Prep", description = "Obtains recipes for MealPrep from ingredients") +public class MealPrepControllerAdapter { + + private final GetMealPrepFromIngredientsCommand getMealPrepFromIngredientsCommand; + private final GetMealPrepByIdQuery getMealPrepByIdQuery; + + public MealPrepControllerAdapter( + GetMealPrepFromIngredientsCommand getMealPrepFromIngredientsCommand, + GetMealPrepByIdQuery getMealPrepByIdQuery + ) { + this.getMealPrepFromIngredientsCommand = getMealPrepFromIngredientsCommand; + this.getMealPrepByIdQuery = getMealPrepByIdQuery; + } + + @PostMapping + @Operation(summary = "Create meal preps") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the created meal preps", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = MealPrepResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> generate(@RequestBody MealPrepRequest mealPrepRequest) { + + log.info("Executing GET mealPrep from ingredients with body {}", mealPrepRequest); + + List mealPreps = getMealPrepFromIngredientsCommand.execute(buildGenerateMealPrepCommand(mealPrepRequest)); + List mealPrepsResponse = mealPreps.stream().map(this::buildResponse).toList(); + + log.info("Successfully generated recipes"); + return ResponseEntity.ok(mealPrepsResponse); + } + + @GetMapping("/{id}") + @Operation(summary = "Get meal prep by ID") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return the meal prep ", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = MealPrepResponse.class) + ) + ), + @ApiResponse( + responseCode = "404", + description = "Meal prep not found with the provided ID", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity getById(@PathVariable Long id) { + log.info("Executing GET for find meal prep with ID {}", id); + + MealPrep mealPrep = getMealPrepByIdQuery.execute(id); + MealPrepResponse mealPrepResponse = buildResponse(mealPrep); + + return ResponseEntity.ok(mealPrepResponse); + } + + private GetMealPrepFromIngredientsCommand.Command buildGenerateMealPrepCommand(MealPrepRequest mealPrepRequest) { + + if(mealPrepRequest.getFilters() == null) mealPrepRequest.setFilters(new MealPrepFilterRequest()); + if(mealPrepRequest.getConfiguration() == null) mealPrepRequest.setConfiguration(new MealPrepConfigurationRequest()); + + return GetMealPrepFromIngredientsCommand.Command.builder() + .ingredients(mealPrepRequest.getIngredients().stream().map(this::buildIngredient).toList()) + .freeze(mealPrepRequest.getFilters().getFreeze()) + .preparationTimeId(mealPrepRequest.getFilters().getPreparationTimeId()) + .servings(mealPrepRequest.getFilters().getServings()) + .cookLevelId(mealPrepRequest.getFilters().getCookLevelId()) + .dietId(mealPrepRequest.getFilters().getDietId()) + .typeIds(mealPrepRequest.getFilters().getTypeIds()) + .allergiesIds(mealPrepRequest.getFilters().getAllergiesIds()) + .dietaryNeedsIds(mealPrepRequest.getFilters().getDietaryNeedsIds()) + .size(mealPrepRequest.getConfiguration().getSize()) + .notInclude(mealPrepRequest.getConfiguration().getNotInclude()) + .build(); + } + + private Ingredient buildIngredient(IngredientRequest ingredientRequest) { + return Ingredient.builder() + .name(ingredientRequest.getName()) + .build(); + } + + private MealPrepResponse buildResponse(MealPrep mealPrep) { + return MealPrepResponse.builder() + .id(mealPrep.getId()) + .title(mealPrep.getTitle()) + .estimatedCookingTime(mealPrep.getEstimatedCookingTime()) + .favorite(mealPrep.getFavorite()) + .servings(mealPrep.getServings()) + .freeze(mealPrep.getFreeze()) + .steps(mealPrep.getSteps().stream().map(StepResponse::fromDomain).toList()) + .recipes(mealPrep.getRecipes().stream().map(this::buildRecipeResponse).toList()) + .ingredients(mealPrep.getIngredients().stream().map(IngredientResponse::fromDomain).toList()) + .build(); + } + + private RecipeResponse buildRecipeResponse(Recipe recipe) { + return RecipeResponse.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .image(recipe.getImage()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapter.java new file mode 100644 index 0000000..8606afb --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapter.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.port.in.GetAllMealTypesQuery; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/meal-types") +public class MealTypeControllerAdapter { + + private final GetAllMealTypesQuery getAllMealTypesQuery; + + public MealTypeControllerAdapter(GetAllMealTypesQuery getAllMealTypesQuery) { + this.getAllMealTypesQuery = getAllMealTypesQuery; + } + + @GetMapping + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the meal types") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent meal types", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all meal types"); + List mealTypes = getAllMealTypesQuery.execute(); + List response = mealTypes.stream().map(ParametricResponse::fromDomain).toList(); + + log.info("All meal types are retrieved successfully"); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/PlanControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/PlanControllerAdapter.java index eec058c..804aee9 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/PlanControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/PlanControllerAdapter.java @@ -1,10 +1,17 @@ package com.cuoco.adapter.in.controller; import com.cuoco.adapter.in.controller.model.ParametricResponse; -import com.cuoco.application.port.in.GetPlansQuery; +import com.cuoco.application.port.in.GetAllPlansQuery; import com.cuoco.application.usecase.model.Plan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,32 +19,44 @@ import java.util.List; +@Slf4j @RestController -@RequestMapping("/plan") +@RequestMapping("/plans") public class PlanControllerAdapter { - static final Logger log = LoggerFactory.getLogger(PlanControllerAdapter.class); + private final GetAllPlansQuery getAllPlansQuery; - private final GetPlansQuery getPlansQuery; - - public PlanControllerAdapter(GetPlansQuery getPlansQuery) { - this.getPlansQuery = getPlansQuery; + public PlanControllerAdapter(GetAllPlansQuery getAllPlansQuery) { + this.getAllPlansQuery = getAllPlansQuery; } @GetMapping - public ResponseEntity getAll() { - log.info("GET all available plans"); - List plans = getPlansQuery.execute(); - List response = plans.stream().map(this::buildParametricResponse).toList(); + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the plans") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent plans", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all available plans"); + List plans = getAllPlansQuery.execute(); + List response = plans.stream().map(ParametricResponse::fromDomain).toList(); log.info("All plans are retrieved successfully"); return ResponseEntity.ok(response); } - - private ParametricResponse buildParametricResponse(Plan plan) { - return ParametricResponse.builder() - .id(plan.getId()) - .description(plan.getDescription()) - .build(); - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapter.java new file mode 100644 index 0000000..98ae37f --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapter.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.port.in.GetAllPreparationTimesQuery; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/preparation-times") +public class PreparationTimeControllerAdapter { + + private GetAllPreparationTimesQuery getAllPreparationTimesQuery; + + public PreparationTimeControllerAdapter(GetAllPreparationTimesQuery getAllPreparationTimesQuery) { + this.getAllPreparationTimesQuery = getAllPreparationTimesQuery; + } + + @GetMapping + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the preparation times") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent preparation times", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = ParametricResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAllPreparationTimes() { + log.info("Executing GET all preparation times"); + List preparationTimes = getAllPreparationTimesQuery.execute(); + List response = preparationTimes.stream().map(ParametricResponse::fromDomain).toList(); + + log.info("All preparation times are retrieved successfully"); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/RecipeControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/RecipeControllerAdapter.java index ed1eb44..caaa179 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/RecipeControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/RecipeControllerAdapter.java @@ -1,68 +1,265 @@ package com.cuoco.adapter.in.controller; -import com.cuoco.adapter.out.rest.model.gemini.GeminiResponseMapper; +import com.cuoco.adapter.in.controller.model.IngredientRequest; +import com.cuoco.adapter.in.controller.model.IngredientResponse; +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.adapter.in.controller.model.RecipeConfigurationRequest; +import com.cuoco.adapter.in.controller.model.RecipeFilterRequest; import com.cuoco.adapter.in.controller.model.RecipeRequest; +import com.cuoco.adapter.in.controller.model.RecipeResponse; +import com.cuoco.adapter.in.controller.model.StepResponse; +import com.cuoco.adapter.in.utils.UtilsAdapter; +import com.cuoco.application.port.in.FindOrCreateRecipeCommand; +import com.cuoco.application.port.in.FindRecipesCommand; +import com.cuoco.application.port.in.GetRecipeByIdQuery; import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.application.usecase.model.RecipeFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@Slf4j @RestController -@RequestMapping("/api/generate-recipes") +@RequestMapping("/recipes") +@Tag(name = "Recipes", description = "Obtains recipes with ingredients, filters and configuration") public class RecipeControllerAdapter { - static final Logger log = LoggerFactory.getLogger(RecipeControllerAdapter.class); - private final GetRecipesFromIngredientsCommand getRecipesFromIngredientsCommand; - private final GeminiResponseMapper geminiResponseMapper; + private final GetRecipeByIdQuery getRecipeByIdQuery; + private final FindOrCreateRecipeCommand findOrCreateRecipeCommand; + private final FindRecipesCommand findRecipesCommand; - public RecipeControllerAdapter(GetRecipesFromIngredientsCommand getRecipesFromIngredientsCommand, - GeminiResponseMapper geminiResponseMapper) { + public RecipeControllerAdapter( + GetRecipesFromIngredientsCommand getRecipesFromIngredientsCommand, + GetRecipeByIdQuery getRecipeByIdQuery, + FindOrCreateRecipeCommand findOrCreateRecipeCommand, + FindRecipesCommand findRecipesCommand + ) { this.getRecipesFromIngredientsCommand = getRecipesFromIngredientsCommand; - this.geminiResponseMapper = geminiResponseMapper; + this.getRecipeByIdQuery = getRecipeByIdQuery; + this.findOrCreateRecipeCommand = findOrCreateRecipeCommand; + this.findRecipesCommand = findRecipesCommand; } - @PostMapping() - public ResponseEntity generate(@RequestBody RecipeRequest recipeRequest) { - try { - log.info("Executing GET recipes from ingredients with body {}", recipeRequest); + @GetMapping("/{id}") + @Operation(summary = "Get some specific recipe") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return the specific recipe with the provided ID", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = RecipeResponse.class) + ) + ), + @ApiResponse( + responseCode = "404", + description = "Recipe not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity getRecipe(@PathVariable(name = "id") Long recipeId, @RequestParam(required = false) Integer servings) { + log.info("Executing GET for find recipe with ID {}", recipeId); - String recipes = getRecipesFromIngredientsCommand.execute(buildGenerateRecipeCommand(recipeRequest)); + Recipe recipe = getRecipeByIdQuery.execute(recipeId, servings); - log.info("Successfully generated recipes"); + RecipeResponse recipeResponse = buildResponse(recipe); - // Use dedicated mapper to parse Gemini response - Object jsonResponse = geminiResponseMapper.parseToJson(recipes); - return ResponseEntity.ok(jsonResponse); - } catch (Exception e) { - log.error("Error generating recipes: {}", e.getMessage()); - return ResponseEntity.internalServerError().body("Error al generar la receta: " + e.getMessage()); - } + log.info("Successfully obtained recipe with ID {}", recipeResponse.getId()); + return ResponseEntity.ok(recipeResponse); + } + + @PostMapping + @Operation(summary = "Find or create a recipe with the provided ingredients, filters and configuration") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return a list of recipes with the provided ingredients and filters", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = RecipeResponse.class) + ) + ), + @ApiResponse( + responseCode = "404", + description = "Recipe not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> generate(@RequestBody @Valid RecipeRequest recipeRequest) { + + log.info("Executing POST for get or creation of recipes by ingredients with body {}", recipeRequest); + + List recipes = getRecipesFromIngredientsCommand.execute(buildGenerateRecipeCommand(recipeRequest)); + + List recipesResponse = recipes.stream().map(this::buildResponse).toList(); + + log.info("Successfully generated recipes"); + return ResponseEntity.ok(recipesResponse); + } + + @GetMapping + @Operation(summary = "Find or create a recipe from a specific provided name") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return a recipe from the provided name", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = RecipeResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity quickRecipe(@RequestParam String name) { + log.info("Executing find or generate recipe with name: {}", name); + + Recipe recipe = findOrCreateRecipeCommand.execute(buildQuickRecipeCommand(name)); + + RecipeResponse response = buildResponse(recipe); + + log.info("Successfully found or generated recipe: {}", recipe.getName()); + return ResponseEntity.ok(response); + } + + @GetMapping("/random") + @Operation(summary = "Find random recipes with a provided size") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return a recipe from the provided filter", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = RecipeResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getRecipes(@RequestParam Integer size) { + log.info("Executing find recipes with size: {}", size); + + List recipes = findRecipesCommand.execute(buildFindRecipesCommand(size)); + + List recipesResponse = recipes.stream().map(this::buildResponse).toList(); + + log.info("Successfully found recipes"); + return ResponseEntity.ok(recipesResponse); } private GetRecipesFromIngredientsCommand.Command buildGenerateRecipeCommand(RecipeRequest recipeRequest) { - return new GetRecipesFromIngredientsCommand.Command( - recipeRequest.getFilters() != null ? - new RecipeFilter( - recipeRequest.getFilters().getTime(), - recipeRequest.getFilters().getDifficulty(), - recipeRequest.getFilters().getTypes(), - recipeRequest.getFilters().getDiet(), - recipeRequest.getFilters().getQuantity() - ) : null, - recipeRequest.getIngredients().stream().map(ingredient -> - new Ingredient( - ingredient.getName(), - ingredient.getSource(), - ingredient.isConfirmed() - ) - ).toList() - ); + + boolean filtersEnabled = true; + + if(recipeRequest.getFilters() == null) { + filtersEnabled = false; + recipeRequest.setFilters(new RecipeFilterRequest()); + } + + if(recipeRequest.getConfiguration() == null) recipeRequest.setConfiguration(new RecipeConfigurationRequest()); + + return GetRecipesFromIngredientsCommand.Command.builder() + .filtersEnabled(filtersEnabled) + .ingredients(recipeRequest.getIngredients().stream().map(this::buildIngredient).toList()) + .preparationTimeId(recipeRequest.getFilters().getPreparationTimeId()) + .servings(recipeRequest.getFilters().getServings()) + .cookLevelId(recipeRequest.getFilters().getCookLevelId()) + .dietId(recipeRequest.getFilters().getDietId()) + .typeIds(recipeRequest.getFilters().getTypeIds()) + .allergiesIds(recipeRequest.getFilters().getAllergiesIds()) + .dietaryNeedsIds(recipeRequest.getFilters().getDietaryNeedsIds()) + .size(recipeRequest.getConfiguration().getSize()) + .notInclude(recipeRequest.getConfiguration().getNotInclude()) + .build(); + } + + private FindOrCreateRecipeCommand.Command buildQuickRecipeCommand(String name) { + return FindOrCreateRecipeCommand.Command.builder() + .recipeName(name) + .build(); + } + + private FindRecipesCommand.Command buildFindRecipesCommand(Integer size) { + return FindRecipesCommand.Command.builder() + .size(size) + .random(true) + .build(); + } + + private Ingredient buildIngredient(IngredientRequest ingredientRequest) { + return Ingredient.builder() + .name(ingredientRequest.getName()) + .build(); + } + + private RecipeResponse buildResponse(Recipe recipe) { + return RecipeResponse.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .favorite(recipe.getFavorite()) + .steps(recipe.getSteps().stream().map(StepResponse::fromDomain).toList()) + .image(recipe.getImage()) + .preparationTime(ParametricResponse.fromDomain(recipe.getPreparationTime())) + .cookLevel(ParametricResponse.fromDomain(recipe.getCookLevel())) + .diet(UtilsAdapter.mapNull(recipe.getDiet())) + .mealTypes(recipe.getMealTypes().stream().map(ParametricResponse::fromDomain).toList()) + .allergies(UtilsAdapter.mapNullOrEmpty(recipe.getAllergies())) + .dietaryNeeds(UtilsAdapter.mapNullOrEmpty(recipe.getDietaryNeeds())) + .ingredients(recipe.getIngredients().stream().map(IngredientResponse::fromDomain).toList()) + .build(); } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/TextControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/TextControllerAdapter.java deleted file mode 100644 index 8fab103..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/TextControllerAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.cuoco.adapter.in.controller; - -import com.cuoco.adapter.in.controller.model.TextRequest; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.application.port.in.GetIngredientsFromTextCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -@RestController -@RequestMapping("/api/analyze-text") -public class TextControllerAdapter { - - private static final Logger log = LoggerFactory.getLogger(TextControllerAdapter.class); - - private final GetIngredientsFromTextCommand getIngredientsFromTextCommand; - private final IngredientsResponseMapper ingredientsResponseMapper; - - public TextControllerAdapter(GetIngredientsFromTextCommand getIngredientsFromTextCommand, - IngredientsResponseMapper ingredientsResponseMapper) { - this.getIngredientsFromTextCommand = getIngredientsFromTextCommand; - this.ingredientsResponseMapper = ingredientsResponseMapper; - } - - @PostMapping("/") - public ResponseEntity processText(@RequestBody TextRequest request) { - try { - log.info("Processing text for ingredient extraction: {}", request); - - List ingredients = getIngredientsFromTextCommand.execute(buildTextCommand(request)); - - IngredientsResponse response = ingredientsResponseMapper.toResponse(ingredients); - - log.info("Successfully extracted {} ingredients from text", ingredients.size()); - return ResponseEntity.ok(response); - } catch (Exception e) { - log.error("Error processing text: {}", e.getMessage(), e); - return ResponseEntity.internalServerError().body("Error al procesar el texto: " + e.getMessage()); - } - } - - private GetIngredientsFromTextCommand.Command buildTextCommand(TextRequest request) { - return new GetIngredientsFromTextCommand.Command(request.getText(), request.getSource()); - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/UnitControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UnitControllerAdapter.java new file mode 100644 index 0000000..cefabc7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UnitControllerAdapter.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.UnitResponse; +import com.cuoco.application.port.in.GetAllUnitsQuery; +import com.cuoco.application.usecase.model.Unit; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/units") +public class UnitControllerAdapter { + + private final GetAllUnitsQuery getAllUnitsQuery; + + public UnitControllerAdapter(GetAllUnitsQuery getAllUnitsQuery) { + this.getAllUnitsQuery = getAllUnitsQuery; + } + + @GetMapping + @Tag(name = "Parametric Endpoints") + @Operation(summary = "GET all the measure units") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return all the existent units", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = UnitResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET all measure units"); + List units = getAllUnitsQuery.execute(); + List response = units.stream().map(UnitResponse::fromDomain).toList(); + + log.info("All units are retrieved successfully"); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/UploadControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UploadControllerAdapter.java deleted file mode 100644 index c0b3405..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/UploadControllerAdapter.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.cuoco.adapter.in.controller; - -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.application.port.in.GetIngredientsFromFileCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.Map; - -@RestController -@RequestMapping("/api/analyze-images") -public class UploadControllerAdapter { - - private static final Logger log = LoggerFactory.getLogger(UploadControllerAdapter.class); - - private final GetIngredientsFromFileCommand getIngredientsFromFileCommand; - private final IngredientsResponseMapper ingredientsResponseMapper; - - public UploadControllerAdapter(GetIngredientsFromFileCommand getIngredientsFromFileCommand, - IngredientsResponseMapper ingredientsResponseMapper) { - this.getIngredientsFromFileCommand = getIngredientsFromFileCommand; - this.ingredientsResponseMapper = ingredientsResponseMapper; - } - - @PostMapping("/") - public ResponseEntity getIngredients(@RequestParam("files") List files) { - try { - log.info("Processing {} image files for ingredient analysis", files.size()); - - Map> ingredientsByImage = getIngredientsFromFileCommand.executeWithSeparation(buildIngredientsFromFileCommand(files)); - - IngredientsResponse ingredientsResponse = ingredientsResponseMapper.toImageSeparateResponse(ingredientsByImage); - - log.info("Successfully extracted ingredients from {} images with separation", ingredientsByImage.size()); - return ResponseEntity.ok(ingredientsResponse); - } catch (Exception e) { - log.error("Error processing images: {}", e.getMessage(), e); - return ResponseEntity.internalServerError().body("Error al procesar la imagen: " + e.getMessage()); - } - } - - private GetIngredientsFromFileCommand.Command buildIngredientsFromFileCommand(List files) { - return new GetIngredientsFromFileCommand.Command(files); - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapter.java new file mode 100644 index 0000000..3436f04 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapter.java @@ -0,0 +1,148 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.CalendarRecipeResponse; +import com.cuoco.adapter.in.controller.model.CalendarResponse; +import com.cuoco.adapter.in.controller.model.DayResponse; +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.adapter.in.controller.model.RecipeCalendarRequest; +import com.cuoco.adapter.in.controller.model.RecipeResponse; +import com.cuoco.adapter.in.controller.model.UserRecipeCalendarRequest; +import com.cuoco.application.port.in.CreateOrUpdateUserRecipeCalendarCommand; +import com.cuoco.application.port.in.GetUserCalendarQuery; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.CalendarRecipe; +import com.cuoco.application.usecase.model.Day; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/users/calendar") +@Tag(name = "User calendar", description = "Manipulates user planning calendar operations") +public class UserCalendarControllerAdapter { + + private final CreateOrUpdateUserRecipeCalendarCommand createOrUpdateUserRecipeCalendarCommand; + private final GetUserCalendarQuery getUserCalendarQuery; + + public UserCalendarControllerAdapter(CreateOrUpdateUserRecipeCalendarCommand createOrUpdateUserRecipeCalendarCommand, GetUserCalendarQuery getUserCalendarQuery) { + this.createOrUpdateUserRecipeCalendarCommand = createOrUpdateUserRecipeCalendarCommand; + this.getUserCalendarQuery = getUserCalendarQuery; + } + + @PutMapping() + @Operation(summary = "Creates or update the user calendar") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "Return NO CONTENT when the calendar was successfully created for the current user" + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity save(@RequestBody @Valid List requests) { + log.info("Executing POST for user recipe calendar creation"); + + createOrUpdateUserRecipeCalendarCommand.execute(buildCommand(requests)); + + log.info("Calendar successfully created"); + return ResponseEntity.status(HttpStatus.CREATED.value()).build(); + } + + @GetMapping() + @Operation(summary = "GET the user calendar") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return the calendar for the current user", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = CalendarResponse.class)) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> get() { + log.info("Executing GET calendar from authenticated user"); + + List calendarRecipes = getUserCalendarQuery.execute(); + List response = calendarRecipes.stream().map(this::buildCalendarResponse).toList(); + + return ResponseEntity.ok(response); + } + + private CreateOrUpdateUserRecipeCalendarCommand.Command buildCommand(List requests) { + return CreateOrUpdateUserRecipeCalendarCommand.Command.builder() + .calendars(requests.stream().map(this::buildCalendars).toList()) + .build(); + } + + private Calendar buildCalendars(UserRecipeCalendarRequest userRecipeCalendarRequest) { + return Calendar.builder() + .day(Day.builder().id(userRecipeCalendarRequest.getDayId()).build()) + .recipes(userRecipeCalendarRequest.getRecipes().stream().map(this::buildCalendarRecipe).toList()) + .build(); + } + + private CalendarRecipe buildCalendarRecipe(RecipeCalendarRequest recipeCalendarRequest) { + return CalendarRecipe.builder() + .recipe(Recipe.builder().id(recipeCalendarRequest.getRecipeId()).build()) + .mealType(MealType.builder().id(recipeCalendarRequest.getMealTypeId()).build()) + .build(); + } + + private CalendarResponse buildCalendarResponse(Calendar calendar) { + return CalendarResponse.builder() + .day(DayResponse.fromDomain(calendar.getDay())) + .recipes(calendar.getRecipes().stream().map(this::buildCalendarRecipeResponse).toList()) + .build(); + } + + private CalendarRecipeResponse buildCalendarRecipeResponse(CalendarRecipe calendarRecipe) { + return CalendarRecipeResponse.builder() + .recipe(buildReducedRecipeResponse(calendarRecipe.getRecipe())) + .mealType(ParametricResponse.fromDomain(calendarRecipe.getMealType())) + .build(); + } + + private RecipeResponse buildReducedRecipeResponse(Recipe recipe) { + return RecipeResponse.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .description(recipe.getDescription()) + .image(recipe.getImage()) + .build(); + } + +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/UserControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UserControllerAdapter.java new file mode 100644 index 0000000..fb3b61a --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UserControllerAdapter.java @@ -0,0 +1,127 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ChangePasswordRequest; +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.adapter.in.controller.model.UpdateUserRequest; +import com.cuoco.adapter.in.controller.model.UserPreferencesResponse; +import com.cuoco.adapter.in.controller.model.UserResponse; +import com.cuoco.application.port.in.ChangeUserPasswordCommand; +import com.cuoco.application.port.in.UpdateUserProfileCommand; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/users") +@RequiredArgsConstructor +@Tag(name = "User", description = "Manipulate user data") +public class UserControllerAdapter { + + private final UpdateUserProfileCommand updateUserProfileCommand; + private final ChangeUserPasswordCommand changeUserPasswordCommand; + + @PatchMapping() + @Operation(summary = "Update the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Return the updated user data" + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity updateProfile(@RequestBody UpdateUserRequest request) { + log.info("Executing PATCH for user update"); + + User user = updateUserProfileCommand.execute(buildUpdateCommand(request)); + UserResponse userResponse = buildUserResponse(user); + + return ResponseEntity.ok(userResponse); + } + + @PostMapping("/password") + @Operation(summary = "Change password for the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "The user password was updated successfully" + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity changePassword(@RequestBody @Valid ChangePasswordRequest request) { + log.info("Executing POST for change password for the current user"); + + ChangeUserPasswordCommand.Command command = ChangeUserPasswordCommand.Command.builder() + .password(request.getPassword()) + .build(); + + changeUserPasswordCommand.execute(command); + + return ResponseEntity.ok().build(); + } + + private UpdateUserProfileCommand.Command buildUpdateCommand(UpdateUserRequest request) { + return UpdateUserProfileCommand.Command.builder() + .name(request.getName()) + .planId(request.getPlanId()) + .cookLevelId(request.getCookLevelId()) + .dietId(request.getDietId()) + .dietaryNeeds(request.getDietaryNeeds()) + .allergies(request.getAllergies()) + .build(); + } + + private UserResponse buildUserResponse(User user) { + return UserResponse.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .plan(ParametricResponse.fromDomain(user.getPlan())) + .preferences(UserPreferencesResponse.fromDomain(user.getPreferences())) + .dietaryNeeds(buildDietaryNeeds(user.getDietaryNeeds())) + .allergies(buildAllergies(user.getAllergies())) + .build(); + } + + private List buildDietaryNeeds(List dietaryNeeds) { + return dietaryNeeds != null && !dietaryNeeds.isEmpty() ? dietaryNeeds.stream().map(ParametricResponse::fromDomain).toList() : null; + } + + private List buildAllergies(List allergies) { + return allergies != null && !allergies.isEmpty() ? allergies.stream().map(ParametricResponse::fromDomain).toList() : null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapter.java new file mode 100644 index 0000000..9deba83 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapter.java @@ -0,0 +1,174 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.IngredientResponse; +import com.cuoco.adapter.in.controller.model.MealPrepResponse; +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.adapter.in.controller.model.RecipeResponse; +import com.cuoco.adapter.in.controller.model.StepResponse; +import com.cuoco.application.port.in.CreateUserMealPrepCommand; +import com.cuoco.application.port.in.DeleteUserMealPrepCommand; +import com.cuoco.application.port.in.GetAllUserMealPrepsQuery; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/users/meal-preps") +@Tag(name = "User favourites meal preps", description = "Manipulate favourites meal preps saved from the user") +public class UserMealPrepControllerAdapter { + + private final CreateUserMealPrepCommand createUserMealPrepCommand; + private final GetAllUserMealPrepsQuery getAllUserMealPrepsQuery; + private final DeleteUserMealPrepCommand deleteUserMealPrepCommand; + + public UserMealPrepControllerAdapter( + CreateUserMealPrepCommand createUserMealPrepCommand, + GetAllUserMealPrepsQuery getAllUserMealPrepsQuery, + DeleteUserMealPrepCommand deleteUserMealPrepCommand + ) { + this.createUserMealPrepCommand = createUserMealPrepCommand; + this.getAllUserMealPrepsQuery = getAllUserMealPrepsQuery; + this.deleteUserMealPrepCommand = deleteUserMealPrepCommand; + } + + @PostMapping("/{id}") + @Operation(summary = "Creates new user favorite meal prep") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "The meal prep was successfully associated to the user" + ), + @ApiResponse( + responseCode = "404", + description = "Meal prep not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "409", + description = "Meal prep is already associated to the user", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity save(@PathVariable(name = "id") Long mealPrepId) { + log.info("Executing POST for associate meal prep to user"); + + createUserMealPrepCommand.execute(buildCreateCommand(mealPrepId)); + + log.info("Successfully associated meal prep to user"); + return ResponseEntity.status(HttpStatus.CREATED.value()).build(); + } + + @GetMapping + @Operation(summary = "Get all the favorite meal preps for the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Retrieve all the meal prep associated to the authenticated user" + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET for retrieve all user meal preps"); + + List mealPreps = getAllUserMealPrepsQuery.execute(); + + List response = mealPreps.stream().map(this::buildResponse).toList(); + + log.info("Successfully retrieved all user meal preps"); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Deletes one favorite meal prep from the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "The meal prep was successfully deleted from the user. If the meal prep is not associated, it doesn't make any changes." + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity delete(@PathVariable Long id) { + log.info("Executing DELETE for remove meal prep to user"); + + deleteUserMealPrepCommand.execute(buildDeleteCommand(id)); + return ResponseEntity.noContent().build(); + } + + private CreateUserMealPrepCommand.Command buildCreateCommand(Long id) { + return CreateUserMealPrepCommand.Command.builder().id(id).build(); + } + + private DeleteUserMealPrepCommand.Command buildDeleteCommand(Long id) { + return DeleteUserMealPrepCommand.Command.builder().id(id).build(); + } + + private MealPrepResponse buildResponse(MealPrep mealPrep) { + return MealPrepResponse.builder() + .id(mealPrep.getId()) + .title(mealPrep.getTitle()) + .estimatedCookingTime(mealPrep.getEstimatedCookingTime()) + .servings(mealPrep.getServings()) + .freeze(mealPrep.getFreeze()) + .steps(mealPrep.getSteps().stream().map(StepResponse::fromDomain).toList()) + .recipes(mealPrep.getRecipes().stream().map(this::buildRecipeResponse).toList()) + .ingredients(mealPrep.getIngredients().stream().map(IngredientResponse::fromDomain).toList()) + .build(); + } + + private RecipeResponse buildRecipeResponse(Recipe recipe) { + return RecipeResponse.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .mealTypes(recipe.getMealTypes().stream().map(ParametricResponse::fromDomain).toList()) + .image(recipe.getImage()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/UserPaymentsControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UserPaymentsControllerAdapter.java new file mode 100644 index 0000000..4755b8e --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UserPaymentsControllerAdapter.java @@ -0,0 +1,52 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.UserPaymentResponse; +import com.cuoco.application.port.in.CreateUserPaymentCommand; +import com.cuoco.application.port.in.ProcessUserPaymentCommand; +import com.cuoco.application.usecase.model.UserPayment; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/payments") +@RequiredArgsConstructor +public class UserPaymentsControllerAdapter { + + private final CreateUserPaymentCommand createUserPaymentCommand; + private final ProcessUserPaymentCommand processUserPaymentCommand; + + @PostMapping + public ResponseEntity init() { + log.info("Executing POST for init user subscription payment"); + + UserPayment userPayment = createUserPaymentCommand.execute(); + UserPaymentResponse response = UserPaymentResponse.fromDomain(userPayment); + + log.info("User subscription payment response: {}", response); + return ResponseEntity.ok(response); + } + + + @PostMapping("/webhook") + public ResponseEntity processPayment(@RequestBody Map payload) { + log.info("Executing POST for payment webhook: {}", payload); + + ProcessUserPaymentCommand.Command command = ProcessUserPaymentCommand.Command.builder() + .payload(payload) + .build(); + + processUserPaymentCommand.execute(command); + + log.info("User payment webhook processed successfully"); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapter.java new file mode 100644 index 0000000..1f32877 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapter.java @@ -0,0 +1,173 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.adapter.in.controller.model.RecipeResponse; +import com.cuoco.application.port.in.CreateUserRecipeCommand; +import com.cuoco.application.port.in.DeleteUserRecipeCommand; +import com.cuoco.application.port.in.GetAllUserRecipesQuery; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.GlobalExceptionHandler; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Slf4j +@RestController +@RequestMapping("/users/recipes") +@Tag(name = "User favourites recipes", description = "Manipulate favourites recipes saved from the user") +public class UserRecipeControllerAdapter { + + private final CreateUserRecipeCommand createUserRecipeCommand; + private final GetAllUserRecipesQuery getAllUserRecipesQuery; + private final DeleteUserRecipeCommand deleteUserRecipeCommand; + + public UserRecipeControllerAdapter( + CreateUserRecipeCommand createUserRecipeCommand, + GetAllUserRecipesQuery getAllUserRecipesQuery, + DeleteUserRecipeCommand deleteUserRecipeCommand + ) { + this.createUserRecipeCommand = createUserRecipeCommand; + this.getAllUserRecipesQuery = getAllUserRecipesQuery; + this.deleteUserRecipeCommand = deleteUserRecipeCommand; + } + + @PostMapping("/{id}") + @Operation(summary = "Creates new user favorite recipes") + @ApiResponses(value = { + @ApiResponse( + responseCode = "201", + description = "The recipe was successfully associated to the user" + ), + @ApiResponse( + responseCode = "404", + description = "Recipe not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "409", + description = "Recipe is already associated to the user", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity save(@PathVariable(name = "id") Long recipeId) { + log.info("Executing POST for associate recipe to user"); + + createUserRecipeCommand.execute(buildCreateCommand(recipeId)); + + log.info("Successfully associated recipe to user"); + return ResponseEntity.status(HttpStatus.CREATED.value()).build(); + } + + @GetMapping + @Operation(summary = "Get all the favorite recipes for the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Retrieve all the recipes associated to the authenticated user" + ), + @ApiResponse( + responseCode = "404", + description = "Recipe not found", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "409", + description = "Recipe is already associated to the user", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity> getAll() { + log.info("Executing GET for get all user recipes"); + + List recipes = getAllUserRecipesQuery.execute(); + + List response = recipes.stream().map(this::buildRecipeResponse).toList(); + + log.info("Successfully retrieved all user recipes"); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + @Operation(summary = "Deletes one favorite recipe from the current user") + @ApiResponses(value = { + @ApiResponse( + responseCode = "204", + description = "The recipe was successfully deleted from the user. If the recipe doesn't exists, it doesn't make any changes." + ), + @ApiResponse( + responseCode = "503", + description = "Service unavailable", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) + ) + ) + }) + public ResponseEntity delete(@PathVariable(name = "id") Long recipeId) { + log.info("Executing DELETE for remove recipe to user"); + + deleteUserRecipeCommand.execute(buildDeleteCommand(recipeId)); + return ResponseEntity.noContent().build(); + } + + private CreateUserRecipeCommand.Command buildCreateCommand(Long recipeId) { + return CreateUserRecipeCommand.Command.builder().recipeId(recipeId).build(); + } + + private DeleteUserRecipeCommand.Command buildDeleteCommand(Long recipeId) { + return DeleteUserRecipeCommand.Command.builder().id(recipeId).build(); + } + + private RecipeResponse buildRecipeResponse(Recipe recipe) { + return RecipeResponse.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .mealTypes(recipe.getMealTypes().stream().map(ParametricResponse::fromDomain).toList()) + .image(recipe.getImage()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/VoiceControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/VoiceControllerAdapter.java deleted file mode 100644 index af618e2..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/VoiceControllerAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.cuoco.adapter.in.controller; - -import com.cuoco.adapter.in.controller.helper.AudioFileProcessor; -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.application.port.in.GetIngredientsFromVoiceCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -@RestController -@RequestMapping("/api/analyze-voice") -public class VoiceControllerAdapter { - - private static final Logger log = LoggerFactory.getLogger(VoiceControllerAdapter.class); - - private final GetIngredientsFromVoiceCommand getIngredientsFromVoiceCommand; - private final IngredientsResponseMapper ingredientsResponseMapper; - private final AudioFileProcessor audioFileProcessor; - - public VoiceControllerAdapter(GetIngredientsFromVoiceCommand getIngredientsFromVoiceCommand, - IngredientsResponseMapper ingredientsResponseMapper, - AudioFileProcessor audioFileProcessor) { - this.getIngredientsFromVoiceCommand = getIngredientsFromVoiceCommand; - this.ingredientsResponseMapper = ingredientsResponseMapper; - this.audioFileProcessor = audioFileProcessor; - } - - @PostMapping("/") - public ResponseEntity analyzeVoice( - @RequestParam("audio") MultipartFile audioFile, - @RequestParam(value = "language", defaultValue = "es-ES") String language) { - - try { - log.info("Processing voice file: {} (size: {} bytes)", - audioFile.getOriginalFilename(), audioFile.getSize()); - - if (!audioFileProcessor.isValidAudioFile(audioFile)) { - return ResponseEntity.badRequest().body(audioFileProcessor.getSupportedFormatsMessage()); - } - - String audioBase64 = audioFileProcessor.convertToBase64(audioFile); - String format = audioFileProcessor.getAudioFormat(audioFile); - - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - List ingredients = getIngredientsFromVoiceCommand.execute(command); - IngredientsResponse response = ingredientsResponseMapper.toResponse(ingredients); - - log.info("Successfully extracted {} ingredients from voice", ingredients.size()); - return ResponseEntity.ok(response); - - } catch (Exception e) { - log.error("Error processing voice: {}", e.getMessage(), e); - return ResponseEntity.internalServerError() - .body("Error al procesar el audio: " + e.getMessage()); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/helper/AudioFileProcessor.java b/src/main/java/com/cuoco/adapter/in/controller/helper/AudioFileProcessor.java deleted file mode 100644 index ab8b9bb..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/helper/AudioFileProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.cuoco.adapter.in.controller.helper; - -import org.springframework.stereotype.Component; -import org.springframework.web.multipart.MultipartFile; - -import java.util.Base64; -import java.util.List; - -@Component -public class AudioFileProcessor { - - private static final List SUPPORTED_EXTENSIONS = - List.of("mp3", "wav", "ogg", "aac", "flac", "m4a"); - - public boolean isValidAudioFile(MultipartFile file) { - if (file == null || file.isEmpty()) { - return false; - } - - String contentType = file.getContentType(); - String filename = file.getOriginalFilename(); - - if (contentType != null) { - return contentType.startsWith("audio/"); - } - - if (filename != null && filename.contains(".")) { - String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); - return SUPPORTED_EXTENSIONS.contains(extension); - } - - return false; - } - - public String getAudioFormat(MultipartFile file) { - String contentType = file.getContentType(); - - if (contentType != null && contentType.contains("/")) { - return contentType.substring(contentType.indexOf('/') + 1); - } - - String filename = file.getOriginalFilename(); - if (filename != null && filename.contains(".")) { - return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); - } - - return "mp3"; - } - - public String convertToBase64(MultipartFile file) throws Exception { - return Base64.getEncoder().encodeToString(file.getBytes()); - } - - public String getSupportedFormatsMessage() { - return "Archivo no válido. Formatos soportados: MP3, WAV, OGG, AAC, FLAC, M4A"; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/AuthDataResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/AuthDataResponse.java index 3e81389..eddf5aa 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/AuthDataResponse.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/AuthDataResponse.java @@ -4,13 +4,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @Data @Builder -@AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/AuthOperationRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/AuthOperationRequest.java new file mode 100644 index 0000000..af569bb --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/AuthOperationRequest.java @@ -0,0 +1,21 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthOperationRequest { + @NotBlank(message = "Email is required") + @Email(message = "Email is not valid") + private String email; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/AuthRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/AuthRequest.java index bf0bc66..6a5d9eb 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/AuthRequest.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/AuthRequest.java @@ -4,10 +4,13 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Data; @Data +@Builder @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) @@ -17,5 +20,6 @@ public class AuthRequest { private String password; @NotBlank(message = "Email is required") + @Email(message = "Email is not valid") private String email; } diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/AuthResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/AuthResponse.java index 2dcecca..d050bac 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/AuthResponse.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/AuthResponse.java @@ -4,13 +4,11 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @Data @Builder -@AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/CalendarRecipeResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/CalendarRecipeResponse.java new file mode 100644 index 0000000..067edf7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/CalendarRecipeResponse.java @@ -0,0 +1,20 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CalendarRecipeResponse { + + private RecipeResponse recipe; + private ParametricResponse mealType; + +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/CalendarResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/CalendarResponse.java new file mode 100644 index 0000000..7c2668e --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/CalendarResponse.java @@ -0,0 +1,22 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CalendarResponse { + + private DayResponse day; + private List recipes; + +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/ChangePasswordRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/ChangePasswordRequest.java new file mode 100644 index 0000000..c1c8373 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/ChangePasswordRequest.java @@ -0,0 +1,19 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ChangePasswordRequest { + @NotBlank(message = "Password is required") + private String password; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/DayResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/DayResponse.java new file mode 100644 index 0000000..629b281 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/DayResponse.java @@ -0,0 +1,26 @@ +package com.cuoco.adapter.in.controller.model; + +import com.cuoco.application.usecase.model.Day; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class DayResponse { + public Integer id; + public String description; + + public static DayResponse fromDomain(Day day) { + return DayResponse.builder() + .id(day.getId()) + .description(day.getDescription()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/FilterRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/FilterRequest.java deleted file mode 100644 index 13d4b44..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/model/FilterRequest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.cuoco.adapter.in.controller.model; - -import java.util.List; - -public class FilterRequest { - - private String time; - private String difficulty; - private List types; - private String diet; - private int quantity; - private Integer people; // Nuevo campo para el frontend - private Boolean useProfilePreferences; // Nuevo campo para el frontend - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public String getDifficulty() { - return difficulty; - } - - public void setDifficulty(String difficulty) { - this.difficulty = difficulty; - } - - public List getTypes() { - return types; - } - - public void setTypes(List types) { - this.types = types; - } - - public String getDiet() { - return diet; - } - - public void setDiet(String diet) { - this.diet = diet; - } - - public int getQuantity() { - return quantity; - } - - public void setQuantity(int quantity) { - this.quantity = quantity; - } - - public Integer getPeople() { - return people; - } - - public void setPeople(Integer people) { - this.people = people; - if (people != null) { - this.quantity = people; - } - } - - public Boolean getUseProfilePreferences() { - return useProfilePreferences; - } - - public void setUseProfilePreferences(Boolean useProfilePreferences) { - this.useProfilePreferences = useProfilePreferences; - } - - @Override - public String toString() { - return "FilterRequest{" + - "time='" + time + '\'' + - ", difficulty='" + difficulty + '\'' + - ", types=" + types + - ", diet='" + diet + '\'' + - ", quantity=" + quantity + - '}'; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/ImageIngredientsResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/ImageIngredientsResponse.java new file mode 100644 index 0000000..18e4bd2 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/ImageIngredientsResponse.java @@ -0,0 +1,20 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ImageIngredientsResponse { + private String filename; + private List ingredients; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientRequest.java index 9af053d..e3102be 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientRequest.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientRequest.java @@ -4,22 +4,23 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Data; -import lombok.Getter; @Data +@Builder @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) public class IngredientRequest { - + @NotBlank private String name; - private String source; - private boolean confirmed; + @Schema(example = "10.5") + private Double quantity; + private Integer unitId; - public IngredientRequest(String name) { - this.name = name; - } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientResponse.java new file mode 100644 index 0000000..ecd8e9b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientResponse.java @@ -0,0 +1,34 @@ +package com.cuoco.adapter.in.controller.model; + +import com.cuoco.application.usecase.model.Ingredient; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class IngredientResponse { + + private Long id; + private String name; + private Double quantity; + private UnitResponse unit; + private Boolean optional; + private String source; + private Boolean confirmed; + + public static IngredientResponse fromDomain(Ingredient domain) { + return IngredientResponse.builder() + .name(domain.getName()) + .quantity(domain.getQuantity()) + .unit(UnitResponse.fromDomain(domain.getUnit())) + .build(); + } + +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponse.java deleted file mode 100644 index 0de4217..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponse.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.cuoco.adapter.in.controller.model; - -import com.cuoco.application.usecase.model.Ingredient; -import com.fasterxml.jackson.annotation.JsonValue; -import java.util.List; -import java.util.Map; - -public class IngredientsResponse { - private List ingredients; - private Map> ingredientsByImage; - - public IngredientsResponse(List ingredients) { - this.ingredients = ingredients; - } - - public IngredientsResponse(Map> ingredientsByImage) { - this.ingredientsByImage = ingredientsByImage; - } - - @JsonValue - public Object getResponse() { - if (ingredientsByImage != null) { - return ingredientsByImage; - } - return ingredients.stream() - .map(Ingredient::getName) - .toList(); - } - - public List getIngredients() { - return ingredients; - } - - public void setIngredients(List ingredients) { - this.ingredients = ingredients; - } - - public Map> getIngredientsByImage() { - return ingredientsByImage; - } - - public void setIngredientsByImage(Map> ingredientsByImage) { - this.ingredientsByImage = ingredientsByImage; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponseMapper.java b/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponseMapper.java deleted file mode 100644 index 0b83766..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/model/IngredientsResponseMapper.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.cuoco.adapter.in.controller.model; - -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.application.usecase.model.Ingredient; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Component -public class IngredientsResponseMapper { - - public List toSimpleStringArray(IngredientsResponse ingredientsResponse) { - if (ingredientsResponse == null || ingredientsResponse.getIngredients() == null) { - return List.of(); - } - - return ingredientsResponse.getIngredients() - .stream() - .map(Ingredient::getName) - .collect(Collectors.toList()); - } - - public IngredientsResponse toImageSeparateResponse(Map> ingredientsByImage) { - if (ingredientsByImage == null || ingredientsByImage.isEmpty()) { - return new IngredientsResponse(Map.of()); - } - - Map> mappedIngredients = ingredientsByImage.entrySet() - .stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().stream() - .map(Ingredient::getName) - .collect(Collectors.toList()) - )); - - return new IngredientsResponse(mappedIngredients); - } - - - public IngredientsResponse toResponse(List ingredients) { - if (ingredients == null || ingredients.isEmpty()) { - return new IngredientsResponse(Collections.emptyList()); - } - return new IngredientsResponse(ingredients); - } - -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepConfigurationRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepConfigurationRequest.java new file mode 100644 index 0000000..055aa06 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepConfigurationRequest.java @@ -0,0 +1,18 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepConfigurationRequest { + private Integer size; + private List notInclude; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepFilterRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepFilterRequest.java new file mode 100644 index 0000000..73a880d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepFilterRequest.java @@ -0,0 +1,26 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepFilterRequest { + private Boolean freeze; + private Integer preparationTimeId; + private Integer servings; + private Integer cookLevelId; + private List typeIds; + private Integer dietId; + private List allergiesIds; + private List dietaryNeedsIds; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepRequest.java new file mode 100644 index 0000000..2dba38f --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepRequest.java @@ -0,0 +1,23 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepRequest { + @NotEmpty(message = "Es requerido un ingrediente como minimo") + private List ingredients; + private MealPrepFilterRequest filters; + private MealPrepConfigurationRequest configuration; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepResponse.java new file mode 100644 index 0000000..d5a534b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/MealPrepResponse.java @@ -0,0 +1,27 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepResponse { + private Long id; + private String title; + private String estimatedCookingTime; + private Boolean favorite; + private Integer servings; + private Boolean freeze; + private List steps; + private List recipes; + private List ingredients; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/ParametricResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/ParametricResponse.java index 7c8fd65..e0e7c96 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/ParametricResponse.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/ParametricResponse.java @@ -1,5 +1,6 @@ package com.cuoco.adapter.in.controller.model; +import com.cuoco.application.usecase.model.Parametric; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; @@ -15,4 +16,11 @@ public class ParametricResponse { private Integer id; private String description; + + public static ParametricResponse fromDomain(Parametric domain) { + return ParametricResponse.builder() + .id(domain.getId()) + .description(domain.getDescription()) + .build(); + } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeCalendarRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeCalendarRequest.java new file mode 100644 index 0000000..29035c1 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeCalendarRequest.java @@ -0,0 +1,11 @@ +package com.cuoco.adapter.in.controller.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class RecipeCalendarRequest { + private Long recipeId; + private Integer mealTypeId; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeConfigurationRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeConfigurationRequest.java new file mode 100644 index 0000000..9dd505c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeConfigurationRequest.java @@ -0,0 +1,18 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RecipeConfigurationRequest { + private Integer size; + private List notInclude; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeFilterRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeFilterRequest.java new file mode 100644 index 0000000..3905b7c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeFilterRequest.java @@ -0,0 +1,25 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RecipeFilterRequest { + + private Integer preparationTimeId; + private Integer servings; + private Integer cookLevelId; + private List typeIds; + private Integer dietId; + private List allergiesIds; + private List dietaryNeedsIds; + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeRequest.java index e755324..967b868 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeRequest.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeRequest.java @@ -4,19 +4,20 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; import lombok.Data; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; import java.util.List; @Data -@ToString +@Builder @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) public class RecipeRequest { + @NotEmpty(message = "Es requerido un ingrediente como minimo") private List ingredients; - private FilterRequest filters; + private RecipeFilterRequest filters; + private RecipeConfigurationRequest configuration; } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeResponse.java index 0162a60..51a7255 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/RecipeResponse.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/RecipeResponse.java @@ -4,102 +4,29 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data +@Builder @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) public class RecipeResponse { - private String id; + private Long id; private String name; - private String preparationTime; - private String image; private String subtitle; private String description; - private String ingredients; - private String instructions; - - public RecipeResponse() {} - - public RecipeResponse(String id, String name, String preparationTime, String image, String subtitle) { - this.id = id; - this.name = name; - this.preparationTime = preparationTime; - this.image = image; - this.subtitle = subtitle; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPreparationTime() { - return preparationTime; - } - - public void setPreparationTime(String preparationTime) { - this.preparationTime = preparationTime; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public String getSubtitle() { - return subtitle; - } - - public void setSubtitle(String subtitle) { - this.subtitle = subtitle; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getIngredients() { - return ingredients; - } - - public void setIngredients(String ingredients) { - this.ingredients = ingredients; - } - - public String getInstructions() { - return instructions; - } - - public void setInstructions(String instructions) { - this.instructions = instructions; - } - - @Override - public String toString() { - return "RecipeResponse{" + - "id='" + id + '\'' + - ", name='" + name + '\'' + - ", preparationTime='" + preparationTime + '\'' + - ", image='" + image + '\'' + - ", subtitle='" + subtitle + '\'' + - '}'; - } + private Boolean favorite; + private List steps; + private String image; + private ParametricResponse preparationTime; + private ParametricResponse cookLevel; + private ParametricResponse diet; + private List mealTypes; + private List allergies; + private List dietaryNeeds; + private List ingredients; } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/StepResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/StepResponse.java new file mode 100644 index 0000000..2f70ff8 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/StepResponse.java @@ -0,0 +1,38 @@ +package com.cuoco.adapter.in.controller.model; + +import com.cuoco.application.usecase.model.Step; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class StepResponse { + private Long id; + private Integer stepNumber; + private String title; + private String description; + private String time; + private String imageName; + + public static StepResponse fromDomain(Step domain) { + return StepResponse.builder() + .id(domain.getId()) + .stepNumber(domain.getNumber()) + .title(domain.getTitle()) + .description(domain.getDescription()) + .time(domain.getTime()) + .imageName(domain.getImageName()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/SubscriptionResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/SubscriptionResponse.java new file mode 100644 index 0000000..e63db28 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/SubscriptionResponse.java @@ -0,0 +1,4 @@ +package com.cuoco.adapter.in.controller.model; + +public class SubscriptionResponse { +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/TextRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/TextRequest.java index c09e240..d315ee2 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/TextRequest.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/TextRequest.java @@ -4,15 +4,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.AllArgsConstructor; import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; @Data -@ToString -@NoArgsConstructor -@AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UnitResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/UnitResponse.java new file mode 100644 index 0000000..5b32d07 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UnitResponse.java @@ -0,0 +1,28 @@ +package com.cuoco.adapter.in.controller.model; + +import com.cuoco.application.usecase.model.Unit; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class UnitResponse { + private Integer id; + private String description; + private String symbol; + + public static UnitResponse fromDomain(Unit domain) { + return UnitResponse.builder() + .id(domain.getId()) + .description(domain.getDescription()) + .symbol(domain.getSymbol()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UpdateUserRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/UpdateUserRequest.java new file mode 100644 index 0000000..544df0d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UpdateUserRequest.java @@ -0,0 +1,26 @@ +package com.cuoco.adapter.in.controller.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class UpdateUserRequest { + + private String name; + private Integer planId; + private Integer cookLevelId; + private Integer dietId; + private List dietaryNeeds; + private List allergies; + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UserPaymentResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/UserPaymentResponse.java new file mode 100644 index 0000000..7dbca1e --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UserPaymentResponse.java @@ -0,0 +1,33 @@ +package com.cuoco.adapter.in.controller.model; + +import com.cuoco.application.usecase.model.UserPayment; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserPaymentResponse { + + private String externalId; + private String externalReference; + private String checkoutUrl; + + public static UserPaymentResponse fromDomain(UserPayment userPayment) { + return UserPaymentResponse.builder() + .externalId(userPayment.getExternalId()) + .externalReference(userPayment.getExternalReference()) + .checkoutUrl(userPayment.getCheckoutUrl()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UserPreferencesResponse.java b/src/main/java/com/cuoco/adapter/in/controller/model/UserPreferencesResponse.java index 4ba6520..62d4f58 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/UserPreferencesResponse.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UserPreferencesResponse.java @@ -1,5 +1,6 @@ package com.cuoco.adapter.in.controller.model; +import com.cuoco.application.usecase.model.UserPreferences; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategies; @@ -15,4 +16,11 @@ public class UserPreferencesResponse { private ParametricResponse cookLevel; private ParametricResponse diet; + + public static UserPreferencesResponse fromDomain(UserPreferences domain) { + return UserPreferencesResponse.builder() + .cookLevel(ParametricResponse.fromDomain(domain.getCookLevel())) + .diet(ParametricResponse.fromDomain(domain.getDiet())) + .build(); + } } diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UserRecipeCalendarRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/UserRecipeCalendarRequest.java new file mode 100644 index 0000000..da2c16a --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UserRecipeCalendarRequest.java @@ -0,0 +1,13 @@ +package com.cuoco.adapter.in.controller.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class UserRecipeCalendarRequest { + private Integer dayId; + private List recipes; +} diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/UserRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/UserRequest.java index 4aba05b..4d0a553 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/model/UserRequest.java +++ b/src/main/java/com/cuoco/adapter/in/controller/model/UserRequest.java @@ -7,13 +7,13 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import java.util.List; @Data -@AllArgsConstructor +@Builder @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/src/main/java/com/cuoco/adapter/in/controller/model/VoiceRequest.java b/src/main/java/com/cuoco/adapter/in/controller/model/VoiceRequest.java deleted file mode 100644 index b3d2cd8..0000000 --- a/src/main/java/com/cuoco/adapter/in/controller/model/VoiceRequest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.cuoco.adapter.in.controller.model; - -import lombok.Getter; - -@Getter -public class VoiceRequest { - // Getters and setters - private String audioBase64; // Audio encoded in base64 - private String format; // "wav", "mp3", etc. - private String language; // "es-ES", "en-US", etc. - - public VoiceRequest() {} - - public VoiceRequest(String audioBase64, String format, String language) { - this.audioBase64 = audioBase64; - this.format = format; - this.language = language; - } - - public void setAudioBase64(String audioBase64) { - this.audioBase64 = audioBase64; - } - - public void setFormat(String format) { - this.format = format; - } - - public void setLanguage(String language) { - this.language = language; - } - - @Override - public String toString() { - return "VoiceRequest{" + - "format='" + format + '\'' + - ", language='" + language + '\'' + - ", audioBase64Length=" + (audioBase64 != null ? audioBase64.length() : 0) + - '}'; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilter.java b/src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapter.java similarity index 50% rename from src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilter.java rename to src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapter.java index a3502ae..13d33bd 100644 --- a/src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapter.java @@ -1,11 +1,13 @@ package com.cuoco.adapter.in.security; +import com.cuoco.application.exception.UnauthorizedException; import com.cuoco.application.port.in.AuthenticateUserCommand; import com.cuoco.application.usecase.model.AuthenticatedUser; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; @@ -17,12 +19,13 @@ import java.io.IOException; import java.util.stream.Collectors; +@Slf4j @Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { +public class JwtAuthenticationFilterAdapter extends OncePerRequestFilter { private final AuthenticateUserCommand authenticateUserCommand; - public JwtAuthenticationFilter(AuthenticateUserCommand authenticateUserCommand) { + public JwtAuthenticationFilterAdapter(AuthenticateUserCommand authenticateUserCommand) { this.authenticateUserCommand = authenticateUserCommand; } @@ -30,18 +33,24 @@ public JwtAuthenticationFilter(AuthenticateUserCommand authenticateUserCommand) protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - final String authHeader = request.getHeader("Authorization"); + try { + final String authHeader = request.getHeader("Authorization"); - AuthenticatedUser authenticatedUser = authenticateUserCommand.execute(buildCommand(authHeader)); + AuthenticatedUser authenticatedUser = authenticateUserCommand.execute(buildCommand(authHeader)); - if (authenticatedUser != null) { - UsernamePasswordAuthenticationToken userToken = buildToken(authenticatedUser); + if (authenticatedUser != null) { + UsernamePasswordAuthenticationToken userToken = buildToken(authenticatedUser); - userToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(userToken); - } + userToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(userToken); + } + + filterChain.doFilter(request, response); - filterChain.doFilter(request, response); + } catch (UnauthorizedException e) { + log.warn("Unauthorized: {}", e.getDescription()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getDescription()); + } } private AuthenticateUserCommand.Command buildCommand(String authHeader) { @@ -50,7 +59,7 @@ private AuthenticateUserCommand.Command buildCommand(String authHeader) { private UsernamePasswordAuthenticationToken buildToken(AuthenticatedUser authenticatedUser) { return new UsernamePasswordAuthenticationToken( - authenticatedUser.getUser().getEmail(), + authenticatedUser.getUser(), null, authenticatedUser.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()) ); @@ -61,10 +70,16 @@ protected boolean shouldNotFilter(HttpServletRequest request) { AntPathMatcher matcher = new AntPathMatcher(); return matcher.match("/auth/**", request.getRequestURI()) || matcher.match("/actuator/health", request.getRequestURI()) - || matcher.match("/plan", request.getRequestURI()) - || matcher.match("/allergy", request.getRequestURI()) - || matcher.match("/diet", request.getRequestURI()) - || matcher.match("/dietary-need", request.getRequestURI()) - || matcher.match("/cook-level", request.getRequestURI()); + || matcher.match("/plans", request.getRequestURI()) + || matcher.match("/allergies", request.getRequestURI()) + || matcher.match("/diets", request.getRequestURI()) + || matcher.match("/dietary-needs", request.getRequestURI()) + || matcher.match("/cook-levels", request.getRequestURI()) + || matcher.match("/meal-types", request.getRequestURI()) + || matcher.match("/preparation-times", request.getRequestURI()) + || matcher.match("/payments/webhook", request.getRequestURI()) + || matcher.match("/v3/api-docs/**", request.getRequestURI()) + || matcher.match("/swagger-ui/**", request.getRequestURI()) + || matcher.match("/swagger-ui.html", request.getRequestURI()); } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/in/utils/UtilsAdapter.java b/src/main/java/com/cuoco/adapter/in/utils/UtilsAdapter.java new file mode 100644 index 0000000..a970c0b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/in/utils/UtilsAdapter.java @@ -0,0 +1,24 @@ +package com.cuoco.adapter.in.utils; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.usecase.model.Parametric; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class UtilsAdapter { + + public static ParametricResponse mapNull(Parametric source) { + return source != null ? ParametricResponse.fromDomain(source) : null; + } + + public static List mapNullOrEmpty(Collection source) { + return Optional.ofNullable(source) + .orElse(Collections.emptyList()) + .stream() + .map(ParametricResponse::fromDomain) + .toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java new file mode 100644 index 0000000..fa9e3b6 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java @@ -0,0 +1,82 @@ +package com.cuoco.adapter.out.email; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.io.UnsupportedEncodingException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SendConfirmationNotificationEmailRepositoryAdapter implements SendConfirmationEmailRepository { + + private final String EMAIL_BODY = FileReader.execute("email/userConfirmation.html"); + + @Value("${shared.email.no-reply.name}") + private String fromName; + + @Value("${shared.email.no-reply.from}") + private String fromEmail; + + private final HttpServletRequest request; + private final JavaMailSender mailSender; + + @Override + public void execute(User user, String token) { + try { + log.info("Executing send confirmation email for user with ID {}", user.getId()); + + String confirmationLink = buildConfirmationLink(token); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + + helper.setFrom(new InternetAddress(fromEmail, fromName)); + helper.setTo(user.getEmail()); + helper.setSubject(user.getName() +", ¡Confirmá tu cuenta en Cuoco!"); + + String content = EMAIL_BODY + .replace("{{NAME}}", user.getName()) + .replace("{{LINK}}", confirmationLink); + + helper.setText(content, true); + + mailSender.send(message); + + log.info("Successfully sended confirmation email to {} with link {}", user.getEmail(), confirmationLink); + } catch (MessagingException | UnsupportedEncodingException e) { + log.error("Error sending confirmation email to {}: {}", user.getEmail(), e.getMessage()); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildConfirmationLink(String token) { + + String baseUrl = request.getRequestURL().toString() + .replace(request.getRequestURI(), ""); + + String contextPath = baseUrl.contains("cuoco.com.ar") ? "/api" : ""; + + String confirmationLink = baseUrl + .concat(contextPath) + .concat("/auth/confirm?token=") + .concat(token); + + log.info("Builded URL link from this base {} and this context {}", baseUrl, contextPath); + return confirmationLink; + } +} + diff --git a/src/main/java/com/cuoco/adapter/out/email/SendResetPasswordConfirmationEmailRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/email/SendResetPasswordConfirmationEmailRepositoryAdapter.java new file mode 100644 index 0000000..2beb85c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/email/SendResetPasswordConfirmationEmailRepositoryAdapter.java @@ -0,0 +1,74 @@ +package com.cuoco.adapter.out.email; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.application.port.out.SendResetPasswordConfirmationRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.io.UnsupportedEncodingException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SendResetPasswordConfirmationEmailRepositoryAdapter implements SendResetPasswordConfirmationRepository { + + private final String EMAIL_BODY = FileReader.execute("email/resetPassword.html"); + + @Value("${shared.email.no-reply.name}") + private String fromName; + + @Value("${shared.email.no-reply.from}") + private String fromEmail; + + private final HttpServletRequest request; + private final JavaMailSender mailSender; + + @Override + public void execute(User user, String token) { + try { + log.info("Executing send reset password email for user with ID {}", user.getId()); + + String passwordLink = buildPasswordLink(token); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + + helper.setFrom(new InternetAddress(fromEmail, fromName)); + helper.setTo(user.getEmail()); + helper.setSubject("Solicitud de cambio de contraseña"); + + String content = EMAIL_BODY + .replace("{{NAME}}", user.getName()) + .replace("{{LINK}}", passwordLink); + + helper.setText(content, true); + + mailSender.send(message); + + log.info("Successfully sended confirmation email to {} with link {}", user.getEmail(), passwordLink); + } catch (MessagingException | UnsupportedEncodingException e) { + log.error("Error sending confirmation email to {}: {}", user.getEmail(), e.getMessage()); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildPasswordLink(String token) { + + String baseUrl = request.getRequestURL().toString().replace(request.getRequestURI(), ""); + + return baseUrl + .concat("/password/change?token=") + .concat(token); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllMealPrepsDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllMealPrepsDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..8c0581a --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllMealPrepsDatabaseRepositoryAdapter.java @@ -0,0 +1,162 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateAllMealPrepsHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateIngredientHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetIngredientByNameHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateAllMealPrepsRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Repository +public class CreateAllMealPrepsDatabaseRepositoryAdapter implements CreateAllMealPrepsRepository { + + private final CreateAllMealPrepsHibernateRepositoryAdapter createAllMealPrepsHibernateRepositoryAdapter; + + private final GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter; + private final CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter; + + public CreateAllMealPrepsDatabaseRepositoryAdapter( + CreateAllMealPrepsHibernateRepositoryAdapter createAllMealPrepsHibernateRepositoryAdapter, + GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter, + CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter + ) { + this.createAllMealPrepsHibernateRepositoryAdapter = createAllMealPrepsHibernateRepositoryAdapter; + this.getIngredientByNameHibernateRepositoryAdapter = getIngredientByNameHibernateRepositoryAdapter; + this.createIngredientHibernateRepositoryAdapter = createIngredientHibernateRepositoryAdapter; + } + + @Override + public List execute(List mealPreps) { + log.info("Saving {} meal preps in database", mealPreps.size()); + + List mealPrepsToSave = mealPreps.stream().map(this::buildMealPrepHibernateModel).toList(); + + List savedMealPreps = createAllMealPrepsHibernateRepositoryAdapter.saveAll(mealPrepsToSave); + + List mealPrepsResponse = savedMealPreps.stream().map(MealPrepHibernateModel::toDomain).toList(); + + log.info("Successfully saved {} meal preps ", mealPrepsResponse.size()); + + return mealPrepsResponse; + } + + private MealPrepHibernateModel buildMealPrepHibernateModel(MealPrep mealPrep) { + MealPrepHibernateModel mealPrepToSave = MealPrepHibernateModel.builder() + .title(mealPrep.getTitle()) + .estimatedCookingTime(mealPrep.getEstimatedCookingTime()) + .servings(mealPrep.getServings()) + .freeze(mealPrep.getFreeze()) + .recipes(mealPrep.getRecipes().stream().map(this::buildRecipeHibernateModel).toList()) + .build(); + + List stepsToSave = mealPrep.getSteps().stream() + .map(step -> buildMealPrepStepHibernateModel(mealPrepToSave, step)) + .toList(); + + mealPrepToSave.setSteps(stepsToSave); + + List ingredientsToSave = mealPrep.getIngredients().stream() + .map(ingredient -> buildMealPrepIngredientsHibernateModel(mealPrepToSave, ingredient)) + .toList(); + + mealPrepToSave.setIngredients(ingredientsToSave); + + return mealPrepToSave; + } + + private MealPrepStepsHibernateModel buildMealPrepStepHibernateModel(MealPrepHibernateModel mealPrep, Step step) { + return MealPrepStepsHibernateModel.builder() + .mealPrep(mealPrep) + .title(step.getTitle()) + .number(step.getNumber()) + .description(step.getDescription()) + .time(step.getTime()) + .imageName(step.getImageName()) + .build(); + } + + private MealPrepIngredientsHibernateModel buildMealPrepIngredientsHibernateModel(MealPrepHibernateModel mealPrep, Ingredient ingredient) { + Optional oSavedIngredient = getIngredientByNameHibernateRepositoryAdapter.findByName(ingredient.getName()); + IngredientHibernateModel savedIngredient = oSavedIngredient.orElseGet(() -> + createIngredientHibernateRepositoryAdapter.save(IngredientHibernateModel.fromDomain(ingredient)) + ); + + return MealPrepIngredientsHibernateModel.builder() + .mealPrep(mealPrep) + .ingredient(savedIngredient) + .quantity(ingredient.getQuantity()) + .build(); + } + + private RecipeHibernateModel buildRecipeHibernateModel(Recipe recipe) { + RecipeHibernateModel recipeHibernate = RecipeHibernateModel.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .imageUrl(recipe.getImage()) + .preparationTime(PreparationTimeHibernateModel.fromDomain(recipe.getPreparationTime())) + .cookLevel(CookLevelHibernateModel.fromDomain(recipe.getCookLevel())) + .diet(recipe.getDiet() != null ? DietHibernateModel.fromDomain(recipe.getDiet()) : null) + .mealTypes(recipe.getMealTypes().stream().map(MealTypeHibernateModel::fromDomain).toList()) + .allergies(recipe.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList()) + .dietaryNeeds(recipe.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList()) + .build(); + + List stepsHibernateModel = recipe.getSteps().stream() + .map(step -> buildRecipeStepHibernateModel(recipeHibernate, step)) + .toList(); + + recipeHibernate.setSteps(stepsHibernateModel); + + List recipeIngredientsToSave = recipe.getIngredients().stream() + .map(ingredient -> buildRecipeIngredientHibernateModel(recipeHibernate, ingredient)) + .toList(); + + recipeHibernate.setIngredients(recipeIngredientsToSave); + + return recipeHibernate; + } + + private RecipeStepsHibernateModel buildRecipeStepHibernateModel(RecipeHibernateModel savedRecipe, Step step) { + return RecipeStepsHibernateModel.builder() + .recipe(savedRecipe) + .number(step.getNumber()) + .title(step.getTitle()) + .description(step.getDescription()) + .imageName(step.getImageName()) + .build(); + } + + @NotNull + private RecipeIngredientsHibernateModel buildRecipeIngredientHibernateModel(RecipeHibernateModel savedRecipe, Ingredient ingredient) { + return RecipeIngredientsHibernateModel.builder() + .recipe(savedRecipe) + .ingredient(IngredientHibernateModel.fromDomain(ingredient)) + .quantity(ingredient.getQuantity()) + .optional(ingredient.getOptional()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllRecipesDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllRecipesDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..98386ad --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateAllRecipesDatabaseRepositoryAdapter.java @@ -0,0 +1,127 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateAllRecipesHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateIngredientHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.FindRecipeByNameHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetIngredientByNameHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateAllRecipesRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Repository +public class CreateAllRecipesDatabaseRepositoryAdapter implements CreateAllRecipesRepository { + + private final GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter; + private final FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter; + private final CreateAllRecipesHibernateRepositoryAdapter createAllRecipesHibernateRepositoryAdapter; + private final CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter; + + public CreateAllRecipesDatabaseRepositoryAdapter( + GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter, + FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter, + CreateAllRecipesHibernateRepositoryAdapter createAllRecipesHibernateRepositoryAdapter, + CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter + ) { + this.getIngredientByNameHibernateRepositoryAdapter = getIngredientByNameHibernateRepositoryAdapter; + this.findRecipeByNameHibernateRepositoryAdapter = findRecipeByNameHibernateRepositoryAdapter; + this.createAllRecipesHibernateRepositoryAdapter = createAllRecipesHibernateRepositoryAdapter; + this.createIngredientHibernateRepositoryAdapter = createIngredientHibernateRepositoryAdapter; + } + + @Override + public List execute(List recipes) { + log.info("Saving {} recipes and ingredients in database", recipes.size()); + + List recipesToSave = recipes.stream().map(this::buildRecipeHibernateModel).toList(); + + List savedRecipes = createAllRecipesHibernateRepositoryAdapter.saveAll(recipesToSave); + + List recipesResponse = savedRecipes.stream().map(RecipeHibernateModel::toDomain).toList(); + + log.info("Successfully saved {} recipes and ingredients ", recipesResponse.size()); + + return recipesResponse; + } + + private RecipeHibernateModel buildRecipeHibernateModel(Recipe recipe) { + + Optional existingRecipe = findRecipeByNameHibernateRepositoryAdapter.findByNameIgnoreCase(recipe.getName().trim()); + + if (existingRecipe.isPresent()) { + log.info("Recipe with name '{}' already exists with ID {}. Returning existing recipe.", recipe.getName(), existingRecipe.get().getId()); + return existingRecipe.get(); + } + + RecipeHibernateModel recipeHibernate = RecipeHibernateModel.builder() + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .imageUrl(recipe.getImage()) + .preparationTime(PreparationTimeHibernateModel.fromDomain(recipe.getPreparationTime())) + .cookLevel(CookLevelHibernateModel.fromDomain(recipe.getCookLevel())) + .diet(recipe.getDiet() != null ? DietHibernateModel.fromDomain(recipe.getDiet()) : null) + .mealTypes(recipe.getMealTypes().stream().map(MealTypeHibernateModel::fromDomain).toList()) + .allergies(recipe.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList()) + .dietaryNeeds(recipe.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList()) + .build(); + + List stepsHibernateModel = recipe.getSteps().stream() + .map(step -> buildRecipeStepHibernateModel(recipeHibernate, step)) + .toList(); + + recipeHibernate.setSteps(stepsHibernateModel); + + List recipeIngredientsToSave = recipe.getIngredients().stream() + .map(ingredient -> buildRecipeIngredientHibernateModel(recipeHibernate, ingredient)) + .toList(); + + recipeHibernate.setIngredients(recipeIngredientsToSave); + + return recipeHibernate; + } + + private RecipeStepsHibernateModel buildRecipeStepHibernateModel(RecipeHibernateModel savedRecipe, Step step) { + return RecipeStepsHibernateModel.builder() + .recipe(savedRecipe) + .number(step.getNumber()) + .title(step.getTitle()) + .description(step.getDescription()) + .imageName(step.getImageName()) + .build(); + } + + @NotNull + private RecipeIngredientsHibernateModel buildRecipeIngredientHibernateModel(RecipeHibernateModel savedRecipe, Ingredient ingredient) { + + Optional oSavedIngredient = getIngredientByNameHibernateRepositoryAdapter.findByName(ingredient.getName()); + + IngredientHibernateModel savedIngredient = oSavedIngredient.orElseGet(() -> + createIngredientHibernateRepositoryAdapter.save(IngredientHibernateModel.fromDomain(ingredient)) + ); + + return RecipeIngredientsHibernateModel.builder() + .recipe(savedRecipe) + .ingredient(savedIngredient) + .quantity(ingredient.getQuantity()) + .optional(ingredient.getOptional()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..f26e946 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeDatabaseRepositoryAdapter.java @@ -0,0 +1,135 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateIngredientHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateRecipeHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateRecipeIngredientsHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.FindRecipeByNameHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetIngredientByNameHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateRecipeRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Repository +@Transactional +public class CreateRecipeDatabaseRepositoryAdapter implements CreateRecipeRepository { + + private final GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter; + private final CreateRecipeHibernateRepositoryAdapter createRecipeHibernateRepositoryAdapter; + private final CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter; + private final CreateRecipeIngredientsHibernateRepositoryAdapter createRecipeIngredientsHibernateRepositoryAdapter; + private final FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter; + + public CreateRecipeDatabaseRepositoryAdapter( + GetIngredientByNameHibernateRepositoryAdapter getIngredientByNameHibernateRepositoryAdapter, + CreateRecipeHibernateRepositoryAdapter createRecipeHibernateRepositoryAdapter, + CreateIngredientHibernateRepositoryAdapter createIngredientHibernateRepositoryAdapter, + CreateRecipeIngredientsHibernateRepositoryAdapter createRecipeIngredientsHibernateRepositoryAdapter, + FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter + ) { + this.getIngredientByNameHibernateRepositoryAdapter = getIngredientByNameHibernateRepositoryAdapter; + this.createRecipeHibernateRepositoryAdapter = createRecipeHibernateRepositoryAdapter; + this.createIngredientHibernateRepositoryAdapter = createIngredientHibernateRepositoryAdapter; + this.createRecipeIngredientsHibernateRepositoryAdapter = createRecipeIngredientsHibernateRepositoryAdapter; + this.findRecipeByNameHibernateRepositoryAdapter = findRecipeByNameHibernateRepositoryAdapter; + } + + @Override + public Recipe execute(Recipe recipe) { + log.info("Saving recipe and ingredients in database: {}", recipe); + + RecipeHibernateModel savedRecipe = createRecipeHibernateRepositoryAdapter.save(buildRecipeHibernateModel(recipe)); + + List recipeIngredientsHibernateModels = recipe.getIngredients().stream().map(ingredient -> buildRecipeIngredientHibernateModel(savedRecipe, ingredient)).toList(); + List savedRecipeIngredients = createRecipeIngredientsHibernateRepositoryAdapter.saveAll(recipeIngredientsHibernateModels); + savedRecipe.setIngredients(savedRecipeIngredients); + + Recipe recipeResponse = savedRecipe.toDomain(); + + log.info("Successfully saved recipe and ingredients with ID {}", recipeResponse.getId()); + + return recipeResponse; + } + + private RecipeHibernateModel buildRecipeHibernateModel(Recipe recipe) { + + Optional existingRecipe = findRecipeByNameHibernateRepositoryAdapter.findByNameIgnoreCase(recipe.getName().trim()); + + if (existingRecipe.isPresent()) { + log.info("Recipe with name '{}' already exists with ID {}. Returning existing recipe.", recipe.getName(), existingRecipe.get().getId()); + return existingRecipe.get(); + } + + RecipeHibernateModel recipeHibernate = RecipeHibernateModel.builder() + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .imageUrl(recipe.getImage()) + .preparationTime(PreparationTimeHibernateModel.fromDomain(recipe.getPreparationTime())) + .cookLevel(CookLevelHibernateModel.fromDomain(recipe.getCookLevel())) + .diet(recipe.getDiet() != null ? DietHibernateModel.fromDomain(recipe.getDiet()) : null) + .mealTypes(recipe.getMealTypes().stream().map(MealTypeHibernateModel::fromDomain).toList()) + .allergies(recipe.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList()) + .dietaryNeeds(recipe.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList()) + .build(); + + List stepsHibernateModel = recipe.getSteps().stream() + .map(step -> buildRecipeStepHibernateModel(recipeHibernate, step)) + .toList(); + + recipeHibernate.setSteps(stepsHibernateModel); + + List recipeIngredientsToSave = recipe.getIngredients().stream() + .map(ingredient -> buildRecipeIngredientHibernateModel(recipeHibernate, ingredient)) + .toList(); + + recipeHibernate.setIngredients(recipeIngredientsToSave); + + return recipeHibernate; + } + + private RecipeStepsHibernateModel buildRecipeStepHibernateModel(RecipeHibernateModel savedRecipe, Step step) { + return RecipeStepsHibernateModel.builder() + .recipe(savedRecipe) + .number(step.getNumber()) + .title(step.getTitle()) + .description(step.getDescription()) + .imageName(step.getImageName()) + .build(); + } + + @NotNull + private RecipeIngredientsHibernateModel buildRecipeIngredientHibernateModel(RecipeHibernateModel savedRecipe, Ingredient ingredient) { + + Optional oSavedIngredient = getIngredientByNameHibernateRepositoryAdapter.findByName(ingredient.getName()); + + IngredientHibernateModel savedIngredient = oSavedIngredient.orElseGet(() -> + createIngredientHibernateRepositoryAdapter.save(IngredientHibernateModel.fromDomain(ingredient)) + ); + + return RecipeIngredientsHibernateModel.builder() + .recipe(savedRecipe) + .ingredient(savedIngredient) + .quantity(ingredient.getQuantity()) + .optional(ingredient.getOptional()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeImagesDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeImagesDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..9b4d996 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateRecipeImagesDatabaseRepositoryAdapter.java @@ -0,0 +1,53 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateRecipeImagesHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateRecipeImagesRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class CreateRecipeImagesDatabaseRepositoryAdapter implements CreateRecipeImagesRepository { + + private final CreateRecipeImagesHibernateRepositoryAdapter createRecipeImagesHibernateRepositoryAdapter; + + public CreateRecipeImagesDatabaseRepositoryAdapter(CreateRecipeImagesHibernateRepositoryAdapter createRecipeImagesHibernateRepositoryAdapter) { + this.createRecipeImagesHibernateRepositoryAdapter = createRecipeImagesHibernateRepositoryAdapter; + } + + @Override + public List execute(Recipe recipe) { + log.info("Executing recipe images creation in database for recipe with ID {} and with {} images", recipe.getId(), recipe.getImages().size()); + + RecipeHibernateModel recipeHibernateModel = buildRecipeHibernateModel(recipe); + + List recipeImages = recipe.getImages().stream() + .map(recipeImage -> buildRecipeImagesHibernateModel(recipeHibernateModel, recipeImage)) + .toList(); + + List savedImages = createRecipeImagesHibernateRepositoryAdapter.saveAll(recipeImages); + + log.info("Successfully saved recipe images"); + + return savedImages.stream().map(RecipeStepsHibernateModel::toDomain).toList(); + } + + private RecipeHibernateModel buildRecipeHibernateModel(Recipe recipe) { + return RecipeHibernateModel.builder() + .id(recipe.getId()) + .build(); + } + + private RecipeStepsHibernateModel buildRecipeImagesHibernateModel(RecipeHibernateModel recipe, Step step) { + return RecipeStepsHibernateModel.builder() + .recipe(recipe) + .imageName(step.getImageName()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserCalendarDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserCalendarDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..da50abd --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserCalendarDatabaseRepositoryAdapter.java @@ -0,0 +1,78 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserCalendarRecipesHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateAllUserRecipeCalendarsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateUserCalendarRepository; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.CalendarRecipe; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserCalendar; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class CreateUserCalendarDatabaseRepositoryAdapter implements CreateUserCalendarRepository { + + private final CreateAllUserRecipeCalendarsHibernateRepositoryAdapter createAllUserRecipeCalendarsHibernateRepositoryAdapter; + + @Override + public void execute(UserCalendar userRecipeCalendars) { + log.info("Executing create calendar for user with id {} in database", userRecipeCalendars.getUser().getId()); + + UserHibernateModel user = buildUserModel(userRecipeCalendars.getUser()); + + List userCalendars = userRecipeCalendars.getCalendars().stream() + .map(calendar -> buildUserRecipeCalendarModel(user, calendar)) + .toList(); + + createAllUserRecipeCalendarsHibernateRepositoryAdapter.saveAll(userCalendars); + } + + private UserCalendarsHibernateModel buildUserRecipeCalendarModel(UserHibernateModel user, Calendar calendar) { + UserCalendarsHibernateModel userCalendar = UserCalendarsHibernateModel.builder() + .user(user) + .plannedDate(calendar.getDate()) + .build(); + + userCalendar.setRecipes(calendar.getRecipes().stream().map(recipe -> buildCalendarRecipeModel(userCalendar, recipe)).toList()); + + return userCalendar; + } + + private UserCalendarRecipesHibernateModel buildCalendarRecipeModel(UserCalendarsHibernateModel calendar, CalendarRecipe calendarRecipe) { + return UserCalendarRecipesHibernateModel.builder() + .calendar(calendar) + .recipe(buildRecipe(calendarRecipe.getRecipe())) + .mealType(buildMealTypeModel(calendarRecipe.getMealType())) + .build(); + } + + private MealTypeHibernateModel buildMealTypeModel(MealType mealType) { + return MealTypeHibernateModel.builder() + .id(mealType.getId()) + .build(); + } + + private UserHibernateModel buildUserModel(User user) { + return UserHibernateModel.builder() + .id(user.getId()) + .build(); + } + + private RecipeHibernateModel buildRecipe(Recipe recipe) { + return RecipeHibernateModel.builder() + .id(recipe.getId()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapter.java index 26faa36..9b6df84 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapter.java @@ -5,25 +5,17 @@ import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; -import com.cuoco.adapter.out.hibernate.model.UserAllergiesHibernateModel; -import com.cuoco.adapter.out.hibernate.model.UserDietaryNeedsHibernateModel; import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.CreateUserAllergiesHibernateRepositoryAdapter; -import com.cuoco.adapter.out.hibernate.repository.CreateUserDietaryNeedsHibernateRepositoryAdapter; import com.cuoco.adapter.out.hibernate.repository.CreateUserHibernateRepositoryAdapter; import com.cuoco.adapter.out.hibernate.repository.CreateUserPreferencesHibernateRepositoryAdapter; import com.cuoco.application.port.out.CreateUserRepository; -import com.cuoco.application.usecase.model.Allergy; -import com.cuoco.application.usecase.model.DietaryNeed; import com.cuoco.application.usecase.model.User; import com.cuoco.application.usecase.model.UserPreferences; import jakarta.transaction.Transactional; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; @Repository @Transactional @@ -31,19 +23,13 @@ public class CreateUserDatabaseRepositoryAdapter implements CreateUserRepository private final CreateUserHibernateRepositoryAdapter createUserHibernateRepositoryAdapter; private final CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter; - private final CreateUserDietaryNeedsHibernateRepositoryAdapter createUserDietaryNeedsHibernateRepositoryAdapter; - private final CreateUserAllergiesHibernateRepositoryAdapter createUserAllergiesHibernateRepositoryAdapter; public CreateUserDatabaseRepositoryAdapter( CreateUserHibernateRepositoryAdapter createUserHibernateRepositoryAdapter, - CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter, - CreateUserDietaryNeedsHibernateRepositoryAdapter createUserDietaryNeedsHibernateRepositoryAdapter, - CreateUserAllergiesHibernateRepositoryAdapter createUserAllergiesHibernateRepositoryAdapter + CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter ) { this.createUserHibernateRepositoryAdapter = createUserHibernateRepositoryAdapter; this.createUserPreferencesHibernateRepositoryAdapter = createUserPreferencesHibernateRepositoryAdapter; - this.createUserDietaryNeedsHibernateRepositoryAdapter = createUserDietaryNeedsHibernateRepositoryAdapter; - this.createUserAllergiesHibernateRepositoryAdapter = createUserAllergiesHibernateRepositoryAdapter; } @Override @@ -54,10 +40,6 @@ public User execute(User user) { UserHibernateModel savedUser = createUserHibernateRepositoryAdapter.save(userToSave); UserPreferencesHibernateModel savedPreferences = savePreferences(user, savedUser); - - saveDietaryNeeds(user, savedUser); - - saveAllergies(user, savedUser); User userResponse = savedUser.toDomain(); UserPreferences preferences = savedPreferences.toDomain(); @@ -74,39 +56,18 @@ private UserPreferencesHibernateModel savePreferences(User user, UserHibernateMo return createUserPreferencesHibernateRepositoryAdapter.save(preferences); } - private void saveAllergies(User user, UserHibernateModel savedUser) { - List allergies = user.getAllergies() - .stream() - .map(allergy -> buildUserAllergies(savedUser, allergy)) - .toList(); - - createUserAllergiesHibernateRepositoryAdapter.saveAll(allergies); - } - - private void saveDietaryNeeds(User user, UserHibernateModel savedUser) { - List dietaryNeeds = user.getDietaryNeeds() - .stream() - .map(dietaryNeed -> buildUserDietaryNeedsHibernateModel(savedUser, dietaryNeed)) - .toList(); - - createUserDietaryNeedsHibernateRepositoryAdapter.saveAll(dietaryNeeds); - } - private UserHibernateModel buildHibernateUser(User user) { - return new UserHibernateModel( - null, - user.getName(), - user.getEmail(), - user.getPassword(), - new PlanHibernateModel( - user.getPlan().getId(), - user.getPlan().getDescription() - ), - user.getActive(), - LocalDateTime.now(), - LocalDateTime.now(), - null - ); + return UserHibernateModel.builder() + .name(user.getName()) + .email(user.getEmail()) + .password(user.getPassword()) + .plan(PlanHibernateModel.fromDomain(user.getPlan())) + .active(user.getActive()) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .dietaryNeeds(user.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList()) + .allergies(user.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList()) + .build(); } private UserPreferencesHibernateModel buildUserPreferences(User user, UserHibernateModel savedUser) { @@ -123,30 +84,4 @@ private UserPreferencesHibernateModel buildUserPreferences(User user, UserHibern ) ); } - - private UserDietaryNeedsHibernateModel buildUserDietaryNeedsHibernateModel(UserHibernateModel user, DietaryNeed dietaryNeed) { - return UserDietaryNeedsHibernateModel.builder() - .user(user) - .dietaryNeed(buildDietaryNeedHibernateModel(dietaryNeed)) - .build(); - } - - private DietaryNeedHibernateModel buildDietaryNeedHibernateModel(DietaryNeed dietaryNeed) { - return DietaryNeedHibernateModel.builder() - .description(dietaryNeed.getDescription()) - .build(); - } - - private UserAllergiesHibernateModel buildUserAllergies(UserHibernateModel user, Allergy allergy) { - return UserAllergiesHibernateModel.builder() - .user(user) - .allergy(buildAllergy(allergy)) - .build(); - } - - private AllergyHibernateModel buildAllergy(Allergy allergy) { - return AllergyHibernateModel.builder() - .description(allergy.getDescription()) - .build(); - } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..993b46d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapter.java @@ -0,0 +1,35 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserMealPrepHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateUserMealPrepRepository; +import com.cuoco.application.usecase.model.UserMealPrep; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class CreateUserMealPrepDatabaseRepositoryAdapter implements CreateUserMealPrepRepository { + + private final CreateUserMealPrepHibernateRepositoryAdapter createUserMealPrepHibernateRepositoryAdapter; + + public CreateUserMealPrepDatabaseRepositoryAdapter(CreateUserMealPrepHibernateRepositoryAdapter createUserMealPrepHibernateRepositoryAdapter) { + this.createUserMealPrepHibernateRepositoryAdapter = createUserMealPrepHibernateRepositoryAdapter; + } + + @Override + public void execute(UserMealPrep userMealPrep) { + log.info("Executing create user meal prep in database with body {}", userMealPrep); + createUserMealPrepHibernateRepositoryAdapter.save(buildUserMealPrepModel(userMealPrep)); + } + + private UserMealPrepHibernateModel buildUserMealPrepModel(UserMealPrep userMealPrep) { + return UserMealPrepHibernateModel + .builder() + .user(UserHibernateModel.builder().id(userMealPrep.getUser().getId()).build()) + .mealPrep(MealPrepHibernateModel.builder().id(userMealPrep.getMealPrep().getId()).build()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserPaymentDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserPaymentDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..a7bb771 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserPaymentDatabaseRepositoryAdapter.java @@ -0,0 +1,37 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserPaymentHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateUserPaymentRepository; +import com.cuoco.application.usecase.model.UserPayment; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@Qualifier("repository") +@RequiredArgsConstructor +public class CreateUserPaymentDatabaseRepositoryAdapter implements CreateUserPaymentRepository { + + private final CreateUserPaymentHibernateRepositoryAdapter createUserPaymentHibernateRepositoryAdapter; + + @Override + public UserPayment execute(UserPayment userPayment) { + log.info("Executing create user payment in database"); + + UserPaymentsHibernateModel userPaymentToSave = UserPaymentsHibernateModel.fromDomain(userPayment); + + UserPaymentsHibernateModel savedUserPayment = createUserPaymentHibernateRepositoryAdapter.save(userPaymentToSave); + + log.info("Saved user payment with ID {}", savedUserPayment.getId()); + + UserPayment response = savedUserPayment.toDomain(); + + response.setUser(userPayment.getUser()); + + return response; + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..201e8f6 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapter.java @@ -0,0 +1,35 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserRecipeHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateUserRecipeRepository; +import com.cuoco.application.usecase.model.UserRecipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class CreateUserRecipeDatabaseRepositoryAdapter implements CreateUserRecipeRepository { + + private final CreateUserRecipeHibernateRepositoryAdapter createUserRecipeHibernateRepositoryAdapter; + + public CreateUserRecipeDatabaseRepositoryAdapter(CreateUserRecipeHibernateRepositoryAdapter createUserRecipeHibernateRepositoryAdapter) { + this.createUserRecipeHibernateRepositoryAdapter = createUserRecipeHibernateRepositoryAdapter; + } + + @Override + public void execute(UserRecipe userRecipe) { + log.info("Executing create user recipe in database with body {}", userRecipe); + createUserRecipeHibernateRepositoryAdapter.save(buildUserRecipeModel(userRecipe)); + } + + private UserRecipesHibernateModel buildUserRecipeModel(UserRecipe userRecipe) { + return UserRecipesHibernateModel + .builder() + .user(UserHibernateModel.builder().id(userRecipe.getUser().getId()).build()) + .recipe(RecipeHibernateModel.builder().id(userRecipe.getRecipe().getId()).build()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserCalendarDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserCalendarDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..7ba3262 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserCalendarDatabaseRepositoryAdapter.java @@ -0,0 +1,57 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserCalendarRecipesHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.DeleteUserCalendarRepository; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.CalendarRecipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserCalendar; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@Repository +@Transactional +@RequiredArgsConstructor +public class DeleteUserCalendarDatabaseRepositoryAdapter implements DeleteUserCalendarRepository { + + private final DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter deleteAllUserRecipeCalendarsHibernateRepositoryAdapter; + + @Override + public void execute(UserCalendar userCalendar) { + List userCalendars = userCalendar.getCalendars().stream() + .map(calendar -> buildUserRecipeCalendarModel(userCalendar.getUser(), calendar)) + .toList(); + + deleteAllUserRecipeCalendarsHibernateRepositoryAdapter.deleteAll(userCalendars); + } + + private UserCalendarsHibernateModel buildUserRecipeCalendarModel(User user, Calendar calendar) { + UserCalendarsHibernateModel userCalendar = UserCalendarsHibernateModel.builder() + .id(calendar.getId()) + .user(UserHibernateModel.builder().id(user.getId()).build()) + .plannedDate(calendar.getDate()) + .build(); + + userCalendar.setRecipes(calendar.getRecipes().stream().map(recipe -> buildCalendarRecipeModel(userCalendar, recipe)).toList()); + + return userCalendar; + } + + private UserCalendarRecipesHibernateModel buildCalendarRecipeModel(UserCalendarsHibernateModel calendar, CalendarRecipe calendarRecipe) { + return UserCalendarRecipesHibernateModel.builder() + .calendar(calendar) + .recipe(RecipeHibernateModel.builder().id(calendarRecipe.getRecipe().getId()).build()) + .mealType(MealTypeHibernateModel.builder().id(calendarRecipe.getMealType().getId()).build()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..a77d761 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapter.java @@ -0,0 +1,25 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.DeleteUserMealPrepsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.DeleteUserMealPrepRepository; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@Transactional +public class DeleteUserMealPrepDatabaseRepositoryAdapter implements DeleteUserMealPrepRepository { + + private final DeleteUserMealPrepsHibernateRepositoryAdapter deleteUserMealPrepsHibernateRepositoryAdapter; + + public DeleteUserMealPrepDatabaseRepositoryAdapter(DeleteUserMealPrepsHibernateRepositoryAdapter deleteUserMealPrepsHibernateRepositoryAdapter) { + this.deleteUserMealPrepsHibernateRepositoryAdapter = deleteUserMealPrepsHibernateRepositoryAdapter; + } + + @Override + public void execute(Long userId, Long mealPrepId) { + log.info("Executing delete meal prep from user database repository with user ID {} and meal prep ID {}", userId, mealPrepId); + deleteUserMealPrepsHibernateRepositoryAdapter.deleteAllByUserIdAndMealPrepId(userId, mealPrepId); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..67c08e6 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapter.java @@ -0,0 +1,25 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.DeleteUserRecipeHibernateRepositoryAdapter; +import com.cuoco.application.port.out.DeleteUserRecipeRepository; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@Transactional +public class DeleteUserRecipeDatabaseRepositoryAdapter implements DeleteUserRecipeRepository { + + private final DeleteUserRecipeHibernateRepositoryAdapter deleteUserRecipeHibernateRepositoryAdapter; + + public DeleteUserRecipeDatabaseRepositoryAdapter(DeleteUserRecipeHibernateRepositoryAdapter deleteUserRecipeHibernateRepositoryAdapter) { + this.deleteUserRecipeHibernateRepositoryAdapter = deleteUserRecipeHibernateRepositoryAdapter; + } + + @Override + public void execute(Long userId, Long recipeId) { + log.info("Executing delete recipe from user database repository with user ID {} and recipe ID {}", userId, recipeId); + deleteUserRecipeHibernateRepositoryAdapter.deleteAllByUserIdAndRecipeId(userId, recipeId); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserByEmailDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserByEmailDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..be98e10 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserByEmailDatabaseRepositoryAdapter.java @@ -0,0 +1,21 @@ +package com.cuoco.adapter.out.hibernate; + + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserByEmailHibernateRepositoryAdapter; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import org.springframework.stereotype.Repository; + +@Repository +public class ExistsUserByEmailDatabaseRepositoryAdapter implements ExistsUserByEmailRepository { + + private final ExistsUserByEmailHibernateRepositoryAdapter existsUserByEmailHibernateRepositoryAdapter; + + public ExistsUserByEmailDatabaseRepositoryAdapter(ExistsUserByEmailHibernateRepositoryAdapter existsUserByEmailHibernateRepositoryAdapter) { + this.existsUserByEmailHibernateRepositoryAdapter = existsUserByEmailHibernateRepositoryAdapter; + } + + @Override + public Boolean execute(String email) { + return existsUserByEmailHibernateRepositoryAdapter.existsByEmail(email); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserMealPrepDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserMealPrepDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..ca842a5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserMealPrepDatabaseRepositoryAdapter.java @@ -0,0 +1,23 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.ExistsUserMealPrepRepository; +import com.cuoco.application.usecase.model.UserMealPrep; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class ExistsUserMealPrepDatabaseRepositoryAdapter implements ExistsUserMealPrepRepository { + + private final ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter existsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter; + + public ExistsUserMealPrepDatabaseRepositoryAdapter(ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter existsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter) { + this.existsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter = existsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter; + } + + @Override + public boolean execute(UserMealPrep userMealPrep) { + return existsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter.existsByUserIdAndMealPrepId(userMealPrep.getUser().getId(), userMealPrep.getMealPrep().getId()); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeCalendarDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeCalendarDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..93403ad --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeCalendarDatabaseRepositoryAdapter.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserRecipeCalendarHibernateRepositoryAdapter; +import com.cuoco.application.port.out.ExistsUserRecipeCalendarRepository; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class ExistsUserRecipeCalendarDatabaseRepositoryAdapter implements ExistsUserRecipeCalendarRepository { + + ExistsUserRecipeCalendarHibernateRepositoryAdapter existsUserRecipeCalendarHibernateRepositoryAdapter; + + public ExistsUserRecipeCalendarDatabaseRepositoryAdapter( + ExistsUserRecipeCalendarHibernateRepositoryAdapter existsUserRecipeCalendarHibernateRepositoryAdapter + ) { + this.existsUserRecipeCalendarHibernateRepositoryAdapter = existsUserRecipeCalendarHibernateRepositoryAdapter; + } + + @Override + public Boolean execute(User user, Calendar calendar, Recipe recipe) { + return existsUserRecipeCalendarHibernateRepositoryAdapter.existsByUserIdAndPlannedDateAndRecipesRecipeId( + user.getId(), + calendar.getDate(), + recipe.getId() + ); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..6570656 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeDatabaseRepositoryAdapter.java @@ -0,0 +1,24 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.ExistsUserRecipeByUserIdAndRecipeIdRepository; +import com.cuoco.application.usecase.model.UserRecipe; +import org.springframework.stereotype.Repository; + +@Repository +public class ExistsUserRecipeDatabaseRepositoryAdapter implements ExistsUserRecipeByUserIdAndRecipeIdRepository { + + private final ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter existsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter; + + public ExistsUserRecipeDatabaseRepositoryAdapter( + ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter existsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter + ) { + this.existsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter = existsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter; + } + + @Override + public boolean execute(UserRecipe userRecipe) { + return existsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter.existsByUserIdAndRecipeId(userRecipe.getUser().getId(), userRecipe.getRecipe().getId()); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipeByNameDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipeByNameDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..561d371 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipeByNameDatabaseRepositoryAdapter.java @@ -0,0 +1,36 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.FindRecipeByNameHibernateRepositoryAdapter; +import com.cuoco.application.port.out.FindRecipeByNameRepository; +import com.cuoco.application.usecase.model.Recipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Slf4j +@Repository +public class FindRecipeByNameDatabaseRepositoryAdapter implements FindRecipeByNameRepository { + + private final FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter; + + public FindRecipeByNameDatabaseRepositoryAdapter(FindRecipeByNameHibernateRepositoryAdapter findRecipeByNameHibernateRepositoryAdapter) { + this.findRecipeByNameHibernateRepositoryAdapter = findRecipeByNameHibernateRepositoryAdapter; + } + + @Override + public Recipe execute(String recipeName) { + log.info("Searching recipe in database by name: {}", recipeName); + + Optional recipeModel = findRecipeByNameHibernateRepositoryAdapter.findByNameIgnoreCase(recipeName.trim()); + + if (recipeModel.isPresent()) { + log.info("Recipe found in database: {}", recipeModel.get().getName()); + return recipeModel.get().toDomain(); + } + + log.info("Cannot find recipe in database with name: {}", recipeName); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipesByFiltersDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipesByFiltersDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..11bf4e0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/FindRecipesByFiltersDatabaseRepositoryAdapter.java @@ -0,0 +1,61 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.FindRecipesByFiltersHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetRecipesMaxIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.FindRecipesByFiltersRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.SearchFilters; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +@Slf4j +@Repository +public class FindRecipesByFiltersDatabaseRepositoryAdapter implements FindRecipesByFiltersRepository { + + private final GetRecipesMaxIdHibernateRepositoryAdapter getRecipesMaxIdHibernateRepositoryAdapter; + private final FindRecipesByFiltersHibernateRepositoryAdapter findRecipesByFiltersHibernateRepositoryAdapter; + + public FindRecipesByFiltersDatabaseRepositoryAdapter( + GetRecipesMaxIdHibernateRepositoryAdapter getRecipesMaxIdHibernateRepositoryAdapter, + FindRecipesByFiltersHibernateRepositoryAdapter findRecipesByFiltersHibernateRepositoryAdapter + ) { + this.getRecipesMaxIdHibernateRepositoryAdapter = getRecipesMaxIdHibernateRepositoryAdapter; + this.findRecipesByFiltersHibernateRepositoryAdapter = findRecipesByFiltersHibernateRepositoryAdapter; + } + + @Override + public List execute(SearchFilters filters) { + log.info("Executing find recipes in database by filters: {}", filters); + + List ids = filters.getRandom() ? generateRandomIds(filters.getSize()) : List.of(-1L); + + List savedRecipes = findRecipesByFiltersHibernateRepositoryAdapter.execute(ids); + + List recipesResponse = savedRecipes.stream() + .map(RecipeHibernateModel::toDomain) + .toList(); + + log.info("Successfully retrieved {} recipes from filters", recipesResponse.size()); + return recipesResponse; + } + + private List generateRandomIds(Integer size) { + Long maxId = getRecipesMaxIdHibernateRepositoryAdapter.execute(); + Set ids = new HashSet<>(); + Random random = new Random(); + + while (ids.size() < size) { + long id = 1 + random.nextLong(maxId); + ids.add(id); + } + + return new ArrayList<>(ids); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapter.java similarity index 52% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapter.java index 7d4391a..64620f4 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapter.java @@ -1,31 +1,29 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetAllAllergiesHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetAllAllergiesHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllAllergiesRepository; import com.cuoco.application.usecase.model.Allergy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; +@Slf4j @Repository -public class GetAllAllergiesDatabaseRepository implements GetAllAllergiesRepository { +public class GetAllAllergiesDatabaseRepositoryAdapter implements GetAllAllergiesRepository { - static final Logger log = LoggerFactory.getLogger(GetAllAllergiesDatabaseRepository.class); + private final GetAllAllergiesHibernateRepositoryAdapter getAllAllergiesHibernateRepositoryAdapter; - private final GetAllAllergiesHibernateRepository getAllAllergiesHibernateRepository; - - public GetAllAllergiesDatabaseRepository(GetAllAllergiesHibernateRepository getAllAllergiesHibernateRepository) { - this.getAllAllergiesHibernateRepository = getAllAllergiesHibernateRepository; + public GetAllAllergiesDatabaseRepositoryAdapter(GetAllAllergiesHibernateRepositoryAdapter getAllAllergiesHibernateRepositoryAdapter) { + this.getAllAllergiesHibernateRepositoryAdapter = getAllAllergiesHibernateRepositoryAdapter; } @Override public List execute() { log.info("Get all allergies from database"); - List response = getAllAllergiesHibernateRepository.findAll(); + List response = getAllAllergiesHibernateRepositoryAdapter.findAll(); return response.stream().map(AllergyHibernateModel::toDomain).toList(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapter.java similarity index 52% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapter.java index f0237e4..510dd86 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapter.java @@ -1,31 +1,29 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetAllCookLevelsHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetAllCookLevelsHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllCookLevelsRepository; import com.cuoco.application.usecase.model.CookLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; +@Slf4j @Repository -public class GetAllCookLevelsDatabaseRepository implements GetAllCookLevelsRepository { +public class GetAllCookLevelsDatabaseRepositoryAdapter implements GetAllCookLevelsRepository { - static final Logger log = LoggerFactory.getLogger(GetAllCookLevelsDatabaseRepository.class); + private final GetAllCookLevelsHibernateRepositoryAdapter getAllCookLevelsHibernateRepositoryAdapter; - private final GetAllCookLevelsHibernateRepository getAllCookLevelsHibernateRepository; - - public GetAllCookLevelsDatabaseRepository(GetAllCookLevelsHibernateRepository getAllCookLevelsHibernateRepository) { - this.getAllCookLevelsHibernateRepository = getAllCookLevelsHibernateRepository; + public GetAllCookLevelsDatabaseRepositoryAdapter(GetAllCookLevelsHibernateRepositoryAdapter getAllCookLevelsHibernateRepositoryAdapter) { + this.getAllCookLevelsHibernateRepositoryAdapter = getAllCookLevelsHibernateRepositoryAdapter; } @Override public List execute() { log.info("Get all cook levels from database"); - List response = getAllCookLevelsHibernateRepository.findAll(); + List response = getAllCookLevelsHibernateRepositoryAdapter.findAll(); return response.stream().map(CookLevelHibernateModel::toDomain).toList(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapter.java similarity index 51% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapter.java index 05d4c10..edae60b 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapter.java @@ -1,31 +1,29 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetAllDietaryNeedsHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetAllDietaryNeedsHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllDietaryNeedsRepository; import com.cuoco.application.usecase.model.DietaryNeed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; +@Slf4j @Repository -public class GetAllDietaryNeedsDatabaseRepository implements GetAllDietaryNeedsRepository { +public class GetAllDietaryNeedsDatabaseRepositoryAdapter implements GetAllDietaryNeedsRepository { - static final Logger log = LoggerFactory.getLogger(GetAllDietaryNeedsDatabaseRepository.class); + private final GetAllDietaryNeedsHibernateRepositoryAdapter getAllDietaryNeedsHibernateRepositoryAdapter; - private final GetAllDietaryNeedsHibernateRepository getAllDietaryNeedsHibernateRepository; - - public GetAllDietaryNeedsDatabaseRepository(GetAllDietaryNeedsHibernateRepository getAllDietaryNeedsHibernateRepository) { - this.getAllDietaryNeedsHibernateRepository = getAllDietaryNeedsHibernateRepository; + public GetAllDietaryNeedsDatabaseRepositoryAdapter(GetAllDietaryNeedsHibernateRepositoryAdapter getAllDietaryNeedsHibernateRepositoryAdapter) { + this.getAllDietaryNeedsHibernateRepositoryAdapter = getAllDietaryNeedsHibernateRepositoryAdapter; } @Override public List execute() { log.info("Get all dietary needs from database"); - List response = getAllDietaryNeedsHibernateRepository.findAll(); + List response = getAllDietaryNeedsHibernateRepositoryAdapter.findAll(); return response.stream().map(DietaryNeedHibernateModel::toDomain).toList(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapter.java similarity index 53% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapter.java index ee537bf..f708e32 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapter.java @@ -1,31 +1,29 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetAllDietsHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetAllDietsHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllDietsRepository; import com.cuoco.application.usecase.model.Diet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; +@Slf4j @Repository -public class GetAllDietsDatabaseRepository implements GetAllDietsRepository { +public class GetAllDietsDatabaseRepositoryAdapter implements GetAllDietsRepository { - static final Logger log = LoggerFactory.getLogger(GetAllDietsDatabaseRepository.class); + private final GetAllDietsHibernateRepositoryAdapter getAllDietsHibernateRepositoryAdapter; - private final GetAllDietsHibernateRepository getAllDietsHibernateRepository; - - public GetAllDietsDatabaseRepository(GetAllDietsHibernateRepository getAllDietsHibernateRepository) { - this.getAllDietsHibernateRepository = getAllDietsHibernateRepository; + public GetAllDietsDatabaseRepositoryAdapter(GetAllDietsHibernateRepositoryAdapter getAllDietsHibernateRepositoryAdapter) { + this.getAllDietsHibernateRepositoryAdapter = getAllDietsHibernateRepositoryAdapter; } @Override public List execute() { log.info("Get all diets from database"); - List response = getAllDietsHibernateRepository.findAll(); + List response = getAllDietsHibernateRepositoryAdapter.findAll(); return response.stream().map(DietHibernateModel::toDomain).toList(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealPrepsByIdsDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealPrepsByIdsDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..6444d80 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealPrepsByIdsDatabaseRepositoryAdapter.java @@ -0,0 +1,27 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllMealPrepsByIdsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllMealPrepsByIdsRepository; +import com.cuoco.application.usecase.model.MealPrep; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class GetAllMealPrepsByIdsDatabaseRepositoryAdapter implements GetAllMealPrepsByIdsRepository { + + private final GetAllMealPrepsByIdsHibernateRepositoryAdapter getAllMealPrepsByIdsHibernateRepositoryAdapter; + + @Override + public List execute(List ids) { + log.info("Get all meal preps by ids: {}", ids); + + List recipes = getAllMealPrepsByIdsHibernateRepositoryAdapter.findAllById(ids); + return recipes.stream().map(MealPrepHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..a437df9 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapter.java @@ -0,0 +1,30 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllMealTypesHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllMealTypesRepository; +import com.cuoco.application.usecase.model.MealType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllMealTypesDatabaseRepositoryAdapter implements GetAllMealTypesRepository { + + private final GetAllMealTypesHibernateRepositoryAdapter getAllMealTypesHibernateRepositoryAdapter; + + public GetAllMealTypesDatabaseRepositoryAdapter(GetAllMealTypesHibernateRepositoryAdapter getAllMealTypesHibernateRepositoryAdapter) { + this.getAllMealTypesHibernateRepositoryAdapter = getAllMealTypesHibernateRepositoryAdapter; + } + + @Override + public List execute() { + log.info("Get all meal types from database"); + + List response = getAllMealTypesHibernateRepositoryAdapter.findAll(); + + return response.stream().map(MealTypeHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPaymentStatusDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPaymentStatusDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..42b8de5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPaymentStatusDatabaseRepositoryAdapter.java @@ -0,0 +1,28 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PaymentStatusHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllPaymentStatusHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllPaymentStatusRepository; +import com.cuoco.application.usecase.model.PaymentStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class GetAllPaymentStatusDatabaseRepositoryAdapter implements GetAllPaymentStatusRepository { + + private final GetAllPaymentStatusHibernateRepositoryAdapter getAllPaymentStatusHibernateRepositoryAdapter; + + @Override + public List getAll() { + log.info("Get all payment statuses from database"); + + List response = getAllPaymentStatusHibernateRepositoryAdapter.findAll(); + + return response.stream().map(PaymentStatusHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapter.java similarity index 53% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapter.java index b0be7db..865bab9 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapter.java @@ -1,31 +1,29 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetAllPlansHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetAllPlansHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllPlansRepository; import com.cuoco.application.usecase.model.Plan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; +@Slf4j @Repository -public class GetAllPlansDatabaseRepository implements GetAllPlansRepository { +public class GetAllPlansDatabaseRepositoryAdapter implements GetAllPlansRepository { - static final Logger log = LoggerFactory.getLogger(GetAllPlansDatabaseRepository.class); + private final GetAllPlansHibernateRepositoryAdapter getAllPlansHibernateRepositoryAdapter; - private final GetAllPlansHibernateRepository getAllPlansHibernateRepository; - - public GetAllPlansDatabaseRepository(GetAllPlansHibernateRepository getAllPlansHibernateRepository) { - this.getAllPlansHibernateRepository = getAllPlansHibernateRepository; + public GetAllPlansDatabaseRepositoryAdapter(GetAllPlansHibernateRepositoryAdapter getAllPlansHibernateRepositoryAdapter) { + this.getAllPlansHibernateRepositoryAdapter = getAllPlansHibernateRepositoryAdapter; } @Override public List execute() { log.info("Get all plans from database"); - List response = getAllPlansHibernateRepository.findAll(); + List response = getAllPlansHibernateRepositoryAdapter.findAll(); return response.stream().map(PlanHibernateModel::toDomain).toList(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..29d330d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapter.java @@ -0,0 +1,28 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllPreparationTimesHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllPreparationTimesRepository; +import com.cuoco.application.usecase.model.PreparationTime; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllPreparationTimesDatabaseRepositoryAdapter implements GetAllPreparationTimesRepository { + + private GetAllPreparationTimesHibernateRepositoryAdapter getAllPreparationTimesHibernateRepositoryAdapter; + + public GetAllPreparationTimesDatabaseRepositoryAdapter(GetAllPreparationTimesHibernateRepositoryAdapter getAllPreparationTimesHibernateRepositoryAdapter) { + this.getAllPreparationTimesHibernateRepositoryAdapter = getAllPreparationTimesHibernateRepositoryAdapter; + } + + @Override + public List execute() { + log.info("Get all preparation times from database"); + List response = getAllPreparationTimesHibernateRepositoryAdapter.findAll(); + return response.stream().map(PreparationTimeHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllRecipesByIdsDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllRecipesByIdsDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..c3f41ce --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllRecipesByIdsDatabaseRepositoryAdapter.java @@ -0,0 +1,29 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllRecipesByIdsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllRecipesByIdsRepository; +import com.cuoco.application.usecase.model.Recipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllRecipesByIdsDatabaseRepositoryAdapter implements GetAllRecipesByIdsRepository { + + private final GetAllRecipesByIdsHibernateRepositoryAdapter getAllRecipesByIdsHibernateRepositoryAdapter; + + public GetAllRecipesByIdsDatabaseRepositoryAdapter(GetAllRecipesByIdsHibernateRepositoryAdapter getAllRecipesByIdsHibernateRepositoryAdapter) { + this.getAllRecipesByIdsHibernateRepositoryAdapter = getAllRecipesByIdsHibernateRepositoryAdapter; + } + + @Override + public List execute(List ids) { + log.info("Get all recipes by ids: {}", ids); + + List recipes = getAllRecipesByIdsHibernateRepositoryAdapter.findAllById(ids); + return recipes.stream().map(RecipeHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..c82b8b7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapter.java @@ -0,0 +1,28 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllUnitsHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllUnitsRepository; +import com.cuoco.application.usecase.model.Unit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllUnitsDatabaseRepositoryAdapter implements GetAllUnitsRepository { + + private final GetAllUnitsHibernateRepositoryAdapter getAllUnitsHibernateRepositoryAdapter; + + public GetAllUnitsDatabaseRepositoryAdapter(GetAllUnitsHibernateRepositoryAdapter getAllUnitsHibernateRepositoryAdapter) { + this.getAllUnitsHibernateRepositoryAdapter = getAllUnitsHibernateRepositoryAdapter; + } + + @Override + public List execute() { + log.info("Get all units from database"); + List response = getAllUnitsHibernateRepositoryAdapter.findAll(); + return response.stream().map(UnitHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserMealPrepsByUserIdDatabaseByUserIdRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserMealPrepsByUserIdDatabaseByUserIdRepositoryAdapter.java new file mode 100644 index 0000000..d84e6fb --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserMealPrepsByUserIdDatabaseByUserIdRepositoryAdapter.java @@ -0,0 +1,32 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllUserMealPrepsByUserIdRepository; +import com.cuoco.application.usecase.model.UserMealPrep; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllUserMealPrepsByUserIdDatabaseByUserIdRepositoryAdapter implements GetAllUserMealPrepsByUserIdRepository { + + private final GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter getAllUserMealPrepsByUserIdHibernateRepositoryAdapter; + + public GetAllUserMealPrepsByUserIdDatabaseByUserIdRepositoryAdapter( + GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter getAllUserMealPrepsByUserIdHibernateRepositoryAdapter + ) { + this.getAllUserMealPrepsByUserIdHibernateRepositoryAdapter = getAllUserMealPrepsByUserIdHibernateRepositoryAdapter; + } + + @Override + public List execute(Long userId) { + log.info("Executing get all user meal preps database repository"); + + List response = getAllUserMealPrepsByUserIdHibernateRepositoryAdapter.findByUserId(userId); + + return response.stream().map(UserMealPrepHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesByUserUserIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesByUserUserIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..a1d1eb0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesByUserUserIdDatabaseRepositoryAdapter.java @@ -0,0 +1,29 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllUserRecipesByUserIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetAllUserRecipesByUserIdRepository; +import com.cuoco.application.usecase.model.UserRecipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllUserRecipesByUserUserIdDatabaseRepositoryAdapter implements GetAllUserRecipesByUserIdRepository { + + private final GetAllUserRecipesByUserIdHibernateRepositoryAdapter getAllUserRecipesByUserIdHibernateRepositoryAdapter; + + public GetAllUserRecipesByUserUserIdDatabaseRepositoryAdapter(GetAllUserRecipesByUserIdHibernateRepositoryAdapter getAllUserRecipesByUserIdHibernateRepositoryAdapter) { + this.getAllUserRecipesByUserIdHibernateRepositoryAdapter = getAllUserRecipesByUserIdHibernateRepositoryAdapter; + } + + @Override + public List execute(Long userId) { + log.info("Executing get all user recipes database repository"); + + List response = getAllUserRecipesByUserIdHibernateRepositoryAdapter.findByUserId(userId); + return response.stream().map(UserRecipesHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesCalendarByUserByUserIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesCalendarByUserByUserIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..b63a031 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllUserRecipesCalendarByUserByUserIdDatabaseRepositoryAdapter.java @@ -0,0 +1,30 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllUserCalendarsByUserIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetUserCalendarByUserIdRepository; +import com.cuoco.application.usecase.model.Calendar; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Slf4j +@Repository +public class GetAllUserRecipesCalendarByUserByUserIdDatabaseRepositoryAdapter implements GetUserCalendarByUserIdRepository { + + private final GetAllUserCalendarsByUserIdHibernateRepositoryAdapter getAllUserCalendarsByUserIdHibernateRepositoryAdapter; + + public GetAllUserRecipesCalendarByUserByUserIdDatabaseRepositoryAdapter( + GetAllUserCalendarsByUserIdHibernateRepositoryAdapter getAllUserCalendarsByUserIdHibernateRepositoryAdapter + ) { + this.getAllUserCalendarsByUserIdHibernateRepositoryAdapter = getAllUserCalendarsByUserIdHibernateRepositoryAdapter; + } + + @Override + public List execute(Long userId) { + log.info("Executing get all calendars for user with ID {} in database", userId); + List response = getAllUserCalendarsByUserIdHibernateRepositoryAdapter.findAllByUserId(userId); + return response.stream().map(UserCalendarsHibernateModel::toDomain).toList(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapter.java index ee16d47..8885e6b 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapter.java @@ -4,18 +4,16 @@ import com.cuoco.adapter.out.hibernate.repository.GetAllergiesByIdHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetAllergiesByIdRepository; import com.cuoco.application.usecase.model.Allergy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; import java.util.stream.Collectors; +@Slf4j @Repository public class GetAllergiesByIdDatabaseRepositoryAdapter implements GetAllergiesByIdRepository { - static final Logger log = LoggerFactory.getLogger(GetAllergiesByIdDatabaseRepositoryAdapter.class); - private final GetAllergiesByIdHibernateRepositoryAdapter getAllergiesByIdHibernateRepositoryAdapter; public GetAllergiesByIdDatabaseRepositoryAdapter(GetAllergiesByIdHibernateRepositoryAdapter getAllergiesByIdHibernateRepositoryAdapter) { diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapter.java similarity index 61% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapter.java index 7f5b602..6ab1291 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapter.java @@ -1,29 +1,28 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetCookLevelByIdHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetCookLevelByIdHibernateRepositoryAdapter; import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.out.GetCookLevelByIdRepository; import com.cuoco.application.usecase.model.CookLevel; import com.cuoco.shared.model.ErrorDescription; -import jakarta.persistence.EntityManager; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public class GetCookLevelByIdDatabaseRepository implements GetCookLevelByIdRepository { +public class GetCookLevelByIdDatabaseRepositoryAdapter implements GetCookLevelByIdRepository { - private GetCookLevelByIdHibernateRepository getCookLevelByIdHibernateRepository; + private GetCookLevelByIdHibernateRepositoryAdapter getCookLevelByIdHibernateRepositoryAdapter; - public GetCookLevelByIdDatabaseRepository(GetCookLevelByIdHibernateRepository getCookLevelByIdHibernateRepository) { - this.getCookLevelByIdHibernateRepository = getCookLevelByIdHibernateRepository; + public GetCookLevelByIdDatabaseRepositoryAdapter(GetCookLevelByIdHibernateRepositoryAdapter getCookLevelByIdHibernateRepositoryAdapter) { + this.getCookLevelByIdHibernateRepositoryAdapter = getCookLevelByIdHibernateRepositoryAdapter; } @Override public CookLevel execute(Integer id) { - Optional cookLevel = getCookLevelByIdHibernateRepository.findById(id); + Optional cookLevel = getCookLevelByIdHibernateRepositoryAdapter.findById(id); if (cookLevel.isPresent()) { return cookLevel.get().toDomain(); diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapter.java similarity index 63% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapter.java index 27d2b44..a0086a3 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapter.java @@ -1,7 +1,7 @@ package com.cuoco.adapter.out.hibernate; import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.GetDietByIdHibernateRepository; +import com.cuoco.adapter.out.hibernate.repository.GetDietByIdHibernateRepositoryAdapter; import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.out.GetDietByIdRepository; import com.cuoco.application.usecase.model.Diet; @@ -11,17 +11,17 @@ import java.util.Optional; @Repository -public class GetDietByIdDatabaseRepository implements GetDietByIdRepository { +public class GetDietByIdDatabaseRepositoryAdapter implements GetDietByIdRepository { - private GetDietByIdHibernateRepository getDietByIdHibernateRepository; + private final GetDietByIdHibernateRepositoryAdapter getDietByIdHibernateRepositoryAdapter; - public GetDietByIdDatabaseRepository(GetDietByIdHibernateRepository getDietByIdHibernateRepository) { - this.getDietByIdHibernateRepository = getDietByIdHibernateRepository; + public GetDietByIdDatabaseRepositoryAdapter(GetDietByIdHibernateRepositoryAdapter getDietByIdHibernateRepositoryAdapter) { + this.getDietByIdHibernateRepositoryAdapter = getDietByIdHibernateRepositoryAdapter; } @Override public Diet execute(Integer id) { - Optional diet = getDietByIdHibernateRepository.findById(id); + Optional diet = getDietByIdHibernateRepositoryAdapter.findById(id); if (diet.isPresent()) { return diet.get().toDomain(); diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapter.java index fc7b49e..33779fb 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapter.java @@ -4,18 +4,16 @@ import com.cuoco.adapter.out.hibernate.repository.GetDietaryNeedsByIdHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; import com.cuoco.application.usecase.model.DietaryNeed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.List; import java.util.stream.Collectors; +@Slf4j @Repository public class GetDietaryNeedsByIdDatabaseRepositoryAdapter implements GetDietaryNeedsByIdRepository { - static final Logger log = LoggerFactory.getLogger(GetDietaryNeedsByIdDatabaseRepositoryAdapter.class); - private final GetDietaryNeedsByIdHibernateRepositoryAdapter getDietaryNeedsByIdHibernateRepositoryAdapter; public GetDietaryNeedsByIdDatabaseRepositoryAdapter(GetDietaryNeedsByIdHibernateRepositoryAdapter getDietaryNeedsByIdHibernateRepositoryAdapter) { diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..79d486e --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapter.java @@ -0,0 +1,34 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetMealPrepByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.out.GetMealPrepByIdRepository; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Slf4j +@Repository +public class GetMealPrepByIdDatabaseRepositoryAdapter implements GetMealPrepByIdRepository { + + private final GetMealPrepByIdHibernateRepositoryAdapter getMealPrepByIdHibernateRepositoryAdapter; + + public GetMealPrepByIdDatabaseRepositoryAdapter(GetMealPrepByIdHibernateRepositoryAdapter getMealPrepByIdHibernateRepositoryAdapter) { + this.getMealPrepByIdHibernateRepositoryAdapter = getMealPrepByIdHibernateRepositoryAdapter; + } + + @Override + public MealPrep execute(Long id) { + Optional oMealPrep = getMealPrepByIdHibernateRepositoryAdapter.findById(id); + + if (oMealPrep.isPresent()) { + return oMealPrep.get().toDomain(); + } else { + throw new BadRequestException(ErrorDescription.MEAL_PREP_NOT_EXISTS.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetMealTypeByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetMealTypeByIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..883e232 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetMealTypeByIdDatabaseRepositoryAdapter.java @@ -0,0 +1,33 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetMealTypeByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.out.GetMealTypeByIdRepository; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.shared.model.ErrorDescription; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class GetMealTypeByIdDatabaseRepositoryAdapter implements GetMealTypeByIdRepository { + + private final GetMealTypeByIdHibernateRepositoryAdapter getMealTypeByIdHibernateRepositoryAdapter; + + public GetMealTypeByIdDatabaseRepositoryAdapter(GetMealTypeByIdHibernateRepositoryAdapter getMealTypeByIdHibernateRepositoryAdapter) { + this.getMealTypeByIdHibernateRepositoryAdapter = getMealTypeByIdHibernateRepositoryAdapter; + } + + @Override + public MealType execute(Integer id) { + Optional type = getMealTypeByIdHibernateRepositoryAdapter.findById(id); + + if (type.isPresent()) { + return type.get().toDomain(); + } else { + throw new BadRequestException(ErrorDescription.MEAL_TYPE_NOT_EXISTS.getValue()); + } + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapter.java similarity index 83% rename from src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapter.java index e4e7cfb..8c70058 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapter.java @@ -11,11 +11,11 @@ import java.util.Optional; @Repository -public class GetPlanByIdDatabaseRepository implements GetPlanByIdRepository { +public class GetPlanByIdDatabaseRepositoryAdapter implements GetPlanByIdRepository { private final GetPlanByIdHibernateRepositoryAdapter getPlanByIdHibernateRepositoryAdapter; - public GetPlanByIdDatabaseRepository(GetPlanByIdHibernateRepositoryAdapter getPlanByIdHibernateRepositoryAdapter) { + public GetPlanByIdDatabaseRepositoryAdapter(GetPlanByIdHibernateRepositoryAdapter getPlanByIdHibernateRepositoryAdapter) { this.getPlanByIdHibernateRepositoryAdapter = getPlanByIdHibernateRepositoryAdapter; } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetPreparationTimeByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetPreparationTimeByIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..61df78a --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetPreparationTimeByIdDatabaseRepositoryAdapter.java @@ -0,0 +1,33 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetPreparationTimeByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.out.GetPreparationTimeByIdRepository; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.shared.model.ErrorDescription; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class GetPreparationTimeByIdDatabaseRepositoryAdapter implements GetPreparationTimeByIdRepository { + + public GetPreparationTimeByIdHibernateRepositoryAdapter getPreparationTimeByIdHibernateRepositoryAdapter; + + public GetPreparationTimeByIdDatabaseRepositoryAdapter(GetPreparationTimeByIdHibernateRepositoryAdapter getPreparationTimeByIdHibernateRepositoryAdapter) { + this.getPreparationTimeByIdHibernateRepositoryAdapter = getPreparationTimeByIdHibernateRepositoryAdapter; + } + + @Override + public PreparationTime execute(Integer id) { + Optional preparationTime = getPreparationTimeByIdHibernateRepositoryAdapter.findById(id); + + if (preparationTime.isPresent()) { + return preparationTime.get().toDomain(); + } else { + throw new BadRequestException(ErrorDescription.PREPARATION_TIME_NOT_EXISTS.getValue()); + } + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..0918da3 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapter.java @@ -0,0 +1,32 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetRecipeByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.out.GetRecipeByIdRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.model.ErrorDescription; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class GetRecipeByIdDatabaseRepositoryAdapter implements GetRecipeByIdRepository { + + private final GetRecipeByIdHibernateRepositoryAdapter getRecipeByIdHibernateRepositoryAdapter; + + public GetRecipeByIdDatabaseRepositoryAdapter(GetRecipeByIdHibernateRepositoryAdapter getRecipeByIdHibernateRepositoryAdapter) { + this.getRecipeByIdHibernateRepositoryAdapter = getRecipeByIdHibernateRepositoryAdapter; + } + + @Override + public Recipe execute(Long id) { + Optional recipe = getRecipeByIdHibernateRepositoryAdapter.findById(id); + + if (recipe.isPresent()) { + return recipe.get().toDomain(); + } else { + throw new BadRequestException(ErrorDescription.RECIPE_NOT_EXISTS.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipesFromIngredientsDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipesFromIngredientsDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..2889381 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetRecipesFromIngredientsDatabaseRepositoryAdapter.java @@ -0,0 +1,127 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllRecipesByIdsHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.utils.Constants; +import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Filters; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; + +@Slf4j +@Repository +@Qualifier("repository") +public class GetRecipesFromIngredientsDatabaseRepositoryAdapter implements GetRecipesFromIngredientsRepository { + + private final String getRecipeIdsByFilters = FileReader.execute("sql/getRecipeIdsByFilters.sql"); + + private final GetAllRecipesByIdsHibernateRepositoryAdapter getAllRecipesByIdsHibernateRepositoryAdapter; + + private final NamedParameterJdbcTemplate jdbcTemplate; + + public GetRecipesFromIngredientsDatabaseRepositoryAdapter( + GetAllRecipesByIdsHibernateRepositoryAdapter getAllRecipesByIdsHibernateRepositoryAdapter, + NamedParameterJdbcTemplate jdbcTemplate + ) { + this.getAllRecipesByIdsHibernateRepositoryAdapter = getAllRecipesByIdsHibernateRepositoryAdapter; + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public List execute(Recipe recipe) { + try { + List ingredientNames = recipe.getIngredients().stream() + .map(i -> i.getName().toLowerCase()) + .toList(); + + log.info("Getting recipes by ingredients {} and filters from database", ingredientNames); + + Integer ingredientCount = ingredientNames.size(); + Filters filters = recipe.getFilters(); + + List notIncludeIdsRaw = Optional.ofNullable(recipe.getConfiguration().getNotInclude()) + .orElse(List.of()) + .stream() + .map(Recipe::getId) + .toList(); + + boolean notIncludeIdsIsEmpty = notIncludeIdsRaw.isEmpty(); + List safeNotIncludeIds = notIncludeIdsIsEmpty ? List.of(-1L) : notIncludeIdsRaw; + + Integer preparationTimeId = Optional.ofNullable(filters.getPreparationTime()).map(PreparationTime::getId).orElse(null); + Integer cookLevelId = Optional.ofNullable(filters.getCookLevel()).map(CookLevel::getId).orElse(null); + Integer dietId = Optional.ofNullable(filters.getDiet()).map(Diet::getId).orElse(null); + + List mealTypesIdsRaw = Optional.ofNullable(filters.getMealTypes()).orElse(List.of()).stream().map(MealType::getId).toList(); + boolean mealTypesIdsIsEmpty = mealTypesIdsRaw.isEmpty(); + List safeMealTypeIds = mealTypesIdsIsEmpty ? List.of(-1) : mealTypesIdsRaw; + + List allergiesIdsRaw = Optional.ofNullable(filters.getAllergies()).orElse(List.of()).stream().map(Allergy::getId).toList(); + boolean allergiesIdsIsEmpty = allergiesIdsRaw.isEmpty(); + List safeAllergyIds = allergiesIdsIsEmpty ? List.of(-1) : allergiesIdsRaw; + + List dietaryNeedsIdsRaw = Optional.ofNullable(filters.getDietaryNeeds()).orElse(List.of()).stream().map(DietaryNeed::getId).toList(); + boolean dietaryNeedsIdsIsEmpty = dietaryNeedsIdsRaw.isEmpty(); + List safeDietaryNeedIds = dietaryNeedsIdsIsEmpty ? List.of(-1) : dietaryNeedsIdsRaw; + + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue(Constants.RESULT_SIZE.getValue(), recipe.getConfiguration().getSize()) + .addValue(Constants.INGREDIENT_NAMES.getValue(), ingredientNames) + .addValue(Constants.INGREDIENT_COUNT.getValue(), ingredientCount) + .addValue(Constants.PREPARATION_TIME_ID.getValue(), preparationTimeId) + .addValue(Constants.COOK_LEVEL_ID.getValue(), cookLevelId) + .addValue(Constants.DIET_ID.getValue(), dietId) + .addValue(Constants.MEAL_TYPES_IDS_IS_EMPTY.getValue(), mealTypesIdsIsEmpty) + .addValue(Constants.MEAL_TYPES_IDS.getValue(), safeMealTypeIds) + .addValue(Constants.ALLERGY_IDS_IS_EMPTY.getValue(), allergiesIdsIsEmpty) + .addValue(Constants.ALLERGY_IDS.getValue(), safeAllergyIds) + .addValue(Constants.DIETARY_NEEDS_IDS_IS_EMPTY.getValue(), dietaryNeedsIdsIsEmpty) + .addValue(Constants.DIETARY_NEEDS_IDS.getValue(), safeDietaryNeedIds) + .addValue(Constants.DIETARY_NEEDS_COUNT.getValue(), dietaryNeedsIdsRaw.size()) + .addValue(Constants.NOT_INCLUDE_IDS_IS_EMPTY.getValue(), notIncludeIdsIsEmpty) + .addValue(Constants.NOT_INCLUDE_IDS.getValue(), safeNotIncludeIds); + + List recipeIds = jdbcTemplate.query( + getRecipeIdsByFilters, + params, + (rs, rowNum) -> rs.getLong("id") + ); + + if (recipeIds.isEmpty()) { + log.info("No recipes found in database with the provided ingredients and filters"); + return Collections.emptyList(); + } + + List savedRecipes = getAllRecipesByIdsHibernateRepositoryAdapter.findAllById(recipeIds); + + List recipesResponse = savedRecipes.stream() + .map(RecipeHibernateModel::toDomain) + .toList(); + + log.info("Successfully retrieved {} recipes from ingredients and filters", recipesResponse.size()); + return recipesResponse; + + } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) { + log.error(ErrorDescription.UNEXPECTED_ERROR.getValue(), e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapter.java index 1dbd3fc..293e313 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapter.java @@ -1,40 +1,42 @@ package com.cuoco.adapter.out.hibernate; -import com.cuoco.adapter.exception.NotFoundException; +import com.cuoco.adapter.exception.ForbiddenException; import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; -import com.cuoco.adapter.out.hibernate.repository.FindUserByEmailHibernateRepositoryAdapter; -import com.cuoco.adapter.out.hibernate.repository.FindUserPreferencesByIdHibernateRepositoryAdapter; -import com.cuoco.application.usecase.model.User; +import com.cuoco.adapter.out.hibernate.repository.GetUserByEmailHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetUserPreferencesByUserIdHibernateRepositoryAdapter; import com.cuoco.application.port.out.GetUserByEmailRepository; -import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.application.usecase.model.User; import com.cuoco.shared.model.ErrorDescription; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository +@Transactional public class GetUserByEmailDatabaseRepositoryAdapter implements GetUserByEmailRepository { - private final FindUserByEmailHibernateRepositoryAdapter findUserByEmailHibernateRepositoryAdapter; - private final FindUserPreferencesByIdHibernateRepositoryAdapter findUserPreferencesByIdHibernateRepositoryAdapter; + private final GetUserByEmailHibernateRepositoryAdapter getUserByEmailHibernateRepositoryAdapter; + private final GetUserPreferencesByUserIdHibernateRepositoryAdapter getUserPreferencesByUserIdHibernateRepositoryAdapter; public GetUserByEmailDatabaseRepositoryAdapter( - FindUserByEmailHibernateRepositoryAdapter findUserByEmailHibernateRepositoryAdapter, - FindUserPreferencesByIdHibernateRepositoryAdapter findUserPreferencesByIdHibernateRepositoryAdapter + GetUserByEmailHibernateRepositoryAdapter getUserByEmailHibernateRepositoryAdapter, + GetUserPreferencesByUserIdHibernateRepositoryAdapter getUserPreferencesByUserIdHibernateRepositoryAdapter ) { - this.findUserByEmailHibernateRepositoryAdapter = findUserByEmailHibernateRepositoryAdapter; - this.findUserPreferencesByIdHibernateRepositoryAdapter = findUserPreferencesByIdHibernateRepositoryAdapter; + this.getUserByEmailHibernateRepositoryAdapter = getUserByEmailHibernateRepositoryAdapter; + this.getUserPreferencesByUserIdHibernateRepositoryAdapter = getUserPreferencesByUserIdHibernateRepositoryAdapter; } @Override public User execute(String email) { - Optional userResult = findUserByEmailHibernateRepositoryAdapter.findByEmail(email); + Optional userResult = getUserByEmailHibernateRepositoryAdapter.findByEmail(email); if (userResult.isPresent()) { - Optional userPreferencesResult = findUserPreferencesByIdHibernateRepositoryAdapter.findById(userResult.get().getId()); + Optional userPreferencesResult = getUserPreferencesByUserIdHibernateRepositoryAdapter.getByUserId(userResult.get().getId()); if(userPreferencesResult.isPresent()) { @@ -43,6 +45,6 @@ public User execute(String email) { return user; } else throw new UnprocessableException(ErrorDescription.UNEXPECTED_ERROR.getValue()); - } else throw new NotFoundException(ErrorDescription.USER_NOT_EXISTS.getValue()); + } else throw new ForbiddenException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByIdDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByIdDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..ea281a4 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserByIdDatabaseRepositoryAdapter.java @@ -0,0 +1,50 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetUserByIdHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetUserPreferencesByUserIdHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Slf4j +@Repository +public class GetUserByIdDatabaseRepositoryAdapter implements GetUserByIdRepository { + + private final GetUserByIdHibernateRepositoryAdapter getUserByIdHibernateRepositoryAdapter; + private final GetUserPreferencesByUserIdHibernateRepositoryAdapter getUserPreferencesByUserIdHibernateRepositoryAdapter; + + public GetUserByIdDatabaseRepositoryAdapter( + GetUserByIdHibernateRepositoryAdapter getUserByIdHibernateRepositoryAdapter, + GetUserPreferencesByUserIdHibernateRepositoryAdapter getUserPreferencesByUserIdHibernateRepositoryAdapter + ) { + this.getUserByIdHibernateRepositoryAdapter = getUserByIdHibernateRepositoryAdapter; + this.getUserPreferencesByUserIdHibernateRepositoryAdapter = getUserPreferencesByUserIdHibernateRepositoryAdapter; + } + + @Override + public User execute(Long id) { + Optional userResult = getUserByIdHibernateRepositoryAdapter.findById(id); + + if (userResult.isEmpty()) { + throw new UnprocessableException(ErrorDescription.UNEXPECTED_ERROR.getValue()); + } + + Optional userPreferencesResult = getUserPreferencesByUserIdHibernateRepositoryAdapter.getByUserId(userResult.get().getId()); + + if(userPreferencesResult.isEmpty()) { + throw new UnprocessableException(ErrorDescription.UNEXPECTED_ERROR.getValue()); + } + + User user = userResult.get().toDomain(); + user.setPreferences(userPreferencesResult.get().toDomain()); + + return user; + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/GetUserPaymentByExternalReferenceDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserPaymentByExternalReferenceDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..988a0c9 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/GetUserPaymentByExternalReferenceDatabaseRepositoryAdapter.java @@ -0,0 +1,36 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.exception.NotFoundException; +import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetUserPaymentByExternalReferenceHibernateRepositoryAdapter; +import com.cuoco.application.port.out.GetUserPaymentByExternalReferenceRepository; +import com.cuoco.application.usecase.model.UserPayment; +import com.cuoco.shared.model.ErrorDescription; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class GetUserPaymentByExternalReferenceDatabaseRepositoryAdapter implements GetUserPaymentByExternalReferenceRepository { + + private final GetUserPaymentByExternalReferenceHibernateRepositoryAdapter getUserPaymentByExternalReferenceHibernateRepositoryAdapter; + + @Override + public UserPayment execute(String externalReference) { + log.info("Executing get user payment by external reference {}", externalReference); + + Optional maybeResponse = getUserPaymentByExternalReferenceHibernateRepositoryAdapter.findByExternalReference(externalReference); + + if(!maybeResponse.isPresent()) { + log.info("Not found user payment with external reference {}", externalReference); + throw new NotFoundException(ErrorDescription.NOT_FOUND.getValue()); + } + + UserPaymentsHibernateModel response = maybeResponse.get(); + return response.toDomain(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/SimpleTextParsingRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/SimpleTextParsingRepositoryAdapter.java index 8ff2eb5..ddd914c 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/SimpleTextParsingRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/SimpleTextParsingRepositoryAdapter.java @@ -2,8 +2,7 @@ import com.cuoco.application.port.out.GetIngredientsFromTextRepository; import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.util.Arrays; @@ -11,11 +10,10 @@ import java.util.List; import java.util.stream.Collectors; +@Slf4j @Repository public class SimpleTextParsingRepositoryAdapter implements GetIngredientsFromTextRepository { - static final Logger log = LoggerFactory.getLogger(SimpleTextParsingRepositoryAdapter.class); - @Override public List execute(String text) { log.info("Processing text for ingredients: {}", text); @@ -28,7 +26,11 @@ public List execute(String text) { List ingredients = Arrays.stream(text.split(",")) .map(String::trim) .filter(ingredient -> !ingredient.isEmpty()) - .map(ingredient -> new Ingredient(ingredient, "text", false)) + .map(ingredient -> Ingredient.builder() + .name(ingredient) + .source("text") + .confirmed(false) + .build()) .collect(Collectors.toList()); log.info("Successfully parsed {} ingredients from text", ingredients.size()); diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..45916c0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java @@ -0,0 +1,81 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateUserPreferencesHibernateRepositoryAdapter; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPreferences; +import jakarta.transaction.Transactional; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +@Transactional +public class UpdateUserDatabaseRepositoryAdapter implements UpdateUserRepository { + + private final CreateUserHibernateRepositoryAdapter createUserHibernateRepositoryAdapter; + private final CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter; + + public UpdateUserDatabaseRepositoryAdapter( + CreateUserHibernateRepositoryAdapter createUserHibernateRepositoryAdapter, + CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter + ) { + this.createUserHibernateRepositoryAdapter = createUserHibernateRepositoryAdapter; + this.createUserPreferencesHibernateRepositoryAdapter = createUserPreferencesHibernateRepositoryAdapter; + } + + @Override + public User execute(User user) { + + UserHibernateModel userToUpdate = buildUserHibernateModel(user); + + UserHibernateModel savedUser = createUserHibernateRepositoryAdapter.save(userToUpdate); + UserPreferencesHibernateModel savedPreferences = savePreferences(user, savedUser); + + User userResponse = savedUser.toDomain(); + UserPreferences preferences = savedPreferences.toDomain(); + + userResponse.setPreferences(preferences); + userResponse.setDietaryNeeds(user.getDietaryNeeds()); + userResponse.setAllergies(user.getAllergies()); + + return userResponse; + } + + private UserPreferencesHibernateModel savePreferences(User user, UserHibernateModel savedUser) { + UserPreferencesHibernateModel preferences = buildUserPreferences(user.getPreferences(), savedUser); + return createUserPreferencesHibernateRepositoryAdapter.save(preferences); + } + + private UserHibernateModel buildUserHibernateModel(User user) { + return UserHibernateModel.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .password(user.getPassword()) + .active(user.getActive()) + .plan(PlanHibernateModel.fromDomain(user.getPlan())) + .dietaryNeeds(user.getDietaryNeeds() != null ? user.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList() : List.of()) + .allergies(user.getAllergies() != null ? user.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList() : List.of()) + .updatedAt(LocalDateTime.now()) + .build(); + } + + private UserPreferencesHibernateModel buildUserPreferences(UserPreferences userPreferences, UserHibernateModel savedUser) { + return UserPreferencesHibernateModel.builder() + .id(userPreferences.getId()) + .user(savedUser) + .diet(DietHibernateModel.fromDomain(userPreferences.getDiet())) + .cookLevel(CookLevelHibernateModel.fromDomain(userPreferences.getCookLevel())) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserPaymentDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserPaymentDatabaseRepositoryAdapter.java new file mode 100644 index 0000000..eafebc8 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserPaymentDatabaseRepositoryAdapter.java @@ -0,0 +1,40 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserPaymentHibernateRepositoryAdapter; +import com.cuoco.application.port.out.CreateUserPaymentRepository; +import com.cuoco.application.port.out.UpdateUserPaymentRepository; +import com.cuoco.application.usecase.model.UserPayment; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@Qualifier("repository") +@RequiredArgsConstructor +public class UpdateUserPaymentDatabaseRepositoryAdapter implements UpdateUserPaymentRepository { + + private final CreateUserPaymentHibernateRepositoryAdapter createUserPaymentHibernateRepositoryAdapter; + + @Override + public UserPayment execute(UserPayment userPayment) { + log.info("Executing update user payment in database"); + + UserPaymentsHibernateModel userPaymentToUpdate = UserPaymentsHibernateModel.fromDomain(userPayment); + + userPaymentToUpdate.setUser(UserHibernateModel.builder().id(userPayment.getUser().getId()).build()); + + UserPaymentsHibernateModel updatedUserPayment = createUserPaymentHibernateRepositoryAdapter.save(userPaymentToUpdate); + + log.info("Updated user payment with ID {}", updatedUserPayment.getId()); + + UserPayment response = updatedUserPayment.toDomain(); + + response.setUser(userPayment.getUser()); + + return response; + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapter.java deleted file mode 100644 index 2cc7710..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.cuoco.adapter.out.hibernate; - - -import com.cuoco.adapter.out.hibernate.repository.UserExistsByEmailHibernateRepositoryAdapter; -import com.cuoco.application.port.out.UserExistsByEmailRepository; -import org.springframework.stereotype.Repository; - -@Repository -public class UserExistsByEmailDatabaseRepositoryAdapter implements UserExistsByEmailRepository { - - private UserExistsByEmailHibernateRepositoryAdapter userExistsByEmailHibernateRepositoryAdapter; - - public UserExistsByEmailDatabaseRepositoryAdapter(UserExistsByEmailHibernateRepositoryAdapter userExistsByEmailHibernateRepositoryAdapter) { - this.userExistsByEmailHibernateRepositoryAdapter = userExistsByEmailHibernateRepositoryAdapter; - } - - @Override - public Boolean execute(String email) { - - Boolean exists = userExistsByEmailHibernateRepositoryAdapter.existsByEmail(email); - - return exists; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/AllergyHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/AllergyHibernateModel.java index 2bca569..a6677e4 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/AllergyHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/AllergyHibernateModel.java @@ -10,7 +10,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "allergy") +@Entity(name = "allergies") @Data @Builder @NoArgsConstructor @@ -22,6 +22,13 @@ public class AllergyHibernateModel { private Integer id; private String description; + public static AllergyHibernateModel fromDomain(Allergy allergy) { + return AllergyHibernateModel.builder() + .id(allergy.getId()) + .description(allergy.getDescription()) + .build(); + } + public Allergy toDomain() { return Allergy.builder() .id(id) diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/CategoryHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/CategoryHibernateModel.java index 27dedb3..b0e145c 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/CategoryHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/CategoryHibernateModel.java @@ -9,7 +9,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "category") +@Entity(name = "categories") @Data @Builder @NoArgsConstructor diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/CookLevelHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/CookLevelHibernateModel.java index 8420529..940164c 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/CookLevelHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/CookLevelHibernateModel.java @@ -10,7 +10,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "cook_level") +@Entity(name = "cook_levels") @Data @Builder @NoArgsConstructor @@ -22,9 +22,15 @@ public class CookLevelHibernateModel { private Integer id; private String description; + public static CookLevelHibernateModel fromDomain(CookLevel cookLevel) { + return CookLevelHibernateModel.builder() + .id(cookLevel.getId()) + .description(cookLevel.getDescription()) + .build(); + } + public CookLevel toDomain() { - return CookLevel - .builder() + return CookLevel.builder() .id(id) .description(description) .build(); diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/DietHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/DietHibernateModel.java index d508bec..becd323 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/DietHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/DietHibernateModel.java @@ -10,7 +10,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "diet") +@Entity(name = "diets") @Data @Builder @NoArgsConstructor @@ -22,6 +22,13 @@ public class DietHibernateModel { private Integer id; private String description; + public static DietHibernateModel fromDomain(Diet diet) { + return DietHibernateModel.builder() + .id(diet.getId()) + .description(diet.getDescription()) + .build(); + } + public Diet toDomain() { return Diet .builder() diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/DietaryNeedHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/DietaryNeedHibernateModel.java index ec11ec3..dac88d4 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/DietaryNeedHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/DietaryNeedHibernateModel.java @@ -10,7 +10,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "dietary_need") +@Entity(name = "dietary_needs") @Data @Builder @NoArgsConstructor @@ -22,6 +22,13 @@ public class DietaryNeedHibernateModel { private Integer id; private String description; + public static DietaryNeedHibernateModel fromDomain(DietaryNeed dietaryNeed) { + return DietaryNeedHibernateModel.builder() + .id(dietaryNeed.getId()) + .description(dietaryNeed.getDescription()) + .build(); + } + public DietaryNeed toDomain() { return DietaryNeed.builder() .id(id) diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/IngredientHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/IngredientHibernateModel.java index ad32b71..4210427 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/IngredientHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/IngredientHibernateModel.java @@ -1,5 +1,6 @@ package com.cuoco.adapter.out.hibernate.model; +import com.cuoco.application.usecase.model.Ingredient; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -11,7 +12,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "ingredient") +@Entity(name = "ingredients") @Data @Builder @NoArgsConstructor @@ -28,6 +29,25 @@ public class IngredientHibernateModel { private CategoryHibernateModel category; @ManyToOne - @JoinColumn(name = "measure_unit_id", referencedColumnName = "id") - private MeasureUnitHibernateModel measureUnit; + @JoinColumn(name = "unit_id", referencedColumnName = "id") + private UnitHibernateModel unit; + + public static IngredientHibernateModel fromDomain(Ingredient ingredient) { + return IngredientHibernateModel.builder() + .id(ingredient.getId()) + .name(ingredient.getName()) + .unit(UnitHibernateModel.fromDomain(ingredient.getUnit())) + .build(); + } + + public Ingredient toDomain(Double quantity, Boolean optional) { + return Ingredient.builder() + .id(id) + .name(name) + .quantity(quantity) + .optional(optional) + .unit(unit.toDomain()) + .build(); + } + } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepHibernateModel.java new file mode 100644 index 0000000..8c21afb --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepHibernateModel.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.MealPrep; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Entity(name = "meal_preps") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MealPrepHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; + private String estimatedCookingTime; + private Integer servings; + private Boolean freeze; + + @OneToMany(mappedBy = "mealPrep", cascade = CascadeType.PERSIST, orphanRemoval = true, fetch = FetchType.LAZY) + private List steps; + + @ManyToMany + @JoinTable( + name = "meal_prep_recipes", + joinColumns = @JoinColumn(name = "meal_prep_id"), + inverseJoinColumns = @JoinColumn(name = "recipe_id") + ) + private List recipes; + + @OneToMany(mappedBy = "mealPrep", cascade = CascadeType.PERSIST, orphanRemoval = true, fetch = FetchType.LAZY) + private List ingredients; + + public MealPrep toDomain() { + return MealPrep.builder() + .id(id) + .title(title) + .estimatedCookingTime(estimatedCookingTime) + .servings(servings) + .freeze(freeze) + .steps(steps.stream().map(MealPrepStepsHibernateModel::toDomain).toList()) + .recipes(recipes.stream().map(RecipeHibernateModel::toDomain).toList()) + .ingredients(ingredients.stream().map(MealPrepIngredientsHibernateModel::toDomain).toList()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepIngredientsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepIngredientsHibernateModel.java new file mode 100644 index 0000000..b079d87 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepIngredientsHibernateModel.java @@ -0,0 +1,40 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.Ingredient; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "meal_prep_ingredients") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MealPrepIngredientsHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "meal_prep_id", referencedColumnName = "id") + private MealPrepHibernateModel mealPrep; + + @ManyToOne + @JoinColumn(name = "ingredient_id", referencedColumnName = "id") + private IngredientHibernateModel ingredient; + + private Double quantity; + + public Ingredient toDomain() { + return ingredient.toDomain(quantity, false); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepStepsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepStepsHibernateModel.java new file mode 100644 index 0000000..cccc2b8 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealPrepStepsHibernateModel.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate.model; + + +import com.cuoco.application.usecase.model.Step; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "meal_prep_steps") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MealPrepStepsHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "meal_prep_id", referencedColumnName = "id") + private MealPrepHibernateModel mealPrep; + + private String title; + private Integer number; + @Lob + @Column(name = "description", columnDefinition = "TEXT") + private String description; + private String time; + private String imageName; + + public Step toDomain() { + return Step.builder() + .id(id) + .title(title) + .number(number) + .description(description) + .time(time) + .imageName(imageName) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/MealTypeHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealTypeHibernateModel.java new file mode 100644 index 0000000..c0024db --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/MealTypeHibernateModel.java @@ -0,0 +1,39 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.MealType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "meal_types") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MealTypeHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String description; + + public static MealTypeHibernateModel fromDomain(MealType mealType) { + return MealTypeHibernateModel.builder() + .id(mealType.getId()) + .description(mealType.getDescription()) + .build(); + } + + public MealType toDomain() { + return MealType.builder() + .id(id) + .description(description) + .build(); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/MeasureUnitHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/MeasureUnitHibernateModel.java deleted file mode 100644 index 50e465c..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/MeasureUnitHibernateModel.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.cuoco.adapter.out.hibernate.model; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Entity(name = "measure_unit") -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MeasureUnitHibernateModel { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - private String description; - private String symbol; -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/PaymentStatusHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/PaymentStatusHibernateModel.java new file mode 100644 index 0000000..d110762 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/PaymentStatusHibernateModel.java @@ -0,0 +1,38 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.PaymentStatus; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "payment_status") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PaymentStatusHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String description; + + public static PaymentStatusHibernateModel fromDomain(PaymentStatus paymentStatus) { + return PaymentStatusHibernateModel.builder() + .id(paymentStatus.getId()) + .description(paymentStatus.getDescription()) + .build(); + } + + public PaymentStatus toDomain() { + return PaymentStatus.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanConfigurationHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanConfigurationHibernateModel.java new file mode 100644 index 0000000..ba7a44e --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanConfigurationHibernateModel.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.PlanConfiguration; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Entity(name = "plan_configuration") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PlanConfigurationHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String title; + private String description; + private Integer quantity; + private BigDecimal price; + private String currency; + + public static PlanConfigurationHibernateModel fromDomain(PlanConfiguration planConfiguration) { + return PlanConfigurationHibernateModel.builder() + .title(planConfiguration.getTitle()) + .description(planConfiguration.getDescription()) + .quantity(planConfiguration.getQuantity()) + .price(planConfiguration.getPrice()) + .currency(planConfiguration.getCurrency()) + .build(); + } + + public PlanConfiguration toDomain() { + return PlanConfiguration.builder() + .title(title) + .description(description) + .quantity(quantity) + .price(price) + .currency(currency) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanHibernateModel.java index e495ce6..80e8540 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/PlanHibernateModel.java @@ -5,12 +5,14 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "plan") +@Entity(name = "plans") @Data @Builder @NoArgsConstructor @@ -22,10 +24,23 @@ public class PlanHibernateModel { private Integer id; private String description; + @OneToOne + @JoinColumn(name = "configuration_id") + private PlanConfigurationHibernateModel configuration; + + public static PlanHibernateModel fromDomain(Plan plan) { + return PlanHibernateModel.builder() + .id(plan.getId()) + .description(plan.getDescription()) + .configuration(plan.getConfiguration() != null ? PlanConfigurationHibernateModel.fromDomain(plan.getConfiguration()) : null) + .build(); + } + public Plan toDomain() { return Plan.builder() .id(id) .description(description) + .configuration(configuration != null ? configuration.toDomain() : null) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/PreparationTimeHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/PreparationTimeHibernateModel.java new file mode 100644 index 0000000..8b94528 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/PreparationTimeHibernateModel.java @@ -0,0 +1,38 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.PreparationTime; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "preparation_times") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PreparationTimeHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String description; + + public static PreparationTimeHibernateModel fromDomain(PreparationTime preparationTime) { + return PreparationTimeHibernateModel.builder() + .id(preparationTime.getId()) + .description(preparationTime.getDescription()) + .build(); + } + + public PreparationTime toDomain() { + return PreparationTime.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeHibernateModel.java index b61c4e8..efbe61d 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeHibernateModel.java @@ -1,10 +1,26 @@ package com.cuoco.adapter.out.hibernate.model; -import jakarta.persistence.*; -import lombok.*; -import java.time.LocalDateTime; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; -@Entity(name = "recipe") +import java.util.List; + +@Entity(name = "recipes") @Data @Builder @NoArgsConstructor @@ -14,10 +30,69 @@ public class RecipeHibernateModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String title; + private String name; + private String subtitle; private String description; - private String steps; private String imageUrl; - private Integer estimatedTime; - private String difficulty; + + @OneToMany(mappedBy = "recipe", cascade = CascadeType.PERSIST, orphanRemoval = true, fetch = FetchType.LAZY) + private List steps; + + @ManyToOne + private PreparationTimeHibernateModel preparationTime; + + @ManyToOne + private CookLevelHibernateModel cookLevel; + + @ManyToOne + private DietHibernateModel diet; + + @ManyToMany + @JoinTable( + name = "recipe_meal_types", + joinColumns = @JoinColumn(name = "recipe_id"), + inverseJoinColumns = @JoinColumn(name = "meal_type_id") + ) + private List mealTypes; + + @ManyToMany + @JoinTable( + name = "recipe_allergies", + joinColumns = @JoinColumn(name = "recipe_id"), + inverseJoinColumns = @JoinColumn(name = "allergy_id") + ) + private List allergies; + + @ManyToMany + @JoinTable( + name = "recipe_dietary_needs", + joinColumns = @JoinColumn(name = "recipe_id"), + inverseJoinColumns = @JoinColumn(name = "dietary_need_id") + ) + private List dietaryNeeds; + + @OneToMany(mappedBy = "recipe", cascade = CascadeType.PERSIST, orphanRemoval = true, fetch = FetchType.LAZY) + private List ingredients; + + public Recipe toDomain() { + List domainIngredients = ingredients.stream() + .map(ri -> ri.toDomain().getIngredient()) + .toList(); + + return Recipe.builder() + .id(id) + .name(name) + .subtitle(subtitle) + .description(description) + .steps(steps.stream().map(RecipeStepsHibernateModel::toDomain).toList()) + .image(imageUrl) + .preparationTime(preparationTime.toDomain()) + .cookLevel(cookLevel.toDomain()) + .diet(diet != null ? diet.toDomain() : null) + .mealTypes(mealTypes.stream().map(MealTypeHibernateModel::toDomain).toList()) + .allergies(allergies.stream().map(AllergyHibernateModel::toDomain).toList()) + .dietaryNeeds(dietaryNeeds.stream().map(DietaryNeedHibernateModel::toDomain).toList()) + .ingredients(domainIngredients) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeIngredientsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeIngredientsHibernateModel.java index 0c4c951..5c339ae 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeIngredientsHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeIngredientsHibernateModel.java @@ -1,5 +1,6 @@ package com.cuoco.adapter.out.hibernate.model; +import com.cuoco.application.usecase.model.RecipeIngredient; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -31,4 +32,11 @@ public class RecipeIngredientsHibernateModel { private IngredientHibernateModel ingredient; private Double quantity; + private Boolean optional; + + public RecipeIngredient toDomain() { + return RecipeIngredient.builder() + .ingredient(ingredient.toDomain(quantity, optional)) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeStepsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeStepsHibernateModel.java new file mode 100644 index 0000000..396444c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/RecipeStepsHibernateModel.java @@ -0,0 +1,48 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.Step; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "recipe_steps") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RecipeStepsHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "recipe_id", referencedColumnName = "id") + private RecipeHibernateModel recipe; + + private Integer number; + private String title; + @Lob + @Column(name = "description", columnDefinition = "TEXT") + private String description; + private String imageName; + + public Step toDomain() { + return Step.builder() + .id(id) + .number(number) + .title(title) + .description(description) + .imageName(imageName) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UnitHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UnitHibernateModel.java new file mode 100644 index 0000000..fa50030 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UnitHibernateModel.java @@ -0,0 +1,41 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.Unit; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "units") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UnitHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String description; + private String symbol; + + public static UnitHibernateModel fromDomain(Unit unit) { + return UnitHibernateModel.builder() + .id(unit.getId()) + .description(unit.getDescription()) + .symbol(unit.getSymbol()) + .build(); + } + + public Unit toDomain() { + return Unit.builder() + .id(id) + .description(description) + .symbol(symbol) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserAllergiesHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserAllergiesHibernateModel.java deleted file mode 100644 index 2a4058a..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserAllergiesHibernateModel.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cuoco.adapter.out.hibernate.model; - -import jakarta.persistence.*; -import lombok.*; -import java.time.LocalDateTime; - -@Entity(name = "user_allergies") -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class UserAllergiesHibernateModel { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "user_id", referencedColumnName = "id") - private UserHibernateModel user; - - @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "allergy_id", referencedColumnName = "id") - private AllergyHibernateModel allergy; -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarRecipesHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarRecipesHibernateModel.java new file mode 100644 index 0000000..0ba4528 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarRecipesHibernateModel.java @@ -0,0 +1,45 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.CalendarRecipe; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity(name = "user_calendar_recipes") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserCalendarRecipesHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_calendar_id", referencedColumnName = "id") + private UserCalendarsHibernateModel calendar; + + @ManyToOne + @JoinColumn(name = "recipe_id", referencedColumnName = "id") + private RecipeHibernateModel recipe; + + @ManyToOne + @JoinColumn(name = "meal_type_id", referencedColumnName = "id") + private MealTypeHibernateModel mealType; + + public CalendarRecipe toDomain() { + return CalendarRecipe.builder() + .recipe(recipe.toDomain()) + .mealType(mealType.toDomain()) + .build(); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarsHibernateModel.java new file mode 100644 index 0000000..8f1820d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserCalendarsHibernateModel.java @@ -0,0 +1,46 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.Calendar; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +@Entity(name = "user_calendars") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserCalendarsHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private LocalDate plannedDate; + + @ManyToOne + @JoinColumn(name = "user_id", referencedColumnName = "id") + private UserHibernateModel user; + + @OneToMany(mappedBy = "calendar", cascade = CascadeType.ALL, orphanRemoval = true) + private List recipes; + + public Calendar toDomain() { + return Calendar.builder() + .id(id) + .date(plannedDate) + .recipes(recipes.stream().map(UserCalendarRecipesHibernateModel::toDomain).toList()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserHibernateModel.java index 32b8e23..d002f32 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserHibernateModel.java @@ -3,10 +3,14 @@ import com.cuoco.application.usecase.model.User; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import lombok.AllArgsConstructor; import lombok.Builder; @@ -14,12 +18,13 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; -@Entity(name = "user") +@Entity(name = "users") @Data +@Builder @NoArgsConstructor @AllArgsConstructor -@Builder public class UserHibernateModel { @Id @@ -36,6 +41,60 @@ public class UserHibernateModel { private LocalDateTime updatedAt; private LocalDateTime deletedAt; + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + private UserPreferencesHibernateModel preferences; + + @ManyToMany + @JoinTable( + name = "user_allergies", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "allergy_id") + ) + private List allergies; + + @ManyToMany + @JoinTable( + name = "user_dietary_needs", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "dietary_need_id") + ) + private List dietaryNeeds; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "user_recipes", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "recipe_id") + ) + private List recipes; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "user_meal_preps", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "meal_prep_id") + ) + private List mealPreps; + + @OneToMany(mappedBy = "user") + private List calendars; + + public static UserHibernateModel fromDomain(User user) { + return UserHibernateModel.builder() + .id(user.getId()) + .name(user.getName()) + .email(user.getEmail()) + .password(user.getPassword()) + .plan(PlanHibernateModel.fromDomain(user.getPlan())) + .active(user.getActive()) + .preferences(UserPreferencesHibernateModel.fromDomain(user.getPreferences())) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .dietaryNeeds(user.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList()) + .allergies(user.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList()) + .build(); + } + public User toDomain() { return User.builder() .id(id) @@ -44,6 +103,10 @@ public User toDomain() { .password(password) .plan(plan.toDomain()) .active(active) + .recipes(recipes != null ? recipes.stream().map(RecipeHibernateModel::toDomain).toList() : null) + .mealPreps(mealPreps != null ? mealPreps.stream().map(MealPrepHibernateModel::toDomain).toList() : null) + .allergies(allergies != null ? allergies.stream().map(AllergyHibernateModel::toDomain).toList() : null) + .dietaryNeeds(dietaryNeeds != null ? dietaryNeeds.stream().map(DietaryNeedHibernateModel::toDomain).toList() : null) .createdAt(createdAt) .build(); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserDietaryNeedsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserMealPrepHibernateModel.java similarity index 57% rename from src/main/java/com/cuoco/adapter/out/hibernate/model/UserDietaryNeedsHibernateModel.java rename to src/main/java/com/cuoco/adapter/out/hibernate/model/UserMealPrepHibernateModel.java index cb0732c..d2dd6d1 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserDietaryNeedsHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserMealPrepHibernateModel.java @@ -1,6 +1,6 @@ package com.cuoco.adapter.out.hibernate.model; -import jakarta.persistence.CascadeType; +import com.cuoco.application.usecase.model.UserMealPrep; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -12,22 +12,29 @@ import lombok.Data; import lombok.NoArgsConstructor; -@Entity(name = "user_dietary_needs") +@Entity(name = "user_meal_preps") @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class UserDietaryNeedsHibernateModel { +public class UserMealPrepHibernateModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne @JoinColumn(name = "user_id", referencedColumnName = "id") private UserHibernateModel user; - @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "dietary_need_id", referencedColumnName = "id") - private DietaryNeedHibernateModel dietaryNeed; -} \ No newline at end of file + @ManyToOne + @JoinColumn(name = "meal_prep_id", referencedColumnName = "id") + private MealPrepHibernateModel mealPrep; + + public UserMealPrep toDomain() { + return UserMealPrep.builder() + .user(user.toDomain()) + .mealPrep(mealPrep.toDomain()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPaymentsHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPaymentsHibernateModel.java new file mode 100644 index 0000000..7e9bc8f --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPaymentsHibernateModel.java @@ -0,0 +1,72 @@ +package com.cuoco.adapter.out.hibernate.model; + +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPayment; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity(name = "user_payments") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserPaymentsHibernateModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(name = "user_id", referencedColumnName = "id") + private UserHibernateModel user; + + @OneToOne + @JoinColumn(name = "to_plan_id") + private PlanHibernateModel toPlan; + + @OneToOne + @JoinColumn(name = "status_id") + private PaymentStatusHibernateModel status; + + private String externalId; + private String externalReference; + private String checkoutUrl; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private LocalDateTime deletedAt; + + public static UserPaymentsHibernateModel fromDomain(UserPayment userPayment) { + return UserPaymentsHibernateModel.builder() + .id(userPayment.getId()) + .user(UserHibernateModel.fromDomain(userPayment.getUser())) + .toPlan(PlanHibernateModel.fromDomain(userPayment.getPlan())) + .status(PaymentStatusHibernateModel.fromDomain(userPayment.getStatus())) + .externalId(userPayment.getExternalId()) + .externalReference(userPayment.getExternalReference()) + .checkoutUrl(userPayment.getCheckoutUrl()) + .build(); + } + + public UserPayment toDomain() { + return UserPayment.builder() + .id(id) + .user(user.toDomain()) + .plan(toPlan.toDomain()) + .status(status.toDomain()) + .externalId(externalId) + .externalReference(externalReference) + .checkoutUrl(checkoutUrl) + .build(); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPreferencesHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPreferencesHibernateModel.java index 1830e46..c0e9ce8 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPreferencesHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserPreferencesHibernateModel.java @@ -36,8 +36,17 @@ public class UserPreferencesHibernateModel { @JoinColumn(name = "diet_id", referencedColumnName = "id") private DietHibernateModel diet; + public static UserPreferencesHibernateModel fromDomain(UserPreferences userPreferences) { + return UserPreferencesHibernateModel.builder() + .id(userPreferences.getId()) + .cookLevel(CookLevelHibernateModel.fromDomain(userPreferences.getCookLevel())) + .diet(DietHibernateModel.fromDomain(userPreferences.getDiet())) + .build(); + } + public UserPreferences toDomain() { return UserPreferences.builder() + .id(id) .cookLevel(cookLevel.toDomain()) .diet(diet.toDomain()) .build(); diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserRecipesHibernateModel.java b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserRecipesHibernateModel.java index 976a05e..ae71122 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/model/UserRecipesHibernateModel.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/model/UserRecipesHibernateModel.java @@ -1,5 +1,6 @@ package com.cuoco.adapter.out.hibernate.model; +import com.cuoco.application.usecase.model.UserRecipe; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -30,5 +31,10 @@ public class UserRecipesHibernateModel { @JoinColumn(name = "recipe_id", referencedColumnName = "id") private RecipeHibernateModel recipe; - private Boolean favorite; + public UserRecipe toDomain() { + return UserRecipe.builder() + .user(user.toDomain()) + .recipe(recipe.toDomain()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllMealPrepsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllMealPrepsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..e69b9b5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllMealPrepsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateAllMealPrepsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllRecipesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllRecipesHibernateRepositoryAdapter.java new file mode 100644 index 0000000..a529432 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllRecipesHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateAllRecipesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllUserRecipeCalendarsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllUserRecipeCalendarsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..ea86a06 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateAllUserRecipeCalendarsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateAllUserRecipeCalendarsHibernateRepositoryAdapter extends JpaRepository {} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateIngredientHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateIngredientHibernateRepositoryAdapter.java new file mode 100644 index 0000000..8018d70 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateIngredientHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateIngredientHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeHibernateRepositoryAdapter.java new file mode 100644 index 0000000..33ff0c3 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CreateRecipeHibernateRepositoryAdapter extends JpaRepository { + Optional findByNameIgnoreCase(String name); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeImagesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeImagesHibernateRepositoryAdapter.java new file mode 100644 index 0000000..ddd0071 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeImagesHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateRecipeImagesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeIngredientsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeIngredientsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..06028a4 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateRecipeIngredientsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateRecipeIngredientsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserAllergiesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserAllergiesHibernateRepositoryAdapter.java deleted file mode 100644 index 7dc76e9..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserAllergiesHibernateRepositoryAdapter.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.cuoco.adapter.out.hibernate.repository; - -import com.cuoco.adapter.out.hibernate.model.UserAllergiesHibernateModel; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CreateUserAllergiesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserDietaryNeedsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserDietaryNeedsHibernateRepositoryAdapter.java deleted file mode 100644 index c14772c..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserDietaryNeedsHibernateRepositoryAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cuoco.adapter.out.hibernate.repository; - -import com.cuoco.adapter.out.hibernate.model.UserDietaryNeedsHibernateModel; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface CreateUserDietaryNeedsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserHibernateRepositoryAdapter.java index fd00757..9aa0da2 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserHibernateRepositoryAdapter.java @@ -1,11 +1,6 @@ package com.cuoco.adapter.out.hibernate.repository; -import com.cuoco.application.usecase.model.User; import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface CreateUserHibernateRepositoryAdapter extends JpaRepository { - UserHibernateModel save(User user); -} +public interface CreateUserHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserMealPrepHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserMealPrepHibernateRepositoryAdapter.java new file mode 100644 index 0000000..daad245 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserMealPrepHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateUserMealPrepHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPaymentHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPaymentHibernateRepositoryAdapter.java new file mode 100644 index 0000000..57e6afe --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPaymentHibernateRepositoryAdapter.java @@ -0,0 +1,7 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateUserPaymentHibernateRepositoryAdapter extends JpaRepository { +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPreferencesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPreferencesHibernateRepositoryAdapter.java index f54e236..480d00e 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPreferencesHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserPreferencesHibernateRepositoryAdapter.java @@ -2,9 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface CreateUserPreferencesHibernateRepositoryAdapter extends JpaRepository { - UserPreferencesHibernateModel save(UserPreferencesHibernateModel userPreferences); -} +public interface CreateUserPreferencesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserRecipeHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserRecipeHibernateRepositoryAdapter.java new file mode 100644 index 0000000..da1636f --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/CreateUserRecipeHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CreateUserRecipeHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..9f59a7d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DeleteAllUserRecipeCalendarsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserMealPrepsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserMealPrepsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..ac22f4b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserMealPrepsHibernateRepositoryAdapter.java @@ -0,0 +1,8 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DeleteUserMealPrepsHibernateRepositoryAdapter extends JpaRepository { + void deleteAllByUserIdAndMealPrepId(Long userId, Long mealPrepId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserRecipeHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserRecipeHibernateRepositoryAdapter.java new file mode 100644 index 0000000..c51f1b9 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/DeleteUserRecipeHibernateRepositoryAdapter.java @@ -0,0 +1,8 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DeleteUserRecipeHibernateRepositoryAdapter extends JpaRepository { + void deleteAllByUserIdAndRecipeId(Long userId, Long recipeId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/UserExistsByEmailHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserByEmailHibernateRepositoryAdapter.java similarity index 68% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/UserExistsByEmailHibernateRepositoryAdapter.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserByEmailHibernateRepositoryAdapter.java index d8eac8e..a2e5365 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/UserExistsByEmailHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserByEmailHibernateRepositoryAdapter.java @@ -2,9 +2,7 @@ import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface UserExistsByEmailHibernateRepositoryAdapter extends JpaRepository { +public interface ExistsUserByEmailHibernateRepositoryAdapter extends JpaRepository { Boolean existsByEmail(String email); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..80e92b7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter.java @@ -0,0 +1,8 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExistsUserMealPrepByUserIdAndMealPrepIdHibernateRepositoryAdapter extends JpaRepository { + boolean existsByUserIdAndMealPrepId(Long userId, Long mealPrepId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..a2ace98 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter.java @@ -0,0 +1,8 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter extends JpaRepository { + boolean existsByUserIdAndRecipeId(Long userId, Long recipeId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeCalendarHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeCalendarHibernateRepositoryAdapter.java new file mode 100644 index 0000000..85b1b31 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/ExistsUserRecipeCalendarHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; + +public interface ExistsUserRecipeCalendarHibernateRepositoryAdapter extends JpaRepository { + boolean existsByUserIdAndPlannedDateAndRecipesRecipeId(Long userId, LocalDate plannedDate, Long recipeId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipeByNameHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipeByNameHibernateRepositoryAdapter.java new file mode 100644 index 0000000..b96e9e3 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipeByNameHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FindRecipeByNameHibernateRepositoryAdapter extends JpaRepository { + Optional findByNameIgnoreCase(String name); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipesByFiltersHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipesByFiltersHibernateRepositoryAdapter.java new file mode 100644 index 0000000..d7a1b49 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindRecipesByFiltersHibernateRepositoryAdapter.java @@ -0,0 +1,14 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FindRecipesByFiltersHibernateRepositoryAdapter extends JpaRepository { + + @Query("SELECT r FROM recipes r WHERE r.id IN :ids") + List execute(@Param("ids") List ids); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserPreferencesByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserPreferencesByIdHibernateRepositoryAdapter.java deleted file mode 100644 index 59448fb..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserPreferencesByIdHibernateRepositoryAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cuoco.adapter.out.hibernate.repository; - -import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface FindUserPreferencesByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepositoryAdapter.java similarity index 51% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepositoryAdapter.java index 21faeeb..0af9de0 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllAllergiesHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface GetAllAllergiesHibernateRepository extends JpaRepository {} +public interface GetAllAllergiesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepositoryAdapter.java similarity index 51% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepositoryAdapter.java index 34f71ba..36662a0 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface GetCookLevelByIdHibernateRepository extends JpaRepository {} +public interface GetAllCookLevelsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepositoryAdapter.java similarity index 51% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepositoryAdapter.java index 35e67f2..c9a777a 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietaryNeedsHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface GetAllDietaryNeedsHibernateRepository extends JpaRepository {} +public interface GetAllDietaryNeedsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepositoryAdapter.java similarity index 63% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepositoryAdapter.java index 40b764a..ca61ad0 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllDietsHibernateRepositoryAdapter.java @@ -3,4 +3,4 @@ import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -public interface GetAllDietsHibernateRepository extends JpaRepository {} +public interface GetAllDietsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealPrepsByIdsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealPrepsByIdsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..a512237 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealPrepsByIdsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllMealPrepsByIdsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealTypesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealTypesHibernateRepositoryAdapter.java new file mode 100644 index 0000000..9595190 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllMealTypesHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllMealTypesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPaymentStatusHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPaymentStatusHibernateRepositoryAdapter.java new file mode 100644 index 0000000..2183d21 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPaymentStatusHibernateRepositoryAdapter.java @@ -0,0 +1,7 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.PaymentStatusHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllPaymentStatusHibernateRepositoryAdapter extends JpaRepository { +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepository.java deleted file mode 100644 index ca3fd06..0000000 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.cuoco.adapter.out.hibernate.repository; - -import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; -import com.cuoco.application.usecase.model.Plan; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface GetAllPlansHibernateRepository extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepositoryAdapter.java new file mode 100644 index 0000000..225999c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPlansHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllPlansHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPreparationTimesHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPreparationTimesHibernateRepositoryAdapter.java new file mode 100644 index 0000000..e4dc68c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllPreparationTimesHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllPreparationTimesHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllRecipesByIdsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllRecipesByIdsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..4c2ce60 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllRecipesByIdsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllRecipesByIdsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUnitsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUnitsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..b95f585 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUnitsHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetAllUnitsHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserCalendarsByUserIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserCalendarsByUserIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..1b100f9 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserCalendarsByUserIdHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserCalendarsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface GetAllUserCalendarsByUserIdHibernateRepositoryAdapter extends JpaRepository { + List findAllByUserId(Long id); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..2f1b338 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserMealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface GetAllUserMealPrepsByUserIdHibernateRepositoryAdapter extends JpaRepository { + List findByUserId(Long userId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserRecipesByUserIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserRecipesByUserIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..1292078 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllUserRecipesByUserIdHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface GetAllUserRecipesByUserIdHibernateRepositoryAdapter extends JpaRepository { + List findByUserId(Long userId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepositoryAdapter.java similarity index 51% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepositoryAdapter.java index 76b6405..522e1cc 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetAllCookLevelsHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetCookLevelByIdHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface GetAllCookLevelsHibernateRepository extends JpaRepository {} +public interface GetCookLevelByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepository.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepositoryAdapter.java similarity index 52% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepository.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepositoryAdapter.java index d254dc5..457006b 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepository.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetDietByIdHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface GetDietByIdHibernateRepository extends JpaRepository {} +public interface GetDietByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/IngredienteHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetIngredientByNameHibernateRepositoryAdapter.java similarity index 61% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/IngredienteHibernateRepositoryAdapter.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetIngredientByNameHibernateRepositoryAdapter.java index d13ac35..1b104bd 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/IngredienteHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetIngredientByNameHibernateRepositoryAdapter.java @@ -2,11 +2,9 @@ import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.Optional; -@Repository -public interface IngredienteHibernateRepositoryAdapter extends JpaRepository { +public interface GetIngredientByNameHibernateRepositoryAdapter extends JpaRepository { Optional findByName(String name); } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealPrepByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealPrepByIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..83a7742 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealPrepByIdHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetMealPrepByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealTypeByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealTypeByIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..257339a --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetMealTypeByIdHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetMealTypeByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPlanByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPlanByIdHibernateRepositoryAdapter.java index daf2ee0..c78e5d5 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPlanByIdHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPlanByIdHibernateRepositoryAdapter.java @@ -2,7 +2,5 @@ import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository public interface GetPlanByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPreparationTimeByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPreparationTimeByIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..c19d80b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetPreparationTimeByIdHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetPreparationTimeByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/RecetaHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeByIdHibernateRepositoryAdapter.java similarity index 62% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/RecetaHibernateRepositoryAdapter.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeByIdHibernateRepositoryAdapter.java index b44d7a7..d608a28 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/RecetaHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeByIdHibernateRepositoryAdapter.java @@ -3,5 +3,4 @@ import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; import org.springframework.data.jpa.repository.JpaRepository; -public interface RecetaHibernateRepositoryAdapter extends JpaRepository { -} \ No newline at end of file +public interface GetRecipeByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeIngredientsByRecipeIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeIngredientsByRecipeIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..2fd9fb6 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipeIngredientsByRecipeIdHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface GetRecipeIngredientsByRecipeIdHibernateRepositoryAdapter extends JpaRepository { + List findByRecipeId(Long id); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesByIngredientsAndFiltersHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesByIngredientsAndFiltersHibernateRepositoryAdapter.java new file mode 100644 index 0000000..cd61751 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesByIngredientsAndFiltersHibernateRepositoryAdapter.java @@ -0,0 +1,43 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface GetRecipesByIngredientsAndFiltersHibernateRepositoryAdapter extends JpaRepository { + + @Query(""" + SELECT DISTINCT r FROM recipes r + LEFT JOIN r.mealTypes mt + LEFT JOIN r.dietaryNeeds dn + WHERE r.id IN :recipeIds + AND (:preparationTimeId IS NULL OR r.preparationTime.id = :preparationTimeId) + AND (:cookLevelId IS NULL OR r.cookLevel.id = :cookLevelId) + AND (:mealTypesIds IS NULL OR mt.id IN :mealTypesIds) + AND (:dietId IS NULL OR r.diet.id = :dietId) + AND (:dietaryNeedIds IS NULL OR ( + SELECT COUNT(dn2) FROM recipes r2 + JOIN r2.dietaryNeeds dn2 + WHERE r2.id = r.id AND dn2.id IN :dietaryNeedIds + ) = :#{#dietaryNeedIds == null ? 0 : #dietaryNeedIds.size()}) + AND (:allergyIds IS NULL OR NOT EXISTS ( + SELECT 1 FROM recipes r2 + JOIN r2.allergies a2 + WHERE r2.id = r.id AND a2.id IN :allergyIds + )) + """) + List execute( + @Param("recipeIds") List recipeIds, + @Param("preparationTimeId") Integer preparationTimeId, + @Param("cookLevelId") Integer cookLevelId, + @Param("dietId") Integer dietId, + @Param("mealTypesIds") List mealTypesIds, + @Param("allergyIds") List allergyIds, + @Param("dietaryNeedIds") List dietaryNeedIds, + Pageable pageable + ); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesIdsByIngredientsHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesIdsByIngredientsHibernateRepositoryAdapter.java new file mode 100644 index 0000000..5ff2ef4 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesIdsByIngredientsHibernateRepositoryAdapter.java @@ -0,0 +1,41 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface GetRecipesIdsByIngredientsHibernateRepositoryAdapter extends JpaRepository { + + @Query(value = """ + SELECT r.id + FROM recipes r + JOIN recipe_ingredients ri ON ri.recipe_id = r.id + JOIN ingredients i ON i.id = ri.ingredient_id + WHERE LOWER(i.name) IN :ingredientNames + GROUP BY r.id + HAVING COUNT(DISTINCT LOWER(i.name)) = :ingredientCount + """, nativeQuery = true) + List execute( + @Param("ingredientNames") List ingredientNames, + @Param("ingredientCount") Integer ingredientCount + ); + + @Query(value = """ + SELECT r.id + FROM recipes r + JOIN recipe_ingredients ri ON ri.recipe_id = r.id + JOIN ingredients i ON i.id = ri.ingredient_id + WHERE LOWER(i.name) IN :ingredientNames + AND r.id NOT IN :notIncludeIds + GROUP BY r.id + HAVING COUNT(DISTINCT LOWER(i.name)) = :ingredientCount + """, nativeQuery = true) + List execute( + @Param("ingredientNames") List ingredientNames, + @Param("notIncludeIds") List notIncludeIds, + @Param("ingredientCount") Integer ingredientCount + ); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesMaxIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesMaxIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..d8291a7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetRecipesMaxIdHibernateRepositoryAdapter.java @@ -0,0 +1,11 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface GetRecipesMaxIdHibernateRepositoryAdapter extends JpaRepository { + + @Query("SELECT MAX(r.id) FROM recipes r") + Long execute(); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUnitBySymbolHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUnitBySymbolHibernateRepositoryAdapter.java new file mode 100644 index 0000000..95bac44 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUnitBySymbolHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface GetUnitBySymbolHibernateRepositoryAdapter extends JpaRepository { + Optional findBySymbolEqualsIgnoreCase(String symbol); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserByEmailHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByEmailHibernateRepositoryAdapter.java similarity index 71% rename from src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserByEmailHibernateRepositoryAdapter.java rename to src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByEmailHibernateRepositoryAdapter.java index f13e7eb..7d677a5 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/repository/FindUserByEmailHibernateRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByEmailHibernateRepositoryAdapter.java @@ -5,6 +5,6 @@ import java.util.Optional; -public interface FindUserByEmailHibernateRepositoryAdapter extends JpaRepository { +public interface GetUserByEmailHibernateRepositoryAdapter extends JpaRepository { Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..d399b18 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserByIdHibernateRepositoryAdapter.java @@ -0,0 +1,6 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GetUserByIdHibernateRepositoryAdapter extends JpaRepository {} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPaymentByExternalReferenceHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPaymentByExternalReferenceHibernateRepositoryAdapter.java new file mode 100644 index 0000000..5a14ae5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPaymentByExternalReferenceHibernateRepositoryAdapter.java @@ -0,0 +1,12 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserPaymentsHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface GetUserPaymentByExternalReferenceHibernateRepositoryAdapter extends JpaRepository { + + Optional findByExternalReference(String externalReference); + +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPreferencesByUserIdHibernateRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPreferencesByUserIdHibernateRepositoryAdapter.java new file mode 100644 index 0000000..1cefdef --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/repository/GetUserPreferencesByUserIdHibernateRepositoryAdapter.java @@ -0,0 +1,10 @@ +package com.cuoco.adapter.out.hibernate.repository; + +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface GetUserPreferencesByUserIdHibernateRepositoryAdapter extends JpaRepository { + Optional getByUserId(Long userId); +} diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/utils/Constants.java b/src/main/java/com/cuoco/adapter/out/hibernate/utils/Constants.java new file mode 100644 index 0000000..d364a12 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/hibernate/utils/Constants.java @@ -0,0 +1,27 @@ +package com.cuoco.adapter.out.hibernate.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Constants { + + RESULT_SIZE("RESULT_SIZE"), + INGREDIENT_NAMES("INGREDIENT_NAMES"), + INGREDIENT_COUNT("INGREDIENT_COUNT"), + PREPARATION_TIME_ID("PREPARATION_TIME_ID"), + COOK_LEVEL_ID("COOK_LEVEL_ID"), + DIET_ID("DIET_ID"), + ALLERGY_IDS("ALLERGY_IDS"), + ALLERGY_IDS_IS_EMPTY("ALLERGY_IDS_IS_EMPTY"), + DIETARY_NEEDS_IDS("DIETARY_NEEDS_IDS"), + DIETARY_NEEDS_IDS_IS_EMPTY("DIETARY_NEEDS_IDS_IS_EMPTY"), + DIETARY_NEEDS_COUNT("DIETARY_NEEDS_COUNT"), + NOT_INCLUDE_IDS("NOT_INCLUDE_IDS"), + NOT_INCLUDE_IDS_IS_EMPTY("NOT_INCLUDE_IDS_IS_EMPTY"), + MEAL_TYPES_IDS_IS_EMPTY("MEAL_TYPES_IDS_IS_EMPTY"), + MEAL_TYPES_IDS("MEAL_TYPES_IDS"); + + private final String value; +} diff --git a/src/main/java/com/cuoco/adapter/out/mercadopago/CreateUserPaymentMercadoPagoRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/mercadopago/CreateUserPaymentMercadoPagoRepositoryAdapter.java new file mode 100644 index 0000000..d26c5ea --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/mercadopago/CreateUserPaymentMercadoPagoRepositoryAdapter.java @@ -0,0 +1,126 @@ +package com.cuoco.adapter.out.mercadopago; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.application.port.out.CreateUserPaymentRepository; +import com.cuoco.application.usecase.model.PaymentStatus; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.application.usecase.model.PlanConfiguration; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPayment; +import com.cuoco.shared.config.mercadopago.MercadoPagoConfig; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.Constants; +import com.cuoco.shared.utils.PaymentConstants; +import com.mercadopago.client.preference.PreferenceBackUrlsRequest; +import com.mercadopago.client.preference.PreferenceClient; +import com.mercadopago.client.preference.PreferenceItemRequest; +import com.mercadopago.client.preference.PreferenceRequest; +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.resources.preference.Preference; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Component +@Qualifier("provider") +@RequiredArgsConstructor +public class CreateUserPaymentMercadoPagoRepositoryAdapter implements CreateUserPaymentRepository { + + private static final String EXTERNAL_REFERENCE_PREFIX = "CUOCO_PRO_UPGRADE"; + private static final String HOST_DOMAIN = "cuoco.com.ar"; + private static final String API_CONTEXT = "/api"; + + private final HttpServletRequest request; + private final MercadoPagoConfig config; + + @Override + public UserPayment execute(UserPayment userPayment) { + + com.mercadopago.MercadoPagoConfig.setAccessToken(config.getAccessToken()); + + User user = userPayment.getUser(); + Plan plan = userPayment.getPlan(); + + log.info("Executing MercadoPago preference payment for user ID {} and plan ID {}", user.getId(), plan.getId()); + + try { + PreferenceRequest request = buildPreference(userPayment); + + PreferenceClient client = new PreferenceClient(); + + Preference preference = client.create(request); + + UserPayment response = buildResponse(userPayment, preference); + + log.info("Payment created with external ID {}", response.getExternalId()); + + return response; + + } catch (MPException e) { + log.error("Error while creating preference in MercadoPago SDK", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } catch (MPApiException e) { + log.error("Failed to create preference payment with MercadoPago API", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String generateExternalReference(Long userId) { + return EXTERNAL_REFERENCE_PREFIX + .concat(Constants.UNDERSCORE.getValue()) + .concat(userId.toString()) + .concat(Constants.UNDERSCORE.getValue()) + .concat(UUID.randomUUID().toString().substring(0, 8)); + } + + private PreferenceRequest buildPreference(UserPayment userPayment) { + return PreferenceRequest.builder() + .items(List.of(buildItems(userPayment.getPlan()))) + .externalReference(generateExternalReference(userPayment.getUser().getId())) + .backUrls(buildBackUrls()) + .autoReturn("approved") + .build(); + } + + private PreferenceBackUrlsRequest buildBackUrls() { + + String baseUrl = request.getRequestURL().toString().replace(request.getRequestURI(), Constants.EMPTY.getValue()); + + return PreferenceBackUrlsRequest.builder() + .success(baseUrl + config.getCallbacks().getSuccess()) + .pending(baseUrl + config.getCallbacks().getPending()) + .failure(baseUrl + config.getCallbacks().getFailure()) + .build(); + } + + private PreferenceItemRequest buildItems(Plan plan) { + PlanConfiguration planConfiguration = plan.getConfiguration(); + + return PreferenceItemRequest.builder() + .title(planConfiguration.getTitle()) + .description(planConfiguration.getDescription()) + .quantity(planConfiguration.getQuantity()) + .unitPrice(planConfiguration.getPrice()) + .currencyId(planConfiguration.getCurrency()) + .build(); + } + + private UserPayment buildResponse(UserPayment request, Preference preference) { + return UserPayment.builder() + .user(request.getUser()) + .plan(request.getPlan()) + .status(PaymentStatus.builder().id(PaymentConstants.STATUS_PENDING.getValue()).build()) + .externalId(preference.getId()) + .checkoutUrl(preference.getInitPoint()) + .externalReference(preference.getExternalReference()) + .build(); + + } +} diff --git a/src/main/java/com/cuoco/adapter/out/mercadopago/ProcessUserPaymentMercadoPagoRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/mercadopago/ProcessUserPaymentMercadoPagoRepositoryAdapter.java new file mode 100644 index 0000000..4287759 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/mercadopago/ProcessUserPaymentMercadoPagoRepositoryAdapter.java @@ -0,0 +1,46 @@ +package com.cuoco.adapter.out.mercadopago; + +import com.cuoco.adapter.exception.UnauthorizedException; +import com.cuoco.application.port.out.ProcessUserPaymentRepository; +import com.cuoco.application.usecase.model.PaymentStatus; +import com.cuoco.application.usecase.model.UserPayment; +import com.cuoco.shared.config.mercadopago.MercadoPagoConfig; +import com.cuoco.shared.model.ErrorDescription; +import com.mercadopago.client.payment.PaymentClient; +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.resources.payment.Payment; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +@RequiredArgsConstructor +public class ProcessUserPaymentMercadoPagoRepositoryAdapter implements ProcessUserPaymentRepository { + + private final MercadoPagoConfig config; + + @Override + public UserPayment execute(String paymentId) { + + com.mercadopago.MercadoPagoConfig.setAccessToken(config.getAccessToken()); + + try { + PaymentClient client = new PaymentClient(); + Payment payment = client.get(Long.parseLong(paymentId)); + + UserPayment response = UserPayment.builder() + .status(PaymentStatus.builder().description(payment.getStatus()).build()) + .externalReference(payment.getExternalReference()) + .build(); + + log.info("Payment info received with status={} and external_reference={}", response.getStatus(), response.getExternalReference()); + + return response; + } catch (MPException | MPApiException e) { + log.error("Error fetching payment details from MercadoPago", e); + throw new UnauthorizedException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java deleted file mode 100644 index 4bbdcf5..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.cuoco.adapter.out.rest; - -import com.cuoco.adapter.out.rest.model.gemini.ContentGeminiRequestModel; -import com.cuoco.adapter.out.rest.model.gemini.GenerationConfigurationGeminiRequestModel; -import com.cuoco.adapter.out.rest.model.gemini.PartGeminiRequestModel; -import com.cuoco.adapter.out.rest.model.gemini.PromptBodyGeminiRequestModel; -import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; -import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.shared.FileReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.List; -import java.util.stream.Collectors; - -@Component -public class GetRecipesFromIngredientsGeminiRestRepositoryAdapter implements GetRecipesFromIngredientsRepository { - - static final Logger log = LoggerFactory.getLogger(GetRecipesFromIngredientsGeminiRestRepositoryAdapter.class); - - private final String PROMPT = FileReader.execute("prompt/generateRecipe.txt"); - - @Value("${gemini.api.url}") - private String url; - - @Value("${gemini.api.key}") - private String apiKey; - - private final RestTemplate restTemplate; - - public GetRecipesFromIngredientsGeminiRestRepositoryAdapter(RestTemplate restTemplate) { - this.restTemplate = restTemplate; - } - - @Override - public String execute(List ingredients) { - - log.info("Executing get recipes from gemini rest adapter with ingredients: {}", ingredients); - - PromptBodyGeminiRequestModel prompt = buildPromptBody(ingredients); - - String geminiUrl = url + "?key=" + apiKey; - - String response = restTemplate.postForObject(geminiUrl, prompt, String.class); - - log.info("Successfully generated recipes from Gemini"); - - return response; - } - - private PromptBodyGeminiRequestModel buildPromptBody(List ingredients) { - return new PromptBodyGeminiRequestModel( - buildContentRequest(ingredients), - new GenerationConfigurationGeminiRequestModel( - 0.4 - ) - ); - } - - private List buildContentRequest(List ingredients) { - return List.of( - new ContentGeminiRequestModel(buildPartsRequest(ingredients)) - ); - } - - private List buildPartsRequest(List ingredients) { - - String ingredientNames = ingredients.stream().map(Ingredient::getName).collect(Collectors.joining(",")); - - return List.of( - new PartGeminiRequestModel( - null, - PROMPT.formatted(ingredientNames) - ) - ); - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapter.java new file mode 100644 index 0000000..b36bac5 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapter.java @@ -0,0 +1,106 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.RecipeResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.CreateRecipeByNameRepository; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Slf4j +@Component +public class CreateRecipeByNameGeminiRestRespositoryAdapter implements CreateRecipeByNameRepository { + + private final String BASIC_PROMPT = FileReader.execute("prompt/generaterecipes/generateRecipeFromNameHeaderPrompt.txt"); + private final String PARAMETRIC_PROMPT = FileReader.execute("prompt/generaterecipes/generateRecipeParametricDataPrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate; + + public CreateRecipeByNameGeminiRestRespositoryAdapter(ObjectMapper objectMapper, RestTemplate restTemplate) { + this.objectMapper = objectMapper; + this.restTemplate = restTemplate; + } + + @Override + public Recipe execute(String name, ParametricData parametricData) { + try { + log.info("Executing quick recipe generation from Gemini rest adapter with name: {}", name); + + String basicPrompt = BASIC_PROMPT.replace(Constants.RECIPE_NAME.getValue(), name); + + String basicWithParametricPrompt = basicPrompt.concat(buildParametricPrompt(parametricData)); + + String geminiUrl = url + "?key=" + apiKey; + GeminiResponseModel response = restTemplate.postForObject( + geminiUrl, + Utils.buildPromptBody(basicWithParametricPrompt, temperature), + GeminiResponseModel.class + ); + + if(response == null) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + String recipeResponseText = Utils.sanitizeJsonResponse(response); + + List recipesResponseFromGemini = objectMapper.readValue( + recipeResponseText, + new TypeReference<>() {} + ); + + if(recipesResponseFromGemini.isEmpty()) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + Recipe recipeResponse = recipesResponseFromGemini.get(0).toDomain(); + + log.info("Generated recipe from Gemini successfully. Recipe: {}", recipeResponse); + + return recipeResponse; + } catch (JsonProcessingException e) { + log.error("Failed to convert some properties to JSON. ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } catch (UnprocessableException e) { + log.error("Failed to get response from Gemini: ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } catch (Exception e) { + log.error("Error getting recipes from ingredients in Gemini. ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildParametricPrompt(ParametricData parametricData) throws JsonProcessingException { + return PARAMETRIC_PROMPT + .replace(Constants.PARAMETRIC_UNITS.getValue(), objectMapper.writeValueAsString(parametricData.getUnits())) + .replace(Constants.PARAMETRIC_PREPARATION_TIMES.getValue(), objectMapper.writeValueAsString(parametricData.getPreparationTimes())) + .replace(Constants.PARAMETRIC_COOK_LEVELS.getValue(), objectMapper.writeValueAsString(parametricData.getCookLevels())) + .replace(Constants.PARAMETRIC_DIETS.getValue(), objectMapper.writeValueAsString(parametricData.getDiets())) + .replace(Constants.PARAMETRIC_MEAL_TYPES.getValue(), objectMapper.writeValueAsString(parametricData.getMealTypes())) + .replace(Constants.PARAMETRIC_ALLERGIES.getValue(), objectMapper.writeValueAsString(parametricData.getAllergies())) + .replace(Constants.PARAMETRIC_DIETARY_NEEDS.getValue(), objectMapper.writeValueAsString(parametricData.getDietaryNeeds())); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..24bd122 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter.java @@ -0,0 +1,40 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.application.port.out.GetIngredientsFromAudioAsyncRepository; +import com.cuoco.application.port.out.GetIngredientsFromAudioRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Component +public class GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter implements GetIngredientsFromAudioAsyncRepository { + + private final GetIngredientsFromAudioRepository getIngredientsFromAudioRepository; + + public GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter(GetIngredientsFromAudioRepository getIngredientsFromAudioRepository) { + this.getIngredientsFromAudioRepository = getIngredientsFromAudioRepository; + } + + @Async + @Override + public CompletableFuture> execute(String audioBase64, String format, String language) { + log.info("Executing asynchronous voice processing in Gemini with format {} and language {}", format, language); + + return CompletableFuture.supplyAsync(() -> { + try { + return getIngredientsFromAudioRepository.execute(audioBase64, format, language); + } catch (Exception e) { + log.error("Async error processing voice: {}", e.getMessage(), e); + throw new UnprocessableException(ErrorDescription.AUDIO_FILE_PROCESSING_ERROR.getValue()); + } + }); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..fb2fa01 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapter.java @@ -0,0 +1,103 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.InlineDataGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.GetIngredientsFromAudioRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.utils.FileUtils; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +@Slf4j +@Component +public class GetIngredientsFromAudioGeminiRestRepositoryAdapter implements GetIngredientsFromAudioRepository { + + private final String VOICE_PROMPT = FileReader.execute("prompt/recognizeIngredientsFromAudioPrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final RestTemplate restTemplate; + + public GetIngredientsFromAudioGeminiRestRepositoryAdapter(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public List execute(String audioBase64, String format, String language) { + log.info("Executing voice processing with Gemini with format {} and language {}", format, language); + + try { + PromptBodyGeminiRequestModel request = buildPromptBody(audioBase64, format, VOICE_PROMPT); + + String geminiUrl = url + "?key=" + apiKey; + + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, request, GeminiResponseModel.class); + + String recipeResponseText = Utils.sanitizeJsonResponse(response); + + ObjectMapper mapper = new ObjectMapper(); + + List ingredientsResponse = mapper.readValue( + recipeResponseText, + new TypeReference<>() {} + ); + + List ingredients = ingredientsResponse.stream().map(IngredientResponseGeminiModel::toDomain).toList(); + + log.info("Successfully extracted {} ingredients from audio", ingredients.size()); + + return ingredients; + } catch (Exception e) { + log.error("Error processing voice with Gemini: {}", e.getMessage(), e); + throw new UnprocessableException("Error processing voice: " + e.getMessage()); + } + } + + private PromptBodyGeminiRequestModel buildPromptBody(String audioBase64, String format, String prompt) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(audioBase64, format, prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().temperature(temperature).build()) + .build(); + } + + private List buildPartsRequest(String audioBase64, String format, String prompt) { + return List.of( + PartGeminiRequestModel.builder() + .text(prompt) + .build(), + PartGeminiRequestModel.builder() + .inlineData(buildInlineData(audioBase64, format)) + .build() + ); + } + + private InlineDataGeminiRequestModel buildInlineData(String audioBase64, String format) { + String mimeType = FileUtils.getAudioMimeType(format); + + return InlineDataGeminiRequestModel.builder() + .mimeType(mimeType) + .data(audioBase64) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapter.java new file mode 100644 index 0000000..05111a1 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapter.java @@ -0,0 +1,112 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.InlineDataGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.GetIngredientsFromImageRepository; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Component +public class GetIngredientsFromImageGeminiRestImageRepositoryAdapter implements GetIngredientsFromImageRepository { + + private final static String SOURCE = "image"; + private final String PROMPT = FileReader.execute("prompt/recognizeIngredientsFromImagePrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final RestTemplate restTemplate; + + public GetIngredientsFromImageGeminiRestImageRepositoryAdapter(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + @Override + public List execute(List files) { + log.info("Getting all ingredients processed by Gemini from images"); + + List ingredients = new ArrayList<>(); + + try { + for (File file : files) { + PromptBodyGeminiRequestModel prompt = buildPromptBody(file.getFileBase64(), file.getMimeType(), PROMPT); + String geminiUrl = url + "?key=" + apiKey; + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, prompt, GeminiResponseModel.class); + + String recipeResponseText = Utils.sanitizeJsonResponse(response); + + ObjectMapper mapper = new ObjectMapper(); + + List ingredientsResponse = mapper.readValue( + recipeResponseText, + new TypeReference<>() {} + ); + + List ingredientsFromImage = ingredientsResponse.stream().map(IngredientResponseGeminiModel::toDomain).toList(); + + log.info("Extracted {} ingredients from image {}", ingredientsFromImage.size(), file.getFileName()); + + ingredients.addAll(ingredientsFromImage); + } + + ingredients.forEach(ingredient -> ingredient.setSource(SOURCE)); + + log.info("Successfully got all {} ingredients from images processed by Gemini", ingredients.size()); + + return ingredients; + } catch (Exception e) { + log.error("Error sending images to Gemini to process ingredients: ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private PromptBodyGeminiRequestModel buildPromptBody(String imageBase64, String mimeType, String prompt) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(imageBase64, mimeType, prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().temperature(temperature).build()) + .build(); + } + + private List buildPartsRequest(String imageBase64, String mimeType, String prompt) { + return List.of( + PartGeminiRequestModel.builder() + .text(prompt) + .build(), + PartGeminiRequestModel.builder() + .inlineData(buildInlineData(imageBase64, mimeType)) + .build() + ); + } + + private InlineDataGeminiRequestModel buildInlineData(String imageBase64, String mimeType) { + return InlineDataGeminiRequestModel.builder() + .mimeType(mimeType) + .data(imageBase64) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter.java new file mode 100644 index 0000000..cf93fa2 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter.java @@ -0,0 +1,125 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.InlineDataGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.GetIngredientsGroupedFromImagesRepository; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +public class GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter implements GetIngredientsGroupedFromImagesRepository { + + private final static String SOURCE = "image"; + private final String PROMPT = FileReader.execute("prompt/recognizeIngredientsFromImagePrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate; + + public GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter( + ObjectMapper objectMapper, + RestTemplate restTemplate + ) { + this.objectMapper = objectMapper; + this.restTemplate = restTemplate; + } + + @Override + public Map> execute(List files, ParametricData parametricData) { + log.info("Getting ingredients from Gemini grouped by image"); + + Map> ingredientsByImage = new LinkedHashMap<>(); + + try { + String promptWithData = PROMPT.replace(Constants.PARAMETRIC_UNITS.getValue(), objectMapper.writeValueAsString(parametricData.getUnits())); + + for (File file : files) { + PromptBodyGeminiRequestModel prompt = buildPromptBody(file.getFileBase64(), file.getMimeType(), promptWithData); + String geminiUrl = url + "?key=" + apiKey; + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, prompt, GeminiResponseModel.class); + + String recipeResponseText = Utils.sanitizeJsonResponse(response); + + ObjectMapper mapper = new ObjectMapper(); + + List ingredientsResponse = mapper.readValue( + recipeResponseText, + new TypeReference<>() {} + ); + + List ingredientsFromImage = ingredientsResponse.stream().map(IngredientResponseGeminiModel::toDomain).toList(); + + ingredientsFromImage.forEach(ingredient -> ingredient.setSource(SOURCE)); + + log.info("Extracted {} ingredients from image {}", ingredientsFromImage.size(), file.getFileName()); + + ingredientsByImage.put(file.getFileName(), ingredientsFromImage); + } + + log.info("Successfully got all ingredients grouped by image processed by Gemini"); + return ingredientsByImage; + } catch (JsonProcessingException e) { + log.error("Failed to process JSON: ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } catch (Exception e) { + log.error("Error sending images to Gemini to process ingredients: ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private PromptBodyGeminiRequestModel buildPromptBody(String imageBase64, String mimeType, String prompt) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(imageBase64, mimeType, prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().temperature(temperature).build()) + .build(); + } + + private List buildPartsRequest(String imageBase64, String mimeType, String prompt) { + return List.of( + PartGeminiRequestModel.builder() + .text(prompt) + .build(), + PartGeminiRequestModel.builder() + .inlineData(buildInlineData(imageBase64, mimeType)) + .build() + ); + } + + private InlineDataGeminiRequestModel buildInlineData(String imageBase64, String mimeType) { + return InlineDataGeminiRequestModel.builder() + .mimeType(mimeType) + .data(imageBase64) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..66f035c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter.java @@ -0,0 +1,160 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.MealPrepResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.RecipeRequestGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.GetMealPrepsFromIngredientsRepository; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +@Qualifier("provider") +public class GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter implements GetMealPrepsFromIngredientsRepository { + + private final String BASIC_PROMPT = FileReader.execute("prompt/generateMealPrep/generateMealPrepFromIngredientsHeaderPrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate; + + public GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter(ObjectMapper objectMapper, RestTemplate restTemplate) { + this.objectMapper = objectMapper; + this.restTemplate = restTemplate; + } + + @Override + public List execute(MealPrep mealPrep) { + try { + log.info("Executing meal prep generation from Gemini with ingredients: {}", mealPrep.getIngredients()); + + String recipesJson = buildRecipesJson(mealPrep.getRecipes()); + String mealPrepsToNotInclude = buildMealPrepsToNotInclude(mealPrep.getConfiguration().getNotInclude()); + String parametricUnits = objectMapper.writeValueAsString(mealPrep.getConfiguration().getParametricData().getUnits()); + + String basicPrompt = BASIC_PROMPT + .replace(Constants.RECIPES.getValue(), recipesJson) + .replace(Constants.NOT_INCLUDE.getValue(), mealPrepsToNotInclude) + .replace(Constants.MAX_MEAL_PREPS.getValue(), mealPrep.getConfiguration().getSize().toString()) + .replace(Constants.FREEZE.getValue(), mealPrep.getFilters().getFreeze().toString()) + .replace(Constants.PARAMETRIC_UNITS.getValue(), parametricUnits); + + PromptBodyGeminiRequestModel prompt = buildPromptBody(basicPrompt); + + String geminiUrl = url + "?key=" + apiKey; + + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, prompt, GeminiResponseModel.class); + + if (response == null) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + String sanitizedResponse = Utils.sanitizeJsonResponse(response); + ObjectMapper mapper = new ObjectMapper(); + + List mealPrepResponses = mapper.readValue( + sanitizedResponse, + new TypeReference<>() {} + ); + + List mealPreps = mealPrepResponses.stream() + .map(mp -> buildMealPrepResponse(mp, mealPrep.getRecipes())) + .collect(Collectors.toList()); + + + log.info("Generated {} meal preps from Gemini successfully", mealPreps.size()); + + return mealPreps; + } catch (JsonProcessingException e) { + log.error("Failed to convert JSON in meal preps gemini adapter. ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } catch (Exception e) { + log.error("Error generating meal preps from Gemini", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildRecipesJson(List recipes) throws JsonProcessingException { + List requests = recipes.stream().map(this::buildRecipeRequest).toList(); + return objectMapper.writeValueAsString(requests); + } + + private RecipeRequestGeminiModel buildRecipeRequest(Recipe recipe) { + return RecipeRequestGeminiModel.builder() + .id(recipe.getId()) + .name(recipe.getName()) + .subtitle(recipe.getSubtitle()) + .description(recipe.getDescription()) + .preparationTime(recipe.getPreparationTime().getDescription()) + .cookLevelName(recipe.getCookLevel().getDescription()) + .dietName(recipe.getDiet().getDescription()) + .mealTypesNames(recipe.getMealTypes().stream().map(MealType::getDescription).toList()) + .allergiesNames(recipe.getAllergies().stream().map(Allergy::getDescription).toList()) + .dietaryNeedsNames(recipe.getDietaryNeeds().stream().map(DietaryNeed::getDescription).toList()) + .ingredientNames(recipe.getIngredients().stream().map(Ingredient::getName).toList()) + .build(); + } + + private String buildMealPrepsToNotInclude(List mealPrepsToNotInclude) throws JsonProcessingException { + List notInclude = mealPrepsToNotInclude.stream().map(MealPrep::getTitle).toList(); + return objectMapper.writeValueAsString(notInclude); + } + + private PromptBodyGeminiRequestModel buildPromptBody(String prompt) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().temperature(temperature).build()) + .build(); + } + + private List buildPartsRequest(String prompt) { + return List.of(PartGeminiRequestModel.builder().text(prompt).build()); + } + + private MealPrep buildMealPrepResponse(MealPrepResponseGeminiModel mealPrepGeminiResponse, List recipes) { + MealPrep mealPrepResponse = mealPrepGeminiResponse.toDomain(); + + List recipeId = mealPrepGeminiResponse.getRecipeIds(); + + List filteredRecipes = recipes.stream() + .filter(recipe -> recipeId.contains(recipe.getId())) + .toList(); + + mealPrepResponse.setRecipes(filteredRecipes); + + return mealPrepResponse; + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..fff39e6 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapter.java @@ -0,0 +1,107 @@ +package com.cuoco.adapter.out.rest.gemini; + +import autovalue.shaded.kotlin.Pair; +import com.cuoco.adapter.exception.ConflictException; +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.ImageUtils; +import com.cuoco.application.port.out.GenerateRecipeMainImageRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.FileUtils; +import com.cuoco.shared.utils.ImageConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; +import org.springframework.web.client.RestTemplate; + +import java.util.stream.Collectors; + +@Slf4j +@Repository +public class GetRecipeMainImageGeminiRestRepositoryAdapter implements GenerateRecipeMainImageRepository { + + private final String DELIMITER = com.cuoco.shared.utils.Constants.COMMA.getValue(); + + private final String MAIN_IMAGE_PROMPT = FileReader.execute("prompt/generateimages/generateRecipeImagePrompt.txt"); + + @Value("${gemini.image.url}") + private String imageUrl; + + @Value("${gemini.api.key}") + private String apiKey; + + private final RestTemplate restTemplate; + private final ImageUtils imageUtils; + + public GetRecipeMainImageGeminiRestRepositoryAdapter(RestTemplate restTemplate, ImageUtils imageUtils) { + this.restTemplate = restTemplate; + this.imageUtils = imageUtils; + } + + @Override + public boolean execute(Recipe recipe) { + log.info("Generating main image for recipe with ID {}", recipe.getId()); + + try { + if(imageUtils.imageExists(recipe.getId(), ImageConstants.MAIN_IMAGE_NAME.getValue())) { + throw new ConflictException("Main image for recipe with ID " + recipe.getId() + " already exists"); + } + + String mainIngredients = recipe.getIngredients().stream() + .map(Ingredient::getName) + .collect(Collectors.joining(DELIMITER)); + + String prompt = MAIN_IMAGE_PROMPT + .replace(Constants.RECIPE_NAME.getValue(), recipe.getName()) + .replace(Constants.MAIN_INGREDIENTS.getValue(), mainIngredients); + + Pair imageData = sendToGemini(prompt); + + if (imageData != null) { + log.info("Generated main image. Saving file in recipe folder."); + + String imageName = ImageConstants.MAIN_IMAGE_NAME.getValue() + FileUtils.getImageFormat(imageData.getFirst()); + + imageUtils.saveImageFile(imageData.getSecond(), recipe.getId(), imageName); + + log.info("Successfully generated and saved main image for recipe with ID {}", recipe.getId()); + + return true; + } else { + log.warn("Failed to generate main image for recipe with ID {}", recipe.getId()); + return false; + } + } catch (ConflictException e) { + log.info("Main image for recipe with ID {} already exists", recipe.getId()); + return true; + } catch (Exception e) { + log.error("Error generating images for recipe with ID {}", recipe.getId(), e); + throw new NotAvailableException("Could not generate main image for recipe with ID " + recipe.getId()); + } + } + + private Pair sendToGemini(String prompt) { + try { + PromptBodyGeminiRequestModel requestBody = imageUtils.buildPromptBody(prompt); + + String geminiUrl = imageUrl + "?key=" + apiKey; + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, requestBody, GeminiResponseModel.class); + + if (response == null) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + return imageUtils.extractImageFromResponse(response); + + } catch (Exception e) { + log.error("Error calling Gemini image generation API: ", e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeStepsImagesGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeStepsImagesGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..95cd025 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipeStepsImagesGeminiRestRepositoryAdapter.java @@ -0,0 +1,107 @@ +package com.cuoco.adapter.out.rest.gemini; + +import autovalue.shaded.kotlin.Pair; +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.ImageUtils; +import com.cuoco.application.port.out.GetRecipeStepsImagesRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.FileUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; + +import static com.cuoco.shared.utils.ImageConstants.STEP_INFIX; + +@Slf4j +@Repository +public class GetRecipeStepsImagesGeminiRestRepositoryAdapter implements GetRecipeStepsImagesRepository { + + private final String STEP_IMAGE_PROMPT = FileReader.execute("prompt/generateimages/generateStepImagePrompt.txt"); + + @Value("${gemini.image.url}") + private String imageUrl; + + @Value("${gemini.api.key}") + private String apiKey; + + private final RestTemplate restTemplate; + private final ImageUtils imageUtils; + + public GetRecipeStepsImagesGeminiRestRepositoryAdapter(RestTemplate restTemplate, ImageUtils imageUtils) { + this.restTemplate = restTemplate; + this.imageUtils = imageUtils; + } + + @Override + public List execute(Recipe recipe) { + log.info("Generating steps images for recipe with ID {}", recipe.getId()); + + List imagesCreated = new ArrayList<>(); + + try { + List stepImages = recipe.getImages().stream().map(stepImage -> buildStepImage(recipe.getId(), stepImage)).toList(); + + if(!stepImages.isEmpty()) { + imagesCreated.addAll(stepImages); + } + + log.info("Generated {} steps images for recipe with ID {}", imagesCreated.size(), recipe.getId()); + + return imagesCreated; + + } catch (Exception e) { + log.error("Error generating steps images for recipe: {}", recipe.getName(), e); + throw new NotAvailableException("Could not generate steps images for recipe: " + recipe.getName()); + } + } + + private Step buildStepImage(Long recipeId, Step stepImage) { + String prompt = STEP_IMAGE_PROMPT.replace(Constants.STEP_INSTRUCTION.getValue(), stepImage.getDescription()); + + Pair imageData = sendToGemini(prompt); + + if (imageData != null) { + log.info("Recipe ID {}: Generated image for step {}. Saving file in recipe folder.", recipeId, stepImage.getNumber()); + + String imageName = recipeId + STEP_INFIX.getValue() + stepImage.getNumber() + FileUtils.getImageFormat(imageData.getFirst()); + + imageUtils.saveImageFile(imageData.getSecond(), recipeId, imageName); + + stepImage.setImageName(imageName); + + return stepImage; + } else { + log.warn("Failed to create step {} image for recipe with ID {}. Skipping", stepImage.getNumber(), recipeId); + return null; + } + } + + private Pair sendToGemini(String prompt) { + try { + PromptBodyGeminiRequestModel requestBody = imageUtils.buildPromptBody(prompt); + + String geminiUrl = imageUrl + "?key=" + apiKey; + GeminiResponseModel response = restTemplate.postForObject(geminiUrl, requestBody, GeminiResponseModel.class); + + if (response == null) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + return imageUtils.extractImageFromResponse(response); + } catch (Exception e) { + log.error("Error calling Gemini image generation API: ", e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java new file mode 100644 index 0000000..88451db --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapter.java @@ -0,0 +1,188 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.RecipeResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.utils.Constants; +import com.cuoco.adapter.out.rest.gemini.utils.Utils; +import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; +import com.cuoco.application.usecase.model.Filters; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.FileReader; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +@Qualifier("provider") +public class GetRecipesFromIngredientsGeminiRestRepositoryAdapter implements GetRecipesFromIngredientsRepository { + + private final String DELIMITER = com.cuoco.shared.utils.Constants.COMMA.getValue(); + private final String EMPTY_STRING = com.cuoco.shared.utils.Constants.EMPTY.getValue(); + + private final String BASIC_PROMPT = FileReader.execute("prompt/generaterecipes/generateRecipeFromIngredientsHeaderPrompt.txt"); + private final String PARAMETRIC_PROMPT = FileReader.execute("prompt/generaterecipes/generateRecipeParametricDataPrompt.txt"); + private final String FILTERS_PROMPT = FileReader.execute("prompt/generaterecipes/generateRecipesFiltersPrompt.txt"); + + @Value("${gemini.api.url}") + private String url; + + @Value("${gemini.api.key}") + private String apiKey; + + @Value("${gemini.temperature}") + private Double temperature; + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate; + + public GetRecipesFromIngredientsGeminiRestRepositoryAdapter(ObjectMapper objectMapper, RestTemplate restTemplate) { + this.objectMapper = objectMapper; + this.restTemplate = restTemplate; + } + + @Override + public List execute(Recipe recipe) { + try { + log.info("Executing recipes generation from Gemini rest adapter with ingredients: {}", recipe.getIngredients()); + + String ingredients = buildIngredients(recipe.getIngredients()); + String recipesToNotInclude = buildRecipesToNotInclude(recipe.getConfiguration().getNotInclude()); + + String basicPrompt = BASIC_PROMPT + .replace(Constants.INGREDIENTS.getValue(), ingredients) + .replace(Constants.MAX_RECIPES.getValue(), recipe.getConfiguration().getSize().toString()) + .replace(Constants.NOT_INCLUDE.getValue(), recipesToNotInclude); + + String basicWithParametricPrompt = basicPrompt.concat(buildParametricPrompt(recipe.getConfiguration().getParametricData())); + + String filtersPrompt = buildFiltersPrompt(recipe.getFilters()); + + String finalPrompt = filtersPrompt == null ? basicWithParametricPrompt : basicWithParametricPrompt.concat(filtersPrompt); + + String geminiUrl = url + "?key=" + apiKey; + + GeminiResponseModel response = restTemplate.postForObject( + geminiUrl, + Utils.buildPromptBody(finalPrompt, temperature), + GeminiResponseModel.class + ); + + if(response == null) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + String recipeResponseText = Utils.sanitizeJsonResponse(response); + + List recipesResponseFromGemini = objectMapper.readValue( + recipeResponseText, + new TypeReference<>() {} + ); + + List recipesResponse = recipesResponseFromGemini.stream() + .map(RecipeResponseGeminiModel::toDomain) + .toList(); + + log.info("Generated {} recipes from Gemini successfully", recipesResponse.size()); + + return recipesResponse; + } catch (JsonProcessingException e) { + log.error("Failed to convert some properties to JSON. ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + }catch (Exception e) { + log.error("Error getting recipes from ingredients in Gemini. ", e); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildRecipesToNotInclude(List recipesToNotInclude) throws JsonProcessingException { + List notInclude = recipesToNotInclude.stream().map(Recipe::getName).toList(); + return objectMapper.writeValueAsString(notInclude); + } + + private String buildParametricPrompt(ParametricData parametricData) throws JsonProcessingException { + return PARAMETRIC_PROMPT + .replace(Constants.PARAMETRIC_UNITS.getValue(), objectMapper.writeValueAsString(parametricData.getUnits())) + .replace(Constants.PARAMETRIC_PREPARATION_TIMES.getValue(), objectMapper.writeValueAsString(parametricData.getPreparationTimes())) + .replace(Constants.PARAMETRIC_COOK_LEVELS.getValue(), objectMapper.writeValueAsString(parametricData.getCookLevels())) + .replace(Constants.PARAMETRIC_DIETS.getValue(), objectMapper.writeValueAsString(parametricData.getDiets())) + .replace(Constants.PARAMETRIC_MEAL_TYPES.getValue(), objectMapper.writeValueAsString(parametricData.getMealTypes())) + .replace(Constants.PARAMETRIC_ALLERGIES.getValue(), objectMapper.writeValueAsString(parametricData.getAllergies())) + .replace(Constants.PARAMETRIC_DIETARY_NEEDS.getValue(), objectMapper.writeValueAsString(parametricData.getDietaryNeeds())); + } + + private String buildIngredients(List ingredients) throws JsonProcessingException { + ingredients.forEach(ingredient -> { + ingredient.setOptional(null); + ingredient.setSource(null); + ingredient.setConfirmed(null); + }); + + return objectMapper.writeValueAsString(ingredients); + } + + private String buildFiltersPrompt(Filters filters) { + + if(filters.getEnable()) { + + String preparationTimeId = EMPTY_STRING; + String cookLevelId = EMPTY_STRING; + String dietId = EMPTY_STRING; + String mealTypesIds = EMPTY_STRING; + String allergiesIds = EMPTY_STRING; + String dietaryNeedsIds = EMPTY_STRING; + + if(filters.getPreparationTime() != null && filters.getPreparationTime().getId() != null) { + preparationTimeId = filters.getPreparationTime().getId().toString(); + } + + if(filters.getCookLevel() != null && filters.getCookLevel().getId() != null) { + cookLevelId = filters.getCookLevel().getId().toString(); + } + + if(filters.getDiet() != null && filters.getDiet().getId() != null) { + dietId = filters.getDiet().getId().toString(); + } + + if(filters.getCookLevel() != null && filters.getCookLevel().getId() != null) { + cookLevelId = filters.getCookLevel().getId().toString(); + } + + if(filters.getMealTypes() != null && !filters.getMealTypes().isEmpty()) { + mealTypesIds = filters.getMealTypes().stream().map(mt -> mt.getId().toString()).collect(Collectors.joining(DELIMITER)); + } + + if(filters.getAllergies() != null && !filters.getAllergies().isEmpty()) { + allergiesIds = filters.getAllergies().stream().map(a -> a.getId().toString()).collect(Collectors.joining(DELIMITER)); + } + + if(filters.getDietaryNeeds() != null && !filters.getDietaryNeeds().isEmpty()) { + dietaryNeedsIds = filters.getDietaryNeeds().stream().map(dn -> dn.getId().toString()).collect(Collectors.joining(DELIMITER)); + } + + return FILTERS_PROMPT + .replace(Constants.PREPARATION_TIME.getValue(), preparationTimeId) + .replace(Constants.COOK_LEVEL.getValue(), cookLevelId) + .replace(Constants.DIET.getValue(), dietId) + .replace(Constants.MEAL_TYPES.getValue(), mealTypesIds) + .replace(Constants.ALLERGIES.getValue(), allergiesIds) + .replace(Constants.DIETARY_NEEDS.getValue(), dietaryNeedsIds); + } + + return null; + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/AllergyResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/AllergyResponseGeminiModel.java new file mode 100644 index 0000000..e531c5d --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/AllergyResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Allergy; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AllergyResponseGeminiModel { + + private Integer id; + private String description; + + public Allergy toDomain() { + return Allergy.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/CookLevelResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/CookLevelResponseGeminiModel.java new file mode 100644 index 0000000..0662206 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/CookLevelResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.CookLevel; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CookLevelResponseGeminiModel { + + private Integer id; + private String description; + + public CookLevel toDomain() { + return CookLevel.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietResponseGeminiModel.java new file mode 100644 index 0000000..9ad6728 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Diet; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class DietResponseGeminiModel { + + private Integer id; + private String description; + + public Diet toDomain() { + return Diet.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietaryNeedResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietaryNeedResponseGeminiModel.java new file mode 100644 index 0000000..53ce395 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/DietaryNeedResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.DietaryNeed; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class DietaryNeedResponseGeminiModel { + + private Integer id; + private String description; + + public DietaryNeed toDomain() { + return DietaryNeed.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/IngredientResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/IngredientResponseGeminiModel.java new file mode 100644 index 0000000..d0cbab7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/IngredientResponseGeminiModel.java @@ -0,0 +1,36 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Unit; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class IngredientResponseGeminiModel { + + private String name; + private Double quantity; + private Integer unitId; + private Boolean optional; + + public Ingredient toDomain() { + return Ingredient.builder() + .name(name) + .quantity(quantity) + .unit(Unit.builder().id(unitId).build()) + .optional(optional) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/InstructionResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/InstructionResponseGeminiModel.java new file mode 100644 index 0000000..26fc52c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/InstructionResponseGeminiModel.java @@ -0,0 +1,34 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Instruction; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class InstructionResponseGeminiModel { + private String title; + private String time; + private String description; + + public Instruction toDomain() { + return Instruction.builder() + .title(title) + .time(time) + .description(description) + .build(); + } + } + diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealPrepResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealPrepResponseGeminiModel.java new file mode 100644 index 0000000..1ba2b91 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealPrepResponseGeminiModel.java @@ -0,0 +1,41 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.MealPrep; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepResponseGeminiModel { + private String title; + private String estimatedCookingTime; + private Integer servings; + private Boolean freeze; + private List recipeIds; + private List steps; + private List ingredients; + + public MealPrep toDomain() { + return MealPrep.builder() + .title(title) + .estimatedCookingTime(estimatedCookingTime) + .servings(servings) + .freeze(freeze) + .steps(steps.stream().map(StepResponseGeminiModel::toDomain).toList()) + .ingredients(ingredients.stream().map(IngredientResponseGeminiModel::toDomain).toList()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealTypeResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealTypeResponseGeminiModel.java new file mode 100644 index 0000000..9e9b9c0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/MealTypeResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.MealType; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealTypeResponseGeminiModel { + + private Integer id; + private String description; + + public MealType toDomain() { + return MealType.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/PreparationTimeResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/PreparationTimeResponseGeminiModel.java new file mode 100644 index 0000000..e0a161c --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/PreparationTimeResponseGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.PreparationTime; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PreparationTimeResponseGeminiModel { + + private Integer id; + private String description; + + public PreparationTime toDomain() { + return PreparationTime.builder() + .id(id) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeRequestGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeRequestGeminiModel.java new file mode 100644 index 0000000..0cc6bd0 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeRequestGeminiModel.java @@ -0,0 +1,31 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RecipeRequestGeminiModel { + + private Long id; + private String name; + private String subtitle; + private String description; + + private String preparationTime; + private String cookLevelName; + private String dietName; + private List mealTypesNames; + private List allergiesNames; + private List dietaryNeedsNames; + private List ingredientNames; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeResponseGeminiModel.java new file mode 100644 index 0000000..71b7188 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/RecipeResponseGeminiModel.java @@ -0,0 +1,54 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.utils.ImageConstants; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RecipeResponseGeminiModel { + private String id; + private String name; + private String subtitle; + private String description; + private List steps; + private String image; + private PreparationTimeResponseGeminiModel preparationTime; + private CookLevelResponseGeminiModel cookLevel; + private DietResponseGeminiModel diet; + private List mealTypes; + private List allergies; + private List dietaryNeeds; + private List ingredients; + + public Recipe toDomain() { + return Recipe.builder() + .name(name) + .subtitle(subtitle) + .description(description) + .steps(steps.stream().map(StepResponseGeminiModel::toDomain).toList()) + .image(ImageConstants.MAIN_IMAGE_NAME.getValue()) + .preparationTime(preparationTime.toDomain()) + .cookLevel(cookLevel.toDomain()) + .diet(diet != null ? diet.toDomain() : null) + .mealTypes(mealTypes != null ? mealTypes.stream().map(MealTypeResponseGeminiModel::toDomain).toList() : List.of()) + .allergies(allergies != null ? allergies.stream().map(AllergyResponseGeminiModel::toDomain).toList() : List.of()) + .dietaryNeeds(dietaryNeeds != null ? dietaryNeeds.stream().map(DietaryNeedResponseGeminiModel::toDomain).toList() : List.of()) + .ingredients(ingredients.stream().map(IngredientResponseGeminiModel::toDomain).toList()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/StepResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/StepResponseGeminiModel.java new file mode 100644 index 0000000..ff63428 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/StepResponseGeminiModel.java @@ -0,0 +1,34 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Step; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class StepResponseGeminiModel { + private String title; + private Integer number; + private String description; + private String time; + + public Step toDomain() { + return Step.builder() + .title(title) + .number(number) + .description(description) + .time(time) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/UnitResponseGeminiModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/UnitResponseGeminiModel.java new file mode 100644 index 0000000..f12c157 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/UnitResponseGeminiModel.java @@ -0,0 +1,33 @@ +package com.cuoco.adapter.out.rest.gemini.model; + +import com.cuoco.application.usecase.model.Unit; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class UnitResponseGeminiModel { + + private Integer id; + private String description; + private String symbol; + + public Unit toDomain() { + return Unit.builder() + .id(id) + .description(description) + .symbol(symbol) + .build(); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/CandidateGeminiResponseModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/CandidateGeminiResponseModel.java new file mode 100644 index 0000000..2522387 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/CandidateGeminiResponseModel.java @@ -0,0 +1,17 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CandidateGeminiResponseModel { + private ContentGeminiRequestModel content; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/ContentGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/ContentGeminiRequestModel.java new file mode 100644 index 0000000..1abfb31 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/ContentGeminiRequestModel.java @@ -0,0 +1,19 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContentGeminiRequestModel { + private List parts; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GeminiResponseModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GeminiResponseModel.java new file mode 100644 index 0000000..c90bea7 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GeminiResponseModel.java @@ -0,0 +1,21 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class GeminiResponseModel { + private List candidates; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GenerationConfigurationGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GenerationConfigurationGeminiRequestModel.java new file mode 100644 index 0000000..88291ad --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/GenerationConfigurationGeminiRequestModel.java @@ -0,0 +1,20 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class GenerationConfigurationGeminiRequestModel { + private Double temperature; + private List responseModalities; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/InlineDataGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/InlineDataGeminiRequestModel.java new file mode 100644 index 0000000..8904399 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/InlineDataGeminiRequestModel.java @@ -0,0 +1,19 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class InlineDataGeminiRequestModel { + private String mimeType; + private String data; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PartGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PartGeminiRequestModel.java new file mode 100644 index 0000000..9ebcfde --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PartGeminiRequestModel.java @@ -0,0 +1,18 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PartGeminiRequestModel { + private InlineDataGeminiRequestModel inlineData; + private String text; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PromptBodyGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PromptBodyGeminiRequestModel.java new file mode 100644 index 0000000..8df9091 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/model/wrapper/PromptBodyGeminiRequestModel.java @@ -0,0 +1,20 @@ +package com.cuoco.adapter.out.rest.gemini.model.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PromptBodyGeminiRequestModel { + private List contents; + private GenerationConfigurationGeminiRequestModel generationConfig; +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Constants.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Constants.java new file mode 100644 index 0000000..0b5057b --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Constants.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.rest.gemini.utils; + +public enum Constants { + + // Recipe creation placeholders + RECIPE_NAME("RECIPE_NAME"), + INGREDIENTS("INGREDIENTS"), + RECIPES("RECIPES"), + MAX_RECIPES("MAX_RECIPES"), + MAX_STEP_IMAGES("MAX_STEP_IMAGES"), + MAX_MEAL_PREPS("MAX_MEAL_PREPS"), + NOT_INCLUDE("NOT_INCLUDE"), + + // Image creation placeholders + MAIN_INGREDIENTS("MAIN_INGREDIENTS"), + STEP_NUMBER("STEP_NUMBER"), + STEP_INSTRUCTION("STEP_INSTRUCTION"), + + // Data for Gemini to create recipes + PARAMETRIC_UNITS("PARAMETRIC_UNITS"), + PARAMETRIC_PREPARATION_TIMES("PARAMETRIC_PREPARATION_TIMES"), + PARAMETRIC_COOK_LEVELS("PARAMETRIC_COOK_LEVELS"), + PARAMETRIC_DIETS("PARAMETRIC_DIETS"), + PARAMETRIC_MEAL_TYPES("PARAMETRIC_MEAL_TYPES"), + PARAMETRIC_ALLERGIES("PARAMETRIC_ALLERGIES"), + PARAMETRIC_DIETARY_NEEDS("PARAMETRIC_DIETARY_NEEDS"), + + // Filters placeholders + PREPARATION_TIME("COOK_TIME"), + COOK_LEVEL("COOK_LEVEL"), + DIET("DIET"), + MEAL_TYPES("MEAL_TYPES"), + ALLERGIES("ALLERGIES"), + DIETARY_NEEDS("DIETARY_NEEDS"), + QUANTITY("QUANTITY"), + FREEZE("FREEZE"); + + private final String value; + + Constants(String value) { this.value = value; } + + public String getValue() { + + String WILDCARD_START = "{{"; + String WILDCARD_END = "}}"; + + return WILDCARD_START + value + WILDCARD_END; + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/ImageUtils.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/ImageUtils.java new file mode 100644 index 0000000..4579f86 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/ImageUtils.java @@ -0,0 +1,92 @@ +package com.cuoco.adapter.out.rest.gemini.utils; + +import autovalue.shaded.kotlin.Pair; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.CandidateGeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.InlineDataGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.List; + +@Slf4j +@Component +public class ImageUtils { + + @Value("${shared.recipes.images.base-path}") + private String BASE_PATH; + + public boolean imageExists(Long recipeId, String imageName) { + Path recipeDir = Paths.get(BASE_PATH, recipeId.toString()); + + if (!Files.exists(recipeDir) || !Files.isDirectory(recipeDir)) { + return false; + } + + try (DirectoryStream stream = Files.newDirectoryStream(recipeDir, imageName + ".*")) { + return stream.iterator().hasNext(); + } catch (IOException e) { + log.error("Error checking for main image in {}: {}", recipeDir, e.getMessage(), e); + return false; + } + } + + public void saveImageFile(byte[] imageData, Long recipeId, String fileName) { + Path recipeDir = Paths.get(BASE_PATH, recipeId.toString()); + Path imagePath = recipeDir.resolve(fileName); + + try { + Files.createDirectories(recipeDir); + Files.write(imagePath, imageData); + log.info("Image saved: {}", imagePath); + } catch (IOException e) { + log.error("Error saving created image for recipe {}: {}", recipeId, e.getMessage(), e); + } + } + + public Pair extractImageFromResponse(GeminiResponseModel response) { + + if(response.getCandidates() != null && !response.getCandidates().isEmpty()) { + CandidateGeminiResponseModel candidate = response.getCandidates().get(0); + + if(candidate.getContent() != null && candidate.getContent().getParts() != null) { + + for (PartGeminiRequestModel part : candidate.getContent().getParts()) { + InlineDataGeminiRequestModel inlineData = part.getInlineData(); + + if (inlineData != null) { + String base64Data = inlineData.getData(); + + if (base64Data != null && !base64Data.trim().isEmpty()) { + return new Pair<>(inlineData.getMimeType(), Base64.getDecoder().decode(base64Data)); + } + } + } + } + } + + return null; + } + + public PromptBodyGeminiRequestModel buildPromptBody(String prompt) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().responseModalities(List.of("TEXT","IMAGE")).build()) + .build(); + } + + private List buildPartsRequest(String prompt) { + return List.of(PartGeminiRequestModel.builder().text(prompt).build()); + } +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Utils.java b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Utils.java new file mode 100644 index 0000000..d634f82 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/rest/gemini/utils/Utils.java @@ -0,0 +1,68 @@ +package com.cuoco.adapter.out.rest.gemini.utils; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.CandidateGeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GenerationConfigurationGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PromptBodyGeminiRequestModel; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.Constants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class Utils { + + private final static String INIT_JSON_STRING = "```json"; + private final static String FINAL_JSON_STRING = "```"; + + @NotNull + public static String sanitizeJsonResponse(GeminiResponseModel response) { + String recipeResponseText = validate(response); + + if(recipeResponseText == null || recipeResponseText.isEmpty()) { + throw new UnprocessableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + + return recipeResponseText + .replaceAll(INIT_JSON_STRING, Constants.EMPTY.getValue()) + .replaceAll(FINAL_JSON_STRING, Constants.EMPTY.getValue()) + .trim(); + } + + @Nullable + private static String validate(GeminiResponseModel response) { + String recipeResponseText = null; + + if(response.getCandidates() != null && !response.getCandidates().isEmpty()) { + CandidateGeminiResponseModel candidate = response.getCandidates().get(0); + + if(candidate.getContent() != null) { + ContentGeminiRequestModel content = candidate.getContent(); + + if(content.getParts() != null && !content.getParts().isEmpty()) { + PartGeminiRequestModel parts = content.getParts().get(0); + + recipeResponseText = parts.getText(); + } + } + } + + return recipeResponseText; + } + + public static PromptBodyGeminiRequestModel buildPromptBody(String prompt, Double temperature) { + return PromptBodyGeminiRequestModel.builder() + .contents(List.of(ContentGeminiRequestModel.builder().parts(buildPartsRequest(prompt)).build())) + .generationConfig(GenerationConfigurationGeminiRequestModel.builder().temperature(temperature).build()) + .build(); + } + + private static List buildPartsRequest(String prompt) { + return List.of(PartGeminiRequestModel.builder().text(prompt).build()); + } + +} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/ContentGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/ContentGeminiRequestModel.java deleted file mode 100644 index 0d714bd..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/ContentGeminiRequestModel.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import lombok.Getter; - -import java.util.List; - -@Getter -public class ContentGeminiRequestModel { - private List parts; - - public ContentGeminiRequestModel(List parts) { - this.parts = parts; - } - - public void setParts(List parts) { - this.parts = parts; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiImageToIngredientsRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiImageToIngredientsRepositoryAdapter.java deleted file mode 100644 index c6ea6ad..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiImageToIngredientsRepositoryAdapter.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import com.cuoco.application.port.out.GetIngredientsFromRepository; -import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.shared.FileReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.multipart.MultipartFile; - -import java.util.ArrayList; -import java.util.Base64; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -@Component -public class GeminiImageToIngredientsRepositoryAdapter implements GetIngredientsFromRepository { - - static final Logger log = LoggerFactory.getLogger(GeminiImageToIngredientsRepositoryAdapter.class); - - private final String PROMPT = FileReader.execute("prompt/generateIngredients.txt"); - - @Value("${gemini.api.url}") - private String url; - - @Value("${gemini.api.key}") - private String apiKey; - - private final RestTemplate restTemplate; - private final GeminiResponseMapper geminiResponseMapper; - - public GeminiImageToIngredientsRepositoryAdapter(RestTemplate restTemplate, GeminiResponseMapper geminiResponseMapper) { - this.restTemplate = restTemplate; - this.geminiResponseMapper = geminiResponseMapper; - } - - @Override - public List execute(List files) { - - log.info("Executing get ingredients from gemini rest adapter with files: {}", files); - - List allIngredients = new ArrayList<>(); - - for (MultipartFile file : files) { - try { - PromptBodyGeminiRequestModel prompt = buildPromptBody(file); - - String geminiUrl = url + "?key=" + apiKey; - - String response = restTemplate.postForObject(geminiUrl, prompt, String.class); - - List ingredients = parseIngredientsFromResponse(response); - allIngredients.addAll(ingredients); - - } catch (Exception e) { - log.error("Error processing file {}: {}", file.getOriginalFilename(), e.getMessage(), e); - } - } - - log.info("Successfully extracted ingredients from Gemini"); - - return allIngredients; - } - - @Override - public Map> executeWithSeparation(List files) { - log.info("Executing get ingredients from gemini rest adapter with separation for {} files", files.size()); - - Map> ingredientsByImage = new LinkedHashMap<>(); - - for (MultipartFile file : files) { - try { - String filename = file.getOriginalFilename() != null ? file.getOriginalFilename() : "unknown_" + System.currentTimeMillis(); - - PromptBodyGeminiRequestModel prompt = buildPromptBody(file); - - String geminiUrl = url + "?key=" + apiKey; - - String response = restTemplate.postForObject(geminiUrl, prompt, String.class); - - List ingredients = parseIngredientsFromResponse(response); - ingredientsByImage.put(filename, ingredients); - - log.info("Extracted {} ingredients from file: {}", ingredients.size(), filename); - - } catch (Exception e) { - String filename = file.getOriginalFilename() != null ? file.getOriginalFilename() : "unknown"; - log.error("Error processing file {}: {}", filename, e.getMessage(), e); - ingredientsByImage.put(filename, new ArrayList<>()); - } - } - - log.info("Successfully extracted ingredients from {} images", ingredientsByImage.size()); - return ingredientsByImage; - } - - private PromptBodyGeminiRequestModel buildPromptBody(MultipartFile file) { - return new PromptBodyGeminiRequestModel( - buildContentRequest(file), - new GenerationConfigurationGeminiRequestModel( - 0.2 - ) - ); - } - - private List buildContentRequest(MultipartFile file) { - return List.of( - new ContentGeminiRequestModel(buildPartsRequest(file)) - ); - } - - private List buildPartsRequest(MultipartFile file) { - try { - String imageBase64 = Base64.getEncoder().encodeToString(file.getBytes()); - String mimeType = file.getContentType(); - - return List.of( - new PartGeminiRequestModel( - null, - PROMPT - ), - new PartGeminiRequestModel( - new InlineDataGeminiRequestModel( - mimeType != null ? mimeType : "image/jpeg", - imageBase64 - ), - null - ) - ); - } catch (Exception e) { - throw new RuntimeException("Error processing image file: " + e.getMessage(), e); - } - } - - private List parseIngredientsFromResponse(String response) { - if (response == null || response.trim().isEmpty()) { - return new ArrayList<>(); - } - - // Usar el GeminiResponseMapper para extraer el texto - String extractedText = geminiResponseMapper.extractTextFromResponse(response); - - if (extractedText != null && !extractedText.trim().isEmpty()) { - return parseIngredientNamesFromText(extractedText); - } - - // Fallback: parsear como string simple - return parseIngredientNamesFromText(response); - } - - private List parseIngredientNamesFromText(String text) { - List ingredients = new ArrayList<>(); - String[] ingredientNames = text.split(","); - - for (String name : ingredientNames) { - String cleanName = name.trim().toLowerCase(); - if (!cleanName.isEmpty()) { - ingredients.add(new Ingredient(cleanName, "imagen", false)); - } - } - - return ingredients; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiResponseMapper.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiResponseMapper.java deleted file mode 100644 index 19bccc5..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiResponseMapper.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.Optional; - -@Component -public class GeminiResponseMapper { - - private static final Logger log = LoggerFactory.getLogger(GeminiResponseMapper.class); - private final ObjectMapper objectMapper = new ObjectMapper(); - - public Object parseToJson(String geminiResponse) { - return extractText(geminiResponse) - .map(text -> text.replace("```json", "").replace("```", "").trim()) - .map(this::parseJsonSafely) - .orElse(parseJsonSafely(geminiResponse)); - } - - public String extractTextFromResponse(String geminiResponse) { - return extractText(geminiResponse).orElse(geminiResponse); - } - - public Object parseToJsonArray(String geminiResponse) { - return parseJsonSafely(geminiResponse); - } - - private Optional extractText(String geminiResponse) { - try { - JsonNode root = objectMapper.readTree(geminiResponse); - return Optional.ofNullable(root.path("candidates") - .get(0) - .path("content") - .path("parts") - .get(0) - .path("text") - .asText(null)); - } catch (Exception e) { - log.warn("Failed to extract text: {}", e.getMessage()); - return Optional.empty(); - } - } - - private Object parseJsonSafely(String json) { - try { - return objectMapper.readValue(json, Object.class); - } catch (Exception e) { - return json; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiVoiceRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiVoiceRepositoryAdapter.java deleted file mode 100644 index 44e0c01..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GeminiVoiceRepositoryAdapter.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import com.cuoco.adapter.out.rest.model.gemini.voice.VoiceRequestBuilder; -import com.cuoco.adapter.out.rest.model.gemini.voice.VoiceResponseParser; -import com.cuoco.adapter.out.rest.model.gemini.PromptBodyGeminiRequestModel; -import com.cuoco.application.port.out.GetIngredientsFromVoiceRepository; -import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.shared.FileReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@Component -public class GeminiVoiceRepositoryAdapter implements GetIngredientsFromVoiceRepository { - - private static final Logger log = LoggerFactory.getLogger(GeminiVoiceRepositoryAdapter.class); - - private final String VOICE_PROMPT = FileReader.execute("prompt/generateIngredientsFromVoice.txt"); - - @Value("${gemini.api.url}") - private String url; - - @Value("${gemini.api.key}") - private String apiKey; - - private final RestTemplate restTemplate; - private final VoiceRequestBuilder voiceRequestBuilder; - private final VoiceResponseParser voiceResponseParser; - - public GeminiVoiceRepositoryAdapter(RestTemplate restTemplate, - VoiceRequestBuilder voiceRequestBuilder, - VoiceResponseParser voiceResponseParser) { - this.restTemplate = restTemplate; - this.voiceRequestBuilder = voiceRequestBuilder; - this.voiceResponseParser = voiceResponseParser; - } - - @Override - public List processVoice(String audioBase64, String format, String language) { - log.info("🎙️ Processing voice with Gemini - format: {}, language: {}", format, language); - - try { - // 1. Construir request usando builder modular - PromptBodyGeminiRequestModel request = voiceRequestBuilder.buildVoiceRequest(audioBase64, format, VOICE_PROMPT); - String geminiUrl = url + "?key=" + apiKey; - - // 2. HTTP call a Gemini API - String response = restTemplate.postForObject(geminiUrl, request, String.class); - - // 3. Parsear respuesta usando parser modular - List ingredients = voiceResponseParser.parseIngredientsFromResponse(response); - - log.info("✅ Successfully extracted {} ingredients from voice", ingredients.size()); - return ingredients; - - } catch (Exception e) { - log.error("❌ Error processing voice with Gemini: {}", e.getMessage(), e); - throw new RuntimeException("Error processing voice: " + e.getMessage(), e); - } - } - - @Override - @Async - public CompletableFuture> processVoiceAsync(String audioBase64, String format, String language) { - log.info("🎙️ Processing voice ASYNC with Gemini - format: {}, language: {}", format, language); - - return CompletableFuture.supplyAsync(() -> { - try { - return processVoice(audioBase64, format, language); - } catch (Exception e) { - log.error("❌ Async error processing voice: {}", e.getMessage(), e); - throw new RuntimeException("Async error processing voice: " + e.getMessage(), e); - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GenerationConfigurationGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GenerationConfigurationGeminiRequestModel.java deleted file mode 100644 index 3e84ae7..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/GenerationConfigurationGeminiRequestModel.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -public class GenerationConfigurationGeminiRequestModel { - private Double temperature; - - public GenerationConfigurationGeminiRequestModel(Double temperature) { - this.temperature = temperature; - } - - public Double getTemperature() { - return temperature; - } - - public void setTemperature(Double temperature) { - this.temperature = temperature; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/InlineDataGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/InlineDataGeminiRequestModel.java deleted file mode 100644 index ddb2624..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/InlineDataGeminiRequestModel.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -public class InlineDataGeminiRequestModel { - private String mimeType; - private String data; - - public InlineDataGeminiRequestModel(String mimeType, String data) { - this.mimeType = mimeType; - this.data = data; - } - - public String getMimeType() { - return mimeType; - } - - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PartGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PartGeminiRequestModel.java deleted file mode 100644 index 851c469..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PartGeminiRequestModel.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import com.fasterxml.jackson.annotation.JsonInclude; - -@JsonInclude(JsonInclude.Include.NON_NULL) -public class PartGeminiRequestModel { - private InlineDataGeminiRequestModel inlineData; - private String text; - - public PartGeminiRequestModel(InlineDataGeminiRequestModel inlineData, String text) { - this.inlineData = inlineData; - this.text = text; - } - - public InlineDataGeminiRequestModel getInlineData() { - return inlineData; - } - - public void setInlineData(InlineDataGeminiRequestModel inlineData) { - this.inlineData = inlineData; - } - - public String getText() { - return text; - } - - public void setText(String text) { - this.text = text; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PromptBodyGeminiRequestModel.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PromptBodyGeminiRequestModel.java deleted file mode 100644 index 66a90fe..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/PromptBodyGeminiRequestModel.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini; - -import java.util.List; - -public class PromptBodyGeminiRequestModel { - private List contents; - private GenerationConfigurationGeminiRequestModel generationConfig; - - public PromptBodyGeminiRequestModel(List contents, GenerationConfigurationGeminiRequestModel generationConfig) { - this.contents = contents; - this.generationConfig = generationConfig; - } - - public List getContents() { - return contents; - } - - public void setContents(List contents) { - this.contents = contents; - } - - public GenerationConfigurationGeminiRequestModel getGenerationConfig() { - return generationConfig; - } - - public void setGenerationConfig(GenerationConfigurationGeminiRequestModel generationConfig) { - this.generationConfig = generationConfig; - } -} diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/AudioMimeTypeMapper.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/AudioMimeTypeMapper.java deleted file mode 100644 index 92abe01..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/AudioMimeTypeMapper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini.voice; - -public class AudioMimeTypeMapper { - - /** - * Mapear formatos de archivo a MIME types para Gemini - */ - public static String getMimeType(String format) { - if (format == null) { - return "audio/mp3"; - } - - return switch (format.toLowerCase()) { - case "mp3" -> "audio/mp3"; - case "wav" -> "audio/wav"; - case "ogg" -> "audio/ogg"; - case "aac" -> "audio/aac"; - case "flac" -> "audio/flac"; - case "m4a" -> "audio/mp4"; - default -> "audio/" + format; - }; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceRequestBuilder.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceRequestBuilder.java deleted file mode 100644 index bf858ae..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceRequestBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini.voice; - -import com.cuoco.adapter.out.rest.model.gemini.*; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public class VoiceRequestBuilder { - - /** - * Construir request completo para Gemini con audio + prompt - */ - public PromptBodyGeminiRequestModel buildVoiceRequest(String audioBase64, String format, String prompt) { - return new PromptBodyGeminiRequestModel( - buildContentRequest(audioBase64, format, prompt), - new GenerationConfigurationGeminiRequestModel(0.2) // Temperature baja para consistencia - ); - } - - /** - * Construir contenido multimodal (audio + texto) - */ - private List buildContentRequest(String audioBase64, String format, String prompt) { - return List.of( - new ContentGeminiRequestModel(buildPartsRequest(audioBase64, format, prompt)) - ); - } - - /** - * Construir partes del request (prompt de texto + audio data) - */ - private List buildPartsRequest(String audioBase64, String format, String prompt) { - String mimeType = AudioMimeTypeMapper.getMimeType(format); - - return List.of( - // Parte 1: Prompt de texto con instrucciones - new PartGeminiRequestModel(null, prompt), - - // Parte 2: Audio data en base64 - new PartGeminiRequestModel( - new InlineDataGeminiRequestModel(mimeType, audioBase64), - null - ) - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceResponseParser.java b/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceResponseParser.java deleted file mode 100644 index 829b77d..0000000 --- a/src/main/java/com/cuoco/adapter/out/rest/model/gemini/voice/VoiceResponseParser.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.cuoco.adapter.out.rest.model.gemini.voice; - -import com.cuoco.adapter.out.rest.model.gemini.GeminiResponseMapper; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; - -@Component -public class VoiceResponseParser { - - private static final Logger log = LoggerFactory.getLogger(VoiceResponseParser.class); - - private final GeminiResponseMapper geminiResponseMapper; - - public VoiceResponseParser(GeminiResponseMapper geminiResponseMapper) { - this.geminiResponseMapper = geminiResponseMapper; - } - - /** - * Parsear respuesta de Gemini y extraer ingredientes - */ - public List parseIngredientsFromResponse(String response) { - if (response == null || response.trim().isEmpty()) { - log.warn("⚠️ Empty response from Gemini"); - return new ArrayList<>(); - } - - try { - // Usar mapper existente para extraer texto de JSON - String extractedText = geminiResponseMapper.extractTextFromResponse(response); - - if (extractedText != null && !extractedText.trim().isEmpty()) { - return parseIngredientNamesFromText(extractedText); - } - - log.warn("⚠️ No text extracted from Gemini response"); - return new ArrayList<>(); - - } catch (Exception e) { - log.error("❌ Error parsing Gemini response: {}", e.getMessage(), e); - return new ArrayList<>(); - } - } - - /** - * Parsear texto a lista de ingredientes con filtrado - */ - private List parseIngredientNamesFromText(String text) { - List ingredients = new ArrayList<>(); - String[] ingredientNames = text.split("[,\n]"); - - for (String name : ingredientNames) { - String cleanName = name.trim().toLowerCase(); - - // Filtrar nombres muy cortos o inválidos - if (!cleanName.isEmpty() && cleanName.length() > 1) { - ingredients.add(new Ingredient(cleanName, "voz", false)); - } - } - - log.info("📝 Parsed {} ingredients from text", ingredients.size()); - return ingredients; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/exception/ConflictException.java b/src/main/java/com/cuoco/application/exception/ConflictException.java new file mode 100644 index 0000000..71f32d2 --- /dev/null +++ b/src/main/java/com/cuoco/application/exception/ConflictException.java @@ -0,0 +1,7 @@ +package com.cuoco.application.exception; + +public class ConflictException extends BusinessException { + public ConflictException(String description) { + super(description, null); + } +} diff --git a/src/main/java/com/cuoco/application/exception/ImageGenerationBusinessException.java b/src/main/java/com/cuoco/application/exception/ImageGenerationBusinessException.java new file mode 100644 index 0000000..d0c3a21 --- /dev/null +++ b/src/main/java/com/cuoco/application/exception/ImageGenerationBusinessException.java @@ -0,0 +1,8 @@ +package com.cuoco.application.exception; + +public class ImageGenerationBusinessException extends BusinessException { + + public ImageGenerationBusinessException(String message) { + super(message, null); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/exception/NotAvailableException.java b/src/main/java/com/cuoco/application/exception/NotAvailableException.java new file mode 100644 index 0000000..496c120 --- /dev/null +++ b/src/main/java/com/cuoco/application/exception/NotAvailableException.java @@ -0,0 +1,5 @@ +package com.cuoco.application.exception; + +public class NotAvailableException extends BusinessException { + public NotAvailableException(String description) { super(description, null); } +} diff --git a/src/main/java/com/cuoco/application/exception/RecipeGenerationException.java b/src/main/java/com/cuoco/application/exception/RecipeGenerationException.java new file mode 100644 index 0000000..59c6783 --- /dev/null +++ b/src/main/java/com/cuoco/application/exception/RecipeGenerationException.java @@ -0,0 +1,17 @@ +package com.cuoco.application.exception; + +import com.cuoco.application.usecase.model.MessageError; + +import java.util.Collections; +import java.util.List; + +public class RecipeGenerationException extends BusinessException { + + public RecipeGenerationException(String message) { + super(message, Collections.emptyList()); + } + + public RecipeGenerationException(String message, List errors) { + super(message, errors); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/exception/UnprocessableException.java b/src/main/java/com/cuoco/application/exception/UnprocessableException.java new file mode 100644 index 0000000..245b53c --- /dev/null +++ b/src/main/java/com/cuoco/application/exception/UnprocessableException.java @@ -0,0 +1,15 @@ +package com.cuoco.application.exception; + +import com.cuoco.application.usecase.model.MessageError; + +import java.util.List; + +public class UnprocessableException extends BusinessException { + public UnprocessableException(String description) { + super(description, null); + } + + public UnprocessableException(String description, List messages) { + super(description, messages); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java b/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java new file mode 100644 index 0000000..af3e47e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java @@ -0,0 +1,16 @@ +package com.cuoco.application.port.in; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +public interface ActivateUserCommand { + void execute(Command command); + + @Data + @Builder + @AllArgsConstructor + class Command { + private String token; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/AuthenticateUserCommand.java b/src/main/java/com/cuoco/application/port/in/AuthenticateUserCommand.java index 0ed803b..6cf1dad 100644 --- a/src/main/java/com/cuoco/application/port/in/AuthenticateUserCommand.java +++ b/src/main/java/com/cuoco/application/port/in/AuthenticateUserCommand.java @@ -1,26 +1,17 @@ package com.cuoco.application.port.in; import com.cuoco.application.usecase.model.AuthenticatedUser; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; public interface AuthenticateUserCommand { AuthenticatedUser execute(Command command); + @Data + @Builder + @AllArgsConstructor class Command { private final String authHeader; - - public Command(String authHeader) { - this.authHeader = authHeader; - } - - public String getAuthHeader() { - return authHeader; - } - - @Override - public String toString() { - return "Command{" + - "authHeader='" + authHeader + '\'' + - '}'; - } } } diff --git a/src/main/java/com/cuoco/application/port/in/ChangeUserPasswordCommand.java b/src/main/java/com/cuoco/application/port/in/ChangeUserPasswordCommand.java new file mode 100644 index 0000000..af95a33 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/ChangeUserPasswordCommand.java @@ -0,0 +1,17 @@ +package com.cuoco.application.port.in; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +public interface ChangeUserPasswordCommand { + void execute(Command command); + + @Data + @Builder + @AllArgsConstructor + class Command { + private String token; + private String password; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/CreateOrUpdateUserRecipeCalendarCommand.java b/src/main/java/com/cuoco/application/port/in/CreateOrUpdateUserRecipeCalendarCommand.java new file mode 100644 index 0000000..b93472b --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/CreateOrUpdateUserRecipeCalendarCommand.java @@ -0,0 +1,17 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Calendar; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +public interface CreateOrUpdateUserRecipeCalendarCommand { + void execute(Command command); + + @Data + @Builder + class Command { + private List calendars; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/CreateUserCommand.java b/src/main/java/com/cuoco/application/port/in/CreateUserCommand.java index e2b33f3..e74cbba 100644 --- a/src/main/java/com/cuoco/application/port/in/CreateUserCommand.java +++ b/src/main/java/com/cuoco/application/port/in/CreateUserCommand.java @@ -2,10 +2,9 @@ import com.cuoco.application.usecase.model.User; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; -import lombok.ToString; -import java.time.LocalDate; import java.util.List; public interface CreateUserCommand { @@ -13,7 +12,7 @@ public interface CreateUserCommand { User execute(Command command); @Data - @ToString + @Builder @AllArgsConstructor class Command { diff --git a/src/main/java/com/cuoco/application/port/in/CreateUserMealPrepCommand.java b/src/main/java/com/cuoco/application/port/in/CreateUserMealPrepCommand.java new file mode 100644 index 0000000..93684a9 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/CreateUserMealPrepCommand.java @@ -0,0 +1,14 @@ +package com.cuoco.application.port.in; + +import lombok.Builder; +import lombok.Data; + +public interface CreateUserMealPrepCommand { + void execute(Command command); + + @Data + @Builder + class Command { + private Long id; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/CreateUserPaymentCommand.java b/src/main/java/com/cuoco/application/port/in/CreateUserPaymentCommand.java new file mode 100644 index 0000000..352f851 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/CreateUserPaymentCommand.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.UserPayment; + +public interface CreateUserPaymentCommand { + UserPayment execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/CreateUserRecipeCommand.java b/src/main/java/com/cuoco/application/port/in/CreateUserRecipeCommand.java new file mode 100644 index 0000000..2c64e22 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/CreateUserRecipeCommand.java @@ -0,0 +1,15 @@ +package com.cuoco.application.port.in; + +import lombok.Builder; +import lombok.Data; + +public interface CreateUserRecipeCommand { + + void execute(Command command); + + @Data + @Builder + class Command { + private Long recipeId; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/DeleteUserMealPrepCommand.java b/src/main/java/com/cuoco/application/port/in/DeleteUserMealPrepCommand.java new file mode 100644 index 0000000..50dd0a6 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/DeleteUserMealPrepCommand.java @@ -0,0 +1,14 @@ +package com.cuoco.application.port.in; + +import lombok.Builder; +import lombok.Data; + +public interface DeleteUserMealPrepCommand { + void execute(Command command); + + @Data + @Builder + class Command { + private Long id; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/DeleteUserRecipeCommand.java b/src/main/java/com/cuoco/application/port/in/DeleteUserRecipeCommand.java new file mode 100644 index 0000000..24393ec --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/DeleteUserRecipeCommand.java @@ -0,0 +1,14 @@ +package com.cuoco.application.port.in; + +import lombok.Builder; +import lombok.Data; + +public interface DeleteUserRecipeCommand { + void execute(Command command); + + @Data + @Builder + class Command { + private Long id; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/FindOrCreateRecipeCommand.java b/src/main/java/com/cuoco/application/port/in/FindOrCreateRecipeCommand.java new file mode 100644 index 0000000..6f4e6e7 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/FindOrCreateRecipeCommand.java @@ -0,0 +1,18 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Recipe; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +public interface FindOrCreateRecipeCommand { + + Recipe execute(Command command); + + @Data + @Builder + @ToString + class Command { + private String recipeName; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/FindRecipesCommand.java b/src/main/java/com/cuoco/application/port/in/FindRecipesCommand.java new file mode 100644 index 0000000..abc9234 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/FindRecipesCommand.java @@ -0,0 +1,19 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Recipe; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +public interface FindRecipesCommand { + + List execute(Command command); + + @Data + @Builder + class Command { + private Integer size; + private Boolean random; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/GenerateRecipeImagesCommand.java b/src/main/java/com/cuoco/application/port/in/GenerateRecipeImagesCommand.java new file mode 100644 index 0000000..54b6097 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GenerateRecipeImagesCommand.java @@ -0,0 +1,19 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +public interface GenerateRecipeImagesCommand { + + List execute(Command command); + + @Data + @Builder + class Command { + private final Recipe recipe; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetCookLevelsQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllCookLevelsQuery.java similarity index 78% rename from src/main/java/com/cuoco/application/port/in/GetCookLevelsQuery.java rename to src/main/java/com/cuoco/application/port/in/GetAllCookLevelsQuery.java index 5c8f13a..db9a31f 100644 --- a/src/main/java/com/cuoco/application/port/in/GetCookLevelsQuery.java +++ b/src/main/java/com/cuoco/application/port/in/GetAllCookLevelsQuery.java @@ -4,6 +4,6 @@ import java.util.List; -public interface GetCookLevelsQuery { +public interface GetAllCookLevelsQuery { List execute(); } diff --git a/src/main/java/com/cuoco/application/port/in/GetDietsQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllDietsQuery.java similarity index 79% rename from src/main/java/com/cuoco/application/port/in/GetDietsQuery.java rename to src/main/java/com/cuoco/application/port/in/GetAllDietsQuery.java index 7a61144..58dddb9 100644 --- a/src/main/java/com/cuoco/application/port/in/GetDietsQuery.java +++ b/src/main/java/com/cuoco/application/port/in/GetAllDietsQuery.java @@ -4,6 +4,6 @@ import java.util.List; -public interface GetDietsQuery { +public interface GetAllDietsQuery { List execute(); } diff --git a/src/main/java/com/cuoco/application/port/in/GetAllMealTypesQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllMealTypesQuery.java new file mode 100644 index 0000000..244056e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetAllMealTypesQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.MealType; + +import java.util.List; + +public interface GetAllMealTypesQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetPlansQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllPlansQuery.java similarity index 79% rename from src/main/java/com/cuoco/application/port/in/GetPlansQuery.java rename to src/main/java/com/cuoco/application/port/in/GetAllPlansQuery.java index 122beec..1b715fe 100644 --- a/src/main/java/com/cuoco/application/port/in/GetPlansQuery.java +++ b/src/main/java/com/cuoco/application/port/in/GetAllPlansQuery.java @@ -4,6 +4,6 @@ import java.util.List; -public interface GetPlansQuery { +public interface GetAllPlansQuery { List execute(); } diff --git a/src/main/java/com/cuoco/application/port/in/GetAllPreparationTimesQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllPreparationTimesQuery.java new file mode 100644 index 0000000..9acb955 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetAllPreparationTimesQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.PreparationTime; + +import java.util.List; + +public interface GetAllPreparationTimesQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetAllUnitsQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllUnitsQuery.java new file mode 100644 index 0000000..1f0a63e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetAllUnitsQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Unit; + +import java.util.List; + +public interface GetAllUnitsQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetAllUserMealPrepsQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllUserMealPrepsQuery.java new file mode 100644 index 0000000..f412a4c --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetAllUserMealPrepsQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.MealPrep; + +import java.util.List; + +public interface GetAllUserMealPrepsQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetAllUserRecipesQuery.java b/src/main/java/com/cuoco/application/port/in/GetAllUserRecipesQuery.java new file mode 100644 index 0000000..5991e60 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetAllUserRecipesQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Recipe; + +import java.util.List; + +public interface GetAllUserRecipesQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioAsyncCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioAsyncCommand.java new file mode 100644 index 0000000..a3fd5ec --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioAsyncCommand.java @@ -0,0 +1,23 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Ingredient; +import lombok.Builder; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface GetIngredientsFromAudioAsyncCommand { + + CompletableFuture> execute(Command command); + + @Data + @Builder + class Command { + + private final MultipartFile audioFile; + private final String language; + + } +} diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioCommand.java new file mode 100644 index 0000000..455afe5 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromAudioCommand.java @@ -0,0 +1,24 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Ingredient; +import lombok.Builder; +import lombok.Data; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +public interface GetIngredientsFromAudioCommand { + + List execute(Command command); + + @Data + @Builder + class Command { + + private final MultipartFile audioFile; + private final String language; + + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromFileCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromFileCommand.java deleted file mode 100644 index 077b079..0000000 --- a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromFileCommand.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cuoco.application.port.in; - -import com.cuoco.application.usecase.model.Ingredient; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.Map; - -public interface GetIngredientsFromFileCommand { - - List execute(Command command); - - Map> executeWithSeparation(Command command); - - class Command { - - private final List files; - - public Command(List files) { - this.files = files; - } - - public List getFiles() { - return files; - } - - @Override - public String toString() { - return "Command{" + - "files=" + files + - '}'; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromImagesCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromImagesCommand.java new file mode 100644 index 0000000..9d7b3b8 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromImagesCommand.java @@ -0,0 +1,19 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Ingredient; +import lombok.Builder; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface GetIngredientsFromImagesCommand { + + List execute(Command command); + + @Data + @Builder + class Command { + private final List images; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromTextCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromTextCommand.java index e793db1..e1406e1 100644 --- a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromTextCommand.java +++ b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromTextCommand.java @@ -1,7 +1,8 @@ package com.cuoco.application.port.in; import com.cuoco.application.usecase.model.Ingredient; -import lombok.Getter; +import lombok.Builder; +import lombok.Data; import org.springframework.stereotype.Service; import java.util.List; @@ -11,15 +12,10 @@ public interface GetIngredientsFromTextCommand { List execute(Command command); - @Getter + @Data + @Builder class Command { private final String text; private final String source; - - public Command(String text, String source) { - this.text = text; - this.source = source; - } - } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromVoiceCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsFromVoiceCommand.java deleted file mode 100644 index 92670a6..0000000 --- a/src/main/java/com/cuoco/application/port/in/GetIngredientsFromVoiceCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cuoco.application.port.in; - -import com.cuoco.application.usecase.model.Ingredient; -import lombok.Data; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@Service -public interface GetIngredientsFromVoiceCommand { - - List execute(Command command); - - CompletableFuture> executeAsync(Command command); - - @Data - class Command { - private final String audioBase64; - private final String format; - private final String language; - - public Command(String audioBase64, String format, String language) { - this.audioBase64 = audioBase64; - this.format = format; - this.language = language; - } - - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetIngredientsGroupedFromImagesCommand.java b/src/main/java/com/cuoco/application/port/in/GetIngredientsGroupedFromImagesCommand.java new file mode 100644 index 0000000..8e60b14 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetIngredientsGroupedFromImagesCommand.java @@ -0,0 +1,20 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Ingredient; +import lombok.Builder; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +public interface GetIngredientsGroupedFromImagesCommand { + + Map> execute(Command command); + + @Data + @Builder + class Command { + private final List images; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/in/GetMealPrepByIdQuery.java b/src/main/java/com/cuoco/application/port/in/GetMealPrepByIdQuery.java new file mode 100644 index 0000000..f2fb2d6 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetMealPrepByIdQuery.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.MealPrep; + +public interface GetMealPrepByIdQuery { + MealPrep execute(Long id); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetMealPrepFromIngredientsCommand.java b/src/main/java/com/cuoco/application/port/in/GetMealPrepFromIngredientsCommand.java new file mode 100644 index 0000000..1194a5e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetMealPrepFromIngredientsCommand.java @@ -0,0 +1,34 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealPrep; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +import java.util.List; + +public interface GetMealPrepFromIngredientsCommand { + + List execute(Command command); + + @Data + @Builder + @ToString + class Command { + private List ingredients; + + private Boolean freeze; + private Integer servings; + private Integer preparationTimeId; + private Integer cookLevelId; + private Integer dietId; + private List typeIds; + private List allergiesIds; + private List dietaryNeedsIds; + + private Integer size; + private List notInclude; + } +} + diff --git a/src/main/java/com/cuoco/application/port/in/GetRecipeByIdQuery.java b/src/main/java/com/cuoco/application/port/in/GetRecipeByIdQuery.java new file mode 100644 index 0000000..ff9bc0e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetRecipeByIdQuery.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Recipe; + +public interface GetRecipeByIdQuery { + Recipe execute(Long id, Integer servings); +} diff --git a/src/main/java/com/cuoco/application/port/in/GetRecipesFromIngredientsCommand.java b/src/main/java/com/cuoco/application/port/in/GetRecipesFromIngredientsCommand.java index 7065c06..8e41820 100644 --- a/src/main/java/com/cuoco/application/port/in/GetRecipesFromIngredientsCommand.java +++ b/src/main/java/com/cuoco/application/port/in/GetRecipesFromIngredientsCommand.java @@ -1,39 +1,33 @@ package com.cuoco.application.port.in; import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.application.usecase.model.RecipeFilter; +import com.cuoco.application.usecase.model.Recipe; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; import java.util.List; public interface GetRecipesFromIngredientsCommand { - String execute(Command command); + List execute(Command command); + @Data + @Builder + @ToString class Command { - - private final RecipeFilter filters; - private final List ingredients; - - public Command(RecipeFilter filters, List ingredients) { - this.filters = filters; - this.ingredients = ingredients; - } - - public RecipeFilter getFilters() { - return filters; - } - - public List getIngredients() { - return ingredients; - } - - @Override - public String toString() { - return "Command{" + - "filters=" + filters + - ", ingredients=" + ingredients + - '}'; - } + private List ingredients; + private Boolean filtersEnabled; + + private Integer servings; + private Integer preparationTimeId; + private Integer cookLevelId; + private Integer dietId; + private List typeIds; + private List allergiesIds; + private List dietaryNeedsIds; + + private Integer size; + private List notInclude; } - } diff --git a/src/main/java/com/cuoco/application/port/in/GetUserCalendarQuery.java b/src/main/java/com/cuoco/application/port/in/GetUserCalendarQuery.java new file mode 100644 index 0000000..7a455c7 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/GetUserCalendarQuery.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.Calendar; + +import java.util.List; + +public interface GetUserCalendarQuery { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/in/ProcessUserPaymentCommand.java b/src/main/java/com/cuoco/application/port/in/ProcessUserPaymentCommand.java new file mode 100644 index 0000000..d31a50b --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/ProcessUserPaymentCommand.java @@ -0,0 +1,17 @@ +package com.cuoco.application.port.in; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +public interface ProcessUserPaymentCommand { + void execute(Command command); + + @Data + @Builder + class Command { + private String secret; + private Map payload; + } +} diff --git a/src/main/java/com/cuoco/application/port/in/ResendUserActivationEmailCommand.java b/src/main/java/com/cuoco/application/port/in/ResendUserActivationEmailCommand.java new file mode 100644 index 0000000..d3a6f17 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/ResendUserActivationEmailCommand.java @@ -0,0 +1,5 @@ +package com.cuoco.application.port.in; + +public interface ResendUserActivationEmailCommand { + void execute(String email); +} diff --git a/src/main/java/com/cuoco/application/port/in/ResetUserPasswordConfirmationCommand.java b/src/main/java/com/cuoco/application/port/in/ResetUserPasswordConfirmationCommand.java new file mode 100644 index 0000000..3bcda0e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/ResetUserPasswordConfirmationCommand.java @@ -0,0 +1,5 @@ +package com.cuoco.application.port.in; + +public interface ResetUserPasswordConfirmationCommand { + void execute(String email); +} diff --git a/src/main/java/com/cuoco/application/port/in/SignInUserCommand.java b/src/main/java/com/cuoco/application/port/in/SignInUserCommand.java index b07031c..be59f54 100644 --- a/src/main/java/com/cuoco/application/port/in/SignInUserCommand.java +++ b/src/main/java/com/cuoco/application/port/in/SignInUserCommand.java @@ -2,6 +2,7 @@ import com.cuoco.application.usecase.model.AuthenticatedUser; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; public interface SignInUserCommand { @@ -9,6 +10,7 @@ public interface SignInUserCommand { AuthenticatedUser execute(Command command); @Data + @Builder @AllArgsConstructor class Command { private final String email; diff --git a/src/main/java/com/cuoco/application/port/in/UpdateUserProfileCommand.java b/src/main/java/com/cuoco/application/port/in/UpdateUserProfileCommand.java new file mode 100644 index 0000000..3ee20fa --- /dev/null +++ b/src/main/java/com/cuoco/application/port/in/UpdateUserProfileCommand.java @@ -0,0 +1,25 @@ +package com.cuoco.application.port.in; + +import com.cuoco.application.usecase.model.User; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +public interface UpdateUserProfileCommand { + + User execute(Command command); + + @Data + @Builder + @AllArgsConstructor + class Command { + private final String name; + private final Integer planId; + private final Integer cookLevelId; + private final Integer dietId; + private final List dietaryNeeds; + private final List allergies; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/CreateAllMealPrepsRepository.java b/src/main/java/com/cuoco/application/port/out/CreateAllMealPrepsRepository.java new file mode 100644 index 0000000..49b210e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateAllMealPrepsRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealPrep; + +import java.util.List; + +public interface CreateAllMealPrepsRepository { + List execute(List mealPreps); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateAllRecipesRepository.java b/src/main/java/com/cuoco/application/port/out/CreateAllRecipesRepository.java new file mode 100644 index 0000000..bb1033a --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateAllRecipesRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +import java.util.List; + +public interface CreateAllRecipesRepository { + List execute(List recipes); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateRecipeByNameRepository.java b/src/main/java/com/cuoco/application/port/out/CreateRecipeByNameRepository.java new file mode 100644 index 0000000..0170939 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateRecipeByNameRepository.java @@ -0,0 +1,8 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Recipe; + +public interface CreateRecipeByNameRepository { + Recipe execute(String name, ParametricData parametricData); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateRecipeImagesRepository.java b/src/main/java/com/cuoco/application/port/out/CreateRecipeImagesRepository.java new file mode 100644 index 0000000..8d7bc74 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateRecipeImagesRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; + +import java.util.List; + +public interface CreateRecipeImagesRepository { + List execute(Recipe recipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateRecipeRepository.java b/src/main/java/com/cuoco/application/port/out/CreateRecipeRepository.java new file mode 100644 index 0000000..6b53607 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateRecipeRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +public interface CreateRecipeRepository { + Recipe execute(Recipe recipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateUserCalendarRepository.java b/src/main/java/com/cuoco/application/port/out/CreateUserCalendarRepository.java new file mode 100644 index 0000000..c786cf0 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateUserCalendarRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserCalendar; + +public interface CreateUserCalendarRepository { + void execute(UserCalendar userCalendar); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateUserMealPrepRepository.java b/src/main/java/com/cuoco/application/port/out/CreateUserMealPrepRepository.java new file mode 100644 index 0000000..7e458d2 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateUserMealPrepRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserMealPrep; + +public interface CreateUserMealPrepRepository { + void execute(UserMealPrep userMealPrep); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateUserPaymentRepository.java b/src/main/java/com/cuoco/application/port/out/CreateUserPaymentRepository.java new file mode 100644 index 0000000..09e2e4b --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateUserPaymentRepository.java @@ -0,0 +1,8 @@ +package com.cuoco.application.port.out; + + +import com.cuoco.application.usecase.model.UserPayment; + +public interface CreateUserPaymentRepository { + UserPayment execute(UserPayment userPayment); +} diff --git a/src/main/java/com/cuoco/application/port/out/CreateUserRecipeRepository.java b/src/main/java/com/cuoco/application/port/out/CreateUserRecipeRepository.java new file mode 100644 index 0000000..15172d7 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/CreateUserRecipeRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserRecipe; + +public interface CreateUserRecipeRepository { + void execute(UserRecipe userRecipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/DeleteUserCalendarRepository.java b/src/main/java/com/cuoco/application/port/out/DeleteUserCalendarRepository.java new file mode 100644 index 0000000..1b29300 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/DeleteUserCalendarRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserCalendar; + +public interface DeleteUserCalendarRepository { + void execute(UserCalendar userCalendar); +} diff --git a/src/main/java/com/cuoco/application/port/out/DeleteUserMealPrepRepository.java b/src/main/java/com/cuoco/application/port/out/DeleteUserMealPrepRepository.java new file mode 100644 index 0000000..90eeff1 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/DeleteUserMealPrepRepository.java @@ -0,0 +1,5 @@ +package com.cuoco.application.port.out; + +public interface DeleteUserMealPrepRepository { + void execute(Long userId, Long mealPrepId); +} diff --git a/src/main/java/com/cuoco/application/port/out/DeleteUserRecipeRepository.java b/src/main/java/com/cuoco/application/port/out/DeleteUserRecipeRepository.java new file mode 100644 index 0000000..d503fa3 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/DeleteUserRecipeRepository.java @@ -0,0 +1,5 @@ +package com.cuoco.application.port.out; + +public interface DeleteUserRecipeRepository { + void execute(Long userId, Long recipeId); +} diff --git a/src/main/java/com/cuoco/application/port/out/UserExistsByEmailRepository.java b/src/main/java/com/cuoco/application/port/out/ExistsUserByEmailRepository.java similarity index 62% rename from src/main/java/com/cuoco/application/port/out/UserExistsByEmailRepository.java rename to src/main/java/com/cuoco/application/port/out/ExistsUserByEmailRepository.java index 88b9782..1d059ca 100644 --- a/src/main/java/com/cuoco/application/port/out/UserExistsByEmailRepository.java +++ b/src/main/java/com/cuoco/application/port/out/ExistsUserByEmailRepository.java @@ -1,5 +1,5 @@ package com.cuoco.application.port.out; -public interface UserExistsByEmailRepository { +public interface ExistsUserByEmailRepository { Boolean execute(String email); } diff --git a/src/main/java/com/cuoco/application/port/out/ExistsUserMealPrepRepository.java b/src/main/java/com/cuoco/application/port/out/ExistsUserMealPrepRepository.java new file mode 100644 index 0000000..3bcc616 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/ExistsUserMealPrepRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserMealPrep; + +public interface ExistsUserMealPrepRepository { + boolean execute(UserMealPrep userMealPrep); +} diff --git a/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeByUserIdAndRecipeIdRepository.java b/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeByUserIdAndRecipeIdRepository.java new file mode 100644 index 0000000..6e9b87d --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeByUserIdAndRecipeIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserRecipe; + +public interface ExistsUserRecipeByUserIdAndRecipeIdRepository { + boolean execute(UserRecipe userRecipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeCalendarRepository.java b/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeCalendarRepository.java new file mode 100644 index 0000000..370a988 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/ExistsUserRecipeCalendarRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; + +public interface ExistsUserRecipeCalendarRepository { + Boolean execute(User user, Calendar calendar, Recipe recipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/FindRecipeByNameRepository.java b/src/main/java/com/cuoco/application/port/out/FindRecipeByNameRepository.java new file mode 100644 index 0000000..2ea5ef6 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/FindRecipeByNameRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +public interface FindRecipeByNameRepository { + Recipe execute(String recipeName); +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/FindRecipesByFiltersRepository.java b/src/main/java/com/cuoco/application/port/out/FindRecipesByFiltersRepository.java new file mode 100644 index 0000000..3435f12 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/FindRecipesByFiltersRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.SearchFilters; + +import java.util.List; + +public interface FindRecipesByFiltersRepository { + List execute(SearchFilters filters); +} diff --git a/src/main/java/com/cuoco/application/port/out/FindUserCalendarByUserIdAndDateRepository.java b/src/main/java/com/cuoco/application/port/out/FindUserCalendarByUserIdAndDateRepository.java new file mode 100644 index 0000000..bdfd896 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/FindUserCalendarByUserIdAndDateRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserCalendar; + +import java.time.LocalDate; + +public interface FindUserCalendarByUserIdAndDateRepository { + UserCalendar execute(Long userId, LocalDate plannedDate); +} diff --git a/src/main/java/com/cuoco/application/port/out/GenerateRecipeMainImageRepository.java b/src/main/java/com/cuoco/application/port/out/GenerateRecipeMainImageRepository.java new file mode 100644 index 0000000..fec9bfa --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GenerateRecipeMainImageRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +public interface GenerateRecipeMainImageRepository { + boolean execute(Recipe recipe); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllMealPrepsByIdsRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllMealPrepsByIdsRepository.java new file mode 100644 index 0000000..85e1ea4 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllMealPrepsByIdsRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealPrep; + +import java.util.List; + +public interface GetAllMealPrepsByIdsRepository { + List execute(List ids); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllMealTypesRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllMealTypesRepository.java new file mode 100644 index 0000000..8339a33 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllMealTypesRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealType; + +import java.util.List; + +public interface GetAllMealTypesRepository { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllPaymentStatusRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllPaymentStatusRepository.java new file mode 100644 index 0000000..d580ce2 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllPaymentStatusRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.PaymentStatus; + +import java.util.List; + +public interface GetAllPaymentStatusRepository { + List getAll(); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllPreparationTimesRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllPreparationTimesRepository.java new file mode 100644 index 0000000..050aa9a --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllPreparationTimesRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.PreparationTime; + +import java.util.List; + +public interface GetAllPreparationTimesRepository { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllRecipesByIdsRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllRecipesByIdsRepository.java new file mode 100644 index 0000000..fb423e5 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllRecipesByIdsRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +import java.util.List; + +public interface GetAllRecipesByIdsRepository { + List execute(List ids); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllUnitsRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllUnitsRepository.java new file mode 100644 index 0000000..6da5b60 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllUnitsRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Unit; + +import java.util.List; + +public interface GetAllUnitsRepository { + List execute(); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllUserMealPrepsByUserIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllUserMealPrepsByUserIdRepository.java new file mode 100644 index 0000000..944ec2b --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllUserMealPrepsByUserIdRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserMealPrep; + +import java.util.List; + +public interface GetAllUserMealPrepsByUserIdRepository { + List execute(Long userId); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetAllUserRecipesByUserIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetAllUserRecipesByUserIdRepository.java new file mode 100644 index 0000000..b27abab --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetAllUserRecipesByUserIdRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserRecipe; + +import java.util.List; + +public interface GetAllUserRecipesByUserIdRepository { + List execute(Long userId); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioAsyncRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioAsyncRepository.java new file mode 100644 index 0000000..e7edd83 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioAsyncRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Ingredient; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface GetIngredientsFromAudioAsyncRepository { + CompletableFuture> execute(String audioBase64, String format, String language); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioRepository.java new file mode 100644 index 0000000..2dde23b --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromAudioRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Ingredient; + +import java.util.List; + +public interface GetIngredientsFromAudioRepository { + List execute(String audioBase64, String format, String language); + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromImageRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromImageRepository.java new file mode 100644 index 0000000..cc49dd8 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromImageRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; + +import java.util.List; + +public interface GetIngredientsFromImageRepository { + List execute(List files); +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromRepository.java deleted file mode 100644 index 4de7c9d..0000000 --- a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cuoco.application.port.out; - -import com.cuoco.application.usecase.model.Ingredient; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.Map; - -public interface GetIngredientsFromRepository { - - - List execute(List files); - - /** - * Extract ingredients from uploaded files (images) maintaining separation by filename - * - * @param files The files to analyze for ingredients - * @return Map with filename as key and list of ingredients as value - */ - Map> executeWithSeparation(List files); -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromVoiceRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsFromVoiceRepository.java deleted file mode 100644 index 287d66a..0000000 --- a/src/main/java/com/cuoco/application/port/out/GetIngredientsFromVoiceRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cuoco.application.port.out; - -import com.cuoco.application.usecase.model.Ingredient; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public interface GetIngredientsFromVoiceRepository { - - - List processVoice(String audioBase64, String format, String language); - - - CompletableFuture> processVoiceAsync(String audioBase64, String format, String language); -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/GetIngredientsGroupedFromImagesRepository.java b/src/main/java/com/cuoco/application/port/out/GetIngredientsGroupedFromImagesRepository.java new file mode 100644 index 0000000..a7a8573 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetIngredientsGroupedFromImagesRepository.java @@ -0,0 +1,12 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.ParametricData; + +import java.util.List; +import java.util.Map; + +public interface GetIngredientsGroupedFromImagesRepository { + Map> execute(List files, ParametricData parametricData); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetMealPrepByIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetMealPrepByIdRepository.java new file mode 100644 index 0000000..2d11288 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetMealPrepByIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealPrep; + +public interface GetMealPrepByIdRepository { + MealPrep execute(Long id); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetMealPrepsFromIngredientsRepository.java b/src/main/java/com/cuoco/application/port/out/GetMealPrepsFromIngredientsRepository.java new file mode 100644 index 0000000..523f0d5 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetMealPrepsFromIngredientsRepository.java @@ -0,0 +1,9 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealPrep; + +import java.util.List; + +public interface GetMealPrepsFromIngredientsRepository { + List execute(MealPrep mealPrep); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetMealTypeByIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetMealTypeByIdRepository.java new file mode 100644 index 0000000..003cb10 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetMealTypeByIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.MealType; + +public interface GetMealTypeByIdRepository { + MealType execute(Integer id); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetPreparationTimeByIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetPreparationTimeByIdRepository.java new file mode 100644 index 0000000..36fc504 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetPreparationTimeByIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.PreparationTime; + +public interface GetPreparationTimeByIdRepository { + PreparationTime execute(Integer id); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetRecipeByIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetRecipeByIdRepository.java new file mode 100644 index 0000000..05db40e --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetRecipeByIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; + +public interface GetRecipeByIdRepository { + Recipe execute(Long id); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetRecipeStepsImagesRepository.java b/src/main/java/com/cuoco/application/port/out/GetRecipeStepsImagesRepository.java new file mode 100644 index 0000000..ef9ee0d --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetRecipeStepsImagesRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; + +import java.util.List; + +public interface GetRecipeStepsImagesRepository { + List execute(Recipe recipe); +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/port/out/GetRecipesFromIngredientsRepository.java b/src/main/java/com/cuoco/application/port/out/GetRecipesFromIngredientsRepository.java index 6f00ff6..30596fa 100644 --- a/src/main/java/com/cuoco/application/port/out/GetRecipesFromIngredientsRepository.java +++ b/src/main/java/com/cuoco/application/port/out/GetRecipesFromIngredientsRepository.java @@ -1,9 +1,9 @@ package com.cuoco.application.port.out; -import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; import java.util.List; public interface GetRecipesFromIngredientsRepository { - String execute(List ingredients); + List execute(Recipe recipe); } diff --git a/src/main/java/com/cuoco/application/port/out/GetUserByIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetUserByIdRepository.java new file mode 100644 index 0000000..466f637 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetUserByIdRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.User; + +public interface GetUserByIdRepository { + User execute(Long id); +} diff --git a/src/main/java/com/cuoco/application/port/out/GetUserCalendarByUserIdRepository.java b/src/main/java/com/cuoco/application/port/out/GetUserCalendarByUserIdRepository.java new file mode 100644 index 0000000..37507d4 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetUserCalendarByUserIdRepository.java @@ -0,0 +1,10 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.Calendar; + +import java.util.List; + +public interface GetUserCalendarByUserIdRepository { + List execute(Long id); + +} diff --git a/src/main/java/com/cuoco/application/port/out/GetUserPaymentByExternalReferenceRepository.java b/src/main/java/com/cuoco/application/port/out/GetUserPaymentByExternalReferenceRepository.java new file mode 100644 index 0000000..6339a48 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/GetUserPaymentByExternalReferenceRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserPayment; + +public interface GetUserPaymentByExternalReferenceRepository { + UserPayment execute(String externalReference); +} diff --git a/src/main/java/com/cuoco/application/port/out/ProcessUserPaymentRepository.java b/src/main/java/com/cuoco/application/port/out/ProcessUserPaymentRepository.java new file mode 100644 index 0000000..1546eea --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/ProcessUserPaymentRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserPayment; + +public interface ProcessUserPaymentRepository { + UserPayment execute(String paymentId); +} diff --git a/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java b/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java new file mode 100644 index 0000000..141d2bc --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.User; + +public interface SendConfirmationEmailRepository { + void execute(User user, String token); +} diff --git a/src/main/java/com/cuoco/application/port/out/SendResetPasswordConfirmationRepository.java b/src/main/java/com/cuoco/application/port/out/SendResetPasswordConfirmationRepository.java new file mode 100644 index 0000000..3b6de4c --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/SendResetPasswordConfirmationRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.User; + +public interface SendResetPasswordConfirmationRepository { + void execute(User user, String token); +} diff --git a/src/main/java/com/cuoco/application/port/out/UpdateUserPaymentRepository.java b/src/main/java/com/cuoco/application/port/out/UpdateUserPaymentRepository.java new file mode 100644 index 0000000..0cdfc75 --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/UpdateUserPaymentRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.UserPayment; + +public interface UpdateUserPaymentRepository { + UserPayment execute(UserPayment userPayment); +} diff --git a/src/main/java/com/cuoco/application/port/out/UpdateUserRepository.java b/src/main/java/com/cuoco/application/port/out/UpdateUserRepository.java new file mode 100644 index 0000000..c5b94ed --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/UpdateUserRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.User; + +public interface UpdateUserRepository { + User execute(User user); +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java new file mode 100644 index 0000000..935c0ef --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java @@ -0,0 +1,38 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.ActivateUserCommand; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ActivateUserUseCase implements ActivateUserCommand { + + private final GetUserByIdRepository getUserByIdRepository; + private final UpdateUserRepository updateUserRepository; + private final JwtUtil jwtUtil; + + @Override + @Transactional + public void execute(Command command) { + log.info("Executing user activation with token {}", command.getToken()); + + Long id = Long.valueOf(jwtUtil.extractId(command.getToken())); + + User user = getUserByIdRepository.execute(id); + + user.setActive(true); + + updateUserRepository.execute(user); + + log.info("Successfully activated user with ID {} and email {}", user.getId(), user.getEmail()); + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java index 1b75d56..8d547d6 100644 --- a/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java @@ -1,24 +1,23 @@ package com.cuoco.application.usecase; +import com.cuoco.application.exception.ForbiddenException; import com.cuoco.application.exception.UnauthorizedException; import com.cuoco.application.port.in.AuthenticateUserCommand; import com.cuoco.application.port.out.GetUserByEmailRepository; import com.cuoco.application.usecase.model.AuthenticatedUser; import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; import com.cuoco.shared.model.ErrorDescription; -import com.cuoco.shared.utils.JwtUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.util.Collections; +@Slf4j @Component public class AuthenticateUserUseCase implements AuthenticateUserCommand { - static final Logger log = LoggerFactory.getLogger(AuthenticateUserUseCase.class); - static final String BEARER_PREFIX = "Bearer "; private final JwtUtil jwtUtil; @@ -32,30 +31,41 @@ public AuthenticateUserUseCase(JwtUtil jwtUtil, GetUserByEmailRepository getUser @Override public AuthenticatedUser execute(Command command) { - log.info("Executing authenticate user usecase"); + log.info("Executing user authentication usecase"); String authHeader = command.getAuthHeader(); - if (authHeader == null || !authHeader.startsWith(BEARER_PREFIX)) { - log.info("User don't have a valid auth header"); - throw new UnauthorizedException(ErrorDescription.UNAUTHORIZED.getValue()); + if (authHeader == null) { + log.info("Auth header is not present"); + throw new UnauthorizedException(ErrorDescription.NO_AUTH_TOKEN.getValue()); + } + + if (!authHeader.startsWith(BEARER_PREFIX)) { + log.info("Don't have a valid auth token"); + throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } String receivedJwt = authHeader.substring(7); String email = jwtUtil.extractEmail(receivedJwt); if (email == null || SecurityContextHolder.getContext().getAuthentication() != null) { - log.info("Token is not valid. The email is not present."); - throw new UnauthorizedException(ErrorDescription.INVALID_TOKEN.getValue()); + log.info("Token is not valid: The email is not present."); + throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } User user = getUserByEmailRepository.execute(email); if (user == null || !jwtUtil.validateToken(receivedJwt, user)) { - log.info("Token or user with email {} are not valid", email); - throw new UnauthorizedException(ErrorDescription.INVALID_TOKEN.getValue()); + log.info("Token or user with email {} are not valid or not exists", email); + throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); + } + + if (user.getActive() != null && !user.getActive()) { + log.info("User with email {} is not activated yet", email); + throw new ForbiddenException(ErrorDescription.USER_NOT_ACTIVATED.getValue()); } + log.info("User authenticated with email {}", email); return buildAuthenticatedUser(user); } diff --git a/src/main/java/com/cuoco/application/usecase/ChangeUserPasswordUseCase.java b/src/main/java/com/cuoco/application/usecase/ChangeUserPasswordUseCase.java new file mode 100644 index 0000000..bef86a0 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/ChangeUserPasswordUseCase.java @@ -0,0 +1,51 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.ChangeUserPasswordCommand; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import com.cuoco.application.port.out.GetUserByEmailRepository; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.SendResetPasswordConfirmationRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ChangeUserPasswordUseCase implements ChangeUserPasswordCommand { + + private final UserDomainService userDomainService; + + private final GetUserByIdRepository getUserByIdRepository; + private final UpdateUserRepository updateUserRepository; + + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + + @Override + public void execute(Command command) { + log.info("Executing change user password use case"); + + User user; + + if(command.getToken() != null) { + log.info("Change user password using token"); + + Long id = Long.valueOf(jwtUtil.extractId(command.getToken())); + user = getUserByIdRepository.execute(id); + } else { + log.info("Change user password for authenticated user"); + user = userDomainService.getCurrentUser(); + } + + String encriptedPassword = passwordEncoder.encode(command.getPassword()); + user.setPassword(encriptedPassword); + + updateUserRepository.execute(user); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/CreateOrUpdateUserRecipeCalendarUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateOrUpdateUserRecipeCalendarUseCase.java new file mode 100644 index 0000000..6cfbedd --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/CreateOrUpdateUserRecipeCalendarUseCase.java @@ -0,0 +1,112 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.CreateOrUpdateUserRecipeCalendarCommand; +import com.cuoco.application.port.out.CreateUserCalendarRepository; +import com.cuoco.application.port.out.DeleteUserCalendarRepository; +import com.cuoco.application.port.out.GetMealTypeByIdRepository; +import com.cuoco.application.port.out.GetRecipeByIdRepository; +import com.cuoco.application.usecase.domainservice.CalendarDomainService; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.CalendarRecipe; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserCalendar; +import com.cuoco.shared.model.ErrorDescription; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CreateOrUpdateUserRecipeCalendarUseCase implements CreateOrUpdateUserRecipeCalendarCommand { + + private final UserDomainService userDomainService; + private final CalendarDomainService calendarDomainService; + private final CreateUserCalendarRepository createUserCalendarRepository; + private final DeleteUserCalendarRepository deleteUserCalendarRepository; + private final GetRecipeByIdRepository getRecipeByIdRepository; + private final GetMealTypeByIdRepository getMealTypeByIdRepository; + + @Override + public void execute(Command command) { + User user = userDomainService.getCurrentUser(); + + removeExistentCalendars(user); + + List requestedCalendars = command.getCalendars().stream() + .peek(calendar -> calendar.setDate(toDateFormat(calendar.getDay().getId()))) + .filter(calendar -> !calendar.getRecipes().isEmpty()) + .toList(); + + List calendarsToSave = requestedCalendars.stream() + .map(this::buildCalendarWithValidatedRecipes) + .toList(); + + UserCalendar updated = UserCalendar.builder() + .user(user) + .calendars(calendarsToSave) + .build(); + + createUserCalendarRepository.execute(updated); + } + + private void removeExistentCalendars(User user) { + List currentCalendars = calendarDomainService.getUserCalendar(user); + + if (!currentCalendars.isEmpty()) { + UserCalendar toDelete = UserCalendar.builder() + .user(user) + .calendars(currentCalendars) + .build(); + + deleteUserCalendarRepository.execute(toDelete); + } + } + + private Calendar buildCalendarWithValidatedRecipes(Calendar calendarRequest) { + List validatedRecipes = calendarRequest.getRecipes().stream() + .map(this::buildRecipeCalendar) + .toList(); + + return Calendar.builder() + .date(calendarRequest.getDate()) + .recipes(validatedRecipes) + .build(); + } + + private CalendarRecipe buildRecipeCalendar(CalendarRecipe calendarRecipeRequest) { + Recipe recipe = getRecipeByIdRepository.execute(calendarRecipeRequest.getRecipe().getId()); + MealType mealType = getMealTypeByIdRepository.execute(calendarRecipeRequest.getMealType().getId()); + + return CalendarRecipe.builder() + .recipe(recipe) + .mealType(mealType) + .build(); + } + + private LocalDate toDateFormat(int dayId) { + if (dayId < 1 || dayId > 7) { + throw new BadRequestException(ErrorDescription.INVALID_CALENDAR_DAY.getValue()); + } + + LocalDate today = LocalDate.now(); + DayOfWeek dayOfWeekToday = today.getDayOfWeek(); + int todayValue = dayOfWeekToday.getValue(); + + int difference = dayId - todayValue; + + if (difference < 0) { + difference += 7; + } + + return today.plusDays(difference); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/CreateUserMealPrepUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateUserMealPrepUseCase.java new file mode 100644 index 0000000..5788ce3 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/CreateUserMealPrepUseCase.java @@ -0,0 +1,61 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.ConflictException; +import com.cuoco.application.port.in.CreateUserMealPrepCommand; +import com.cuoco.application.port.out.CreateUserMealPrepRepository; +import com.cuoco.application.port.out.ExistsUserMealPrepRepository; +import com.cuoco.application.port.out.GetMealPrepByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserMealPrep; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CreateUserMealPrepUseCase implements CreateUserMealPrepCommand { + + private final UserDomainService userDomainService; + private final CreateUserMealPrepRepository createUserMealPrepRepository; + private final ExistsUserMealPrepRepository existsUserMealPrepRepository; + private final GetMealPrepByIdRepository getMealPrepByIdRepository; + + public CreateUserMealPrepUseCase( + UserDomainService userDomainService, + CreateUserMealPrepRepository createUserMealPrepRepository, + ExistsUserMealPrepRepository existsUserMealPrepRepository, + GetMealPrepByIdRepository getMealPrepByIdRepository + ) { + this.userDomainService = userDomainService; + this.createUserMealPrepRepository = createUserMealPrepRepository; + this.existsUserMealPrepRepository = existsUserMealPrepRepository; + this.getMealPrepByIdRepository = getMealPrepByIdRepository; + } + + @Override + public void execute(Command command) { + log.info("Executing create user meal prep use case with command {}", command); + + User user = userDomainService.getCurrentUser(); + + MealPrep mealPrep = getMealPrepByIdRepository.execute(command.getId()); + + UserMealPrep userMealPrep = buildUserMealPrep(user, mealPrep); + + if (existsUserMealPrepRepository.execute(userMealPrep)) { + log.info("Meal prep already saved by user with ID {}", user.getId()); + throw new ConflictException(ErrorDescription.DUPLICATED.getValue()); + } + + createUserMealPrepRepository.execute(userMealPrep); + } + + private UserMealPrep buildUserMealPrep(User user, MealPrep mealPrep) { + return UserMealPrep.builder() + .user(user) + .mealPrep(mealPrep) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/CreateUserPaymentUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateUserPaymentUseCase.java new file mode 100644 index 0000000..3c3650a --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/CreateUserPaymentUseCase.java @@ -0,0 +1,62 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.CreateUserPaymentCommand; +import com.cuoco.application.port.out.CreateUserPaymentRepository; +import com.cuoco.application.port.out.GetPlanByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPayment; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.PlanConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CreateUserPaymentUseCase implements CreateUserPaymentCommand { + + private final UserDomainService userDomainService; + private final GetPlanByIdRepository getPlanByIdRepository; + private final CreateUserPaymentRepository createUserPaymentProvider; + private final CreateUserPaymentRepository createUserPaymentRepository; + + public CreateUserPaymentUseCase( + UserDomainService userDomainService, + GetPlanByIdRepository getPlanByIdRepository, + @Qualifier("provider") CreateUserPaymentRepository createUserPaymentProvider, + @Qualifier("repository") CreateUserPaymentRepository createUserPaymentRepository + ) { + this.userDomainService = userDomainService; + this.getPlanByIdRepository = getPlanByIdRepository; + this.createUserPaymentProvider = createUserPaymentProvider; + this.createUserPaymentRepository = createUserPaymentRepository; + } + + @Override + public UserPayment execute() { + User user = userDomainService.getCurrentUser(); + + if(user.getPlan().getId() == PlanConstants.PRO.getValue()) { + log.info("User already has a PRO plan"); + throw new BadRequestException(ErrorDescription.USER_HAS_PRO_PLAN.getValue()); + } + + Plan plan = getPlanByIdRepository.execute(PlanConstants.PRO.getValue()); + + UserPayment userPayment = createUserPaymentProvider.execute(buildUserPayment(user, plan)); + + createUserPaymentRepository.execute(userPayment); + + return userPayment; + } + + private UserPayment buildUserPayment(User user, Plan plan) { + return UserPayment.builder() + .user(user) + .plan(plan) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/CreateUserRecipeUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateUserRecipeUseCase.java new file mode 100644 index 0000000..1d1fbee --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/CreateUserRecipeUseCase.java @@ -0,0 +1,62 @@ +package com.cuoco.application.usecase; + + +import com.cuoco.application.exception.ConflictException; +import com.cuoco.application.port.in.CreateUserRecipeCommand; +import com.cuoco.application.port.out.CreateUserRecipeRepository; +import com.cuoco.application.port.out.ExistsUserRecipeByUserIdAndRecipeIdRepository; +import com.cuoco.application.port.out.GetRecipeByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CreateUserRecipeUseCase implements CreateUserRecipeCommand { + + private final UserDomainService userDomainService; + private final CreateUserRecipeRepository createUserRecipeRepository; + private final ExistsUserRecipeByUserIdAndRecipeIdRepository existsUserRecipeByUserIdAndRecipeIdRepository; + private final GetRecipeByIdRepository getRecipeByIdRepository; + + public CreateUserRecipeUseCase( + UserDomainService userDomainService, + CreateUserRecipeRepository createUserRecipeRepository, + ExistsUserRecipeByUserIdAndRecipeIdRepository existsUserRecipeByUserIdAndRecipeIdRepository, + GetRecipeByIdRepository getRecipeByIdRepository + ) { + this.userDomainService = userDomainService; + this.createUserRecipeRepository = createUserRecipeRepository; + this.existsUserRecipeByUserIdAndRecipeIdRepository = existsUserRecipeByUserIdAndRecipeIdRepository; + this.getRecipeByIdRepository = getRecipeByIdRepository; + } + + @Override + public void execute(Command command) { + log.info("Executing create user recipe use case with command {}", command); + + User user = userDomainService.getCurrentUser(); + + Recipe recipe = getRecipeByIdRepository.execute(command.getRecipeId()); + + UserRecipe userRecipe = buildUserRecipe(user, recipe); + + if(existsUserRecipeByUserIdAndRecipeIdRepository.execute(userRecipe)){ + log.info("Recipe already saved by user with ID {} ", userRecipe.getUser().getId()); + throw new ConflictException(ErrorDescription.DUPLICATED.getValue()); + } + + createUserRecipeRepository.execute(userRecipe); + } + + private UserRecipe buildUserRecipe(User user, Recipe recipe) { + return UserRecipe.builder() + .user(user) + .recipe(recipe) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java index 3d9d5f1..86de879 100644 --- a/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java @@ -3,12 +3,13 @@ import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.in.CreateUserCommand; import com.cuoco.application.port.out.CreateUserRepository; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; import com.cuoco.application.port.out.GetAllergiesByIdRepository; import com.cuoco.application.port.out.GetCookLevelByIdRepository; import com.cuoco.application.port.out.GetDietByIdRepository; import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; import com.cuoco.application.port.out.GetPlanByIdRepository; -import com.cuoco.application.port.out.UserExistsByEmailRepository; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; import com.cuoco.application.usecase.model.Allergy; import com.cuoco.application.usecase.model.CookLevel; import com.cuoco.application.usecase.model.Diet; @@ -16,55 +17,39 @@ import com.cuoco.application.usecase.model.Plan; import com.cuoco.application.usecase.model.User; import com.cuoco.application.usecase.model.UserPreferences; +import com.cuoco.application.utils.JwtUtil; import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.PlanConstants; import jakarta.transaction.Transactional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; +@Slf4j @Component +@RequiredArgsConstructor public class CreateUserUseCase implements CreateUserCommand { - static final Logger log = LoggerFactory.getLogger(CreateUserUseCase.class); - private final PasswordEncoder passwordEncoder; private final CreateUserRepository createUserRepository; - private final UserExistsByEmailRepository userExistsByEmailRepository; + private final ExistsUserByEmailRepository existsUserByEmailRepository; private final GetPlanByIdRepository getPlanByIdRepository; private final GetDietByIdRepository getDietByIdRepository; private final GetCookLevelByIdRepository getCookLevelByIdRepository; private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; private final GetAllergiesByIdRepository getAllergiesByIdRepository; - - public CreateUserUseCase( - PasswordEncoder passwordEncoder, - CreateUserRepository createUserRepository, - UserExistsByEmailRepository userExistsByEmailRepository, - GetPlanByIdRepository getPlanByIdRepository, - GetDietByIdRepository getDietByIdRepository, - GetCookLevelByIdRepository getCookLevelByIdRepository, - GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository, - GetAllergiesByIdRepository getAllergiesByIdRepository - ) { - this.passwordEncoder = passwordEncoder; - this.createUserRepository = createUserRepository; - this.userExistsByEmailRepository = userExistsByEmailRepository; - this.getPlanByIdRepository = getPlanByIdRepository; - this.getDietByIdRepository = getDietByIdRepository; - this.getCookLevelByIdRepository = getCookLevelByIdRepository; - this.getDietaryNeedsByIdRepository = getDietaryNeedsByIdRepository; - this.getAllergiesByIdRepository = getAllergiesByIdRepository; - } + private final SendConfirmationEmailRepository sendConfirmationEmailRepository; + private final JwtUtil jwtUtil; @Transactional public User execute(Command command) { log.info("Executing create user use case for email {}", command.getEmail()); - if(userExistsByEmailRepository.execute(command.getEmail())) { + if(existsUserByEmailRepository.execute(command.getEmail())) { log.info("Email {} already exists", command.getEmail()); throw new BadRequestException(ErrorDescription.USER_DUPLICATED.getValue()); } @@ -72,9 +57,9 @@ public User execute(Command command) { List existingNeeds = getDietaryNeeds(command); List existingAlergies = getAllergies(command); - Plan plan = getPlan(command.getPlanId()); - CookLevel cookLevel = getCookLevel(command.getCookLevelId()); - Diet diet = getDiet(command.getDietId()); + Plan plan = getPlanByIdRepository.execute(PlanConstants.FREE.getValue()); + CookLevel cookLevel = getCookLevelByIdRepository.execute(command.getCookLevelId()); + Diet diet = getDietByIdRepository.execute(command.getDietId()); UserPreferences preferencesToSave = buildUserPreferences(cookLevel, diet); User userToSave = buildUser(command, preferencesToSave, plan, existingNeeds, existingAlergies); @@ -83,25 +68,9 @@ public User execute(Command command) { userCreated.setPassword(null); - return userCreated; - } - - private Plan getPlan(Integer planId) { - Plan plan = getPlanByIdRepository.execute(planId); - if(plan == null) throw new BadRequestException(ErrorDescription.PLAN_NOT_EXISTS.getValue()); - return plan; - } - - private Diet getDiet(Integer dietId) { - Diet diet = getDietByIdRepository.execute(dietId); - if(diet == null) throw new BadRequestException(ErrorDescription.DIET_NOT_EXISTS.getValue()); - return diet; - } + sendConfirmationEmail(userCreated); - private CookLevel getCookLevel(Integer cookLevelId) { - CookLevel cookLevel = getCookLevelByIdRepository.execute(cookLevelId); - if(cookLevel == null) throw new BadRequestException(ErrorDescription.COOK_LEVEL_NOT_EXISTS.getValue()); - return cookLevel; + return userCreated; } private List getDietaryNeeds(Command command) { @@ -149,7 +118,7 @@ private User buildUser( .email(command.getEmail()) .password(encriptedPassword) .plan(plan) - .active(true) + .active(false) .preferences(preferences) .dietaryNeeds(existingNeeds) .allergies(existingAlergies) @@ -163,4 +132,8 @@ private UserPreferences buildUserPreferences(CookLevel cookLevel, Diet diet) { .build(); } + private void sendConfirmationEmail(User user) { + String token = jwtUtil.generateActivationToken(user); + sendConfirmationEmailRepository.execute(user, token); + } } diff --git a/src/main/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCase.java b/src/main/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCase.java new file mode 100644 index 0000000..205aae3 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCase.java @@ -0,0 +1,33 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.DeleteUserMealPrepCommand; +import com.cuoco.application.port.out.DeleteUserMealPrepRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class DeleteUserMealPrepUseCase implements DeleteUserMealPrepCommand { + + private final UserDomainService userDomainService; + private final DeleteUserMealPrepRepository deleteUserMealPrepRepository; + + public DeleteUserMealPrepUseCase( + UserDomainService userDomainService, + DeleteUserMealPrepRepository deleteUserMealPrepRepository + ) { + this.userDomainService = userDomainService; + this.deleteUserMealPrepRepository = deleteUserMealPrepRepository; + } + + @Override + public void execute(Command command) { + log.info("Executing delete meal prep from user with meal prep id {}", command.getId()); + + User user = userDomainService.getCurrentUser(); + + deleteUserMealPrepRepository.execute(user.getId(), command.getId()); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCase.java b/src/main/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCase.java new file mode 100644 index 0000000..0186b4b --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCase.java @@ -0,0 +1,33 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.DeleteUserRecipeCommand; +import com.cuoco.application.port.out.DeleteUserRecipeRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class DeleteUserRepositoryUseCase implements DeleteUserRecipeCommand { + + private final UserDomainService userDomainService; + private final DeleteUserRecipeRepository deleteUserRecipeRepository; + + public DeleteUserRepositoryUseCase( + UserDomainService userDomainService, + DeleteUserRecipeRepository deleteUserRecipeRepository + ) { + this.userDomainService = userDomainService; + this.deleteUserRecipeRepository = deleteUserRecipeRepository; + } + + @Override + public void execute(Command command) { + log.info("Executing delete recipe from user with recipe id {}", command.getId()); + + User user = userDomainService.getCurrentUser(); + + deleteUserRecipeRepository.execute(user.getId(), command.getId()); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/FindOrCreateRecipeUseCase.java b/src/main/java/com/cuoco/application/usecase/FindOrCreateRecipeUseCase.java new file mode 100644 index 0000000..3d0f726 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/FindOrCreateRecipeUseCase.java @@ -0,0 +1,50 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.RecipeGenerationException; +import com.cuoco.application.port.in.FindOrCreateRecipeCommand; +import com.cuoco.application.port.out.CreateRecipeByNameRepository; +import com.cuoco.application.port.out.CreateRecipeRepository; +import com.cuoco.application.port.out.FindRecipeByNameRepository; +import com.cuoco.application.usecase.domainservice.ParametricDataDomainService; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.shared.model.ErrorDescription; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FindOrCreateRecipeUseCase implements FindOrCreateRecipeCommand { + + private final ParametricDataDomainService parametricDataDomainService; + private final CreateRecipeByNameRepository createRecipeByNameRepository; + private final FindRecipeByNameRepository findRecipeByNameRepository; + private final CreateRecipeRepository createRecipeRepository; + + @Override + public Recipe execute(Command command) { + log.info("Executing find or generate recipe use case with name: {}", command.getRecipeName()); + + Recipe existingRecipe = findRecipeByNameRepository.execute(command.getRecipeName()); + + if (existingRecipe != null) { + log.info("Recipe found in database: {}", existingRecipe.getName()); + return existingRecipe; + } + + log.info("Recipe not found in database. Generating new recipe for: {}", command.getRecipeName()); + ParametricData parametricData = parametricDataDomainService.getAll(); + Recipe generatedRecipe = createRecipeByNameRepository.execute(command.getRecipeName(), parametricData); + + if (generatedRecipe == null) { + throw new RecipeGenerationException(ErrorDescription.CANNOT_GENERATE_RECIPE.getValue()); + } + + Recipe savedRecipe = createRecipeRepository.execute(generatedRecipe); + + log.info("Successfully generated and saved new recipe: {}", savedRecipe.getName()); + return savedRecipe; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/FindRecipesUseCase.java b/src/main/java/com/cuoco/application/usecase/FindRecipesUseCase.java new file mode 100644 index 0000000..7eae812 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/FindRecipesUseCase.java @@ -0,0 +1,39 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.FindRecipesCommand; +import com.cuoco.application.port.out.FindRecipesByFiltersRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.SearchFilters; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class FindRecipesUseCase implements FindRecipesCommand { + + private static Integer SEARCH_DEFAULT_SIZE = 5; + + private FindRecipesByFiltersRepository findRecipesByFiltersRepository; + + public FindRecipesUseCase(FindRecipesByFiltersRepository findRecipesByFiltersRepository) { + this.findRecipesByFiltersRepository = findRecipesByFiltersRepository; + } + + @Override + public List execute(Command command) { + log.info("Executing find recipes by filters use case with command {}", command); + + List recipes = findRecipesByFiltersRepository.execute(buildSearchFilters(command)); + + return recipes; + } + + private SearchFilters buildSearchFilters(Command command) { + return SearchFilters.builder() + .size(command.getSize() != null ? command.getSize() : SEARCH_DEFAULT_SIZE) + .random(command.getRandom() != null ? command.getRandom() : false) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCase.java b/src/main/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCase.java new file mode 100644 index 0000000..37fda07 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCase.java @@ -0,0 +1,44 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GenerateRecipeImagesCommand; +import com.cuoco.application.port.out.GetRecipeStepsImagesRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class GenerateRecipeImagesUseCase implements GenerateRecipeImagesCommand { + + private final GetRecipeStepsImagesRepository getRecipeStepsImagesRepository; + + public GenerateRecipeImagesUseCase(GetRecipeStepsImagesRepository getRecipeStepsImagesRepository) { + this.getRecipeStepsImagesRepository = getRecipeStepsImagesRepository; + } + + @Override + public List execute(Command command) { + Recipe recipe = command.getRecipe(); + log.info("Executing recipe images generation for recipe: {}", recipe.getName()); + + try { + List generatedImages = getRecipeStepsImagesRepository.execute(recipe); + + if (generatedImages == null) { + generatedImages = List.of(); + } + + log.info("Successfully generated {} images for recipe: {}", + generatedImages.size(), recipe.getName()); + + return generatedImages; + } catch (Exception e) { + log.error("Error generating images for recipe: {}", recipe.getName(), e); + // Return empty list instead of throwing exception to not break recipe generation flow + return List.of(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/GetAllAllergiesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllAllergiesUseCase.java index 30b8ded..9623c26 100644 --- a/src/main/java/com/cuoco/application/usecase/GetAllAllergiesUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetAllAllergiesUseCase.java @@ -1,20 +1,18 @@ package com.cuoco.application.usecase; -import com.cuoco.application.port.out.GetAllAllergiesRepository; import com.cuoco.application.port.in.GetAllAllergiesQuery; +import com.cuoco.application.port.out.GetAllAllergiesRepository; import com.cuoco.application.usecase.model.Allergy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component public class GetAllAllergiesUseCase implements GetAllAllergiesQuery { - static final Logger log = LoggerFactory.getLogger(GetAllAllergiesUseCase.class); - - private GetAllAllergiesRepository getAllAllergiesRepository; + private final GetAllAllergiesRepository getAllAllergiesRepository; public GetAllAllergiesUseCase(GetAllAllergiesRepository getAllAllergiesRepository) { this.getAllAllergiesRepository = getAllAllergiesRepository; diff --git a/src/main/java/com/cuoco/application/usecase/GetAllCookLevelsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllCookLevelsUseCase.java index 1bce186..0bdbfeb 100644 --- a/src/main/java/com/cuoco/application/usecase/GetAllCookLevelsUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetAllCookLevelsUseCase.java @@ -1,20 +1,18 @@ package com.cuoco.application.usecase; -import com.cuoco.application.port.in.GetCookLevelsQuery; +import com.cuoco.application.port.in.GetAllCookLevelsQuery; import com.cuoco.application.port.out.GetAllCookLevelsRepository; import com.cuoco.application.usecase.model.CookLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component -public class GetAllCookLevelsUseCase implements GetCookLevelsQuery { +public class GetAllCookLevelsUseCase implements GetAllCookLevelsQuery { - static final Logger log = LoggerFactory.getLogger(GetAllCookLevelsUseCase.class); - - private GetAllCookLevelsRepository getAllCookLevelsRepository; + private final GetAllCookLevelsRepository getAllCookLevelsRepository; public GetAllCookLevelsUseCase(GetAllCookLevelsRepository getAllCookLevelsRepository) { this.getAllCookLevelsRepository = getAllCookLevelsRepository; diff --git a/src/main/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCase.java index cb33512..edade28 100644 --- a/src/main/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCase.java @@ -1,20 +1,18 @@ package com.cuoco.application.usecase; -import com.cuoco.application.port.out.GetAllDietaryNeedsRepository; import com.cuoco.application.port.in.GetAllDietaryNeedsQuery; +import com.cuoco.application.port.out.GetAllDietaryNeedsRepository; import com.cuoco.application.usecase.model.DietaryNeed; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component public class GetAllDietaryNeedsUseCase implements GetAllDietaryNeedsQuery { - static final Logger log = LoggerFactory.getLogger(GetAllDietaryNeedsUseCase.class); - - private GetAllDietaryNeedsRepository getAllDietaryNeedsRepository; + private final GetAllDietaryNeedsRepository getAllDietaryNeedsRepository; public GetAllDietaryNeedsUseCase(GetAllDietaryNeedsRepository getAllDietaryNeedsRepository) { this.getAllDietaryNeedsRepository = getAllDietaryNeedsRepository; diff --git a/src/main/java/com/cuoco/application/usecase/GetAllDietsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllDietsUseCase.java index 45f0537..b476063 100644 --- a/src/main/java/com/cuoco/application/usecase/GetAllDietsUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetAllDietsUseCase.java @@ -1,20 +1,18 @@ package com.cuoco.application.usecase; -import com.cuoco.application.port.in.GetDietsQuery; +import com.cuoco.application.port.in.GetAllDietsQuery; import com.cuoco.application.port.out.GetAllDietsRepository; import com.cuoco.application.usecase.model.Diet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component -public class GetAllDietsUseCase implements GetDietsQuery { +public class GetAllDietsUseCase implements GetAllDietsQuery { - static final Logger log = LoggerFactory.getLogger(GetAllDietsUseCase.class); - - private GetAllDietsRepository getAllDietsRepository; + private final GetAllDietsRepository getAllDietsRepository; public GetAllDietsUseCase(GetAllDietsRepository getAllDietsRepository) { this.getAllDietsRepository = getAllDietsRepository; diff --git a/src/main/java/com/cuoco/application/usecase/GetAllMealPrepsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllMealPrepsUseCase.java new file mode 100644 index 0000000..79a0322 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetAllMealPrepsUseCase.java @@ -0,0 +1,39 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetAllUserMealPrepsQuery; +import com.cuoco.application.port.out.GetAllUserMealPrepsByUserIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserMealPrep; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetAllMealPrepsUseCase implements GetAllUserMealPrepsQuery { + + private final UserDomainService userDomainService; + private final GetAllUserMealPrepsByUserIdRepository getAllUserMealPrepsByUserIdRepository; + + public GetAllMealPrepsUseCase( + UserDomainService userDomainService, + GetAllUserMealPrepsByUserIdRepository getAllUserMealPrepsByUserIdRepository + ) { + this.userDomainService = userDomainService; + this.getAllUserMealPrepsByUserIdRepository = getAllUserMealPrepsByUserIdRepository; + } + + @Override + public List execute() { + log.info("Executing get all user meal preps use case"); + + User user = userDomainService.getCurrentUser(); + + List userMealPreps = getAllUserMealPrepsByUserIdRepository.execute(user.getId()); + + return userMealPreps.stream().map(UserMealPrep::getMealPrep).toList(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetAllMealTypesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllMealTypesUseCase.java new file mode 100644 index 0000000..3779fbe --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetAllMealTypesUseCase.java @@ -0,0 +1,26 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetAllMealTypesQuery; +import com.cuoco.application.port.out.GetAllMealTypesRepository; +import com.cuoco.application.usecase.model.MealType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetAllMealTypesUseCase implements GetAllMealTypesQuery { + + private final GetAllMealTypesRepository getAllMealTypesRepository; + + public GetAllMealTypesUseCase(GetAllMealTypesRepository getAllMealTypesRepository) { + this.getAllMealTypesRepository = getAllMealTypesRepository; + } + + @Override + public List execute() { + log.info("Executing get all meal types use case"); + return getAllMealTypesRepository.execute(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetAllPlansUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllPlansUseCase.java index 4d06789..b738ee5 100644 --- a/src/main/java/com/cuoco/application/usecase/GetAllPlansUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetAllPlansUseCase.java @@ -1,20 +1,18 @@ package com.cuoco.application.usecase; -import com.cuoco.application.port.in.GetPlansQuery; +import com.cuoco.application.port.in.GetAllPlansQuery; import com.cuoco.application.port.out.GetAllPlansRepository; import com.cuoco.application.usecase.model.Plan; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component -public class GetAllPlansUseCase implements GetPlansQuery { +public class GetAllPlansUseCase implements GetAllPlansQuery { - static final Logger log = LoggerFactory.getLogger(GetAllPlansUseCase.class); - - private GetAllPlansRepository getAllPlansRepository; + private final GetAllPlansRepository getAllPlansRepository; public GetAllPlansUseCase(GetAllPlansRepository getAllPlansRepository) { this.getAllPlansRepository = getAllPlansRepository; diff --git a/src/main/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCase.java new file mode 100644 index 0000000..ce97057 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCase.java @@ -0,0 +1,27 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetAllPreparationTimesQuery; +import com.cuoco.application.port.out.GetAllPreparationTimesRepository; +import com.cuoco.application.usecase.model.PreparationTime; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetAllPreparationTimesUseCase implements GetAllPreparationTimesQuery { + + private final GetAllPreparationTimesRepository getAllPreparationTimesRepository; + + public GetAllPreparationTimesUseCase(GetAllPreparationTimesRepository getAllPreparationTimesRepository) { + this.getAllPreparationTimesRepository = getAllPreparationTimesRepository; + } + + @Override + public List execute() { + log.info("Executing get all preparation times use case"); + return getAllPreparationTimesRepository.execute(); + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/GetAllUnitsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllUnitsUseCase.java new file mode 100644 index 0000000..a9c2d59 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetAllUnitsUseCase.java @@ -0,0 +1,27 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetAllUnitsQuery; +import com.cuoco.application.port.out.GetAllUnitsRepository; +import com.cuoco.application.usecase.model.Unit; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetAllUnitsUseCase implements GetAllUnitsQuery { + + private final GetAllUnitsRepository getAllUnitsRepository; + + public GetAllUnitsUseCase(GetAllUnitsRepository getAllUnitsRepository) { + this.getAllUnitsRepository = getAllUnitsRepository; + } + + @Override + public List execute() { + log.info("Executing get all units use case"); + return getAllUnitsRepository.execute(); + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/GetAllUserRecipesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetAllUserRecipesUseCase.java new file mode 100644 index 0000000..cc71faa --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetAllUserRecipesUseCase.java @@ -0,0 +1,40 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetAllUserRecipesQuery; +import com.cuoco.application.port.out.GetAllUserRecipesByUserIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetAllUserRecipesUseCase implements GetAllUserRecipesQuery { + + private final UserDomainService userDomainService; + private final GetAllUserRecipesByUserIdRepository getAllUserRecipesByUserIdRepository; + + public GetAllUserRecipesUseCase( + UserDomainService userDomainService, + GetAllUserRecipesByUserIdRepository getAllUserRecipesByUserIdRepository + ) { + this.userDomainService = userDomainService; + this.getAllUserRecipesByUserIdRepository = getAllUserRecipesByUserIdRepository; + } + + @Override + public List execute() { + log.info("Executing get all user recipes use case"); + + User user = userDomainService.getCurrentUser(); + + List userRecipes = getAllUserRecipesByUserIdRepository.execute(user.getId()); + + return userRecipes.stream().map(UserRecipe::getRecipe).toList(); + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCase.java new file mode 100644 index 0000000..452ab11 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCase.java @@ -0,0 +1,54 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.GetIngredientsFromAudioAsyncCommand; +import com.cuoco.application.port.out.GetIngredientsFromAudioAsyncRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Component +public class GetIngredientsFromAudioAsyncUseCase implements GetIngredientsFromAudioAsyncCommand { + + private final GetIngredientsFromAudioAsyncRepository getIngredientsFromAudioAsyncRepository; + private final FileDomainService fileDomainService; + + public GetIngredientsFromAudioAsyncUseCase( + GetIngredientsFromAudioAsyncRepository getIngredientsFromAudioAsyncRepository, + FileDomainService fileDomainService + ) { + this.getIngredientsFromAudioAsyncRepository = getIngredientsFromAudioAsyncRepository; + this.fileDomainService = fileDomainService; + } + + @Override + public CompletableFuture> execute(GetIngredientsFromAudioAsyncCommand.Command command) { + log.info("Executing asynchronously get ingredients from audio use case"); + + if (command.getAudioFile() == null || command.getAudioFile().isEmpty()) { + throw new BadRequestException(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue()); + } + + if (fileDomainService.isValidAudioFile(command.getAudioFile())) { + throw new BadRequestException(ErrorDescription.INVALID_AUDIO_FILE_EXTENSION.getValue()); + } + + String audioBase64 = fileDomainService.convertToBase64(command.getAudioFile()); + String format = fileDomainService.getAudioFormat(command.getAudioFile()); + + return getIngredientsFromAudioAsyncRepository.execute( + audioBase64, + format, + command.getLanguage() + ).thenApply(ingredients -> { + log.info("Successfully extracted {} ingredients from voice ASYNC", ingredients.size()); + return ingredients; + }); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCase.java new file mode 100644 index 0000000..730f0a8 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCase.java @@ -0,0 +1,54 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.GetIngredientsFromAudioCommand; +import com.cuoco.application.port.out.GetIngredientsFromAudioRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetIngredientsFromAudioUseCase implements GetIngredientsFromAudioCommand { + + private final GetIngredientsFromAudioRepository getIngredientsFromAudioRepository; + private final FileDomainService fileDomainService; + + public GetIngredientsFromAudioUseCase( + GetIngredientsFromAudioRepository getIngredientsFromAudioRepository, + FileDomainService fileDomainService + ) { + this.getIngredientsFromAudioRepository = getIngredientsFromAudioRepository; + this.fileDomainService = fileDomainService; + } + + @Override + public List execute(Command command) { + log.info("Executing get ingredients from voice audio file use case"); + + if (command.getAudioFile() == null || command.getAudioFile().isEmpty()) { + throw new BadRequestException(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue()); + } + + if (fileDomainService.isValidAudioFile(command.getAudioFile())) { + throw new BadRequestException(ErrorDescription.INVALID_AUDIO_FILE_EXTENSION.getValue()); + } + + String audioBase64 = fileDomainService.convertToBase64(command.getAudioFile()); + String format = fileDomainService.getAudioFormat(command.getAudioFile()); + + List ingredients = getIngredientsFromAudioRepository.execute( + audioBase64, + format, + command.getLanguage() + ); + + log.info("Successfully extracted {} ingredients from voice", ingredients.size()); + + return ingredients; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCase.java deleted file mode 100644 index f6f894f..0000000 --- a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCase.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetIngredientsFromFileCommand; -import com.cuoco.application.port.out.GetIngredientsFromRepository; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Map; - -@Component -public class GetIngredientsFromFileUseCase implements GetIngredientsFromFileCommand { - - static final Logger log = LoggerFactory.getLogger(GetIngredientsFromFileUseCase.class); - - private final GetIngredientsFromRepository getIngredientsFromRepository; - - public GetIngredientsFromFileUseCase(GetIngredientsFromRepository getIngredientsFromRepository) { - this.getIngredientsFromRepository = getIngredientsFromRepository; - } - - @Override - public List execute(GetIngredientsFromFileCommand.Command command) { - log.info("Executing get ingredients from file use case with {} files", command.getFiles().size()); - - List ingredients = getIngredientsFromRepository.execute(command.getFiles()); - - log.info("Successfully extracted {} ingredients from files", ingredients.size()); - - return ingredients; - } - - @Override - public Map> executeWithSeparation(GetIngredientsFromFileCommand.Command command) { - log.info("Executing get ingredients from file use case with separation for {} files", command.getFiles().size()); - - Map> ingredientsByImage = getIngredientsFromRepository.executeWithSeparation(command.getFiles()); - - log.info("Successfully extracted ingredients from {} files with separation", ingredientsByImage.size()); - - return ingredientsByImage; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCase.java new file mode 100644 index 0000000..5d70480 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCase.java @@ -0,0 +1,49 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetIngredientsFromImagesCommand; +import com.cuoco.application.port.out.GetIngredientsFromImageRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class GetIngredientsFromImagesUseCase implements GetIngredientsFromImagesCommand { + + private final GetIngredientsFromImageRepository getIngredientsFromImageRepository; + private final FileDomainService fileDomainService; + + public GetIngredientsFromImagesUseCase(GetIngredientsFromImageRepository getIngredientsFromImageRepository, FileDomainService fileDomainService) { + this.getIngredientsFromImageRepository = getIngredientsFromImageRepository; + this.fileDomainService = fileDomainService; + } + + @Override + public List execute(Command command) { + log.info("Executing get ingredients from file use case with {} files", command.getImages().size()); + + List images = command.getImages().stream().map(image -> { + + String filename = fileDomainService.getFileName(image); + String imageBase64 = fileDomainService.convertToBase64(image); + String mimeType = fileDomainService.getMimeType(image); + + return File.builder() + .fileName(filename) + .mimeType(mimeType) + .fileBase64(imageBase64) + .build(); + + }).toList(); + + List ingredients = getIngredientsFromImageRepository.execute(images); + + log.info("Successfully extracted {} ingredients from images", ingredients.size()); + + return ingredients; + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCase.java index 7bba968..64eb298 100644 --- a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCase.java @@ -3,17 +3,15 @@ import com.cuoco.application.port.in.GetIngredientsFromTextCommand; import com.cuoco.application.port.out.GetIngredientsFromTextRepository; import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.List; +@Slf4j @Component public class GetIngredientsFromTextUseCase implements GetIngredientsFromTextCommand { - static final Logger log = LoggerFactory.getLogger(GetIngredientsFromTextUseCase.class); - private final GetIngredientsFromTextRepository getIngredientsFromTextRepository; public GetIngredientsFromTextUseCase(GetIngredientsFromTextRepository getIngredientsFromTextRepository) { diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCase.java deleted file mode 100644 index a41e529..0000000 --- a/src/main/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCase.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetIngredientsFromVoiceCommand; -import com.cuoco.application.port.out.GetIngredientsFromVoiceRepository; -import com.cuoco.application.usecase.model.Ingredient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@Component -public class GetIngredientsFromVoiceUseCase implements GetIngredientsFromVoiceCommand { - - private static final Logger log = LoggerFactory.getLogger(GetIngredientsFromVoiceUseCase.class); - - private final GetIngredientsFromVoiceRepository getIngredientsFromVoiceRepository; - - public GetIngredientsFromVoiceUseCase(GetIngredientsFromVoiceRepository getIngredientsFromVoiceRepository) { - this.getIngredientsFromVoiceRepository = getIngredientsFromVoiceRepository; - } - - @Override - public List execute(Command command) { - log.info("Executing get ingredients from voice use case - format: {}, language: {}", - command.getFormat(), command.getLanguage()); - - List ingredients = getIngredientsFromVoiceRepository.processVoice( - command.getAudioBase64(), - command.getFormat(), - command.getLanguage() - ); - - log.info("Successfully extracted {} ingredients from voice", ingredients.size()); - return ingredients; - } - - - @Override - public CompletableFuture> executeAsync(Command command) { - log.info("Executing get ingredients from voice use case ASYNC - format: {}, language: {}", - command.getFormat(), command.getLanguage()); - - return getIngredientsFromVoiceRepository.processVoiceAsync( - command.getAudioBase64(), - command.getFormat(), - command.getLanguage() - ).thenApply(ingredients -> { - log.info("Successfully extracted {} ingredients from voice ASYNC", ingredients.size()); - return ingredients; - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCase.java b/src/main/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCase.java new file mode 100644 index 0000000..1862c7c --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCase.java @@ -0,0 +1,64 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetIngredientsGroupedFromImagesCommand; +import com.cuoco.application.port.out.GetAllUnitsRepository; +import com.cuoco.application.port.out.GetIngredientsGroupedFromImagesRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.ParametricData; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +public class GetIngredientsGroupedFromImagesUseCase implements GetIngredientsGroupedFromImagesCommand { + + private final GetIngredientsGroupedFromImagesRepository getIngredientsGroupedFromImagesRepository; + private final GetAllUnitsRepository getAllUnitsRepository; + private final FileDomainService fileDomainService; + + public GetIngredientsGroupedFromImagesUseCase( + GetIngredientsGroupedFromImagesRepository getIngredientsGroupedFromImagesRepository, + GetAllUnitsRepository getAllUnitsRepository, + FileDomainService fileDomainService + ) { + this.getIngredientsGroupedFromImagesRepository = getIngredientsGroupedFromImagesRepository; + this.getAllUnitsRepository = getAllUnitsRepository; + this.fileDomainService = fileDomainService; + } + + @Override + public Map> execute(GetIngredientsGroupedFromImagesCommand.Command command) { + log.info("Executing get all ingredients grouped by image use case for {} files", command.getImages().size()); + + ParametricData parametricData = buildParametricData(); + + List images = command.getImages().stream().map(image -> { + + String filename = fileDomainService.getFileName(image); + String imageBase64 = fileDomainService.convertToBase64(image); + String mimeType = fileDomainService.getMimeType(image); + + return File.builder() + .fileName(filename) + .mimeType(mimeType) + .fileBase64(imageBase64) + .build(); + + }).toList(); + + Map> ingredientsByImage = getIngredientsGroupedFromImagesRepository.execute(images, parametricData); + + log.info("Successfully extracted ingredients grouped by images"); + + return ingredientsByImage; + } + + private ParametricData buildParametricData() { + return ParametricData.builder().units(getAllUnitsRepository.execute()).build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/GetMealPrepByIdUseCase.java b/src/main/java/com/cuoco/application/usecase/GetMealPrepByIdUseCase.java new file mode 100644 index 0000000..2f1483a --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetMealPrepByIdUseCase.java @@ -0,0 +1,36 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetMealPrepByIdQuery; +import com.cuoco.application.port.out.GetMealPrepByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GetMealPrepByIdUseCase implements GetMealPrepByIdQuery { + + private final UserDomainService userDomainService; + private final GetMealPrepByIdRepository getMealPrepByIdRepository; + + @Override + public MealPrep execute(Long id) { + log.info("Executing get meal prep by id use case with ID: {}", id); + + MealPrep mealPrep = getMealPrepByIdRepository.execute(id); + + isFavorite(mealPrep); + + return mealPrep; + } + + private void isFavorite(MealPrep mealPrep) { + User user = userDomainService.getCurrentUser(); + mealPrep.setFavorite(user.getMealPreps().contains(mealPrep)); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetMealPrepsFromIngredientsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetMealPrepsFromIngredientsUseCase.java new file mode 100644 index 0000000..05fff76 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetMealPrepsFromIngredientsUseCase.java @@ -0,0 +1,212 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.exception.ForbiddenException; +import com.cuoco.application.port.in.GetMealPrepFromIngredientsCommand; +import com.cuoco.application.port.out.CreateAllMealPrepsRepository; +import com.cuoco.application.port.out.GetAllMealPrepsByIdsRepository; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetMealPrepsFromIngredientsRepository; +import com.cuoco.application.port.out.GetMealTypeByIdRepository; +import com.cuoco.application.port.out.GetPreparationTimeByIdRepository; +import com.cuoco.application.usecase.domainservice.ParametricDataDomainService; +import com.cuoco.application.usecase.domainservice.RecipeDomainService; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Filters; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.MealPrepConfiguration; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.RecipeConfiguration; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.PlanConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class GetMealPrepsFromIngredientsUseCase implements GetMealPrepFromIngredientsCommand { + + @Value("${shared.meal-preps.size}") + private int MEAL_PREP_SIZE; + + @Value("${shared.meal-preps.recipes-size}") + private int RECIPES_SIZE_PER_MEAL_PREP; + + private final UserDomainService userDomainService; + private final RecipeDomainService recipeDomainService; + private final ParametricDataDomainService parametricDataDomainService; + + private final GetMealPrepsFromIngredientsRepository getMealPrepsFromIngredientsProvider; + private final GetAllMealPrepsByIdsRepository getAllMealPrepsByIdsRepository; + private final CreateAllMealPrepsRepository createAllMealPrepsRepository; + private final GetPreparationTimeByIdRepository getPreparationTimeByIdRepository; + private final GetCookLevelByIdRepository getCookLevelByIdRepository; + private final GetMealTypeByIdRepository getMealTypeByIdRepository; + private final GetDietByIdRepository getDietByIdRepository; + private final GetAllergiesByIdRepository getAllergiesByIdRepository; + private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + + public GetMealPrepsFromIngredientsUseCase( + UserDomainService userDomainService, + RecipeDomainService recipeDomainService, + ParametricDataDomainService parametricDataDomainService, + @Qualifier("provider") GetMealPrepsFromIngredientsRepository getMealPrepsFromIngredientsProvider, + GetAllMealPrepsByIdsRepository getAllMealPrepsByIdsRepository, + CreateAllMealPrepsRepository createAllMealPrepsRepository, + GetPreparationTimeByIdRepository getPreparationTimeByIdRepository, + GetCookLevelByIdRepository getCookLevelByIdRepository, + GetMealTypeByIdRepository getMealTypeByIdRepository, + GetDietByIdRepository getDietByIdRepository, + GetAllergiesByIdRepository getAllergiesByIdRepository, + GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository + ) { + this.userDomainService = userDomainService; + this.recipeDomainService = recipeDomainService; + this.parametricDataDomainService = parametricDataDomainService; + this.getMealPrepsFromIngredientsProvider = getMealPrepsFromIngredientsProvider; + this.getAllMealPrepsByIdsRepository = getAllMealPrepsByIdsRepository; + this.createAllMealPrepsRepository = createAllMealPrepsRepository; + this.getPreparationTimeByIdRepository = getPreparationTimeByIdRepository; + this.getCookLevelByIdRepository = getCookLevelByIdRepository; + this.getMealTypeByIdRepository = getMealTypeByIdRepository; + this.getDietByIdRepository = getDietByIdRepository; + this.getAllergiesByIdRepository = getAllergiesByIdRepository; + this.getDietaryNeedsByIdRepository = getDietaryNeedsByIdRepository; + } + + @Override + public List execute(Command command) { + log.info("Executing get recipes from ingredients and filters use case with command {}", command); + + User user = validateAndGetUser(); + + List mealPrepsToNotInclude = buildNotIncludes(command.getNotInclude()); + List recipesToNotInclude = extractRecipesToNotInclude(mealPrepsToNotInclude); + + Recipe recipeParameters = buildRecipe(command, recipesToNotInclude); + List recipes = recipeDomainService.getOrCreate(recipeParameters); + + MealPrepConfiguration configuration = buildMealPrepConfiguration(mealPrepsToNotInclude); + + MealPrep mealPrepToGenerate = buildMealPrep( + user, + recipeParameters.getIngredients(), + recipes, + recipeParameters.getFilters(), + configuration + ); + + List generatedMealPreps = getMealPrepsFromIngredientsProvider.execute(mealPrepToGenerate); + List savedMealPrep = createAllMealPrepsRepository.execute(generatedMealPreps); + + log.info("Generated {} meal preps, returning first", savedMealPrep.size()); + return savedMealPrep; + } + + private User validateAndGetUser() { + User user = userDomainService.getCurrentUser(); + + if (user.getPlan().getId() != PlanConstants.PRO.getValue()) { + log.warn("Forbidden: Meal prep feature is only for PRO users."); + throw new ForbiddenException(ErrorDescription.PRO_FEATURE.getValue()); + } + + return user; + } + + private List buildNotIncludes(List notIncludeIds) { + return notIncludeIds != null && !notIncludeIds.isEmpty() ? getAllMealPrepsByIdsRepository.execute(notIncludeIds) : List.of(); + } + + private List extractRecipesToNotInclude(List mealPrepsToNotInclude) { + return mealPrepsToNotInclude.stream() + .flatMap(mealPrep -> mealPrep.getRecipes().stream()) + .distinct() + .collect(Collectors.toList()); + } + + private Recipe buildRecipe(Command command, List notInclude) { + + if(command.getIngredients().isEmpty()) throw new BadRequestException(ErrorDescription.INGREDIENTS_EMPTY.getValue()); + + return Recipe.builder() + .ingredients(command.getIngredients()) + .filters(buildFilters(command)) + .configuration(buildRecipeConfiguration(notInclude)) + .build(); + } + + private Filters buildFilters(Command command) { + PreparationTime preparationTime = command.getPreparationTimeId() != null ? getPreparationTimeByIdRepository.execute(command.getPreparationTimeId()) : null; + CookLevel cookLevel = command.getCookLevelId() != null ? getCookLevelByIdRepository.execute(command.getCookLevelId()) : null; + Diet diet = command.getDietId() != null ? getDietByIdRepository.execute(command.getDietId()) : null; + List types = command.getTypeIds() != null ? command.getTypeIds().stream().map(getMealTypeByIdRepository::execute).toList() : null; + List dietaryNeeds = command.getDietaryNeedsIds() != null ? getDietaryNeedsByIdRepository.execute(command.getDietaryNeedsIds()) : null; + List allergies = command.getAllergiesIds() != null ? getAllergiesByIdRepository.execute(command.getAllergiesIds()) : null; + Boolean freeze = command.getFreeze() != null ? command.getFreeze() : Boolean.FALSE; + + return Filters.builder() + .enable(true) + .freeze(freeze) + .preparationTime(preparationTime) + .cookLevel(cookLevel) + .mealTypes(types) + .diet(diet) + .allergies(allergies) + .dietaryNeeds(dietaryNeeds) + .build(); + } + + private RecipeConfiguration buildRecipeConfiguration(List notInclude) { + int recipesSize = RECIPES_SIZE_PER_MEAL_PREP * MEAL_PREP_SIZE; + + return RecipeConfiguration.builder() + .size(recipesSize) + .notInclude(notInclude) + .build(); + } + + private MealPrep buildMealPrep( + User user, + List ingredients, + List recipes, + Filters filters, + MealPrepConfiguration configuration + ) { + return MealPrep.builder() + .user(user) + .ingredients(ingredients) + .recipes(recipes) + .filters(filters) + .configuration(configuration) + .build(); + } + + private MealPrepConfiguration buildMealPrepConfiguration(List notInclude) { + int mealPrepSize = MEAL_PREP_SIZE; + ParametricData parametricData = parametricDataDomainService.getAll(); + + return MealPrepConfiguration.builder() + .size(mealPrepSize) + .notInclude(notInclude) + .parametricData(parametricData) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetRecipeByIdUseCase.java b/src/main/java/com/cuoco/application/usecase/GetRecipeByIdUseCase.java new file mode 100644 index 0000000..0122ac5 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetRecipeByIdUseCase.java @@ -0,0 +1,39 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetRecipeByIdQuery; +import com.cuoco.application.port.out.GetRecipeByIdRepository; +import com.cuoco.application.usecase.domainservice.RecipeDomainService; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GetRecipeByIdUseCase implements GetRecipeByIdQuery { + + private final UserDomainService userDomainService; + private final RecipeDomainService recipeDomainService; + private final GetRecipeByIdRepository getRecipeByIdRepository; + + @Override + public Recipe execute(Long id, Integer servings) { + log.info("Executing get recipe by id use case with ID: {}", id); + + Recipe recipe = getRecipeByIdRepository.execute(id); + + isFavorite(recipe); + + recipeDomainService.adjustIngredientsByServings(recipe, servings); + + return recipe; + } + + private void isFavorite(Recipe recipe) { + User user = userDomainService.getCurrentUser(); + recipe.setFavorite(user.getRecipes().contains(recipe)); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCase.java b/src/main/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCase.java index 4f5e528..8c32d7a 100644 --- a/src/main/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCase.java @@ -1,28 +1,144 @@ package com.cuoco.application.usecase; +import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; -import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetMealTypeByIdRepository; +import com.cuoco.application.port.out.GetPreparationTimeByIdRepository; +import com.cuoco.application.usecase.domainservice.RecipeDomainService; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Filters; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.RecipeConfiguration; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.PlanConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import java.util.List; + +@Slf4j @Component public class GetRecipesFromIngredientsUseCase implements GetRecipesFromIngredientsCommand { - static final Logger log = LoggerFactory.getLogger(GetRecipesFromIngredientsUseCase.class); + @Value("${shared.recipes.size.free}") + private int FREE_USER_RECIPES_SIZE; + + @Value("${shared.recipes.size.pro}") + private int PRO_USER_RECIPES_SIZE; + + private final RecipeDomainService recipeDomainService; + + private final GetPreparationTimeByIdRepository getPreparationTimeByIdRepository; + private final GetCookLevelByIdRepository getCookLevelByIdRepository; + private final GetMealTypeByIdRepository getMealTypeByIdRepository; + private final GetDietByIdRepository getDietByIdRepository; + private final GetAllergiesByIdRepository getAllergiesByIdRepository; + private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + + public GetRecipesFromIngredientsUseCase( + RecipeDomainService recipeDomainService, + GetPreparationTimeByIdRepository getPreparationTimeByIdRepository, + GetCookLevelByIdRepository getCookLevelByIdRepository, + GetMealTypeByIdRepository getMealTypeByIdRepository, + GetDietByIdRepository getDietByIdRepository, + GetAllergiesByIdRepository getAllergiesByIdRepository, + GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository + ) { + this.recipeDomainService = recipeDomainService; + this.getPreparationTimeByIdRepository = getPreparationTimeByIdRepository; + this.getCookLevelByIdRepository = getCookLevelByIdRepository; + this.getMealTypeByIdRepository = getMealTypeByIdRepository; + this.getDietByIdRepository = getDietByIdRepository; + this.getAllergiesByIdRepository = getAllergiesByIdRepository; + this.getDietaryNeedsByIdRepository = getDietaryNeedsByIdRepository; + } + + public List execute(Command command) { + log.info("Executing get recipes from ingredients and filters use case with command {}", command); + + int userPlan = getUserPlan(); + + Recipe recipeToFind = buildRecipe(command, userPlan); + + List recipesToResponse = recipeDomainService.getOrCreate(recipeToFind); - private final GetRecipesFromIngredientsRepository getRecipesFromIngredientsRepository; + recipesToResponse.forEach(recipe -> recipeDomainService.adjustIngredientsByServings(recipe, command.getServings())); - public GetRecipesFromIngredientsUseCase(GetRecipesFromIngredientsRepository getRecipesFromIngredientsRepository) { - this.getRecipesFromIngredientsRepository = getRecipesFromIngredientsRepository; + return recipesToResponse; } - public String execute(Command command) { - log.info("Executing get recipes from ingredients use case with command {}", command); + private int getUserPlan() { + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return user.getPlan().getId(); + } + + private Recipe buildRecipe(Command command, int userPlan) { + + if(command.getIngredients().isEmpty()) { + throw new BadRequestException(ErrorDescription.INGREDIENTS_EMPTY.getValue()); + } + + return Recipe.builder() + .ingredients(command.getIngredients()) + .filters(buildFilters(command, userPlan)) + .configuration(buildConfiguration(command, userPlan)) + .build(); + } - String recipes = getRecipesFromIngredientsRepository.execute(command.getIngredients()); + private Filters buildFilters(Command command, int userPlan) { - return recipes; + if(userPlan == PlanConstants.FREE.getValue() || !command.getFiltersEnabled()) { + return Filters.builder() + .enable(false) + .build(); + } + + PreparationTime preparationTime = command.getPreparationTimeId() != null ? getPreparationTimeByIdRepository.execute(command.getPreparationTimeId()) : null; + CookLevel cookLevel = command.getCookLevelId() != null ? getCookLevelByIdRepository.execute(command.getCookLevelId()) : null; + Diet diet = command.getDietId() != null ? getDietByIdRepository.execute(command.getDietId()) : null; + List types = command.getTypeIds() != null ? command.getTypeIds().stream().map(getMealTypeByIdRepository::execute).toList() : null; + List dietaryNeeds = command.getDietaryNeedsIds() != null ? getDietaryNeedsByIdRepository.execute(command.getDietaryNeedsIds()) : null; + List allergies = command.getAllergiesIds() != null ? getAllergiesByIdRepository.execute(command.getAllergiesIds()) : null; + + return Filters.builder() + .enable(true) + .preparationTime(preparationTime) + .cookLevel(cookLevel) + .mealTypes(types) + .diet(diet) + .allergies(allergies) + .dietaryNeeds(dietaryNeeds) + .build(); } + private RecipeConfiguration buildConfiguration(Command command, int userPlan) { + if(userPlan == PlanConstants.PRO.getValue()) { + int size = command.getSize() != null ? command.getSize() : PRO_USER_RECIPES_SIZE; + + List notIncludeRecipes = command.getNotInclude() != null + ? command.getNotInclude().stream().map(id -> Recipe.builder().id(id).build()).toList() + : List.of(); + + return RecipeConfiguration.builder() + .size(size) + .notInclude(notIncludeRecipes) + .build(); + } else { + return RecipeConfiguration.builder() + .size(FREE_USER_RECIPES_SIZE) + .build(); + } + } } diff --git a/src/main/java/com/cuoco/application/usecase/GetUserCalendarUseCase.java b/src/main/java/com/cuoco/application/usecase/GetUserCalendarUseCase.java new file mode 100644 index 0000000..ede6dd3 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/GetUserCalendarUseCase.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetUserCalendarQuery; +import com.cuoco.application.usecase.domainservice.CalendarDomainService; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class GetUserCalendarUseCase implements GetUserCalendarQuery { + + private final UserDomainService userDomainService; + private final CalendarDomainService calendarDomainService; + + @Override + public List execute() { + log.info("Executing get user calendar use case"); + + User user = userDomainService.getCurrentUser(); + + List calendarRecipes = calendarDomainService.getUserCalendar(user); + + log.info("Successfully retrieved user calendar with {} dates", calendarRecipes.size()); + return calendarRecipes; + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/ProcessUserPaymentUseCase.java b/src/main/java/com/cuoco/application/usecase/ProcessUserPaymentUseCase.java new file mode 100644 index 0000000..604e596 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/ProcessUserPaymentUseCase.java @@ -0,0 +1,77 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.ProcessUserPaymentCommand; +import com.cuoco.application.port.out.GetAllPaymentStatusRepository; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.GetUserPaymentByExternalReferenceRepository; +import com.cuoco.application.port.out.ProcessUserPaymentRepository; +import com.cuoco.application.port.out.UpdateUserPaymentRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.model.PaymentStatus; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPayment; +import com.cuoco.shared.utils.PaymentConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ProcessUserPaymentUseCase implements ProcessUserPaymentCommand { + + private static final String PAYMENT = "payment"; + private static final String APPROVED = "approved"; + + private final ProcessUserPaymentRepository processPaymentProvider; + private final GetUserPaymentByExternalReferenceRepository getUserPaymentByExternalReferenceRepository; + private final UpdateUserPaymentRepository updateUserPaymentRepository; + private final UpdateUserRepository updateUserRepository; + private final GetUserByIdRepository getUserByIdRepository; + + @Override + public void execute(Command command) { + log.info("Execute user process payment with command {}", command); + + String type = (String) command.getPayload().get("type"); + String paymentId = getPaymentId(command.getPayload()); + + if (PAYMENT.equalsIgnoreCase(type) && (paymentId != null)) { + UserPayment receivedPayment = processPaymentProvider.execute(paymentId); + + if (receivedPayment.getStatus().getDescription().equalsIgnoreCase(APPROVED)) { + + UserPayment userPayment = getUserPaymentByExternalReferenceRepository.execute(receivedPayment.getExternalReference()); + + User user = getUserByIdRepository.execute(userPayment.getUser().getId()); + + userPayment.setUser(user); + + userPayment.setStatus(PaymentStatus.builder().id(PaymentConstants.STATUS_APPROVED.getValue()).build()); + + updateUserPlan(userPayment); + + updateUserPaymentRepository.execute(userPayment); + } + } + } + + private void updateUserPlan(UserPayment userPayment) { + User user = userPayment.getUser(); + user.setPlan(userPayment.getPlan()); + + updateUserRepository.execute(user); + } + + private String getPaymentId(Map payload) { + if(payload.containsKey("data") && payload.get("data") != null) { + return String.valueOf(((Map) payload.get("data")).get("id")); + } + + return null; + } +} diff --git a/src/main/java/com/cuoco/application/usecase/ResendUserActivationEmailUseCase.java b/src/main/java/com/cuoco/application/usecase/ResendUserActivationEmailUseCase.java new file mode 100644 index 0000000..d55856e --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/ResendUserActivationEmailUseCase.java @@ -0,0 +1,35 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.ResendUserActivationEmailCommand; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import com.cuoco.application.port.out.GetUserByEmailRepository; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ResendUserActivationEmailUseCase implements ResendUserActivationEmailCommand { + + private final ExistsUserByEmailRepository existsUserByEmailRepository; + private final GetUserByEmailRepository getUserByEmailRepository; + private final SendConfirmationEmailRepository sendConfirmationEmailRepository; + private final JwtUtil jwtUtil; + + @Override + public void execute(String email) { + log.info("Executing resend user activation email with {} ", email); + + if (existsUserByEmailRepository.execute(email)) { + User user = getUserByEmailRepository.execute(email); + String token = jwtUtil.generateActivationToken(user); + sendConfirmationEmailRepository.execute(user, token); + } else { + log.info("Does not exists user with email {}. Resend email confirmation cancelled.", email); + } + } +} diff --git a/src/main/java/com/cuoco/application/usecase/ResetUserPasswordConfirmationUseCase.java b/src/main/java/com/cuoco/application/usecase/ResetUserPasswordConfirmationUseCase.java new file mode 100644 index 0000000..17417f8 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/ResetUserPasswordConfirmationUseCase.java @@ -0,0 +1,40 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.ResetUserPasswordConfirmationCommand; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import com.cuoco.application.port.out.GetUserByEmailRepository; +import com.cuoco.application.port.out.SendResetPasswordConfirmationRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ResetUserPasswordConfirmationUseCase implements ResetUserPasswordConfirmationCommand { + + private final ExistsUserByEmailRepository existsUserByEmailRepository; + private final GetUserByEmailRepository getUserByEmailRepository; + private final SendResetPasswordConfirmationRepository sendResetPasswordConfirmationRepository; + private final JwtUtil jwtUtil; + + @Override + public void execute(String email) { + log.info("Executing password reset with email {} ", email); + + if (existsUserByEmailRepository.execute(email)) { + User user = getUserByEmailRepository.execute(email); + + if (user.getActive()) { + String token = jwtUtil.generateResetPasswordToken(user); + sendResetPasswordConfirmationRepository.execute(user, token); + } else { + log.info("User with email {} is not actived. Reset password cancelled.", email); + } + } else { + log.info("User does not exists with email {}. Reset password cancelled.", email); + } + } +} diff --git a/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java b/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java index 19b4719..6615a01 100644 --- a/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java @@ -5,20 +5,18 @@ import com.cuoco.application.port.out.GetUserByEmailRepository; import com.cuoco.application.usecase.model.AuthenticatedUser; import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; import com.cuoco.shared.model.ErrorDescription; -import com.cuoco.shared.utils.JwtUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import java.util.Collections; +@Slf4j @Component public class SignInUserUseCase implements SignInUserCommand { - static final Logger log = LoggerFactory.getLogger(SignInUserUseCase.class); - private final GetUserByEmailRepository getUserByEmailRepository; private final JwtUtil jwtUtil; private final PasswordEncoder passwordEncoder; @@ -38,16 +36,19 @@ public AuthenticatedUser execute(Command command) { User user = getUserByEmailRepository.execute(command.getEmail()); - if(!passwordEncoder.matches(command.getPassword(), user.getPassword())) { log.info("Invalid credentials"); throw new ForbiddenException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } + if(user.getActive() != null && !user.getActive()) { + log.info("User with email {} is not activated yet", user.getEmail()); + throw new ForbiddenException(ErrorDescription.USER_NOT_ACTIVATED.getValue()); + } + user.setPassword(null); - AuthenticatedUser authenticatedUser = buildAuthenticatedUser(user); - return authenticatedUser; + return buildAuthenticatedUser(user); } private AuthenticatedUser buildAuthenticatedUser(User user) { diff --git a/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java b/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java new file mode 100644 index 0000000..1ef561f --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java @@ -0,0 +1,123 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.UpdateUserProfileCommand; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPreferences; +import com.cuoco.shared.model.ErrorDescription; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class UpdateUserProfileUseCase implements UpdateUserProfileCommand { + + private final UserDomainService userDomainService; + private final GetUserByIdRepository getUserByIdRepository; + private final UpdateUserRepository updateUserRepository; + private final GetDietByIdRepository getDietByIdRepository; + private final GetCookLevelByIdRepository getCookLevelByIdRepository; + private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + private final GetAllergiesByIdRepository getAllergiesByIdRepository; + + @Override + public User execute(Command command) { + User user = userDomainService.getCurrentUser(); + log.info("Executing update user use case with ID {}", user.getId()); + + User existingUser = getUserByIdRepository.execute(user.getId()); + User userToUpdate = buildUpdateUser(existingUser, command); + + User updatedUser = updateUserRepository.execute(userToUpdate); + + log.info("User with ID {} updated successfully", user.getId()); + return updatedUser; + } + + private User buildUpdateUser(User existingUser, Command command) { + + String updatedName = command.getName() != null ? command.getName() : existingUser.getName(); + + return User.builder() + .id(existingUser.getId()) + .name(updatedName) + .email(existingUser.getEmail()) + .password(existingUser.getPassword()) + .active(existingUser.getActive()) + .plan(existingUser.getPlan()) + .preferences(buildUserPreferences(existingUser.getPreferences(), command)) + .dietaryNeeds(getUpdatedDietaryNeeds(command, existingUser.getDietaryNeeds())) + .allergies(getUpdatedAllergies(command, existingUser.getAllergies())) + .build(); + } + + private UserPreferences buildUserPreferences(UserPreferences existingUserPreferences, Command command) { + Diet updatedDiet = command.getDietId() != null + ? getDietByIdRepository.execute(command.getDietId()) + : existingUserPreferences.getDiet(); + + CookLevel updatedCookLevel = command.getCookLevelId() != null + ? getCookLevelByIdRepository.execute(command.getCookLevelId()) + : existingUserPreferences.getCookLevel(); + + return UserPreferences.builder() + .id(existingUserPreferences.getId()) + .cookLevel(updatedCookLevel) + .diet(updatedDiet) + .build(); + } + + private List getUpdatedDietaryNeeds(Command command, List existingDietaryNeeds) { + List ids = command.getDietaryNeeds(); + + if (ids == null) { + return existingDietaryNeeds; + } + + if (ids.isEmpty()) { + return Collections.emptyList(); + } + + List existing = getDietaryNeedsByIdRepository.execute(ids); + if (existing.size() != ids.size()) { + throw new BadRequestException(ErrorDescription.DIETARY_NEEDS_NOT_EXISTS.getValue()); + } + + return existing; + } + + private List getUpdatedAllergies(Command command, List existingAllergies) { + List ids = command.getAllergies(); + + if (ids == null) { + return existingAllergies; + } + + if (ids.isEmpty()) { + return Collections.emptyList(); + } + + List existing = getAllergiesByIdRepository.execute(ids); + if (existing.size() != ids.size()) { + throw new BadRequestException(ErrorDescription.ALLERGIES_NOT_EXISTS.getValue()); + } + + return existing; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/AsyncRecipeDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/AsyncRecipeDomainService.java new file mode 100644 index 0000000..96043d3 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/AsyncRecipeDomainService.java @@ -0,0 +1,30 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.port.out.GenerateRecipeMainImageRepository; +import com.cuoco.application.usecase.model.Recipe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class AsyncRecipeDomainService { + + private final GenerateRecipeMainImageRepository generateRecipeMainImageRepository; + + public AsyncRecipeDomainService(GenerateRecipeMainImageRepository generateRecipeMainImageRepository) { + this.generateRecipeMainImageRepository = generateRecipeMainImageRepository; + } + + @Async + public void generateMainImages(List recipes) { + + recipes.forEach(recipe -> { + log.info("Executing async main image creation for new recipe with ID {}", recipe.getId()); + + generateRecipeMainImageRepository.execute(recipe); + }); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/CalendarDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/CalendarDomainService.java new file mode 100644 index 0000000..c22a624 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/CalendarDomainService.java @@ -0,0 +1,64 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.port.out.GetUserCalendarByUserIdRepository; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.Day; +import com.cuoco.application.usecase.model.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CalendarDomainService { + + private final GetUserCalendarByUserIdRepository getUserCalendarByUserIdRepository; + + public List getUserCalendar(User user) { + log.info("Getting calendars for user with id {} in calendar domain service", user.getId()); + + List calendarRecipes = getUserCalendarByUserIdRepository.execute(user.getId()); + + calendarRecipes = dropPastDates(calendarRecipes); + + return calendarRecipes; + } + + private List dropPastDates(List calendar) { + LocalDate today = LocalDate.now(); + LocalDate sevenDaysLater = today.plusDays(7); + + return calendar.stream() + .filter(urc -> dropPastDates(urc, today, sevenDaysLater)) + .map(this::normalizeDate) + .collect(Collectors.toList()); + } + + private Calendar normalizeDate(Calendar calendar) { + DayOfWeek dayOfWeek = calendar.getDate().getDayOfWeek(); + String description = capitalize(dayOfWeek.getDisplayName(TextStyle.FULL, new Locale("es"))); + + calendar.setDay(Day.builder().id(dayOfWeek.getValue()).description(description).build()); + + return calendar; + } + + private static boolean dropPastDates(Calendar urc, LocalDate today, LocalDate sevenDaysLater) { + LocalDate Date = urc.getDate(); + return (Date != null && !Date.isBefore(today) && !Date.isAfter(sevenDaysLater)); + } + + private String capitalize(String word) { + if (word == null || word.isEmpty()) return word; + return word.substring(0, 1).toUpperCase() + word.substring(1); + } + +} diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/FileDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/FileDomainService.java new file mode 100644 index 0000000..1030f53 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/FileDomainService.java @@ -0,0 +1,64 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.exception.UnprocessableException; +import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.AudioConstants; +import com.cuoco.shared.utils.Constants; +import com.cuoco.shared.utils.FileUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Base64; + +@Component +public class FileDomainService { + + private final static String AUDIO_FOLDER = "audio/"; + + public String getFileName(MultipartFile file) { + return file.getOriginalFilename() != null && !file.getOriginalFilename().isBlank() ? file.getOriginalFilename() : "unknown_" + System.currentTimeMillis(); + } + + public String getMimeType(MultipartFile file) { + return file.getContentType() != null ? file.getContentType() : "image/jpeg"; + } + + public boolean isValidAudioFile(MultipartFile file) { + String contentType = file.getContentType(); + String filename = file.getOriginalFilename(); + + if (contentType != null) { + return !contentType.startsWith(AUDIO_FOLDER); + } + + if (filename != null && filename.contains(Constants.SLASH.getValue())) { + String extension = filename.substring(filename.lastIndexOf(Constants.DOT.getValue()) + 1).toLowerCase(); + return !FileUtils.SUPPORTED_AUDIO_EXTENSIONS.contains(extension); + } + + return true; + } + + public String getAudioFormat(MultipartFile file) { + String contentType = file.getContentType(); + String filename = file.getOriginalFilename(); + + if (contentType != null && contentType.contains(Constants.SLASH.getValue())) { + return contentType.substring(contentType.indexOf(Constants.SLASH.getValue()) + 1); + } + + if (filename != null && filename.contains(Constants.DOT.getValue())) { + return filename.substring(filename.lastIndexOf(Constants.DOT.getValue()) + 1).toLowerCase(); + } + + return AudioConstants.MP3; + } + + public String convertToBase64(MultipartFile file) { + try { + return Base64.getEncoder().encodeToString(file.getBytes()); + } catch (Exception e) { + throw new UnprocessableException(ErrorDescription.UNHANDLED.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/ParametricDataDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/ParametricDataDomainService.java new file mode 100644 index 0000000..3aefc2e --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/ParametricDataDomainService.java @@ -0,0 +1,39 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.port.out.GetAllAllergiesRepository; +import com.cuoco.application.port.out.GetAllCookLevelsRepository; +import com.cuoco.application.port.out.GetAllDietaryNeedsRepository; +import com.cuoco.application.port.out.GetAllDietsRepository; +import com.cuoco.application.port.out.GetAllMealTypesRepository; +import com.cuoco.application.port.out.GetAllPreparationTimesRepository; +import com.cuoco.application.port.out.GetAllUnitsRepository; +import com.cuoco.application.usecase.model.ParametricData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ParametricDataDomainService { + + private final GetAllUnitsRepository getAllUnitsRepository; + private final GetAllPreparationTimesRepository getAllPreparationTimesRepository; + private final GetAllCookLevelsRepository getAllCookLevelsRepository; + private final GetAllDietsRepository getAllDietsRepository; + private final GetAllMealTypesRepository getAllMealTypesRepository; + private final GetAllAllergiesRepository getAllAllergiesRepository; + private final GetAllDietaryNeedsRepository getAllDietaryNeedsRepository; + + public ParametricData getAll() { + return ParametricData.builder() + .units(getAllUnitsRepository.execute()) + .preparationTimes(getAllPreparationTimesRepository.execute()) + .cookLevels(getAllCookLevelsRepository.execute()) + .diets(getAllDietsRepository.execute()) + .mealTypes(getAllMealTypesRepository.execute()) + .allergies(getAllAllergiesRepository.execute()) + .dietaryNeeds(getAllDietaryNeedsRepository.execute()) + .build(); + } +} diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/RecipeDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/RecipeDomainService.java new file mode 100644 index 0000000..769e8c1 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/RecipeDomainService.java @@ -0,0 +1,139 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.port.out.CreateAllRecipesRepository; +import com.cuoco.application.port.out.CreateRecipeImagesRepository; +import com.cuoco.application.port.out.GetAllRecipesByIdsRepository; +import com.cuoco.application.port.out.GetRecipeStepsImagesRepository; +import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +@Slf4j +@Component +public class RecipeDomainService { + + private final GetRecipesFromIngredientsRepository getRecipesFromIngredientsRepository; + private final GetRecipesFromIngredientsRepository getRecipesFromIngredientsProvider; + private final GetAllRecipesByIdsRepository getAllRecipesByIdsRepository; + private final CreateAllRecipesRepository createAllRecipesRepository; + private final CreateRecipeImagesRepository createRecipeImagesRepository; + private final GetRecipeStepsImagesRepository getRecipeStepsImagesRepository; + private final AsyncRecipeDomainService asyncRecipeDomainService; + private final ParametricDataDomainService parametricDataDomainService; + + public RecipeDomainService( + @Qualifier("repository") GetRecipesFromIngredientsRepository getRecipesFromIngredientsRepository, + @Qualifier("provider") GetRecipesFromIngredientsRepository getRecipesFromIngredientsProvider, + GetAllRecipesByIdsRepository getAllRecipesByIdsRepository, + CreateAllRecipesRepository createAllRecipesRepository, + CreateRecipeImagesRepository createRecipeImagesRepository, + GetRecipeStepsImagesRepository getRecipeStepsImagesRepository, + AsyncRecipeDomainService asyncRecipeDomainService, + ParametricDataDomainService parametricDataDomainService + ) { + this.getRecipesFromIngredientsRepository = getRecipesFromIngredientsRepository; + this.getRecipesFromIngredientsProvider = getRecipesFromIngredientsProvider; + this.getAllRecipesByIdsRepository = getAllRecipesByIdsRepository; + this.createAllRecipesRepository = createAllRecipesRepository; + this.createRecipeImagesRepository = createRecipeImagesRepository; + this.getRecipeStepsImagesRepository = getRecipeStepsImagesRepository; + this.asyncRecipeDomainService = asyncRecipeDomainService; + this.parametricDataDomainService = parametricDataDomainService; + } + + public List getOrCreate(Recipe recipeToFind) { + List foundedRecipes = getRecipesFromIngredientsRepository.execute(recipeToFind); + + int targetSize = recipeToFind.getConfiguration().getSize(); + + if(foundedRecipes.isEmpty()) { + log.info("Can't find saved recipes with the provided ingredients and filters. Generating new ones"); + return generateRecipes(recipeToFind, List.of(), targetSize); + } + + if(foundedRecipes.size() < targetSize) { + int remaining = targetSize - foundedRecipes.size(); + + log.info("Founded only {} saved recipes. Generating {} new recipes to complete", foundedRecipes.size(), remaining); + + List newRecipes = generateRecipes(recipeToFind, foundedRecipes, remaining); + + return Stream.concat(foundedRecipes.stream(), newRecipes.stream()) + .limit(targetSize) + .toList(); + } + + log.info("Founded enough {} saved recipes with the provided ingredients and filters.", foundedRecipes.size()); + return foundedRecipes.stream().limit(targetSize).toList(); + } + + private List generateRecipes(Recipe recipeParameters, List foundedRecipes, int size) { + + recipeParameters.getConfiguration().setParametricData(parametricDataDomainService.getAll()); + + List recipesToNotInclude = buildRecipesToNotInclude(recipeParameters.getConfiguration().getNotInclude(), foundedRecipes); + + recipeParameters.getConfiguration().setNotInclude(recipesToNotInclude); + + List recipesToSave = getRecipesFromIngredientsProvider.execute(recipeParameters); + + List savedRecipes = createAllRecipesRepository.execute(recipesToSave); + + asyncRecipeDomainService.generateMainImages(savedRecipes); + + return savedRecipes.stream().limit(size).toList(); + } + + public Recipe generateImages(Recipe recipe) { + log.info("Executing image creation for recipe with ID {}", recipe.getId()); + + List recipeImagesToSave = getRecipeStepsImagesRepository.execute(recipe); + + recipe.setImages(recipeImagesToSave); + + if(!recipe.getImages().isEmpty()) { + List savedImages = createRecipeImagesRepository.execute(recipe); + recipe.setImages(savedImages); + log.info("Successfully generated {} images for recipe with ID {}", savedImages.size(), recipe.getId()); + } else { + log.info("Failed to create images for recipe with ID {}", recipe.getId()); + } + + return recipe; + } + + public void adjustIngredientsByServings(Recipe recipe, Integer servings) { + if(servings != null && servings > 1) { + log.info("Multiplying recipe ID {} ingredients quantity by {} servings", recipe.getId(), servings); + + for (Ingredient ingredient : recipe.getIngredients()) { + if (ingredient.getQuantity() != null) { + ingredient.setQuantity(ingredient.getQuantity() * servings); + } + } + } + } + + private List buildRecipesToNotInclude(List requiredNotInclude, List foundedRecipes) { + List recipesToNotInclude = new ArrayList<>(foundedRecipes); + + if (requiredNotInclude != null && !requiredNotInclude.isEmpty()) { + List ids = requiredNotInclude.stream() + .map(Recipe::getId) + .toList(); + + List fetched = getAllRecipesByIdsRepository.execute(ids); + recipesToNotInclude.addAll(fetched); + } + + return recipesToNotInclude; + } +} diff --git a/src/main/java/com/cuoco/application/usecase/domainservice/UserDomainService.java b/src/main/java/com/cuoco/application/usecase/domainservice/UserDomainService.java new file mode 100644 index 0000000..0b986a9 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/domainservice/UserDomainService.java @@ -0,0 +1,30 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.exception.NotAvailableException; +import com.cuoco.application.exception.UnauthorizedException; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class UserDomainService { + + public User getCurrentUser() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + + if (principal == null) { + log.warn("User is not authenticated"); + throw new UnauthorizedException(ErrorDescription.UNAUTHORIZED.getValue()); + } + + if (principal instanceof User) { + return (User) principal; + } else { + log.error("Security context error: User in context is not a user class"); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Allergy.java b/src/main/java/com/cuoco/application/usecase/model/Allergy.java index 530ce18..5dd133f 100644 --- a/src/main/java/com/cuoco/application/usecase/model/Allergy.java +++ b/src/main/java/com/cuoco/application/usecase/model/Allergy.java @@ -3,13 +3,11 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.Getter; -import lombok.Setter; @Data @Builder @AllArgsConstructor -public class Allergy { +public class Allergy implements Parametric{ private Integer id; private String description; } diff --git a/src/main/java/com/cuoco/application/usecase/model/AuthenticatedUser.java b/src/main/java/com/cuoco/application/usecase/model/AuthenticatedUser.java index bd3c7ef..19bd71a 100644 --- a/src/main/java/com/cuoco/application/usecase/model/AuthenticatedUser.java +++ b/src/main/java/com/cuoco/application/usecase/model/AuthenticatedUser.java @@ -1,11 +1,13 @@ package com.cuoco.application.usecase.model; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import java.util.List; @Data +@Builder @AllArgsConstructor public class AuthenticatedUser { diff --git a/src/main/java/com/cuoco/application/usecase/model/Calendar.java b/src/main/java/com/cuoco/application/usecase/model/Calendar.java new file mode 100644 index 0000000..2a2b6f1 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Calendar.java @@ -0,0 +1,16 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +public class Calendar { + private Long id; + private Day day; + private LocalDate date; + private List recipes; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/CalendarRecipe.java b/src/main/java/com/cuoco/application/usecase/model/CalendarRecipe.java new file mode 100644 index 0000000..59af823 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/CalendarRecipe.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class CalendarRecipe { + Recipe recipe; + MealType mealType; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/CookLevel.java b/src/main/java/com/cuoco/application/usecase/model/CookLevel.java index f6beeac..352fbcc 100644 --- a/src/main/java/com/cuoco/application/usecase/model/CookLevel.java +++ b/src/main/java/com/cuoco/application/usecase/model/CookLevel.java @@ -7,7 +7,7 @@ @Data @Builder @AllArgsConstructor -public class CookLevel { +public class CookLevel implements Parametric { private Integer id; private String description; } diff --git a/src/main/java/com/cuoco/application/usecase/model/Day.java b/src/main/java/com/cuoco/application/usecase/model/Day.java new file mode 100644 index 0000000..0cb225a --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Day.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Day { + private Integer id; + private String description; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Diet.java b/src/main/java/com/cuoco/application/usecase/model/Diet.java index ead9b91..81a3303 100644 --- a/src/main/java/com/cuoco/application/usecase/model/Diet.java +++ b/src/main/java/com/cuoco/application/usecase/model/Diet.java @@ -7,7 +7,7 @@ @Data @Builder @AllArgsConstructor -public class Diet { +public class Diet implements Parametric { private Integer id; private String description; } diff --git a/src/main/java/com/cuoco/application/usecase/model/DietaryNeed.java b/src/main/java/com/cuoco/application/usecase/model/DietaryNeed.java index 85f0dcb..bc784dd 100644 --- a/src/main/java/com/cuoco/application/usecase/model/DietaryNeed.java +++ b/src/main/java/com/cuoco/application/usecase/model/DietaryNeed.java @@ -7,7 +7,7 @@ @Data @Builder @AllArgsConstructor -public class DietaryNeed { +public class DietaryNeed implements Parametric { private Integer id; private String description; } diff --git a/src/main/java/com/cuoco/application/usecase/model/File.java b/src/main/java/com/cuoco/application/usecase/model/File.java new file mode 100644 index 0000000..49c2cfa --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/File.java @@ -0,0 +1,13 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class File { + private String fileName; + private String fileBase64; + private String format; + private String mimeType; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Filters.java b/src/main/java/com/cuoco/application/usecase/model/Filters.java new file mode 100644 index 0000000..c6816dc --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Filters.java @@ -0,0 +1,29 @@ +package com.cuoco.application.usecase.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Filters { + + private Boolean useProfilePreferences; + private Boolean enable; + + private PreparationTime preparationTime; + private CookLevel cookLevel; + private Diet diet; + private List mealTypes; + private List allergies; + private List dietaryNeeds; + private Boolean freeze; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Ingredient.java b/src/main/java/com/cuoco/application/usecase/model/Ingredient.java index 75d3d47..5fbd88a 100644 --- a/src/main/java/com/cuoco/application/usecase/model/Ingredient.java +++ b/src/main/java/com/cuoco/application/usecase/model/Ingredient.java @@ -1,39 +1,27 @@ package com.cuoco.application.usecase.model; - +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +@Data +@Builder +@ToString +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) public class Ingredient { + private Long id; private String name; + private Double quantity; + private Unit unit; + private Boolean optional; private String source; - private boolean confirmed; - - public Ingredient(String name, String source, boolean confirmed) { - this.name = name; - this.source = source; - this.confirmed = confirmed; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - - public boolean isConfirmed() { - return confirmed; - } + private Boolean confirmed; - public void setConfirmed(boolean confirmed) { - this.confirmed = confirmed; - } } diff --git a/src/main/java/com/cuoco/application/usecase/model/IngredientType.java b/src/main/java/com/cuoco/application/usecase/model/IngredientType.java new file mode 100644 index 0000000..d98c5e9 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/IngredientType.java @@ -0,0 +1,4 @@ +package com.cuoco.application.usecase.model; + +public class IngredientType { +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Instruction.java b/src/main/java/com/cuoco/application/usecase/model/Instruction.java new file mode 100644 index 0000000..ab92349 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Instruction.java @@ -0,0 +1,19 @@ +package com.cuoco.application.usecase.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Instruction { + private String title; + private String time; + private String description; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/MealPrep.java b/src/main/java/com/cuoco/application/usecase/model/MealPrep.java new file mode 100644 index 0000000..86453e1 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/MealPrep.java @@ -0,0 +1,27 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class MealPrep { + + private Long id; + private String title; + private User user; + private String estimatedCookingTime; + private Boolean favorite; + private Integer servings; + private Boolean freeze; + private List steps; + + private List ingredients; + private List recipes; + + private Filters filters; + private MealPrepConfiguration configuration; + +} diff --git a/src/main/java/com/cuoco/application/usecase/model/MealPrepConfiguration.java b/src/main/java/com/cuoco/application/usecase/model/MealPrepConfiguration.java new file mode 100644 index 0000000..11c26e5 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/MealPrepConfiguration.java @@ -0,0 +1,15 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class MealPrepConfiguration { + private Integer size; + private List notInclude; + + private ParametricData parametricData; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/MealPrepFilter.java b/src/main/java/com/cuoco/application/usecase/model/MealPrepFilter.java new file mode 100644 index 0000000..49fb407 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/MealPrepFilter.java @@ -0,0 +1,26 @@ +package com.cuoco.application.usecase.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MealPrepFilter { + private Boolean freeze; + private Integer servings; + private PreparationTime preparationTime; + private CookLevel cookLevel; + private Diet diet; + private List mealTypes; + private List allergies; + private List dietaryNeeds; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/MealPrepIngredient.java b/src/main/java/com/cuoco/application/usecase/model/MealPrepIngredient.java new file mode 100644 index 0000000..0ff8ef3 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/MealPrepIngredient.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MealPrepIngredient { + private MealPrep mealPrep; + private Ingredient ingredient; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/MealType.java b/src/main/java/com/cuoco/application/usecase/model/MealType.java new file mode 100644 index 0000000..0c8afd4 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/MealType.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class MealType implements Parametric { + private Integer id; + private String description; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Parametric.java b/src/main/java/com/cuoco/application/usecase/model/Parametric.java new file mode 100644 index 0000000..da79cf2 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Parametric.java @@ -0,0 +1,6 @@ +package com.cuoco.application.usecase.model; + +public interface Parametric { + Integer getId(); + String getDescription(); +} diff --git a/src/main/java/com/cuoco/application/usecase/model/ParametricData.java b/src/main/java/com/cuoco/application/usecase/model/ParametricData.java new file mode 100644 index 0000000..e63aea1 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/ParametricData.java @@ -0,0 +1,20 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ParametricData { + + private List units; + private List preparationTimes; + private List cookLevels; + private List diets; + private List mealTypes; + private List allergies; + private List dietaryNeeds; + +} diff --git a/src/main/java/com/cuoco/application/usecase/model/PaymentStatus.java b/src/main/java/com/cuoco/application/usecase/model/PaymentStatus.java new file mode 100644 index 0000000..1e7f702 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/PaymentStatus.java @@ -0,0 +1,13 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class PaymentStatus implements Parametric { + private Integer id; + private String description; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Plan.java b/src/main/java/com/cuoco/application/usecase/model/Plan.java index da3793e..34d6821 100644 --- a/src/main/java/com/cuoco/application/usecase/model/Plan.java +++ b/src/main/java/com/cuoco/application/usecase/model/Plan.java @@ -7,7 +7,9 @@ @Data @AllArgsConstructor @Builder -public class Plan { +public class Plan implements Parametric { private Integer id; private String description; + + private PlanConfiguration configuration; } diff --git a/src/main/java/com/cuoco/application/usecase/model/PlanConfiguration.java b/src/main/java/com/cuoco/application/usecase/model/PlanConfiguration.java new file mode 100644 index 0000000..35571a0 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/PlanConfiguration.java @@ -0,0 +1,18 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Builder +public class PlanConfiguration { + + private String title; + private String description; + private Integer quantity; + private BigDecimal price; + private String currency; + +} diff --git a/src/main/java/com/cuoco/application/usecase/model/PreparationTime.java b/src/main/java/com/cuoco/application/usecase/model/PreparationTime.java new file mode 100644 index 0000000..51ae99e --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/PreparationTime.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PreparationTime implements Parametric { + private Integer id; + private String description; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Recipe.java b/src/main/java/com/cuoco/application/usecase/model/Recipe.java new file mode 100644 index 0000000..ade9045 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Recipe.java @@ -0,0 +1,36 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@NoArgsConstructor +public class Recipe { + private Long id; + private String name; + private String subtitle; + private String description; + private Boolean favorite; + private List steps; + private String image; + private PreparationTime preparationTime; + private CookLevel cookLevel; + private Diet diet; + private List mealTypes; + private List allergies; + private List dietaryNeeds; + private List ingredients; + + private List images; + + private Filters filters; + private RecipeConfiguration configuration; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/RecipeConfiguration.java b/src/main/java/com/cuoco/application/usecase/model/RecipeConfiguration.java new file mode 100644 index 0000000..99c2881 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/RecipeConfiguration.java @@ -0,0 +1,16 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class RecipeConfiguration { + + private Integer size; + private List notInclude; + + private ParametricData parametricData; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/RecipeFilter.java b/src/main/java/com/cuoco/application/usecase/model/RecipeFilter.java deleted file mode 100644 index c7db517..0000000 --- a/src/main/java/com/cuoco/application/usecase/model/RecipeFilter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.cuoco.application.usecase.model; - -import java.util.List; - -public class RecipeFilter { - - private String time; - private String difficulty; - private List types; - private String diet; - private int quantity; - - public RecipeFilter(String time, String difficulty, List types, String diet, int quantity) { - this.time = time; - this.difficulty = difficulty; - this.types = types; - this.diet = diet; - this.quantity = quantity; - } - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public String getDifficulty() { - return difficulty; - } - - public void setDifficulty(String difficulty) { - this.difficulty = difficulty; - } - - public List getTypes() { - return types; - } - - public void setTypes(List types) { - this.types = types; - } - - public String getDiet() { - return diet; - } - - public void setDiet(String diet) { - this.diet = diet; - } - - public int getQuantity() { - return quantity; - } - - public void setQuantity(int quantity) { - this.quantity = quantity; - } -} diff --git a/src/main/java/com/cuoco/application/usecase/model/RecipeIngredient.java b/src/main/java/com/cuoco/application/usecase/model/RecipeIngredient.java new file mode 100644 index 0000000..ea76f3a --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/RecipeIngredient.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class RecipeIngredient { + private Recipe recipe; + private Ingredient ingredient; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/SearchFilters.java b/src/main/java/com/cuoco/application/usecase/model/SearchFilters.java new file mode 100644 index 0000000..b30e065 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/SearchFilters.java @@ -0,0 +1,11 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class SearchFilters { + private Integer size; + private Boolean random; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Step.java b/src/main/java/com/cuoco/application/usecase/model/Step.java new file mode 100644 index 0000000..59f49ac --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Step.java @@ -0,0 +1,20 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Step { + private Long id; + private String title; + private Integer number; + private String description; + private String time; + private String imageName; + private byte[] imageData; +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/application/usecase/model/Subscription.java b/src/main/java/com/cuoco/application/usecase/model/Subscription.java new file mode 100644 index 0000000..ec2f28c --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Subscription.java @@ -0,0 +1,9 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Subscription { +} diff --git a/src/main/java/com/cuoco/application/usecase/model/Unit.java b/src/main/java/com/cuoco/application/usecase/model/Unit.java new file mode 100644 index 0000000..d3b4ff6 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/Unit.java @@ -0,0 +1,16 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Unit { + private Integer id; + private String description; + private String symbol; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/User.java b/src/main/java/com/cuoco/application/usecase/model/User.java index e6a508c..47cf4f7 100644 --- a/src/main/java/com/cuoco/application/usecase/model/User.java +++ b/src/main/java/com/cuoco/application/usecase/model/User.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.List; @@ -10,12 +11,14 @@ @Data @Builder @AllArgsConstructor +@NoArgsConstructor public class User { private Long id; private String name; private String email; private String password; + private Boolean changePassword; private Plan plan; private Boolean active; private UserPreferences preferences; @@ -23,6 +26,10 @@ public class User { private List dietaryNeeds; private List allergies; + + private List recipes; + private List mealPreps; + } diff --git a/src/main/java/com/cuoco/application/usecase/model/UserCalendar.java b/src/main/java/com/cuoco/application/usecase/model/UserCalendar.java new file mode 100644 index 0000000..0c07019 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/UserCalendar.java @@ -0,0 +1,17 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserCalendar { + private User user; + private List calendars; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/UserMealPrep.java b/src/main/java/com/cuoco/application/usecase/model/UserMealPrep.java new file mode 100644 index 0000000..55a12f6 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/UserMealPrep.java @@ -0,0 +1,15 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserMealPrep { + private User user; + private MealPrep mealPrep; +} diff --git a/src/main/java/com/cuoco/application/usecase/model/UserPayment.java b/src/main/java/com/cuoco/application/usecase/model/UserPayment.java new file mode 100644 index 0000000..f4a8455 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/UserPayment.java @@ -0,0 +1,18 @@ +package com.cuoco.application.usecase.model; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class UserPayment { + + private Long id; + private User user; + private Plan plan; + private PaymentStatus status; + private String externalId; + private String externalReference; + private String checkoutUrl; + +} diff --git a/src/main/java/com/cuoco/application/usecase/model/UserPreferences.java b/src/main/java/com/cuoco/application/usecase/model/UserPreferences.java index c900b13..3991c3c 100644 --- a/src/main/java/com/cuoco/application/usecase/model/UserPreferences.java +++ b/src/main/java/com/cuoco/application/usecase/model/UserPreferences.java @@ -3,15 +3,12 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.Getter; -import lombok.Setter; - -import java.util.List; @Data @Builder @AllArgsConstructor public class UserPreferences { + private Long id; private CookLevel cookLevel; private Diet diet; } diff --git a/src/main/java/com/cuoco/application/usecase/model/UserRecipe.java b/src/main/java/com/cuoco/application/usecase/model/UserRecipe.java new file mode 100644 index 0000000..bb42de8 --- /dev/null +++ b/src/main/java/com/cuoco/application/usecase/model/UserRecipe.java @@ -0,0 +1,15 @@ +package com.cuoco.application.usecase.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserRecipe { + private User user; + private Recipe recipe; +} diff --git a/src/main/java/com/cuoco/application/utils/JwtUtil.java b/src/main/java/com/cuoco/application/utils/JwtUtil.java new file mode 100644 index 0000000..bf48e90 --- /dev/null +++ b/src/main/java/com/cuoco/application/utils/JwtUtil.java @@ -0,0 +1,86 @@ +package com.cuoco.application.utils; + +import com.cuoco.application.exception.UnauthorizedException; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Slf4j +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String SECRET_KEY; + + public String generateToken(User user) { + return Jwts.builder() + .setId(user.getId().toString()) + .setSubject(user.getEmail()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) + .compact(); + } + + public String generateActivationToken(User user) { + return Jwts.builder() + .setId(user.getId().toString()) + .setSubject(user.getEmail()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 2)) // 2 horas + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) + .compact(); + } + + public String generateResetPasswordToken(User user) { + return Jwts.builder() + .setId(user.getId().toString()) + .setSubject(user.getEmail()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 2)) // 2 horas + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) + .compact(); + } + + public String extractId(String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .getId(); + + } catch (MalformedJwtException e) { + log.warn("Extract ID: Invalid JWT token: {}", e.getMessage()); + throw new UnauthorizedException(ErrorDescription.INVALID_TOKEN.getValue()); + } + } + + public String extractEmail(String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + + } catch (MalformedJwtException e) { + log.warn("Extract email: Invalid JWT token: {}", e.getMessage()); + throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); + } + } + + public boolean validateToken(String token, User user) { + return extractEmail(token).equals(user.getEmail()); + } +} diff --git a/src/main/java/com/cuoco/shared/FileReader.java b/src/main/java/com/cuoco/shared/FileReader.java index aab18ba..d203ed9 100644 --- a/src/main/java/com/cuoco/shared/FileReader.java +++ b/src/main/java/com/cuoco/shared/FileReader.java @@ -1,19 +1,32 @@ package com.cuoco.shared; +import com.cuoco.adapter.exception.NotAvailableException; + import java.io.IOException; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Objects; public class FileReader { public static String execute(String path) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { - return Files.readString(Paths.get(Objects.requireNonNull(contextClassLoader.getResource(path)).toURI())); + // Try to get resource + URL resource = contextClassLoader.getResource(path); + if (resource == null) { + // Try with class loader + resource = FileReader.class.getClassLoader().getResource(path); + } + + if (resource != null) { + return Files.readString(Paths.get(resource.toURI())); + } else { + throw new NotAvailableException("No se pudo encontrar el archivo: " + path); + } } catch (IOException | URISyntaxException e) { - throw new RuntimeException("No se pudo leer el archivo: " + path, e); + throw new NotAvailableException("No se pudo leer el archivo: " + path + " - " + e.getMessage()); } } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/GlobalExceptionHandler.java b/src/main/java/com/cuoco/shared/GlobalExceptionHandler.java index d57ab2b..f5ee24a 100644 --- a/src/main/java/com/cuoco/shared/GlobalExceptionHandler.java +++ b/src/main/java/com/cuoco/shared/GlobalExceptionHandler.java @@ -1,12 +1,13 @@ package com.cuoco.shared; +import com.cuoco.adapter.exception.ConflictException; import com.cuoco.adapter.exception.NotAvailableException; import com.cuoco.adapter.exception.UnprocessableException; import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.exception.BusinessException; -import com.cuoco.application.exception.NotFoundException; import com.cuoco.application.exception.ForbiddenException; +import com.cuoco.application.exception.NotFoundException; import com.cuoco.application.exception.UnauthorizedException; import com.cuoco.application.usecase.model.MessageError; import com.cuoco.shared.model.ErrorDescription; @@ -15,8 +16,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -26,11 +26,10 @@ import java.util.List; import java.util.stream.Collectors; +@Slf4j @ControllerAdvice public class GlobalExceptionHandler { - private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); - @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleValidationException(MethodArgumentNotValidException ex) { List errors = ex.getBindingResult() @@ -48,10 +47,16 @@ public ResponseEntity handleValidationException(MethodArgument return new ResponseEntity<>(apiErrorResponse, HttpStatus.BAD_REQUEST); } - @ExceptionHandler(com.cuoco.adapter.exception.ForbiddenException.class) - public ResponseEntity handle(com.cuoco.adapter.exception.ForbiddenException ex) { - log.info(HttpStatus.FORBIDDEN.getReasonPhrase()); - return buildResponseError(HttpStatus.FORBIDDEN, ex); + @ExceptionHandler(ConflictException.class) + public ResponseEntity handle(ConflictException ex) { + log.warn(HttpStatus.CONFLICT.getReasonPhrase()); + return buildResponseError(HttpStatus.CONFLICT, ex); + } + + @ExceptionHandler(com.cuoco.application.exception.ConflictException.class) + public ResponseEntity handle(com.cuoco.application.exception.ConflictException ex) { + log.warn(HttpStatus.CONFLICT.getReasonPhrase()); + return buildResponseError(HttpStatus.CONFLICT, ex); } @ExceptionHandler(BadRequestException.class) @@ -78,6 +83,12 @@ public ResponseEntity handle(ForbiddenException ex) { return buildResponseError(HttpStatus.FORBIDDEN, ex); } + @ExceptionHandler(com.cuoco.adapter.exception.ForbiddenException.class) + public ResponseEntity handle(com.cuoco.adapter.exception.ForbiddenException ex) { + log.info(HttpStatus.FORBIDDEN.getReasonPhrase()); + return buildResponseError(HttpStatus.FORBIDDEN, ex); + } + @ExceptionHandler(UnauthorizedException.class) public ResponseEntity handle(UnauthorizedException ex) { log.info(HttpStatus.UNAUTHORIZED.getReasonPhrase()); @@ -102,12 +113,24 @@ public ResponseEntity handle(UnprocessableException ex) { return buildResponseError(HttpStatus.UNPROCESSABLE_ENTITY, ex); } + @ExceptionHandler(com.cuoco.application.exception.UnprocessableException.class) + public ResponseEntity handle(com.cuoco.application.exception.UnprocessableException ex) { + log.info(HttpStatus.UNPROCESSABLE_ENTITY.getReasonPhrase(), ex); + return buildResponseError(HttpStatus.UNPROCESSABLE_ENTITY, ex); + } + @ExceptionHandler(NotAvailableException.class) public ResponseEntity handle(NotAvailableException ex) { log.error(HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase(), ex); return buildResponseError(HttpStatus.SERVICE_UNAVAILABLE, ex); } + @ExceptionHandler(com.cuoco.application.exception.NotAvailableException.class) + public ResponseEntity handle(com.cuoco.application.exception.NotAvailableException ex) { + log.error(HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase(), ex); + return buildResponseError(HttpStatus.SERVICE_UNAVAILABLE, ex); + } + @ExceptionHandler(Throwable.class) public ResponseEntity handle(Throwable ex) { log.error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), ex); diff --git a/src/main/java/com/cuoco/shared/config/MailConfiguration.java b/src/main/java/com/cuoco/shared/config/MailConfiguration.java new file mode 100644 index 0000000..1e6906a --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/MailConfiguration.java @@ -0,0 +1,45 @@ +package com.cuoco.shared.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfiguration { + + @Value("${email.host}") + private String host; + + @Value("${email.port}") + private int port; + + @Value("${email.username}") + private String username; + + @Value("${email.password}") + private String password; + + @Bean + public JavaMailSender javaMailSender() { + + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(host); + mailSender.setPort(port); + mailSender.setUsername(username); + mailSender.setPassword(password); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.timeout", "5000"); + props.put("mail.smtp.connectiontimeout", "5000"); + props.put("mail.smtp.writetimeout", "5000"); + + return mailSender; + } +} diff --git a/src/main/java/com/cuoco/shared/config/SwaggerConfiguration.java b/src/main/java/com/cuoco/shared/config/SwaggerConfiguration.java new file mode 100644 index 0000000..6f5923b --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/SwaggerConfiguration.java @@ -0,0 +1,25 @@ +package com.cuoco.shared.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfiguration { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Cuoco API") + .description("API para generar recetas de cocina, obtener ingredientes y planificar") + .version("1.0.0") + .contact(new Contact() + .name("Trabajo práctico integrador - Cuoco") + .url("https://www.cuoco.com.ar") + ) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/config/WebConfiguration.java b/src/main/java/com/cuoco/shared/config/WebConfiguration.java new file mode 100644 index 0000000..a37fe3e --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/WebConfiguration.java @@ -0,0 +1,44 @@ +package com.cuoco.shared.config; + +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfiguration { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(@NotNull CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins( + "http://localhost:3000", + "https://dev.cuoco.com.ar", + "https://www.cuoco.com.ar" + ) + .allowedMethods( + HttpMethod.GET.name(), + HttpMethod.POST.name(), + HttpMethod.PUT.name(), + HttpMethod.PATCH.name(), + HttpMethod.DELETE.name(), + HttpMethod.OPTIONS.name() + ) + .allowedHeaders("*") + .allowCredentials(true); + } + + @Override + public void addResourceHandlers(@NotNull ResourceHandlerRegistry registry) { + registry.addResourceHandler("/api/images/**") + .addResourceLocations("classpath:/imagenes/"); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoBrandingConfig.java b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoBrandingConfig.java new file mode 100644 index 0000000..dd8bc8b --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoBrandingConfig.java @@ -0,0 +1,14 @@ +package com.cuoco.shared.config.mercadopago; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "mercado-pago.branding") +public class MercadoPagoBrandingConfig { + private String primaryColor; + private String secondaryColor; + private boolean showMercadoPagoBranding; +} diff --git a/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoCallbacksConfig.java b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoCallbacksConfig.java new file mode 100644 index 0000000..00a5b9f --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoCallbacksConfig.java @@ -0,0 +1,15 @@ +package com.cuoco.shared.config.mercadopago; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "mercado-pago.callbacks") +public class MercadoPagoCallbacksConfig { + private String success; + private String pending; + private String failure; +} + diff --git a/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoConfig.java b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoConfig.java new file mode 100644 index 0000000..13ec8a1 --- /dev/null +++ b/src/main/java/com/cuoco/shared/config/mercadopago/MercadoPagoConfig.java @@ -0,0 +1,16 @@ +package com.cuoco.shared.config.mercadopago; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "mercado-pago") +public class MercadoPagoConfig { + + private String accessToken; + private MercadoPagoCallbacksConfig callbacks; + private MercadoPagoBrandingConfig branding; + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/config/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/cuoco/shared/config/security/JwtAuthenticationEntryPoint.java index c4169ed..2ca59b6 100644 --- a/src/main/java/com/cuoco/shared/config/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/cuoco/shared/config/security/JwtAuthenticationEntryPoint.java @@ -7,7 +7,6 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; - import java.io.IOException; @Component diff --git a/src/main/java/com/cuoco/shared/config/security/SecurityConfiguration.java b/src/main/java/com/cuoco/shared/config/security/SecurityConfiguration.java index 64d41ee..e0b8fa9 100644 --- a/src/main/java/com/cuoco/shared/config/security/SecurityConfiguration.java +++ b/src/main/java/com/cuoco/shared/config/security/SecurityConfiguration.java @@ -1,6 +1,6 @@ package com.cuoco.shared.config.security; -import com.cuoco.adapter.in.security.JwtAuthenticationFilter; +import com.cuoco.adapter.in.security.JwtAuthenticationFilterAdapter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -16,11 +16,11 @@ @EnableMethodSecurity public class SecurityConfiguration { - private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtAuthenticationFilterAdapter jwtAuthenticationFilterAdapter; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; - public SecurityConfiguration(JwtAuthenticationFilter jwtAuthenticationFilter, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) { - this.jwtAuthenticationFilter = jwtAuthenticationFilter; + public SecurityConfiguration(JwtAuthenticationFilterAdapter jwtAuthenticationFilterAdapter, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) { + this.jwtAuthenticationFilterAdapter = jwtAuthenticationFilterAdapter; this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; } @@ -34,16 +34,22 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers( "/auth/**", "/actuator/health", - "/cook-level", - "/plan", - "/diet", - "/dietary-need", - "/allergy" + "/cook-levels", + "/plans", + "/diets", + "/meal-types", + "/preparation-times", + "/dietary-needs", + "/allergies", + "/payments/webhook", + "/v3/api-docs/**", + "/swagger-ui/**", + "/swagger-ui.html" ).permitAll() .anyRequest().authenticated() ) .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtAuthenticationFilterAdapter, UsernamePasswordAuthenticationFilter.class) .build(); } diff --git a/src/main/java/com/cuoco/shared/config/security/WebConfiguration.java b/src/main/java/com/cuoco/shared/config/security/WebConfiguration.java deleted file mode 100644 index 59b4022..0000000 --- a/src/main/java/com/cuoco/shared/config/security/WebConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cuoco.shared.config.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfiguration { - - @Bean - public WebMvcConfigurer corsConfigurer() { - return new WebMvcConfigurer() { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins( - "http://localhost:3000", - "https://dev.cuoco.com.ar", - "https://www.cuoco.com.ar" - ) - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); - } - }; - } -} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/model/ErrorDescription.java b/src/main/java/com/cuoco/shared/model/ErrorDescription.java index 2416092..c14cbfa 100644 --- a/src/main/java/com/cuoco/shared/model/ErrorDescription.java +++ b/src/main/java/com/cuoco/shared/model/ErrorDescription.java @@ -1,19 +1,46 @@ package com.cuoco.shared.model; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor public enum ErrorDescription { + RECIPE_NOT_EXISTS("El ID de la receta ingresada no existe"), + MEAL_PREP_NOT_EXISTS("El ID del meal prep ingresado no existe"), ALLERGIES_NOT_EXISTS("Uno o mas valores de allergies no existen"), DIETARY_NEEDS_NOT_EXISTS("Uno o mas valores de dietary-needs no existen"), PREFERENCES_NOT_EXISTS("Las preferencias ingresadas no existen"), PLAN_NOT_EXISTS("El plan ingresado no existe"), COOK_LEVEL_NOT_EXISTS("El nivel de dificulad ingresado no existe"), DIET_NOT_EXISTS("La dieta ingresada no existe"), + MEAL_CATEGORY_NOT_EXISTS("La categoria de la receta ingresada no existe"), + MEAL_TYPE_NOT_EXISTS("El tipo de receta ingresado no existe"), + PREPARATION_TIME_NOT_EXISTS("El tiempo de preparacion ingresado no existe"), + + INGREDIENTS_EMPTY("Es necesario por lo menos un ingrediente para continuar"), + + INVALID_CALENDAR_DAY("El dia ingresado no es valido. Debe estar entre 1 y 7"), + USER_NOT_EXISTS("El usuario ingresado no existe"), USER_DUPLICATED("El usuario ya existe"), + USER_NOT_ACTIVATED("El usuario no esta activo"), + USER_HAS_PRO_PLAN("El usuario ya tiene el plan PRO"), + NO_AUTH_TOKEN("El token no esta presente"), + INVALID_TOKEN("El token ingresado no es válido"), INVALID_CREDENTIALS("Las credenciales no son válidas"), - INVALID_TOKEN("El token no es valido"), - EXPIRED_TOKEN("El token ha expirado"), - UNAUTHORIZED("El token no esta presente"), + EXPIRED_CREDENTIALS("El token ha expirado"), + + INVALID_AUDIO_FILE_EXTENSION("La extensión del archivo de audio no es valida"), + AUDIO_FILE_IS_REQUIRED("El archivo de audio no esta presente y es requerido"), + AUDIO_FILE_PROCESSING_ERROR("Error procesando el archivo de audio"), + PRO_FEATURE("Esta funcionalidad solo es para usuarios PRO"), + + CANNOT_GENERATE_RECIPE("Failed to generate recipe"), + CANNOT_GENERATE_MEAL_PREP("Could not generate meal prep"), + + UNAUTHORIZED("No está autorizado a usar esta función"), UNEXPECTED_ERROR("An unexpected error occurred: "), UNHANDLED("Ha ocurrido un error inesperado"), NOT_AVAILABLE("El servicio no esta disponible"), @@ -22,12 +49,4 @@ public enum ErrorDescription { DUPLICATED("El recurso ya existe"); private final String value; - - ErrorDescription(String value) { - this.value = value; - } - - public String getValue() { - return value; - } } \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/utils/AudioConstants.java b/src/main/java/com/cuoco/shared/utils/AudioConstants.java new file mode 100644 index 0000000..edf67ab --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/AudioConstants.java @@ -0,0 +1,15 @@ +package com.cuoco.shared.utils; + +public final class AudioConstants { + + private AudioConstants() {} // evitar instanciación + + public static final String MIMETYPE_BASE = "audio/"; + public static final String MP3 = "mp3"; + public static final String WAV = "wav"; + public static final String OGG = "ogg"; + public static final String AAC = "aac"; + public static final String FLAC = "flac"; + public static final String M4A = "m4a"; + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/utils/Constants.java b/src/main/java/com/cuoco/shared/utils/Constants.java new file mode 100644 index 0000000..9cbc1a0 --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/Constants.java @@ -0,0 +1,20 @@ +package com.cuoco.shared.utils; + +public enum Constants { + + UNDERSCORE("_"), + COMMA(","), + SLASH("/"), + DOT("."), + EMPTY(""); + + private final String value; + + Constants(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/utils/FileUtils.java b/src/main/java/com/cuoco/shared/utils/FileUtils.java new file mode 100644 index 0000000..617f8cd --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/FileUtils.java @@ -0,0 +1,48 @@ +package com.cuoco.shared.utils; + +import java.util.List; +import java.util.Map; + +public class FileUtils { + + public static final List SUPPORTED_AUDIO_EXTENSIONS = List.of( + AudioConstants.MP3, + AudioConstants.WAV, + AudioConstants.OGG, + AudioConstants.AAC, + AudioConstants.FLAC, + AudioConstants.M4A + ); + + public static String getAudioMimeType(String format) { + + if (format == null) { + return AudioConstants.MIMETYPE_BASE + AudioConstants.MP3; + } + + return switch (format.toLowerCase()) { + case AudioConstants.MP3 -> AudioConstants.MIMETYPE_BASE + AudioConstants.MP3; + case AudioConstants.WAV -> AudioConstants.MIMETYPE_BASE + AudioConstants.WAV; + case AudioConstants.OGG -> AudioConstants.MIMETYPE_BASE + AudioConstants.OGG; + case AudioConstants.AAC -> AudioConstants.MIMETYPE_BASE + AudioConstants.AAC; + case AudioConstants.FLAC -> AudioConstants.MIMETYPE_BASE + AudioConstants.FLAC; + case AudioConstants.M4A -> AudioConstants.MIMETYPE_BASE + AudioConstants.M4A; + default -> AudioConstants.MIMETYPE_BASE + format; + }; + } + + public static String getImageFormat(String mimeType) { + + if (mimeType == null) { + return null; + } + + Map formatMap = Map.of( + ImageConstants.MIME_TYPE_BASE.getValue() + ImageConstants.JPEG_FORMAT.getValue(), ImageConstants.JPEG_FORMAT.getValue(), + ImageConstants.MIME_TYPE_BASE.getValue() + ImageConstants.WEBP_FORMAT.getValue(), ImageConstants.WEBP_FORMAT.getValue() + ); + + String format = formatMap.getOrDefault(mimeType, ImageConstants.PNG_FORMAT.getValue()); + return Constants.DOT.getValue() + format; + } +} diff --git a/src/main/java/com/cuoco/shared/utils/ImageConstants.java b/src/main/java/com/cuoco/shared/utils/ImageConstants.java new file mode 100644 index 0000000..03c2050 --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/ImageConstants.java @@ -0,0 +1,27 @@ +package com.cuoco.shared.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ImageConstants { + + MAIN_IMAGE_NAME("main"), + STEP_INFIX("_step_"), + MAIN_TYPE("main"), + STEP_TYPE("step"), + + MAX_STEPS_SIZE_INT("3"), + MAX_INGREDIENTS_SIZE_INT("5"), + INSTRUCTIONS_SPLIT_PATTERN("\\d+\\.|\\n|\\r\\n|;"), + + MIME_TYPE_BASE("image/"), + + JPEG_FORMAT("jpeg"), + PNG_FORMAT("png"), + WEBP_FORMAT("webp"); + + private final String value; + +} diff --git a/src/main/java/com/cuoco/shared/utils/JwtUtil.java b/src/main/java/com/cuoco/shared/utils/JwtUtil.java deleted file mode 100644 index 3d000cc..0000000 --- a/src/main/java/com/cuoco/shared/utils/JwtUtil.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.cuoco.shared.utils; - -import com.cuoco.application.usecase.model.User; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -import java.util.Date; - -@Component -public class JwtUtil { - - @Value("${jwt.secret}") - private String SECRET_KEY; - - public String generateToken(User user) { - return Jwts.builder() - .setSubject(user.getEmail()) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas - .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) - .compact(); - } - - public String extractEmail(String token) { - return Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) - .build() - .parseClaimsJws(token) - .getBody() - .getSubject(); - - } - - public boolean validateToken(String token, User user) { - return extractEmail(token).equals(user.getEmail()); - } -} diff --git a/src/main/java/com/cuoco/shared/utils/PaymentConstants.java b/src/main/java/com/cuoco/shared/utils/PaymentConstants.java new file mode 100644 index 0000000..b3a81cf --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/PaymentConstants.java @@ -0,0 +1,17 @@ +package com.cuoco.shared.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PaymentConstants { + + STATUS_PENDING(1), + STATUS_APPROVED(2), + STATUS_IN_PROCESS(3), + STATUS_REJECTED(4); + + private final int value; + +} \ No newline at end of file diff --git a/src/main/java/com/cuoco/shared/utils/PlanConstants.java b/src/main/java/com/cuoco/shared/utils/PlanConstants.java new file mode 100644 index 0000000..1dcdc23 --- /dev/null +++ b/src/main/java/com/cuoco/shared/utils/PlanConstants.java @@ -0,0 +1,15 @@ +package com.cuoco.shared.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PlanConstants { + + FREE(1), + PRO(2); + + private final int value; + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3e01d9e..d36710a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,10 +9,65 @@ spring: hibernate: ddl-auto: none show-sql: false + servlet: + multipart: + enabled: true + max-file-size: 100MB + max-request-size: 100MB +springdoc: + swagger-ui: + path: /swagger-ui jwt: secret: ${JWT_SECRET} gemini: api: url: ${GEMINI_API_URL} key: ${GEMINI_API_KEY} + image: + url: ${GEMINI_IMAGE_URL} temperature: ${GEMINI_TEMPERATURE} +email: + host: ${EMAIL_HOST:smtp.gmail.com} + port: ${EMAIL_PORT:587} + username: ${EMAIL_USERNAME:cuoco.8bits@gmail.com} + password: ${EMAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + connectiontimeout: 5000 + timeout: 5000 + writetimeout: 5000 +mercado-pago: + access-token: ${MP_ACCESS_TOKEN} + callbacks: + success: ${MP_CALLBACK_SUCCESS:/payment/success} + pending: ${MP_CALLBACK_PENDING:/payment/pending} + failure: ${MP_CALLBACK_FAILURE:/payment/failure} + branding: + primary-color: ${MP_BRANDING_PRIMARY_COLOR:#FF6B35} + secondary-color: ${MP_BRANDING_SECONDARY_COLOR:#FFA500} + show-mercado-pago-branding: ${MP_BRANDING_ENABLE_MP_BRANDING:false} +shared: + email: + no-reply: + name: ${NO_REPLY_NAME:Cuoco} + from: ${NO_REPLY_EMAIL:noresponder@cuoco.com.ar} + notification: + name: ${NOTIFICATION_NAME:Cuoco} + from: ${NOTIFICATION_EMAIL:notifications@cuoco.com.ar} + recipes: + size: + free: ${FREE_USER_RECIPES_SIZE:3} + pro: ${PRO_USER_RECIPES_DEFAULT_SIZE:5} + images: + base-path: ${RECIPE_IMAGES_BASE_PATH} + meal-preps: + size: ${MEAL_PREP_DEFAULT_SIZE:1} + recipes-size: ${RECIPES_SIZE_PER_MEAL_PREP:3} + + + + diff --git a/src/main/resources/email/resetPassword.html b/src/main/resources/email/resetPassword.html new file mode 100644 index 0000000..2ff6f7d --- /dev/null +++ b/src/main/resources/email/resetPassword.html @@ -0,0 +1,9 @@ + + +

Solicitud de cambio de contraseña

+

¡Hola {{NAME}}! Recibimos una solicitud de cambio de contraseña para tu cuenta en Cuoco.

+

Para continuar con el cambio de contraseña, hace clic en el siguiente enlace.

+ Cambiar contraseña +

Si no fuiste tu, podés ignorar este mensaje.

+ + \ No newline at end of file diff --git a/src/main/resources/email/userConfirmation.html b/src/main/resources/email/userConfirmation.html new file mode 100644 index 0000000..8bceeca --- /dev/null +++ b/src/main/resources/email/userConfirmation.html @@ -0,0 +1,8 @@ + + +

¡Hola {{NAME}}! ¡Bienvenido a Cuoco!

+

Confirma tu cuenta haciendo clic en el siguiente enlace y preparate para crear un mundo de recetas:

+ Confirmar cuenta +

Si no creaste una cuenta en Cuoco, puedes ignorar este mensaje.

+ + \ No newline at end of file diff --git a/src/main/resources/prompt/generateIngredients.txt b/src/main/resources/prompt/generateIngredients.txt deleted file mode 100644 index fbd74d3..0000000 --- a/src/main/resources/prompt/generateIngredients.txt +++ /dev/null @@ -1,5 +0,0 @@ -"Analiza esta imagen y extrae SOLO los nombres de ingredientes visibles en español argentino. -Devuelve una lista separada por comas, sin palabras adicionales ni explicaciones. -Enfócate únicamente en ingredientes de comida, no en platos preparados. -Usa nombres comunes argentinos: papa (no patata), palta (no aguacate), choclo (no maíz). -Ejemplos: tomate, papa, cebolla, palta, choclo" \ No newline at end of file diff --git a/src/main/resources/prompt/generateIngredientsFromVoice.txt b/src/main/resources/prompt/generateIngredientsFromVoice.txt deleted file mode 100644 index 4641d50..0000000 --- a/src/main/resources/prompt/generateIngredientsFromVoice.txt +++ /dev/null @@ -1,30 +0,0 @@ -Eres un asistente experto en cocina argentina especializado en identificar ingredientes a partir de audio. - -Tu tarea es analizar el audio proporcionado y extraer una lista de ingredientes mencionados. - -INSTRUCCIONES: -1. Escucha atentamente el audio en español argentino -2. Identifica ÚNICAMENTE ingredientes de cocina (alimentos, especias, condimentos) -3. Ignora palabras que NO sean ingredientes (utensilios, acciones, cantidades, etc.) -4. Usa los nombres más comunes en Argentina (ej: "palta" no "aguacate", "choclo" no "maíz") -5. Devuelve cada ingrediente en una línea separada -6. Si no escuchas ingredientes claros, devuelve una lista vacía - -FORMATO DE RESPUESTA: -- Un ingrediente por línea -- Solo el nombre del ingrediente, sin cantidades ni descripciones -- En minúsculas -- Sin números, sin "kg", "gramos", etc. - -EJEMPLOS: -Si escuchas: "Necesito tomate, cebolla y un poco de ajo para la salsa" -Responde: -tomate -cebolla -ajo - -Si escuchas: "Voy a cocinar con aceite en la sartén" -Responde: -aceite - -Analiza el siguiente audio y extrae los ingredientes: \ No newline at end of file diff --git a/src/main/resources/prompt/generateMealPrep/generateMealPrepFiltersPrompt.txt b/src/main/resources/prompt/generateMealPrep/generateMealPrepFiltersPrompt.txt new file mode 100644 index 0000000..d8ea089 --- /dev/null +++ b/src/main/resources/prompt/generateMealPrep/generateMealPrepFiltersPrompt.txt @@ -0,0 +1,7 @@ +Para generar los meal preps con las instrucciones anteriores, respetar las siguientes condiciones si están presentes (si un valor no está o es null, ignorarlo): + +- Nivel de dificultad: {{COOK_LEVEL}} (puede ser Bajo, Medio o Alto) +- Tipos de comida (por ejemplo: desayuno, cena, snack, etc.): {{MEAL_TYPES}} +- Dieta especial (por ejemplo: vegana, sin gluten): {{DIET}} +- Cantidad exacta de meal preps a generar: {{QUANTITY}} + diff --git a/src/main/resources/prompt/generateMealPrep/generateMealPrepFromIngredientsHeaderPrompt.txt b/src/main/resources/prompt/generateMealPrep/generateMealPrepFromIngredientsHeaderPrompt.txt new file mode 100644 index 0000000..845a65d --- /dev/null +++ b/src/main/resources/prompt/generateMealPrep/generateMealPrepFromIngredientsHeaderPrompt.txt @@ -0,0 +1,80 @@ +CONTEXTO: + +Sos un chef profesional con mas de 10 años de experiencia y debes generar {{MAX_MEAL_PREPS}} meal preps con las recetas dadas. +Un **meal prep** es un plan de cocina organizado para preparar varias comidas en un solo día, optimizando tiempo e ingredientes. +Cada meal prep contiene varias recetas que se cocinan juntas y luego se dividen en porciones para consumir durante la semana. +No es una sola receta, sino un conjunto planificado de recetas. + +DATOS: + +Todas las recetas para generar los meal preps: + +{{RECIPES}} + +OBJETIVO: + +- Agrupa las recetas dadas y genera exactamente {{MAX_MEAL_PREPS}} meal preps con 3 recetas cada uno en formato JSON. +- Cada meal prep debe tener un nombre representativo y atractivo. +- En cada meal prep estimar el tiempo de preparacion y coccion de las tres recetas que incluye y agregarlo en estimated_cooking_time +- Agrupar los ingredientes de cada una de las tres recetas, calcular cantidad en conjunto y agregarla en el atributo ingredients como en el ejemplo +- No repetir ingredientes si están en más de una receta, sino agruparlos +- El unit debe ser OBLIGATORIAMENTE el tipo de unit que se encuentra en el mismo ingrediente en la receta, por ejemplo si leche es ml en las recetas, entonces representar en ml. +- En recipe_ids dame los IDs de las recetas con las cual preparaste el correspondiente meal prep. Los IDs corresponden a la lista de recetas que te pase. +- Tener un paso a paso general para cocinar todas las recetas en conjunto. Cada paso debe tener: + - Título del paso + - Numero de paso + - Descripción del paso + - Duración del paso (por ejemplo: "30 min" o "1 h") + +No incluir estos meal preps (Ignorar si esta vacio): {{NOT_INCLUDE}} + +Ejemplo de la estructura del JSON de respuesta: + +[ + { + "title": "Nombre del meal prep", + "estimated_cooking_time": "1 h 30 min", + "servings": 2, + "freeze": true, + "recipe_ids": [1, 2, 3], + "steps": [ + { + "title": "Preparar ingredientes", + "number": 1, + "description": "Cortar los vegetales, hervir arroz, batir huevos.", + "time": "30 min" + }, + { + "title": "Cocinar recetas", + "number": 1, + "description": "Saltear, hornear o hervir según cada preparación. Armar las porciones.", + "time": "1 hr" + } + ], + "ingredients": [ + { "name": "ingrediente1", "quantity": 200.0, "unit_id": 1, "optional": false }, + { "name": "ingrediente2", "quantity": 100.0, "unit_id": 1, "optional": false }, + { "name": "ingrediente3", "quantity": 1.0, "unit_id": 3, "optional": true } + ] + + } +] + +Condiciones: + +- ¿Apto para freezar?: {{FREEZE}} (sí o no; si es sí, sugerir pasos para almacenamiento y duración) +- Lista de units que se deben usar obligatoriamente: + +{{PARAMETRIC_UNITS}} + +Instrucciones CRITICAS: + +- Usar español argentino (ejemplo: papa, palta, choclo, etc.). +- Los ingredientes proporcionados deben aparecer en la lista de ingredientes de las recetas dadas +- El meal prep y sus steps deben ser lógicos combinando TODOS los ingredientes dados +- Los steps deben ser como minimo 4 +- Las instrucciones deben ir como texto plano, sin '\n' ni saltos de línea. +- Solo usar números decimales en "quantity", sin texto. +- Usar correctamente acentos y la letra ñ. +- No incluir ningún texto adicional ni explicación. +- Devuelve solo el array JSON sin ```json ni explicaciones ni texto adicional. \ No newline at end of file diff --git a/src/main/resources/prompt/generateRecipe.txt b/src/main/resources/prompt/generateRecipe.txt deleted file mode 100644 index 8d40f43..0000000 --- a/src/main/resources/prompt/generateRecipe.txt +++ /dev/null @@ -1,20 +0,0 @@ -Con estos ingredientes: %s - -"Genera recetas argentinas en formato JSON usando estos ingredientes. -Usa español argentino: papa (no patata), palta (no aguacate), choclo (no maíz). -Para las instrucciones usa texto plano sin \\n, que el frontend maneje los saltos de línea. -Incluye acentos correctos y ñ. Tiempo en formato '30 min' o '1 h 30 min'. -Devuelve solo el array JSON sin ```json ni explicaciones." - -[ - { - "id": "recipe-1", - "name": "Nombre de la receta", - "preparationTime": "25'", - "image": "https://ejemplo.com/imagen.jpg", - "subtitle": "Descripción breve y atractiva", - "description": "Descripción detallada del plato y su sabor", - "ingredients": " 200g ingrediente1, 100ml ingrediente2, 1 cucharada ingrediente3", - "instructions": "1. Paso detallado uno, 2. Paso detallado dos, 3. Paso detallado tres"} ] - -Usar ID únicos tipo string (recipe-1, recipe-2, etc.). No agregar texto adicional, solo el JSON. \ No newline at end of file diff --git a/src/main/resources/prompt/generateimages/generateImageFiltersPrompt.txt b/src/main/resources/prompt/generateimages/generateImageFiltersPrompt.txt new file mode 100644 index 0000000..5479970 --- /dev/null +++ b/src/main/resources/prompt/generateimages/generateImageFiltersPrompt.txt @@ -0,0 +1,8 @@ +Para generar las imágenes de recetas con las instrucciones antes dadas, utilizar las siguientes condiciones de estilo: +(Si el valor no esta o es null, ignorar la condicion) + +- Nivel de dificultad visual: {{COOK_LEVEL}} (simple=ingredientes básicos, intermedio=presentación cuidada, avanzado=presentación gourmet) +- Tipos de comida: {{FOOD_TYPES}} (adaptar el estilo visual según el tipo de cocina) +- Dieta: {{DIET}} (mostrar ingredientes apropiados para la dieta específica) +- Porciones: {{QUANTITY}} (ajustar la cantidad visual de comida en el plato) +- Tiempo de cocción: {{COOK_TIME}} (reflejar el nivel de cocción apropiado) \ No newline at end of file diff --git a/src/main/resources/prompt/generateimages/generateRecipeImagePrompt.txt b/src/main/resources/prompt/generateimages/generateRecipeImagePrompt.txt new file mode 100644 index 0000000..a562a7c --- /dev/null +++ b/src/main/resources/prompt/generateimages/generateRecipeImagePrompt.txt @@ -0,0 +1,28 @@ +Generá una imagen fotorealista del siguiente plato terminado: + +Nombre de la receta: {{RECIPE_NAME}} +Ingredientes principales (solo como guía para la generación): {{MAIN_INGREDIENTS}} + +CARACTERISTICAS DE LA IMAGEN: + +- Estilo: comida casera argentina. +- Imagen tomada con celular. +- Iluminación natural (ej. luz de ventana). +- Fondo simple: mesa de madera, ambiente cálido. +- Presentación final del plato, servida y lista para comer. +- Apariencia realista, casera, apetecible. +- Texturas y colores vibrantes, sin filtros artificiales. + +INSTRUCCIONES ESTRICTAS – NO mostrar: + +- Ingredientes sueltos o en preparación. +- Texto de cualquier tipo (letras, números, frases). +- Etiquetas, carteles, stickers o nombres visibles. +- Recetarios, papel, instrucciones impresas. +- Utensilios sucios o manos humanas. + +DEBE MOSTRARSE: + +- Solo la presentación final del plato servido, tal como se ve al terminar de cocinar. +- Debe parecer una foto realista y casera del plato terminado. +- La receta debe ser representada fielmente usando el nombre y los ingredientes como guía interna (pero sin mostrarlos sueltos en la imagen). \ No newline at end of file diff --git a/src/main/resources/prompt/generateimages/generateStepImagePrompt.txt b/src/main/resources/prompt/generateimages/generateStepImagePrompt.txt new file mode 100644 index 0000000..c153ede --- /dev/null +++ b/src/main/resources/prompt/generateimages/generateStepImagePrompt.txt @@ -0,0 +1,23 @@ +Fotografía realista del proceso de cocina: {{STEP_INSTRUCTION}} + +CREAR IMAGEN SIN NINGÚN TEXTO VISIBLE - SOLO MANOS COCINANDO + +PROHIBIDO COMPLETAMENTE: +- Cualquier letra, número, palabra o símbolo escrito +- Etiquetas en frascos, envases o productos +- Carteles, letreros, stickers o calcomanías +- Libros de cocina, recetas impresas o papeles con texto +- Relojes digitales con números +- Envases comerciales con marcas +- Instrucciones escritas de cualquier tipo +- Títulos, subtítulos o leyendas +- Cualquier elemento con escritura + +MOSTRAR ÚNICAMENTE: +- Manos en primer plano preparando comida +- Ingredientes frescos sin envases etiquetados +- Utensilios de cocina básicos (cuchillos, tablas, sartenes) +- Proceso de cocción en acción + +Estilo: Fotografía casera, cocina doméstica argentina, luz natural. +IMAGEN 100% SIN TEXTO DE NINGÚN TIPO. \ No newline at end of file diff --git a/src/main/resources/prompt/generaterecipes/generateRecipeFromIngredientsHeaderPrompt.txt b/src/main/resources/prompt/generaterecipes/generateRecipeFromIngredientsHeaderPrompt.txt new file mode 100644 index 0000000..65f6cd6 --- /dev/null +++ b/src/main/resources/prompt/generaterecipes/generateRecipeFromIngredientsHeaderPrompt.txt @@ -0,0 +1,88 @@ +Generá {{MAX_RECIPES}} recetas argentinas en formato JSON, usando TODOS los ingredientes proporcionados a continuación, incluyendo sus cantidades y unidades de medida: + +{{INGREDIENTS}} + +REGLAS OBLIGATORIAS: + +1. Cada receta DEBE incluir TODOS los ingredientes dados como ingredientes PRINCIPALES. Se pueden agregar más, pero estos deben estar presentes y ser centrales en la preparación. +2. No incluyas recetas que usen solo algunos de los ingredientes proporcionados. +3. Las recetas deben ser lógicas y plausibles dentro de la cocina argentina. +4. Usá español rioplatense: decí "papa" en lugar de "patata", "palta" en lugar de "aguacate", "choclo" en lugar de "maíz", etc. +5. En el campo "steps", defini cada paso que lleva la receta para lograr la coccion completa del plato. En number poner el numero de orden del paso, titulo del paso y la descripcion completa de cada uno de los pasos. MINIMO 3/4. +6. En el campo "quantity" de cada ingrediente, usá SIEMPRE números decimales (ej: 1.00, 250.00, 0.50). Nunca uses palabras como "pizca", "a gusto", etc. +7. En "unit", usá EXACTAMENTE las unidades provistas en el JSON de units (id y symbol correctos). +8. En "preparation_time", "cook_level", "diet", "meal_types", "allergies" y "dietary_needs", usá EXACTAMENTE los valores provistos en los JSON correspondientes. No inventes ni modifiques descripciones, ids o estructuras. + - Si una receta no pertenece a ninguna dieta, no incluyas el campo "diet". + - Si no tiene alergias o necesidades alimenticias, no incluyas esos campos. +9. En el campo "image", usá exactamente esta forma: + "/api/images/recipes/" + nombre_de_la_receta_sin_espacios_ni_acentos_en_minusculas + "_main.jpg" + Ejemplo: Si el nombre es "Milanesa de carne con arroz", entonces: + "image": "/api/images/recipes/milanesadecarneconarroz_main.jpg" +10. Devolvé ÚNICAMENTE el array JSON. No incluyas texto explicativo, comentarios, encabezados ni marcas como ```json. + +NO crear ni incluir las recetas de la siguiente lista de nombres: +(Si la receta esta en la lista, crear otra diferente. Si la lista es vacia o null, ignorar este paso) + +No incluir estas recetas (Ignorar si esta vacio): {{NOT_INCLUDE}} + +ESTRUCTURA DEL JSON (respetar exactamente): + +[ + { + "name": "Nombre de la receta", + "subtitle": "Descripción breve y atractiva", + "description": "Descripción detallada del plato y su sabor", + "steps": [ + { + "number": 1, + "title": "Precalentar horno", //OBLIGATORIO + "description": "Calentar el horno a 200 grados" //OBLIGATORIO + }, + { + "number": 2, + "title": "Mezclar los ingredientes", + "description": "Mezclar el ingrediente a y el b para luego revolver" + } + ], + "preparation_time": { "id": 1, "description": "20 min" }, + "cook_level": { + "id": 1, + "description": "bajo" + }, + "diet": { + "id": 1, + "description": "Vegetariano" + }, + "meal_types": [ + { "id": "2", "description": "Almuerzo" }, + { "id": "4", "description": "Cena" } + ], + "allergies": [ + { "id": "1", "description": "alergia1" }, + { "id": "2", "description": "alergia2" } + ], + "dietary_needs": [ + { "id": "1", "description": "dietary need 1" } + ], + "ingredients": [ + { + "name": "ingrediente1", + "quantity": 200.00, + "unit_id": 2, + "optional": false + }, + { + "name": "ingrediente2", + "quantity": 100.00, + "unit_id": 2, + "optional": false + }, + { + "name": "ingrediente3", + "quantity": 1.50, + "unit_id": 3, + "optional": true + } + ] + } +] diff --git a/src/main/resources/prompt/generaterecipes/generateRecipeFromNameHeaderPrompt.txt b/src/main/resources/prompt/generaterecipes/generateRecipeFromNameHeaderPrompt.txt new file mode 100644 index 0000000..8f09e57 --- /dev/null +++ b/src/main/resources/prompt/generaterecipes/generateRecipeFromNameHeaderPrompt.txt @@ -0,0 +1,86 @@ +Generá 1 (UNA) receta argentina en formato JSON, usando EXACTAMENTE el siguiente nombre: + +{{RECIPE_NAME}} + +REGLAS OBLIGATORIAS: + +1. Cada receta DEBE incluir TODOS los ingredientes dados como ingredientes PRINCIPALES. Se pueden agregar más, pero estos deben estar presentes y ser centrales en la preparación. +2. No incluyas recetas que usen solo algunos de los ingredientes proporcionados. +3. Las recetas deben ser lógicas y plausibles dentro de la cocina argentina. +4. Usá español rioplatense: decí "papa" en lugar de "patata", "palta" en lugar de "aguacate", "choclo" en lugar de "maíz", etc. +5. En el campo "steps", defini cada paso que lleva la receta para lograr la coccion completa del plato. En number poner el numero de orden del paso, titulo del paso y la descripcion completa de cada uno de los pasos. +6. En el campo "quantity" de cada ingrediente, usá SIEMPRE números decimales (ej: 1.00, 250.00, 0.50). Nunca uses palabras como "pizca", "a gusto", etc. +7. En "unit", usá EXACTAMENTE las unidades provistas en el JSON de units (id y symbol correctos). +8. En "preparation_time", "cook_level", "diet", "meal_types", "allergies" y "dietary_needs", usá EXACTAMENTE los valores provistos en los JSON correspondientes. No inventes ni modifiques descripciones, ids o estructuras. + - Si una receta no pertenece a ninguna dieta, no incluyas el campo "diet". + - Si no tiene alergias o necesidades alimenticias, no incluyas esos campos. +9. En el campo "image", usá exactamente esta forma: + "/api/images/recipes/" + nombre_de_la_receta_sin_espacios_ni_acentos_en_minusculas + "_main.jpg" + Ejemplo: Si el nombre es "Milanesa de carne con arroz", entonces: + "image": "/api/images/recipes/milanesadecarneconarroz_main.jpg" +10. Devolvé ÚNICAMENTE el array JSON. No incluyas texto explicativo, comentarios, encabezados ni marcas como ```json. + +NO crear ni incluir las recetas de la siguiente lista de nombres: +(Si la receta esta en la lista, crear otra diferente. Si la lista es vacia o null, ignorar este paso) + +ESTRUCTURA DEL JSON (respetar exactamente): + +[ + { + "name": "Nombre de la receta", + "subtitle": "Descripción breve y atractiva", + "description": "Descripción detallada del plato y su sabor", + "steps": [ + { + "number": 1, + "title": "Precalentar horno", //OBLIGATORIO + "description": "Calentar el horno a 200 grados" //OBLIGATORIO + }, + { + "number": 2, + "title": "Mezclar los ingredientes", + "description": "Mezclar el ingrediente a y el b para luego revolver" + } + ], + "preparation_time": { "id": 1, "description": "20 min" }, + "cook_level": { + "id": 1, + "description": "bajo" + }, + "diet": { + "id": 1, + "description": "Vegetariano" + }, + "meal_types": [ + { "id": "2", "description": "Almuerzo" }, + { "id": "4", "description": "Cena" } + ], + "allergies": [ + { "id": "1", "description": "alergia1" }, + { "id": "2", "description": "alergia2" } + ], + "dietary_needs": [ + { "id": "1", "description": "dietary need 1" } + ], + "ingredients": [ + { + "name": "ingrediente1", + "quantity": 200.00, + "unit": { "id": "1", "symbol": "gr" }, + "optional": false + }, + { + "name": "ingrediente2", + "quantity": 100.00, + "unit": { "id": "2", "symbol": "ml" }, + "optional": false + }, + { + "name": "ingrediente3", + "quantity": 1.50, + "unit": { "id": "3", "symbol": "cd" }, + "optional": true + } + ] + } +] diff --git a/src/main/resources/prompt/generaterecipes/generateRecipeParametricDataPrompt.txt b/src/main/resources/prompt/generaterecipes/generateRecipeParametricDataPrompt.txt new file mode 100644 index 0000000..3490568 --- /dev/null +++ b/src/main/resources/prompt/generaterecipes/generateRecipeParametricDataPrompt.txt @@ -0,0 +1,9 @@ +Datos parametricos para llenar el json: + +UNITS: {{PARAMETRIC_UNITS}} +PREPARATION_TIMES: {{PARAMETRIC_PREPARATION_TIMES}} +COOK_LEVELS: {{PARAMETRIC_COOK_LEVELS}} +DIETS: {{PARAMETRIC_DIETS}} +MEAL_TYPES: {{PARAMETRIC_MEAL_TYPES}} +ALLERGIES: {{PARAMETRIC_ALLERGIES}} +DIETARY_NEEDS: {{PARAMETRIC_DIETARY_NEEDS}} diff --git a/src/main/resources/prompt/generaterecipes/generateRecipesFiltersPrompt.txt b/src/main/resources/prompt/generaterecipes/generateRecipesFiltersPrompt.txt new file mode 100644 index 0000000..c2a1d4f --- /dev/null +++ b/src/main/resources/prompt/generaterecipes/generateRecipesFiltersPrompt.txt @@ -0,0 +1,10 @@ +Para generar las recetas con las instrucciones antes dadas, utilizar OBLIGATORIAMENTE las siguientes condiciones: +El ID pertenece a su correspondiente JSON de informacion parametrica que te pase: +(Si el valor es vacio, array vacio o es null, ignorar la condicion) + +- PREPARATION_TIME: OBLIGATORIAMENTE el tiempo de preparación que lleva la receta completa debe ser de ESPECIFICAMENTE o MAS de: {{PREPARATION_TIME}} +- COOK_LEVEL: Nivel de dificultad de la receta debe ser: {{COOK_LEVEL}} +- DIET: La dieta de la receta debe ser para la siguiente dieta: {{DIET}} +- MEAL_TYPES: Debe ser para estos tipos de receta (Como desayuno, almuerzo, etc.): {{MEAL_TYPES}} +- ALLERGIES: No debe contener ingredientes que pueda tener este tipo de alergias: {{ALLERGIES}} +- DIETARY_NEEDS: Debe estar considerada la siguiente necesidad alimentaria: {{DIETARY_NEEDS}} \ No newline at end of file diff --git a/src/main/resources/prompt/recognizeIngredientsFromAudioPrompt.txt b/src/main/resources/prompt/recognizeIngredientsFromAudioPrompt.txt new file mode 100644 index 0000000..b97317c --- /dev/null +++ b/src/main/resources/prompt/recognizeIngredientsFromAudioPrompt.txt @@ -0,0 +1,53 @@ +Eres un asistente experto en cocina argentina, especializado en identificar ingredientes a partir de audio. + +Objetivo: +Analizar el audio en español argentino y devolver una lista de ingredientes mencionados. + +Instrucciones: +1. Escucha atentamente el audio. +2. Identifica solo ingredientes (alimentos, especias, condimentos). +3. Ignora cualquier cosa que no sea ingrediente (utensilios, acciones, cantidades sin relación con ingredientes, etc.). +4. Usa los nombres comunes en Argentina, por ejemplo: + - palta (no aguacate) + - choclo (no maíz) + - porotos (no frijoles) +5. Devuelve cada ingrediente como un objeto independiente. +6. Si no hay ingredientes claros, responde con una lista vacía. + +Formato de respuesta (JSON): +- Solo un array de objetos JSON. +- Todos los nombres de ingredientes en minúsculas. +- Sin explicaciones. +- Si se menciona una cantidad, inclúyela con los campos quantity y unit. + - unit puede ser "gr", "ml" o "unidad" +- Si no hay cantidad, omite esos campos. + +Ejemplo de estructura: + +[ + { "name": "ingrediente1", "quantity": 2, "unit": "unidad" }, + { "name": "ingrediente2", "quantity": 300, "unit": "gr" }, + { "name": "ingrediente3" } +] + +Ejemplos: + +Entrada de audio: +"Necesito 3 tomates, un kilo de cebolla y un poco de ajo para la salsa." + +Respuesta esperada: +[ + { "name": "tomate", "quantity": 3, "unit": "unidad" }, + { "name": "cebolla", "quantity": 1, "unit": "kg" }, + { "name": "ajo" } +] + +Entrada de audio: +"Voy a cocinar con aceite en la sartén." + +Respuesta esperada: +[ + { "name": "aceite" } +] + +Ahora analiza el siguiente audio y devuelve el JSON de ingredientes: \ No newline at end of file diff --git a/src/main/resources/prompt/recognizeIngredientsFromImagePrompt.txt b/src/main/resources/prompt/recognizeIngredientsFromImagePrompt.txt new file mode 100644 index 0000000..9c4bc98 --- /dev/null +++ b/src/main/resources/prompt/recognizeIngredientsFromImagePrompt.txt @@ -0,0 +1,44 @@ +Analiza esta imagen y extrae SOLO los nombres de ingredientes visibles en español argentino. + +INSTRUCCIONES: +1. Revisa atentamente la imagen +2. Identifica ÚNICAMENTE ingredientes de cocina (alimentos, especias, condimentos), no alimentos preparados +3. Ignora elementos que NO sean ingredientes (utensilios, electrodomesticos, muebles, etc.) +4. Usa los nombres más comunes en Argentina (ej: "palta" no "aguacate", "choclo" no "maíz") +5. Devuelve cada ingrediente en una línea separada +6. Si no encuentras ingredientes claros, devuelve una lista vacía + +FORMATO DE RESPUESTA: +- Devuelve un array JSON con un ingrediente por objeto +- Determina la cantidad posible en Double y la unidad de medicion por simbolo (gr por gramo, ml por mililitro, etc) +- Si el elemento no tiene cantidad posible, determina la unidad como "unidad". Si no puedes considerar la cantidad, no lo agregues. +- En minúsculas +- La estructura del objeto debe ser asi: + +[ + { "name": "ingrediente1", "quantity": "500", "unit": { "id": 1, "description": "gr" } }, + { "name": "ingrediente2", "quantity": "1", "unit": { "id": 2, "description": "ml" } }, + { "name": "ingrediente3" } +] + +EJEMPLOS: +Si ves una banana, una botella de salsa de tomate por la mitad y 3 huevos, responde: + +[ + { "name": "banana", "quantity": 1, "unit": { "id": 1, "description": "gr" } }, + { "name": "salsa de tomate", "quantity": 500, "unit": { "id": 1, "description": "gr" } }, + { "name": "huevo", "quantity": 3, "unit": { "id": 1, "description": "gr" } }, +] + + +Si ves una botella de leche pero no sabes la cantidad exacta, entonces responde: + +[ + { "name": "leche" } +] + +DATOS PARAMETRICOS: + +UNITS POSIBLES (Usa una de las unidades que estan en el siguiente JSON): {{PARAMETRIC_UNITS}} + +No des explicaciones, solo devuelve el JSON correspondiente. \ No newline at end of file diff --git a/src/main/resources/sql/ddl/01_user_tables.sql b/src/main/resources/sql/ddl/01_user_tables.sql new file mode 100644 index 0000000..a9fc466 --- /dev/null +++ b/src/main/resources/sql/ddl/01_user_tables.sql @@ -0,0 +1,119 @@ +CREATE TABLE allergies +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE cook_levels +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE diets +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE plans +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + `configuration_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_plan_configuration_id` FOREIGN KEY (`configuration_id`) REFERENCES `plans` (`id`) +); + +CREATE TABLE plan_configuration +( + `id` int NOT NULL AUTO_INCREMENT, + `title` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `quantity` int DEFAULT NULL, + `price` DECIMAL(10,2) DEFAULT NULL, + `currency` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE payment_status +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE dietary_needs +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE users +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `password` varchar(255) DEFAULT NULL, + `active` bit(1) DEFAULT NULL, + `plan_id` int DEFAULT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_user_plan_id` FOREIGN KEY (`plan_id`) REFERENCES `plans` (`id`) +); + +CREATE TABLE user_allergies +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint DEFAULT NULL, + `allergy_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_user_allergies_allergy_id` FOREIGN KEY (`allergy_id`) REFERENCES `allergies` (`id`), + CONSTRAINT `FK_user_allergies_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); + +CREATE TABLE user_dietary_needs +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint DEFAULT NULL, + `dietary_need_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_user_dietary_needs_dietary_need_id` FOREIGN KEY (`dietary_need_id`) REFERENCES `dietary_needs` (`id`), + CONSTRAINT `FK_user_dietary_needs_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); + +CREATE TABLE user_preferences +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `diet_id` int DEFAULT NULL, + `cook_level_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_user_preferences_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `FK_user_preferences_diet_id` FOREIGN KEY (`diet_id`) REFERENCES `diets` (`id`), + CONSTRAINT `FK_user_preferences_cook_level_id` FOREIGN KEY (`cook_level_id`) REFERENCES `cook_levels` (`id`) +); + +CREATE TABLE user_payments +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `to_plan_id` int DEFAULT NULL, + `status_id` int DEFAULT NULL, + `external_id` varchar(255) DEFAULT NULL, + `external_reference` varchar(255) DEFAULT NULL, + `checkout_url` varchar(255) DEFAULT NULL, + `created_at` datetime(6) DEFAULT NULL, + `updated_at` datetime(6) DEFAULT NULL, + `deleted_at` datetime(6) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_user_payments_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `FK_user_payments_plan_id` FOREIGN KEY (`to_plan_id`) REFERENCES `plans` (`id`), + CONSTRAINT `FK_user_payments_status_id` FOREIGN KEY (`status_id`) REFERENCES `payment_status` (`id`) +); diff --git a/src/main/resources/sql/ddl/02_recipes_tables.sql b/src/main/resources/sql/ddl/02_recipes_tables.sql new file mode 100644 index 0000000..7d428d2 --- /dev/null +++ b/src/main/resources/sql/ddl/02_recipes_tables.sql @@ -0,0 +1,118 @@ +CREATE TABLE `categories` +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `meal_types` +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `preparation_times` +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `units` +( + `id` int NOT NULL AUTO_INCREMENT, + `description` varchar(100) DEFAULT NULL, + `symbol` varchar(10) NOT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `ingredients` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(150) DEFAULT NULL, + `category_id` int DEFAULT NULL, + `unit_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_ingredient_category_id` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`), + CONSTRAINT `FK_ingredient_unit_id` FOREIGN KEY (`unit_id`) REFERENCES `units` (`id`) +); + +CREATE TABLE `recipes` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `subtitle` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `image_url` varchar(255) DEFAULT NULL, + `cook_level_id` int DEFAULT NULL, + `diet_id` int DEFAULT NULL, + `preparation_time_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_recipe_cook_level_id` (`cook_level_id`), + KEY `FK_recipe_diet_id` (`diet_id`), + KEY `FK_recipe_preparation_time_id` (`preparation_time_id`), + CONSTRAINT `FK_recipe_cook_level_id` FOREIGN KEY (`cook_level_id`) REFERENCES `cook_levels` (`id`), + CONSTRAINT `FK_recipe_diet_id` FOREIGN KEY (`diet_id`) REFERENCES `diets` (`id`), + CONSTRAINT `FK_recipe_preparation_time_id` FOREIGN KEY (`preparation_time_id`) REFERENCES `preparation_times` (`id`) +); + +CREATE TABLE `recipe_ingredients` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `recipe_id` bigint DEFAULT NULL, + `ingredient_id` bigint DEFAULT NULL, + `quantity` double DEFAULT NULL, + `optional` bit(1) DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_recipe_ingredients_ingredient_id` FOREIGN KEY (`ingredient_id`) REFERENCES `ingredients` (`id`), + CONSTRAINT `FK_recipe_ingredients_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); + +CREATE TABLE `recipe_allergies` +( + `allergy_id` int NOT NULL, + `recipe_id` bigint NOT NULL, + CONSTRAINT `FK_recipe_allergies_allergy_id` FOREIGN KEY (`allergy_id`) REFERENCES `allergies` (`id`), + CONSTRAINT `FK_recipe_allergies_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); + +CREATE TABLE `recipe_dietary_needs` +( + `dietary_need_id` int NOT NULL, + `recipe_id` bigint NOT NULL, + CONSTRAINT `FK_recipe_dietary_needs_dietary_need_id` FOREIGN KEY (`dietary_need_id`) REFERENCES `dietary_needs` (`id`), + CONSTRAINT `FK_recipe_dietary_needs_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); + +CREATE TABLE `recipe_meal_types` +( + `meal_type_id` int NOT NULL, + `recipe_id` bigint NOT NULL, + CONSTRAINT `FK_recipe_meal_types_meal_type_id` FOREIGN KEY (`meal_type_id`) REFERENCES `meal_types` (`id`), + CONSTRAINT `FK_recipe_meal_types_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); + +CREATE TABLE `recipe_steps` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `recipe_id` bigint NOT NULL, + `number` int, + `title` varchar(100), + `description` text, + `image_name` varchar(100), + PRIMARY KEY (`id`), + CONSTRAINT `FK_recipe_steps__recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); + +CREATE TABLE `user_recipes` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint DEFAULT NULL, + `recipe_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_user_recipes_user_id` (`user_id`), + KEY `FK_user_recipes_recipe_id` (`recipe_id`), + CONSTRAINT `FK_user_recipes_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`), + CONSTRAINT `FK_user_recipes_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); diff --git a/src/main/resources/sql/ddl/03_meal_preps_tables.sql b/src/main/resources/sql/ddl/03_meal_preps_tables.sql new file mode 100644 index 0000000..5d632cc --- /dev/null +++ b/src/main/resources/sql/ddl/03_meal_preps_tables.sql @@ -0,0 +1,58 @@ +CREATE TABLE `meal_preps` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) DEFAULT NULL, + `estimated_cooking_time` varchar(255) DEFAULT NULL, + `freeze` bit(1) DEFAULT NULL, + `servings` int DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `meal_prep_steps` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `title` varchar(255) DEFAULT NULL, + `number` int DEFAULT NULL, + `description` text, + `time` varchar(255) DEFAULT NULL, + `image_name` varchar(255) DEFAULT NULL, + `meal_prep_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_meal_prep_id` (`meal_prep_id`), + CONSTRAINT `FK_meal_prep_steps_meal_prep_id` FOREIGN KEY (`meal_prep_id`) REFERENCES `meal_preps` (`id`) +); + +CREATE TABLE `meal_prep_ingredients` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `meal_prep_id` bigint DEFAULT NULL, + `ingredient_id` bigint DEFAULT NULL, + `quantity` double DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_ingredient_id` (`ingredient_id`), + KEY `FK_meal_prep_id` (`meal_prep_id`), + CONSTRAINT `FK_meal_prep_ingredients_meal_prep_id` FOREIGN KEY (`meal_prep_id`) REFERENCES `meal_preps` (`id`), + CONSTRAINT `FK_meal_prep_ingredients_ingredient_id` FOREIGN KEY (`ingredient_id`) REFERENCES `ingredients` (`id`) +); + +CREATE TABLE `meal_prep_recipes` +( + `meal_prep_id` bigint NOT NULL, + `recipe_id` bigint NOT NULL, + KEY `FK_meal_prep_recipes_recipe_id` (`recipe_id`), + KEY `FK_meal_prep_recipe_meal_prep_id` (`meal_prep_id`), + CONSTRAINT `FK_meal_prep_recipes_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`), + CONSTRAINT `FK_meal_prep_recipe_meal_prep_id` FOREIGN KEY (`meal_prep_id`) REFERENCES `meal_preps` (`id`) +); + +CREATE TABLE `user_meal_preps` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_id` bigint NOT NULL, + `meal_prep_id` bigint NOT NULL, + PRIMARY KEY (`id`), + KEY `FK_user_meal_prep_meal_prep_id` (`meal_prep_id`), + KEY `FK_user_meal_prep_user_id` (`user_id`), + CONSTRAINT `FK_user_meal_prep_user_id` FOREIGN KEY (`meal_prep_id`) REFERENCES `meal_preps` (`id`), + CONSTRAINT `FK_user_meal_prep_meal_prep_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); \ No newline at end of file diff --git a/src/main/resources/sql/ddl/04_calendars.sql b/src/main/resources/sql/ddl/04_calendars.sql new file mode 100644 index 0000000..72fbfdd --- /dev/null +++ b/src/main/resources/sql/ddl/04_calendars.sql @@ -0,0 +1,24 @@ +CREATE TABLE `user_calendars` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `planned_date` date DEFAULT NULL, + `user_id` bigint DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_user_calendars_user_id` (`user_id`), + CONSTRAINT `FK_user_calendars_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); + +CREATE TABLE `user_calendar_recipes` +( + `id` bigint NOT NULL AUTO_INCREMENT, + `user_calendar_id` bigint DEFAULT NULL, + `recipe_id` bigint DEFAULT NULL, + `meal_type_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_user_calendar_recipes_calendar_id` (`user_calendar_id`), + KEY `FK_user_calendar_meal_type_id` (`meal_type_id`), + KEY `FK_user_calendar_recipe_id` (`recipe_id`), + CONSTRAINT `FK_user_calendar_recipes_calendar_id` FOREIGN KEY (`user_calendar_id`) REFERENCES `user_calendars` (`id`), + CONSTRAINT `FK_user_calendar_meal_type_id` FOREIGN KEY (`meal_type_id`) REFERENCES `meal_types` (`id`), + CONSTRAINT `FK_user_calendar_recipe_id` FOREIGN KEY (`recipe_id`) REFERENCES `recipes` (`id`) +); diff --git a/src/main/resources/sql/ddl/05_inserts.sql b/src/main/resources/sql/ddl/05_inserts.sql new file mode 100644 index 0000000..46c4322 --- /dev/null +++ b/src/main/resources/sql/ddl/05_inserts.sql @@ -0,0 +1,100 @@ +INSERT INTO plans (id, description) +VALUES (1, 'Free'), + (2, 'Pro'); + +INSERT INTO plan_configuration (id, title, description, quantity, price, currency) +VALUES (1, 'Cuoco Pro - Plan Premium', 'Actualiza a Pro: Recetas ilimitadas, filtros avanzados, meal preps y mucho más', 1, 500.00, 'ARS'); + +INSERT INTO payment_status (id, description) +VALUES (1, 'pending'), + (2, 'approved'), + (3, 'in_process'), + (4, 'rejected'), + (5, 'cancelled'), + (6, 'refunded'), + (7, 'charged_back'); + +INSERT INTO cook_levels (id, description) +VALUES (1, 'Bajo'), + (2, 'Medio'), + (3, 'Alto'); + +INSERT INTO diets (id, description) +VALUES (1, 'Omnivoro'), + (2, 'Vegetariano'), + (3, 'Vegano'); + +INSERT INTO dietary_needs (id, description) +VALUES (1, 'Sin gluten'), + (2, 'Sin lactosa'), + (3, 'Alta en proteinas'); + +INSERT INTO meal_types (id, description) +VALUES (1, 'Desayuno'), + (2, 'Almuerzo'), + (3, 'Merienda'), + (4, 'Cena'), + (5, 'Postre'), + (6, 'Snack'); + +INSERT INTO preparation_times (id, description) +VALUES (1, '20 min'), + (2, '40 min'), + (3, '1 h'), + (4, '1:30 h'); + +INSERT INTO allergies (id, description) +VALUES (1, 'Leche'), + (2, 'Frutos secos'), + (3, 'Soja'), + (4, 'Crustáceos'), + (5, 'Huevo'), + (6, 'Pescados'), + (7, 'Cereales'), + (8, 'Maní'); + +INSERT INTO categories (id, description) +VALUES (1, 'Verduras'), + (2, 'Frutas'), + (3, 'Carnes'), + (4, 'Pescados y mariscos'), + (5, 'Lácteos'), + (6, 'Huevos'), + (7, 'Cereales y granos'), + (8, 'Legumbres'), + (9, 'Aceites y grasas'), + (10, 'Especias y condimentos'), + (11, 'Hierbas frescas'), + (12, 'Bebidas'), + (13, 'Panadería y pastelería'), + (14, 'Frutos secos y semillas'), + (15, 'Congelados'), + (16, 'Alimentos en conserva'), + (17, 'Salsas y aderezos'), + (18, 'Snacks y golosinas'), + (19, 'Productos veganos'), + (20, 'Otros'); + +INSERT INTO units (id, description, symbol) +VALUES (1, 'Mililitro', 'ml'), + (2, 'Gramo', 'gr'), + (3, 'Kilogramo', 'kg'), + (4, 'Litro', 'l'), + (5, 'Cucharada', 'cda'), + (6, 'Cucharadita', 'cdta'), + (7, 'Unidad', 'ud'), + (8, 'Taza', 'tz'), + (9, 'Pizca', 'pizca'), + (10, 'Diente', 'diente'), + (11, 'Lata', 'lata'), + (12, 'Botella', 'botella'), + (13, 'Sobre', 'sobre'), + (14, 'Rodaja', 'rodaja'), + (15, 'Rebanada', 'rebanada'), + (16, 'Puñado', 'puñado'), + (17, 'Onza', 'oz'), + (18, 'Libra', 'lb'), + (19, 'Miligramo', 'mg'), + (20, 'Centilitro', 'cl'), + (21, 'Copa', 'copa'), + (22, 'Cucharón', 'cucharon'); \ No newline at end of file diff --git a/src/main/resources/sql/getRecipeIdsByFilters.sql b/src/main/resources/sql/getRecipeIdsByFilters.sql new file mode 100644 index 0000000..fe8f5dc --- /dev/null +++ b/src/main/resources/sql/getRecipeIdsByFilters.sql @@ -0,0 +1,25 @@ +SELECT r.id +FROM recipes r + JOIN recipe_ingredients ri ON ri.recipe_id = r.id + JOIN ingredients i ON i.id = ri.ingredient_id + LEFT JOIN recipe_meal_types rmt ON rmt.recipe_id = r.id +WHERE LOWER(i.name) IN (:INGREDIENT_NAMES) + AND (:NOT_INCLUDE_IDS_IS_EMPTY OR r.id NOT IN (:NOT_INCLUDE_IDS)) + AND (:PREPARATION_TIME_ID IS NULL OR r.preparation_time_id = :PREPARATION_TIME_ID) + AND (:COOK_LEVEL_ID IS NULL OR r.cook_level_id = :COOK_LEVEL_ID) + AND (:DIET_ID IS NULL OR r.diet_id = :DIET_ID) + AND (:MEAL_TYPES_IDS_IS_EMPTY OR rmt.meal_type_id IN (:MEAL_TYPES_IDS)) + AND (:DIETARY_NEEDS_IDS_IS_EMPTY OR ( + SELECT COUNT(DISTINCT rdn.dietary_need_id) + FROM recipe_dietary_needs rdn + WHERE rdn.recipe_id = r.id + AND rdn.dietary_need_id IN (:DIETARY_NEEDS_IDS) + ) = :DIETARY_NEEDS_COUNT) + AND (:ALLERGY_IDS_IS_EMPTY OR NOT EXISTS ( + SELECT 1 FROM recipe_allergies ra + WHERE ra.recipe_id = r.id + AND ra.allergy_id IN (:ALLERGY_IDS)) + ) +GROUP BY r.id +HAVING COUNT(DISTINCT LOWER(i.name)) = :INGREDIENT_COUNT +LIMIT :RESULT_SIZE; diff --git a/src/main/resources/sql/user_creation.sql b/src/main/resources/sql/user_creation.sql deleted file mode 100644 index 637af61..0000000 --- a/src/main/resources/sql/user_creation.sql +++ /dev/null @@ -1,97 +0,0 @@ -CREATE TABLE allergy ( - `id` int NOT NULL AUTO_INCREMENT, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE cook_level ( - `id` int NOT NULL AUTO_INCREMENT, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE diet ( - `id` int NOT NULL AUTO_INCREMENT, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE plan ( - `id` int NOT NULL AUTO_INCREMENT, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE dietary_need ( - `id` int NOT NULL AUTO_INCREMENT, - `description` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE user ( - `id` bigint NOT NULL AUTO_INCREMENT, - `name` varchar(255) DEFAULT NULL, - `email` varchar(255) DEFAULT NULL, - `password` varchar(255) DEFAULT NULL, - `active` bit(1) DEFAULT NULL, - `plan_id` int DEFAULT NULL, - `created_at` datetime(6) DEFAULT NULL, - `updated_at` datetime(6) DEFAULT NULL, - `deleted_at` datetime(6) DEFAULT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `FK_user_plan_id` FOREIGN KEY (`plan_id`) REFERENCES `plan` (`id`) -); - -CREATE TABLE user_preferences ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint NOT NULL, - `diet_id` int DEFAULT NULL, - `cook_level_id` int DEFAULT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `FK_user_preference_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), - CONSTRAINT `FK_user_preference_diet_id` FOREIGN KEY (`diet_id`) REFERENCES `diet` (`id`), - CONSTRAINT `FK_user_preference_cook_level_id` FOREIGN KEY (`cook_level_id`) REFERENCES `cook_level` (`id`) -); - -CREATE TABLE user_allergies ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint DEFAULT NULL, - `allergy_id` int DEFAULT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `FK_user_allergies_allergy_id` FOREIGN KEY (`allergy_id`) REFERENCES `allergy` (`id`), - CONSTRAINT `FK_user_allergies_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -); - -CREATE TABLE user_dietary_needs ( - `id` bigint NOT NULL AUTO_INCREMENT, - `user_id` bigint DEFAULT NULL, - `dietary_need_id` int DEFAULT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `FK_user_dietary_needs_dietary_need_id` FOREIGN KEY (`dietary_need_id`) REFERENCES `dietary_need` (`id`), - CONSTRAINT `FK_user_dietary_needs_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -); - -INSERT INTO plan (id, description) -VALUES (1, 'Free'), (2, 'Pro'); - -INSERT INTO cook_level (id, description) -VALUES (1, 'Bajo'), (2, 'Medio'), (3, 'Alto'); - -INSERT INTO diet (id, description) -VALUES (1, 'Omnivoro'), (2, 'Vegetariano'), (3, 'Vegano'), (4, 'Otro'); - -INSERT INTO dietary_need (id, description) -VALUES (1, 'Sin gluten'), (2, 'Sin lactosa'), (3, 'Alta en proteinas'), (4, 'Ninguna en particular'); - -INSERT INTO allergy (id, description) -VALUES - (1, 'Leche'), - (2, 'Frutos secos'), - (3, 'Soja'), - (4, 'Crustáceos'), - (5, 'Huevo'), - (6, 'Pescados'), - (7, 'Cereales'), - (8, 'Maní'), - (9, 'Otro'), - (10, 'Ninguno en particular'); \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/SimpleTextParsingRepositoryAdapterUnitTest.java b/src/test/java/com/cuoco/adapter/SimpleTextParsingRepositoryAdapterUnitTest.java deleted file mode 100644 index 6cb415c..0000000 --- a/src/test/java/com/cuoco/adapter/SimpleTextParsingRepositoryAdapterUnitTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.cuoco.adapter; - -import com.cuoco.adapter.out.hibernate.SimpleTextParsingRepositoryAdapter; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("SimpleTextParsingRepositoryAdapter - TDD") -class SimpleTextParsingRepositoryAdapterUnitTest { - - private SimpleTextParsingRepositoryAdapter repository; - - @BeforeEach - void setUp() { - repository = new SimpleTextParsingRepositoryAdapter(); - } - - @Test - @DisplayName("Test 1: Should parse comma-separated ingredients") - void test1_shouldParseCommaSeparatedIngredients() { - // Given - String text = "tomate, cebolla, ajo"; - - // When - List result = repository.execute(text); - - // Then - assertEquals(3, result.size()); - assertEquals("tomate", result.get(0).getName()); - assertEquals("text", result.get(0).getSource()); - assertFalse(result.get(0).isConfirmed()); - - assertEquals("cebolla", result.get(1).getName()); - assertEquals("ajo", result.get(2).getName()); - } - - @Test - @DisplayName("Test 2: Should handle empty text") - void test2_shouldHandleEmptyText() { - // Given - String text = ""; - - // When - List result = repository.execute(text); - - // Then - assertTrue(result.isEmpty()); - } - - @Test - @DisplayName("Test 3: Should trim whitespace") - void test3_shouldTrimWhitespace() { - // Given - String text = " tomate , cebolla , ajo "; - - // When - List result = repository.execute(text); - - // Then - assertEquals(3, result.size()); - assertEquals("tomate", result.get(0).getName()); - assertEquals("cebolla", result.get(1).getName()); - assertEquals("ajo", result.get(2).getName()); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/AllergyControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/AllergyControllerAdapterTest.java new file mode 100644 index 0000000..9acec2b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/AllergyControllerAdapterTest.java @@ -0,0 +1,60 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.GetAllAllergiesQuery; +import com.cuoco.application.usecase.model.Allergy; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +class AllergyControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private GetAllAllergiesQuery getAllAllergiesQuery; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private AllergyControllerAdapter allergyControllerAdapter; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(allergyControllerAdapter).build(); + } + + @Test + void GIVEN_existing_allergies_WHEN_getAll_THEN_return_list_of_parametric_response() throws Exception { + List allergies = List.of( + Allergy.builder().id(1).description("Mani").build(), + Allergy.builder().id(2).description("Almeja").build() + ); + + when(getAllAllergiesQuery.execute()).thenReturn(allergies); + + mockMvc.perform(get("/allergies").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Mani")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].description").value("Almeja")); + } +} + diff --git a/src/test/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapterTest.java new file mode 100644 index 0000000..3b00e88 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapterTest.java @@ -0,0 +1,114 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.AuthRequest; +import com.cuoco.adapter.in.controller.model.UserRequest; +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.CreateUserCommand; +import com.cuoco.application.port.in.SignInUserCommand; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.AuthenticatedUser; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.UserFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class AuthenticationControllerAdapterTest { + + private MockMvc mockMvc; + + private ObjectMapper objectMapper; + + @Mock + private SignInUserCommand signInUserCommand; + + @Mock + private CreateUserCommand createUserCommand; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private AuthenticationControllerAdapter authenticationControllerAdapter; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockMvc = MockMvcBuilders.standaloneSetup(authenticationControllerAdapter).build(); + } + + @Test + void GIVEN_valid_credentials_WHEN_login_THEN_return_auth_response() throws Exception { + User user = UserFactory.create(); + + AuthenticatedUser authenticatedUser = new AuthenticatedUser( + user, + "token123", + List.of() + ); + + AuthRequest request = AuthRequest.builder() + .email(user.getEmail()) + .password(user.getPassword()) + .build(); + + when(signInUserCommand.execute(any())).thenReturn(authenticatedUser); + + String jsonRequest = objectMapper.writeValueAsString(request); + + mockMvc.perform(post("/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.user.name").value(user.getName())) + .andExpect(jsonPath("$.data.user.email").value(user.getEmail())) + .andExpect(jsonPath("$.data.user.token").value(authenticatedUser.getToken())) + .andExpect(jsonPath("$.data.user.plan.description").value(user.getPlan().getDescription())); + } + + @Test + void GIVEN_valid_user_data_WHEN_register_THEN_return_created_user_response() throws Exception { + User user = UserFactory.create(); + + UserRequest request = UserRequest.builder() + .name(user.getName()) + .email(user.getEmail()) + .password(user.getPassword()) + .planId(user.getPlan().getId()) + .cookLevelId(user.getPreferences().getCookLevel().getId()) + .dietId(user.getPreferences().getDiet().getId()) + .dietaryNeeds(user.getDietaryNeeds().stream().map(DietaryNeed::getId).toList()) + .allergies(user.getAllergies().stream().map(Allergy::getId).toList()) + .build(); + + when(createUserCommand.execute(any())).thenReturn(user); + + String jsonRequest = objectMapper.writeValueAsString(request); + + mockMvc.perform(post("/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.name").value(user.getName())) + .andExpect(jsonPath("$.email").value(user.getEmail())) + .andExpect(jsonPath("$.plan.description").value(user.getPlan().getDescription())); + + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapterTest.java new file mode 100644 index 0000000..8ee7276 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/CookLevelControllerAdapterTest.java @@ -0,0 +1,59 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.GetAllCookLevelsQuery; +import com.cuoco.application.usecase.model.CookLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class CookLevelControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private GetAllCookLevelsQuery getAllCookLevelsQuery; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private CookLevelControllerAdapter cookLevelControllerAdapter; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(cookLevelControllerAdapter).build(); + } + + @Test + void GIVEN_existing_cook_levels_WHEN_getAll_THEN_return_list_of_parametric_response() throws Exception { + List cookLevels = List.of( + CookLevel.builder().id(1).description("Level 1").build(), + CookLevel.builder().id(2).description("Level 2").build() + ); + + when(getAllCookLevelsQuery.execute()).thenReturn(cookLevels); + + mockMvc.perform(get("/cook-levels").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Level 1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].description").value("Level 2")); + } +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/DietControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/DietControllerAdapterTest.java new file mode 100644 index 0000000..7533cb3 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/DietControllerAdapterTest.java @@ -0,0 +1,60 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.GetAllDietsQuery; +import com.cuoco.application.usecase.model.Diet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class DietControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private GetAllDietsQuery getAllDietsQuery; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private DietControllerAdapter dietControllerAdapter; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(dietControllerAdapter).build(); + } + + @Test + void GIVEN_existing_diets_WHEN_getAll_THEN_return_list_of_parametric_response() throws Exception { + List diets = List.of( + Diet.builder().id(1).description("Diet 1").build(), + Diet.builder().id(2).description("Diet 2").build() + ); + + when(getAllDietsQuery.execute()).thenReturn(diets); + + mockMvc.perform(get("/diets").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Diet 1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].description").value("Diet 2")); + } + +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapterTest.java new file mode 100644 index 0000000..b4a2a08 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/DietaryNeedControllerAdapterTest.java @@ -0,0 +1,60 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.GetAllDietaryNeedsQuery; +import com.cuoco.application.usecase.model.DietaryNeed; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class DietaryNeedControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private GetAllDietaryNeedsQuery getAllDietaryNeedsQuery; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private DietaryNeedControllerAdapter dietaryNeedControllerAdapter; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(dietaryNeedControllerAdapter).build(); + } + + @Test + void GIVEN_existing_dietary_needs_WHEN_getAll_THEN_return_list_of_parametric_response() throws Exception { + List dietaryNeeds = List.of( + DietaryNeed.builder().id(1).description("Need 1").build(), + DietaryNeed.builder().id(2).description("Need 2").build() + ); + + when(getAllDietaryNeedsQuery.execute()).thenReturn(dietaryNeeds); + + mockMvc.perform(get("/dietary-needs").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(2)) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].description").value("Need 1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].description").value("Need 2")); + } + +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/IngredientControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/IngredientControllerAdapterTest.java new file mode 100644 index 0000000..690d0d5 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/IngredientControllerAdapterTest.java @@ -0,0 +1,146 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.in.GetIngredientsFromAudioCommand; +import com.cuoco.application.port.in.GetIngredientsFromTextCommand; +import com.cuoco.application.port.in.GetIngredientsGroupedFromImagesCommand; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.factory.domain.IngredientFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class IngredientControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private GetIngredientsFromAudioCommand getIngredientsFromAudioCommand; + + @Mock + private GetIngredientsGroupedFromImagesCommand getIngredientsGroupedFromImagesCommand; + + @Mock + private GetIngredientsFromTextCommand getIngredientsFromTextCommand; + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @InjectMocks + private IngredientControllerAdapter ingredientControllerAdapter; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(ingredientControllerAdapter).build(); + } + + @Test + void GIVEN_audio_file_WHEN_postAudio_THEN_return_ingredient_response() throws Exception { + Ingredient ingredient = IngredientFactory.create(); + + when(getIngredientsFromAudioCommand.execute(any())).thenReturn(List.of(ingredient)); + + MockMultipartFile audioFile = new MockMultipartFile( + "audio", + "audio.wav", + MediaType.MULTIPART_FORM_DATA_VALUE, + "dummy audio content".getBytes(StandardCharsets.UTF_8) + ); + + mockMvc.perform(multipart("/ingredients/audio") + .file(audioFile) + .param("language", "es-ES") + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(1)) + .andExpect(jsonPath("$[0].name").value(ingredient.getName())) + .andExpect(jsonPath("$[0].quantity").value(ingredient.getQuantity())) + .andExpect(jsonPath("$[0].unit.symbol").value(ingredient.getUnit().getSymbol())) + .andExpect(jsonPath("$[0].confirmed").value(ingredient.getConfirmed())) + .andExpect(jsonPath("$[0].source").value(ingredient.getSource())); + } + + @Test + void GIVEN_image_files_WHEN_postImage_THEN_return_grouped_ingredients() throws Exception { + String filenameA = "image1.jpg"; + String filenameB = "image2.jpg"; + + Ingredient ingredientA = IngredientFactory.create("Sal", 1.0, "gr"); + Ingredient ingredientB = IngredientFactory.create("Pimienta", 1.0, "ud"); + + MockMultipartFile imageA = new MockMultipartFile( + "image", + filenameA, + MediaType.IMAGE_JPEG_VALUE, + "dummy image content 1".getBytes(StandardCharsets.UTF_8) + ); + MockMultipartFile imageB = new MockMultipartFile( + "image", + filenameB, + MediaType.IMAGE_JPEG_VALUE, + "dummy image content 2".getBytes(StandardCharsets.UTF_8) + ); + + Map> ingredientsByImage = new LinkedHashMap<>(); + ingredientsByImage.put(filenameA, List.of(ingredientA)); + ingredientsByImage.put(filenameB, List.of(ingredientB)); + + when(getIngredientsGroupedFromImagesCommand.execute(any())).thenReturn(ingredientsByImage); + + mockMvc.perform(multipart("/ingredients/image") + .file(imageA) + .file(imageB) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(2)) + .andExpect(jsonPath("$[0].filename").value(filenameA)) + .andExpect(jsonPath("$[0].ingredients[0].name").value(ingredientA.getName())) + .andExpect(jsonPath("$[1].filename").value(filenameB)) + .andExpect(jsonPath("$[1].ingredients[0].name").value(ingredientB.getName())); + } + + @Test + void GIVEN_text_request_WHEN_postText_THEN_return_ingredient_response() throws Exception { + Ingredient ingredient = IngredientFactory.create(); + + when(getIngredientsFromTextCommand.execute(any())).thenReturn(List.of(ingredient)); + + String jsonRequest = """ + { + "text": "Some ingredient text", + "source": "recipe" + } + """; + + mockMvc.perform(post("/ingredients/text") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.size()").value(1)) + .andExpect(jsonPath("$[0].name").value(ingredient.getName())) + .andExpect(jsonPath("$[0].quantity").value(ingredient.getQuantity())) + .andExpect(jsonPath("$[0].unit.symbol").value(ingredient.getUnit().getSymbol())) + .andExpect(jsonPath("$[0].confirmed").value(ingredient.getConfirmed())) + .andExpect(jsonPath("$[0].source").value(ingredient.getSource())); + } +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapterTest.java new file mode 100644 index 0000000..ce92a6b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/MealPrepControllerAdapterTest.java @@ -0,0 +1,82 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.IngredientRequest; +import com.cuoco.adapter.in.controller.model.MealPrepFilterRequest; +import com.cuoco.adapter.in.controller.model.MealPrepRequest; +import com.cuoco.adapter.in.controller.model.MealPrepResponse; +import com.cuoco.application.port.in.GetMealPrepByIdQuery; +import com.cuoco.application.port.in.GetMealPrepFromIngredientsCommand; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.factory.domain.MealPrepFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class MealPrepControllerAdapterTest { + + @Mock + private GetMealPrepFromIngredientsCommand getMealPrepFromIngredientsCommand; + + @Mock + private GetMealPrepByIdQuery getMealPrepByIdQuery; + + private MealPrepControllerAdapter mealPrepControllerAdapter; + + @BeforeEach + void setUp() { + mealPrepControllerAdapter = new MealPrepControllerAdapter( + getMealPrepFromIngredientsCommand, + getMealPrepByIdQuery + ); + } + + @Test + void shouldGenerateMealPrepsSuccessfully() { + MealPrepRequest request = MealPrepRequest.builder() + .ingredients(List.of(IngredientRequest.builder().name("Tomato").build())) + .filters(new MealPrepFilterRequest() {{ + setFreeze(true); + setServings(4); + }}) + .build(); + + List mealPreps = List.of(MealPrepFactory.create()); + when(getMealPrepFromIngredientsCommand.execute(any(GetMealPrepFromIngredientsCommand.Command.class))) + .thenReturn(mealPreps); + + ResponseEntity> response = mealPrepControllerAdapter.generate(request); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().size()); + verify(getMealPrepFromIngredientsCommand, times(1)).execute(any(GetMealPrepFromIngredientsCommand.Command.class)); + } + + @Test + void shouldGetMealPrepByIdSuccessfully() { + Long mealPrepId = 1L; + MealPrep mealPrep = MealPrepFactory.create(); + when(getMealPrepByIdQuery.execute(mealPrepId)).thenReturn(mealPrep); + + ResponseEntity response = mealPrepControllerAdapter.getById(mealPrepId); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(mealPrep.getId(), response.getBody().getId()); + verify(getMealPrepByIdQuery, times(1)).execute(mealPrepId); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapterTest.java new file mode 100644 index 0000000..8739268 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/MealTypeControllerAdapterTest.java @@ -0,0 +1,50 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.port.in.GetAllMealTypesQuery; +import com.cuoco.application.usecase.model.MealType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class MealTypeControllerAdapterTest { + + @Mock + private GetAllMealTypesQuery getAllMealTypesQuery; + + private MealTypeControllerAdapter mealTypeControllerAdapter; + + @BeforeEach + void setUp() { + mealTypeControllerAdapter = new MealTypeControllerAdapter(getAllMealTypesQuery); + } + + @Test + void shouldGetAllMealTypesSuccessfully() { + List mealTypes = List.of( + MealType.builder().id(1).description("Breakfast").build(), + MealType.builder().id(2).description("Lunch").build() + ); + when(getAllMealTypesQuery.execute()).thenReturn(mealTypes); + + ResponseEntity> response = mealTypeControllerAdapter.getAll(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(2, response.getBody().size()); + verify(getAllMealTypesQuery, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/PlanControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/PlanControllerAdapterTest.java new file mode 100644 index 0000000..09496ff --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/PlanControllerAdapterTest.java @@ -0,0 +1,50 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.port.in.GetAllPlansQuery; +import com.cuoco.application.usecase.model.Plan; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PlanControllerAdapterTest { + + @Mock + private GetAllPlansQuery getAllPlansQuery; + + private PlanControllerAdapter planControllerAdapter; + + @BeforeEach + void setUp() { + planControllerAdapter = new PlanControllerAdapter(getAllPlansQuery); + } + + @Test + void shouldGetAllPlansSuccessfully() { + List plans = List.of( + Plan.builder().id(1).description("Basic Plan").build(), + Plan.builder().id(2).description("Premium Plan").build() + ); + when(getAllPlansQuery.execute()).thenReturn(plans); + + ResponseEntity> response = planControllerAdapter.getAll(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(2, response.getBody().size()); + verify(getAllPlansQuery, times(1)).execute(); + } +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapterTest.java new file mode 100644 index 0000000..27409c3 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/PreparationTimeControllerAdapterTest.java @@ -0,0 +1,53 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.ParametricResponse; +import com.cuoco.application.port.in.GetAllPreparationTimesQuery; +import com.cuoco.application.usecase.model.PreparationTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PreparationTimeControllerAdapterTest { + + @Mock + private GetAllPreparationTimesQuery getAllPreparationTimesQuery; + + private PreparationTimeControllerAdapter preparationTimeControllerAdapter; + + @BeforeEach + void setUp() { + preparationTimeControllerAdapter = new PreparationTimeControllerAdapter(getAllPreparationTimesQuery); + } + + @Test + void shouldGetAllPreparationTimesSuccessfully() { + // Given + List preparationTimes = List.of( + PreparationTime.builder().id(1).description("15 minutes").build(), + PreparationTime.builder().id(2).description("30 minutes").build() + ); + when(getAllPreparationTimesQuery.execute()).thenReturn(preparationTimes); + + // When + ResponseEntity> response = preparationTimeControllerAdapter.getAllPreparationTimes(); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(2, response.getBody().size()); + verify(getAllPreparationTimesQuery, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/RecipeControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/RecipeControllerAdapterTest.java new file mode 100644 index 0000000..3dbfac7 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/RecipeControllerAdapterTest.java @@ -0,0 +1,81 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.RecipeRequest; +import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.RecipeFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class RecipeControllerAdapterTest { + + private MockMvc mockMvc; + + private ObjectMapper objectMapper; + + @Mock + private GetRecipesFromIngredientsCommand getRecipesFromIngredientsCommand; + + @InjectMocks + private RecipeControllerAdapter recipeControllerAdapter; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockMvc = MockMvcBuilders.standaloneSetup(recipeControllerAdapter).build(); + } + + @Test + void GIVEN_valid_ingredients_request_WHEN_generate_THEN_return_recipes_response() throws Exception { + Recipe recipe = RecipeFactory.create(); + RecipeRequest request = RecipeFactory.getRecipeRequest(); + + when(getRecipesFromIngredientsCommand.execute(any())).thenReturn(List.of(recipe)); + + mockMvc.perform(post("/recipes") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].name").value(recipe.getName())) + .andExpect(jsonPath("$[0].preparation_time").exists()) + .andExpect(jsonPath("$[0].image").value(recipe.getImage())) + .andExpect(jsonPath("$[0].subtitle").value(recipe.getSubtitle())) + .andExpect(jsonPath("$[0].description").value(recipe.getDescription())) + .andExpect(jsonPath("$[0].ingredients").isArray()) + .andExpect(jsonPath("$[0].ingredients[0].name").value(recipe.getIngredients().get(0).getName())); + } + + @Test + void GIVEN_valid_ingredients_request_WHEN_generation_fails_THEN_return_recipes_without_images() throws Exception { + Recipe recipe = RecipeFactory.create(); + RecipeRequest request = RecipeFactory.getRecipeRequest(); + + when(getRecipesFromIngredientsCommand.execute(any())).thenReturn(List.of(recipe)); + + mockMvc.perform(post("/recipes") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].name").value(recipe.getName())) + .andExpect(jsonPath("$[0].image").value(recipe.getImage())); + } +} diff --git a/src/test/java/com/cuoco/adapter/in/controller/UnitControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/UnitControllerAdapterTest.java new file mode 100644 index 0000000..2887881 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/UnitControllerAdapterTest.java @@ -0,0 +1,50 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.UnitResponse; +import com.cuoco.application.port.in.GetAllUnitsQuery; +import com.cuoco.application.usecase.model.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UnitControllerAdapterTest { + + @Mock + private GetAllUnitsQuery getAllUnitsQuery; + + private UnitControllerAdapter unitControllerAdapter; + + @BeforeEach + void setUp() { + unitControllerAdapter = new UnitControllerAdapter(getAllUnitsQuery); + } + + @Test + void shouldGetAllUnitsSuccessfully() { + List units = List.of( + Unit.builder().id(1).description("Cup").symbol("cup").build(), + Unit.builder().id(2).description("Gram").symbol("g").build() + ); + when(getAllUnitsQuery.execute()).thenReturn(units); + + ResponseEntity> response = unitControllerAdapter.getAll(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(2, response.getBody().size()); + verify(getAllUnitsQuery, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapterTest.java new file mode 100644 index 0000000..0dc7471 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/UserCalendarControllerAdapterTest.java @@ -0,0 +1,76 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.CalendarResponse; +import com.cuoco.adapter.in.controller.model.RecipeCalendarRequest; +import com.cuoco.adapter.in.controller.model.UserRecipeCalendarRequest; +import com.cuoco.application.port.in.CreateOrUpdateUserRecipeCalendarCommand; +import com.cuoco.application.port.in.GetUserCalendarQuery; +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.factory.domain.CalendarFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserCalendarControllerAdapterTest { + + @Mock + private CreateOrUpdateUserRecipeCalendarCommand createOrUpdateUserRecipeCalendarCommand; + + @Mock + private GetUserCalendarQuery getUserCalendarQuery; + + private UserCalendarControllerAdapter userCalendarControllerAdapter; + + @BeforeEach + void setUp() { + userCalendarControllerAdapter = new UserCalendarControllerAdapter( + createOrUpdateUserRecipeCalendarCommand, + getUserCalendarQuery + ); + } + + @Test + void shouldSaveCalendarSuccessfully() { + List requests = List.of( + UserRecipeCalendarRequest.builder() + .dayId(1) + .recipes(List.of(RecipeCalendarRequest.builder() + .recipeId(1L) + .mealTypeId(1) + .build())) + .build() + ); + + ResponseEntity response = userCalendarControllerAdapter.save(requests); + + assertEquals(HttpStatus.CREATED.value(), response.getStatusCodeValue()); + verify(createOrUpdateUserRecipeCalendarCommand, times(1)).execute(any(CreateOrUpdateUserRecipeCalendarCommand.Command.class)); + } + + @Test + void shouldGetCalendarSuccessfully() { + List calendars = List.of(CalendarFactory.create()); + when(getUserCalendarQuery.execute()).thenReturn(calendars); + + ResponseEntity> response = userCalendarControllerAdapter.get(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().size()); + verify(getUserCalendarQuery, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/UserControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/UserControllerAdapterTest.java new file mode 100644 index 0000000..7f2d12d --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/UserControllerAdapterTest.java @@ -0,0 +1,80 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.UpdateUserRequest; +import com.cuoco.application.port.in.UpdateUserProfileCommand; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import com.cuoco.factory.domain.UserFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +class UserControllerAdapterTest { + + private MockMvc mockMvc; + private ObjectMapper objectMapper; + + @Mock + private UpdateUserProfileCommand updateUserProfileCommand; + + @Mock + private JwtUtil jwtUtil; + + @InjectMocks + private UserControllerAdapter controller; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + void GIVEN_valid_profile_data_WHEN_updateProfile_THEN_return_updated_user_response() throws Exception { + UpdateUserRequest request = UpdateUserRequest.builder() + .name("Juan Pérez") + .planId(2) + .build(); + + User expectedUser = UserFactory.create(); + + when(updateUserProfileCommand.execute(any())).thenReturn(expectedUser); + + mockMvc.perform(patch("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value(expectedUser.getName())) + .andExpect(jsonPath("$.email").value(expectedUser.getEmail())) + .andExpect(jsonPath("$.id").value(expectedUser.getId())); + } + + @Test + void GIVEN_invalid_profile_data_WHEN_updateProfile_THEN_return_bad_request() throws Exception { + UpdateUserRequest request = UpdateUserRequest.builder() + .name("") + .build(); + + User expectedUser = UserFactory.create(); + when(updateUserProfileCommand.execute(any())).thenReturn(expectedUser); + + mockMvc.perform(patch("/users") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapterTest.java new file mode 100644 index 0000000..691d088 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/UserMealPrepControllerAdapterTest.java @@ -0,0 +1,81 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.adapter.in.controller.model.MealPrepResponse; +import com.cuoco.application.port.in.CreateUserMealPrepCommand; +import com.cuoco.application.port.in.DeleteUserMealPrepCommand; +import com.cuoco.application.port.in.GetAllUserMealPrepsQuery; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.factory.domain.MealPrepFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserMealPrepControllerAdapterTest { + + @Mock + private CreateUserMealPrepCommand createUserMealPrepCommand; + + @Mock + private GetAllUserMealPrepsQuery getAllUserMealPrepsQuery; + + @Mock + private DeleteUserMealPrepCommand deleteUserMealPrepCommand; + + private UserMealPrepControllerAdapter userMealPrepControllerAdapter; + + @BeforeEach + void setUp() { + userMealPrepControllerAdapter = new UserMealPrepControllerAdapter( + createUserMealPrepCommand, + getAllUserMealPrepsQuery, + deleteUserMealPrepCommand + ); + } + + @Test + void shouldSaveMealPrepSuccessfully() { + Long mealPrepId = 1L; + + ResponseEntity response = userMealPrepControllerAdapter.save(mealPrepId); + + assertEquals(HttpStatus.CREATED.value(), response.getStatusCodeValue()); + verify(createUserMealPrepCommand, times(1)).execute(any(CreateUserMealPrepCommand.Command.class)); + } + + @Test + void shouldGetAllMealPrepsSuccessfully() { + List mealPreps = List.of(MealPrepFactory.create()); + when(getAllUserMealPrepsQuery.execute()).thenReturn(mealPreps); + + ResponseEntity> response = userMealPrepControllerAdapter.getAll(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().size()); + verify(getAllUserMealPrepsQuery, times(1)).execute(); + } + + @Test + void shouldDeleteMealPrepSuccessfully() { + Long mealPrepId = 1L; + + ResponseEntity response = userMealPrepControllerAdapter.delete(mealPrepId); + + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + verify(deleteUserMealPrepCommand, times(1)).execute(any(DeleteUserMealPrepCommand.Command.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapterTest.java b/src/test/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapterTest.java new file mode 100644 index 0000000..0ac5543 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/controller/UserRecipeControllerAdapterTest.java @@ -0,0 +1,88 @@ +package com.cuoco.adapter.in.controller; + +import com.cuoco.application.port.in.CreateUserRecipeCommand; +import com.cuoco.application.port.in.DeleteUserRecipeCommand; +import com.cuoco.application.port.in.GetAllUserRecipesQuery; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.RecipeFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +public class UserRecipeControllerAdapterTest { + + private MockMvc mockMvc; + + @Mock + private CreateUserRecipeCommand createUserRecipeCommand; + + @Mock + private GetAllUserRecipesQuery getAllUserRecipesQuery; + + @Mock + private DeleteUserRecipeCommand deleteUserRecipeCommand; + + @InjectMocks + private UserRecipeControllerAdapter userRecipeControllerAdapter; + + @BeforeEach + void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(userRecipeControllerAdapter).build(); + } + + @Test + void GIVEN_valid_recipe_id_WHEN_save_recipe_THEN_return_created() throws Exception { + Long recipeId = 1L; + doNothing().when(createUserRecipeCommand).execute(any(CreateUserRecipeCommand.Command.class)); + + mockMvc.perform(post("/users/recipes/{id}", recipeId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } + + @Test + void GIVEN_user_has_recipes_WHEN_get_all_recipes_THEN_return_recipes_list() throws Exception { + Recipe recipe1 = RecipeFactory.create(); + Recipe recipe2 = RecipeFactory.create(); + List recipes = List.of(recipe1, recipe2); + + when(getAllUserRecipesQuery.execute()).thenReturn(recipes); + + mockMvc.perform(get("/users/recipes") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].id").value(recipe1.getId())) + .andExpect(jsonPath("$[0].name").value(recipe1.getName())) + .andExpect(jsonPath("$[1].id").value(recipe2.getId())) + .andExpect(jsonPath("$[1].name").value(recipe2.getName())); + } + + @Test + void GIVEN_valid_recipe_id_WHEN_delete_recipe_THEN_return_no_content() throws Exception { + Long recipeId = 1L; + doNothing().when(deleteUserRecipeCommand).execute(any(DeleteUserRecipeCommand.Command.class)); + + mockMvc.perform(delete("/users/recipes/{id}", recipeId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + } +} diff --git a/src/test/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapterTest.java b/src/test/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapterTest.java new file mode 100644 index 0000000..e1118a5 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/in/security/JwtAuthenticationFilterAdapterTest.java @@ -0,0 +1,117 @@ +package com.cuoco.adapter.in.security; + +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.usecase.model.AuthenticatedUser; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.UserFactory; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class JwtAuthenticationFilterAdapterTest { + + @Mock + private AuthenticateUserCommand authenticateUserCommand; + + @Mock + private FilterChain filterChain; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + private JwtAuthenticationFilterAdapter filter; + + @BeforeEach + void setUp() { + filter = new JwtAuthenticationFilterAdapter(authenticateUserCommand); + } + + @Test + void GIVEN_valid_authorization_header_WHEN_doFilterInternal_THEN_authentication_is_set() throws Exception { + String token = "Bearer valid.jwt.token"; + User user = UserFactory.create(); + List roles = List.of("ROLE_USER"); + + AuthenticatedUser authenticatedUser = AuthenticatedUser.builder() + .user(user) + .roles(roles) + .build(); + + when(request.getHeader("Authorization")).thenReturn(token); + when(authenticateUserCommand.execute(any())).thenReturn(authenticatedUser); + + filter.doFilterInternal(request, response, filterChain); + + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + assertNotNull(auth); + assertEquals(user, auth.getPrincipal()); + assertEquals(1, auth.getAuthorities().size()); + verify(filterChain).doFilter(request, response); + } + + @Test + void GIVEN_null_authentication_WHEN_doFilterInternal_THEN_authentication_is_not_set() throws Exception { + when(request.getHeader("Authorization")).thenReturn("Bearer invalid.token"); + when(authenticateUserCommand.execute(any())).thenReturn(null); + + filter.doFilterInternal(request, response, filterChain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + verify(filterChain).doFilter(request, response); + } + + @ParameterizedTest + @ValueSource(strings = { + "/auth/login", + "/auth/register", + "/actuator/health", + "/plans", + "/allergies", + "/diets", + "/dietary-needs", + "/cook-levels", + "/v3/api-docs/test", + "/swagger-ui/index.html", + "/swagger-ui.html" + }) + void GIVEN_excluded_paths_WHEN_shouldNotFilter_THEN_returns_true(String path) { + when(request.getRequestURI()).thenReturn(path); + assertTrue(filter.shouldNotFilter(request)); + } + + @Test + void GIVEN_non_excluded_path_WHEN_shouldNotFilter_THEN_returns_false() { + when(request.getRequestURI()).thenReturn("/secure-endpoint"); + assertFalse(filter.shouldNotFilter(request)); + } + + @AfterEach + void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..18b4374 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserDatabaseRepositoryAdapterTest.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateUserPreferencesHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.UserFactory; +import com.cuoco.factory.hibernate.UserHibernateModelFactory; +import com.cuoco.factory.hibernate.UserPreferencesHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +class CreateUserDatabaseRepositoryAdapterTest { + + @Mock + private CreateUserHibernateRepositoryAdapter userRepository; + + @Mock + private CreateUserPreferencesHibernateRepositoryAdapter preferencesRepository; + + @InjectMocks + private CreateUserDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + openMocks(this); + } + + @Test + void GIVEN_valid_user_WHEN_execute_THEN_should_persist_user_preferences_dietaryNeeds_allergies() { + User domainUser = UserFactory.create(); + + UserHibernateModel savedUser = UserHibernateModelFactory.create(); + savedUser.setId(1L); + + UserPreferencesHibernateModel savedPreferences = UserPreferencesHibernateModelFactory.create(); + + when(userRepository.save(any(UserHibernateModel.class))).thenReturn(savedUser); + when(preferencesRepository.save(any())).thenReturn(savedPreferences); + + User result = adapter.execute(domainUser); + + assertNotNull(result); + assertEquals(savedUser.getEmail(), result.getEmail()); + assertEquals(savedPreferences.getDiet().getDescription(), result.getPreferences().getDiet().getDescription()); + assertEquals(3, result.getAllergies().size()); + assertEquals(3, result.getDietaryNeeds().size()); + + verify(userRepository).save(any(UserHibernateModel.class)); + verify(preferencesRepository).save(any()); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..33b8312 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserMealPrepDatabaseRepositoryAdapterTest.java @@ -0,0 +1,46 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.CreateUserMealPrepHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserMealPrep; +import com.cuoco.factory.domain.MealPrepFactory; +import com.cuoco.factory.domain.UserFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class CreateUserMealPrepDatabaseRepositoryAdapterTest { + + @Mock + private CreateUserMealPrepHibernateRepositoryAdapter createUserMealPrepHibernateRepositoryAdapter; + + private CreateUserMealPrepDatabaseRepositoryAdapter createUserMealPrepDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + createUserMealPrepDatabaseRepositoryAdapter = new CreateUserMealPrepDatabaseRepositoryAdapter( + createUserMealPrepHibernateRepositoryAdapter + ); + } + + @Test + void GIVEN_valid_user_meal_prep_WHEN_execute_THEN_should_persist_user_meal_preps() { + User user = UserFactory.create(); + MealPrep mealPrep = MealPrepFactory.create(); + UserMealPrep userMealPrep = UserMealPrep.builder() + .user(user) + .mealPrep(mealPrep) + .build(); + + createUserMealPrepDatabaseRepositoryAdapter.execute(userMealPrep); + + verify(createUserMealPrepHibernateRepositoryAdapter).save(any()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..0a9bd74 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/CreateUserRecipeDatabaseRepositoryAdapterTest.java @@ -0,0 +1,44 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserRecipesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserRecipeHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.domain.UserFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CreateUserRecipeDatabaseRepositoryAdapterTest { + + @Mock + private CreateUserRecipeHibernateRepositoryAdapter createUserRecipeHibernateRepositoryAdapter; + + @InjectMocks + private CreateUserRecipeDatabaseRepositoryAdapter repository; + + @Test + public void shouldCallSaveWithCorrectModel() { + User user = UserFactory.create(); + Recipe recipe = RecipeFactory.create(); + UserRecipe userRecipe = UserRecipe.builder() + .user(user) + .recipe(recipe) + .build(); + + when(createUserRecipeHibernateRepositoryAdapter.save(any())).thenReturn(new UserRecipesHibernateModel()); + + repository.execute(userRecipe); + + verify(createUserRecipeHibernateRepositoryAdapter).save(any()); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..8ebc33b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserMealPrepDatabaseRepositoryAdapterTest.java @@ -0,0 +1,36 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.DeleteUserMealPrepsHibernateRepositoryAdapter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class DeleteUserMealPrepDatabaseRepositoryAdapterTest { + + @Mock + private DeleteUserMealPrepsHibernateRepositoryAdapter deleteUserMealPrepsHibernateRepositoryAdapter; + + private DeleteUserMealPrepDatabaseRepositoryAdapter deleteUserMealPrepDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + deleteUserMealPrepDatabaseRepositoryAdapter = new DeleteUserMealPrepDatabaseRepositoryAdapter( + deleteUserMealPrepsHibernateRepositoryAdapter + ); + } + + @Test + void shouldDeleteUserMealPrepSuccessfully() { + Long userId = 1L; + Long mealPrepId = 1L; + + deleteUserMealPrepDatabaseRepositoryAdapter.execute(userId, mealPrepId); + + verify(deleteUserMealPrepsHibernateRepositoryAdapter).deleteAllByUserIdAndMealPrepId(userId, mealPrepId); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..2a27939 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/DeleteUserRecipeDatabaseRepositoryAdapterTest.java @@ -0,0 +1,36 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.DeleteUserRecipeHibernateRepositoryAdapter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class DeleteUserRecipeDatabaseRepositoryAdapterTest { + + @Mock + private DeleteUserRecipeHibernateRepositoryAdapter deleteUserRecipeHibernateRepositoryAdapter; + + private DeleteUserRecipeDatabaseRepositoryAdapter deleteUserRecipeDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + deleteUserRecipeDatabaseRepositoryAdapter = new DeleteUserRecipeDatabaseRepositoryAdapter( + deleteUserRecipeHibernateRepositoryAdapter + ); + } + + @Test + void shouldDeleteUserRecipeSuccessfully() { + Long userId = 1L; + Long recipeId = 1L; + + deleteUserRecipeDatabaseRepositoryAdapter.execute(userId, recipeId); + + verify(deleteUserRecipeHibernateRepositoryAdapter).deleteAllByUserIdAndRecipeId(userId, recipeId); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeByUserIdAndRecipeIdRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeByUserIdAndRecipeIdRepositoryAdapterTest.java new file mode 100644 index 0000000..f5a557b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/ExistsUserRecipeByUserIdAndRecipeIdRepositoryAdapterTest.java @@ -0,0 +1,63 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ExistsUserRecipeByUserIdAndRecipeIdRepositoryAdapterTest { + + private ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter existRepo; + private ExistsUserRecipeDatabaseRepositoryAdapter adapter; + + @BeforeEach + public void setUp() { + existRepo = mock(ExistsUserRecipeByUserIdAndRecipeIdHibernateRepositoryAdapter.class); + adapter = new ExistsUserRecipeDatabaseRepositoryAdapter(existRepo); + } + + @Test + public void shouldReturnTrueWhenRecipeExistsForUser() { + User user = new User(); + user.setId(1L); + + Recipe recipe = new Recipe(); + recipe.setId(2L); + + UserRecipe userRecipe = new UserRecipe(); + userRecipe.setUser(user); + userRecipe.setRecipe(recipe); + + when(existRepo.existsByUserIdAndRecipeId(1L, 2L)).thenReturn(true); + + boolean result = adapter.execute(userRecipe); + + assertTrue(result); + } + + @Test + public void shouldReturnFalseWhenRecipeDoesNotExistForUser() { + User user = new User(); + user.setId(3L); + + Recipe recipe = new Recipe(); + recipe.setId(4L); + + UserRecipe userRecipe = new UserRecipe(); + userRecipe.setUser(user); + userRecipe.setRecipe(recipe); + + when(existRepo.existsByUserIdAndRecipeId(3L, 4L)).thenReturn(false); + + boolean result = adapter.execute(userRecipe); + + assertFalse(result); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..31d9cc4 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllAllergiesDatabaseRepositoryAdapterTest.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllAllergiesHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.factory.hibernate.AllergyHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllAllergiesDatabaseRepositoryAdapterTest { + + @Mock + private GetAllAllergiesHibernateRepositoryAdapter getAllAllergiesHibernateRepository; + + @InjectMocks + private GetAllAllergiesDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_all_allergies() { + List mockAllergies = List.of( + AllergyHibernateModelFactory.create(1, "Mani"), + AllergyHibernateModelFactory.create(2, "Gluten") + ); + + when(getAllAllergiesHibernateRepository.findAll()).thenReturn(mockAllergies); + + List result = adapter.execute(); + + assertEquals(2, result.size()); + assertEquals("Mani", result.get(0).getDescription()); + assertEquals("Gluten", result.get(1).getDescription()); + + verify(getAllAllergiesHibernateRepository).findAll(); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..8c8b758 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllCookLevelsDatabaseRepositoryAdapterTest.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllCookLevelsHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.factory.hibernate.CookLevelHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllCookLevelsDatabaseRepositoryAdapterTest { + + @Mock + private GetAllCookLevelsHibernateRepositoryAdapter getAllCookLevelsHibernateRepository; + + @InjectMocks + private GetAllCookLevelsDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_all_cook_levels() { + List mockCookLevels = List.of( + CookLevelHibernateModelFactory.create(1, "Beginner"), + CookLevelHibernateModelFactory.create(2, "Advanced") + ); + + when(getAllCookLevelsHibernateRepository.findAll()).thenReturn(mockCookLevels); + + List result = adapter.execute(); + + assertEquals(2, result.size()); + assertEquals("Beginner", result.get(0).getDescription()); + assertEquals("Advanced", result.get(1).getDescription()); + + verify(getAllCookLevelsHibernateRepository).findAll(); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..21e1e39 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietaryNeedsDatabaseRepositoryAdapterTest.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllDietaryNeedsHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.factory.hibernate.DietaryNeedHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllDietaryNeedsDatabaseRepositoryAdapterTest { + + @Mock + private GetAllDietaryNeedsHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetAllDietaryNeedsDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_all_dietary_needs() { + List mockList = List.of( + DietaryNeedHibernateModelFactory.create(1, "Vegan"), + DietaryNeedHibernateModelFactory.create(2, "Gluten-Free") + ); + + when(hibernateRepository.findAll()).thenReturn(mockList); + + List result = adapter.execute(); + + assertEquals(2, result.size()); + assertEquals("Vegan", result.get(0).getDescription()); + assertEquals("Gluten-Free", result.get(1).getDescription()); + + verify(hibernateRepository).findAll(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..50e8604 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllDietsDatabaseRepositoryAdapterTest.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllDietsHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.factory.hibernate.DietHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllDietsDatabaseRepositoryAdapterTest { + + @Mock + private GetAllDietsHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetAllDietsDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_all_diets() { + List mockList = List.of( + DietHibernateModelFactory.create(1, "Keto"), + DietHibernateModelFactory.create(2, "Paleo") + ); + + when(hibernateRepository.findAll()).thenReturn(mockList); + + List result = adapter.execute(); + + assertEquals(2, result.size()); + assertEquals("Keto", result.get(0).getDescription()); + assertEquals("Paleo", result.get(1).getDescription()); + + verify(hibernateRepository).findAll(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..c8e7d56 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllMealTypesDatabaseRepositoryAdapterTest.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllMealTypesHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.MealType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetAllMealTypesDatabaseRepositoryAdapterTest { + + @Mock + private GetAllMealTypesHibernateRepositoryAdapter getAllMealTypesHibernateRepositoryAdapter; + + private GetAllMealTypesDatabaseRepositoryAdapter getAllMealTypesDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + getAllMealTypesDatabaseRepositoryAdapter = new GetAllMealTypesDatabaseRepositoryAdapter( + getAllMealTypesHibernateRepositoryAdapter + ); + } + + @Test + void shouldGetAllMealTypesSuccessfully() { + // Given + List expectedMealTypes = List.of( + MealTypeHibernateModel.builder().id(1).description("Breakfast").build(), + MealTypeHibernateModel.builder().id(2).description("Lunch").build() + ); + when(getAllMealTypesHibernateRepositoryAdapter.findAll()).thenReturn(expectedMealTypes); + + // When + List result = getAllMealTypesDatabaseRepositoryAdapter.execute(); + + // Then + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Breakfast", result.get(0).getDescription()); + assertEquals("Lunch", result.get(1).getDescription()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..d440902 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPlansDatabaseRepositoryAdapterTest.java @@ -0,0 +1,49 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllPlansHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.factory.hibernate.PlanHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllPlansDatabaseRepositoryAdapterTest { + + @Mock + private GetAllPlansHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetAllPlansDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_all_plans() { + List mockList = List.of( + PlanHibernateModelFactory.create(1, "Free"), + PlanHibernateModelFactory.create(2, "Pro") + ); + + when(hibernateRepository.findAll()).thenReturn(mockList); + + List result = adapter.execute(); + + assertEquals(2, result.size()); + assertEquals("Free", result.get(0).getDescription()); + assertEquals("Pro", result.get(1).getDescription()); + + verify(hibernateRepository).findAll(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..8f11f56 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllPreparationTimesDatabaseRepositoryAdapterTest.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllPreparationTimesHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.PreparationTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetAllPreparationTimesDatabaseRepositoryAdapterTest { + + @Mock + private GetAllPreparationTimesHibernateRepositoryAdapter getAllPreparationTimesHibernateRepositoryAdapter; + + private GetAllPreparationTimesDatabaseRepositoryAdapter getAllPreparationTimesDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + getAllPreparationTimesDatabaseRepositoryAdapter = new GetAllPreparationTimesDatabaseRepositoryAdapter( + getAllPreparationTimesHibernateRepositoryAdapter + ); + } + + @Test + void shouldGetAllPreparationTimesSuccessfully() { + // Given + List expectedPreparationTimes = List.of( + PreparationTimeHibernateModel.builder().id(1).description("15 minutes").build(), + PreparationTimeHibernateModel.builder().id(2).description("30 minutes").build() + ); + when(getAllPreparationTimesHibernateRepositoryAdapter.findAll()).thenReturn(expectedPreparationTimes); + + // When + List result = getAllPreparationTimesDatabaseRepositoryAdapter.execute(); + + // Then + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("15 minutes", result.get(0).getDescription()); + assertEquals("30 minutes", result.get(1).getDescription()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..91d91ae --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllUnitsDatabaseRepositoryAdapterTest.java @@ -0,0 +1,50 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllUnitsHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetAllUnitsDatabaseRepositoryAdapterTest { + + @Mock + private GetAllUnitsHibernateRepositoryAdapter getAllUnitsHibernateRepositoryAdapter; + + private GetAllUnitsDatabaseRepositoryAdapter getAllUnitsDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + getAllUnitsDatabaseRepositoryAdapter = new GetAllUnitsDatabaseRepositoryAdapter( + getAllUnitsHibernateRepositoryAdapter + ); + } + + @Test + void shouldGetAllUnitsSuccessfully() { + List expectedUnits = List.of( + UnitHibernateModel.builder().id(1).description("Cup").symbol("cup").build(), + UnitHibernateModel.builder().id(2).description("Gram").symbol("g").build() + ); + when(getAllUnitsHibernateRepositoryAdapter.findAll()).thenReturn(expectedUnits); + + List result = getAllUnitsDatabaseRepositoryAdapter.execute(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Cup", result.get(0).getDescription()); + assertEquals("cup", result.get(0).getSymbol()); + assertEquals("Gram", result.get(1).getDescription()); + assertEquals("g", result.get(1).getSymbol()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..bade5c4 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetAllergiesByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetAllergiesByIdHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.factory.hibernate.AllergyHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllergiesByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetAllergiesByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetAllergiesByIdDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_THEN_return_allergies_by_ids() { + List ids = List.of(1, 2); + + List mockList = List.of( + AllergyHibernateModelFactory.create(1, "Mani"), + AllergyHibernateModelFactory.create(2, "Chocolate") + ); + + when(hibernateRepository.findByIdIn(ids)).thenReturn(mockList); + + List result = adapter.execute(ids); + + assertEquals(2, result.size()); + assertEquals("Mani", result.get(0).getDescription()); + assertEquals("Chocolate", result.get(1).getDescription()); + + verify(hibernateRepository).findByIdIn(ids); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..e969733 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetCookLevelByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetCookLevelByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.factory.hibernate.CookLevelHibernateModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetCookLevelByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetCookLevelByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetCookLevelByIdDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_with_existing_id_THEN_return_cook_level() { + Integer id = 1; + CookLevelHibernateModel model = CookLevelHibernateModelFactory.create(id, "Beginner"); + + when(hibernateRepository.findById(id)).thenReturn(Optional.of(model)); + + CookLevel result = adapter.execute(id); + + assertEquals(id, result.getId()); + assertEquals("Beginner", result.getDescription()); + + verify(hibernateRepository).findById(id); + } + + @Test + void WHEN_execute_with_non_existing_id_THEN_throw_exception() { + Integer id = 99; + + when(hibernateRepository.findById(id)).thenReturn(Optional.empty()); + + BadRequestException exception = assertThrows(BadRequestException.class, () -> adapter.execute(id)); + + assertEquals(ErrorDescription.COOK_LEVEL_NOT_EXISTS.getValue(), exception.getDescription()); + + verify(hibernateRepository).findById(id); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..4195dcd --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetDietByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,60 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetDietByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.factory.hibernate.DietHibernateModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetDietByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetDietByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetDietByIdDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_with_existing_id_THEN_return_diet() { + Integer id = 1; + DietHibernateModel model = DietHibernateModelFactory.create(id, "Keto"); + + when(hibernateRepository.findById(id)).thenReturn(Optional.of(model)); + + Diet result = adapter.execute(id); + + assertEquals("Keto", result.getDescription()); + assertEquals(id, result.getId()); + verify(hibernateRepository).findById(id); + } + + @Test + void WHEN_execute_with_non_existing_id_THEN_throw_BadRequestException() { + Integer id = 99; + + when(hibernateRepository.findById(id)).thenReturn(Optional.empty()); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> adapter.execute(id)); + assertEquals(ErrorDescription.DIET_NOT_EXISTS.getValue(), ex.getDescription()); + + verify(hibernateRepository).findById(id); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..26c1a48 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetDietaryNeedsByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,64 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetDietaryNeedsByIdHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.factory.hibernate.DietaryNeedHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetDietaryNeedsByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetDietaryNeedsByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetDietaryNeedsByIdDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_with_valid_ids_THEN_return_dietary_needs() { + List ids = List.of(1, 2); + List models = List.of( + DietaryNeedHibernateModelFactory.create(1, "Vegetariano"), + DietaryNeedHibernateModelFactory.create(2, "Vegano") + ); + + when(hibernateRepository.findByIdIn(ids)).thenReturn(models); + + List result = adapter.execute(ids); + + assertEquals(2, result.size()); + assertEquals("Vegetariano", result.get(0).getDescription()); + assertEquals("Vegano", result.get(1).getDescription()); + + verify(hibernateRepository).findByIdIn(ids); + } + + @Test + void WHEN_execute_with_empty_list_THEN_return_empty_list() { + List ids = List.of(); + List models = List.of(); + + when(hibernateRepository.findByIdIn(ids)).thenReturn(models); + + List result = adapter.execute(ids); + + assertTrue(result.isEmpty()); + verify(hibernateRepository).findByIdIn(ids); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..fe67d1f --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetMealPrepByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,53 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetMealPrepByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.factory.hibernate.MealPrepHibernateModelFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetMealPrepByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetMealPrepByIdHibernateRepositoryAdapter getMealPrepByIdHibernateRepositoryAdapter; + + @InjectMocks + private GetMealPrepByIdDatabaseRepositoryAdapter getMealPrepByIdDatabaseRepositoryAdapter; + + @Test + void shouldGetMealPrepByIdSuccessfully() { + Long mealPrepId = 1L; + MealPrepHibernateModel mealPrepHibernateModel = MealPrepHibernateModelFactory.create(); + when(getMealPrepByIdHibernateRepositoryAdapter.findById(mealPrepId)) + .thenReturn(Optional.of(mealPrepHibernateModel)); + + MealPrep result = getMealPrepByIdDatabaseRepositoryAdapter.execute(mealPrepId); + + assertNotNull(result); + assertEquals(mealPrepHibernateModel.getId(), result.getId()); + } + + @Test + void shouldThrowBadRequestExceptionWhenMealPrepNotFound() { + Long mealPrepId = 999L; + when(getMealPrepByIdHibernateRepositoryAdapter.findById(mealPrepId)) + .thenReturn(Optional.empty()); + + assertThrows(BadRequestException.class, () -> { + getMealPrepByIdDatabaseRepositoryAdapter.execute(mealPrepId); + }); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..e1f243b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetPlanByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,60 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetPlanByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.factory.hibernate.PlanHibernateModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetPlanByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetPlanByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetPlanByIdDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void WHEN_execute_with_existing_id_THEN_return_plan() { + Integer id = 1; + PlanHibernateModel model = PlanHibernateModelFactory.create(id, "Pro"); + + when(hibernateRepository.findById(id)).thenReturn(Optional.of(model)); + + Plan result = adapter.execute(id); + + assertEquals("Pro", result.getDescription()); + assertEquals(id, result.getId()); + verify(hibernateRepository).findById(id); + } + + @Test + void WHEN_execute_with_non_existing_id_THEN_throw_BadRequestException() { + Integer id = 99; + + when(hibernateRepository.findById(id)).thenReturn(Optional.empty()); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> adapter.execute(id)); + assertEquals(ErrorDescription.PLAN_NOT_EXISTS.getValue(), ex.getDescription()); + + verify(hibernateRepository).findById(id); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..9721785 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetRecipeByIdDatabaseRepositoryAdapterTest.java @@ -0,0 +1,51 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetRecipeByIdHibernateRepositoryAdapter; +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.hibernate.RecipeHibernateModelFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class GetRecipeByIdDatabaseRepositoryAdapterTest { + + @Mock + private GetRecipeByIdHibernateRepositoryAdapter hibernateRepository; + + @InjectMocks + private GetRecipeByIdDatabaseRepositoryAdapter adapter; + + @Test + public void shouldReturnRecipeWhenFound() { + long recipeId = 123L; + RecipeHibernateModel mockEntity = RecipeHibernateModelFactory.create(); + Recipe expectedRecipe = mockEntity.toDomain(); + + when(hibernateRepository.findById(recipeId)).thenReturn(Optional.of(mockEntity)); + + Recipe result = adapter.execute(recipeId); + + assertNotNull(result); + assertEquals(expectedRecipe.getId(), result.getId()); + } + + @Test + public void shouldThrowExceptionIfRecipeNotFound() { + long recipeId = 456L; + when(hibernateRepository.findById(recipeId)).thenReturn(Optional.empty()); + + assertThrows(BadRequestException.class, () -> adapter.execute(recipeId)); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..7ae7e29 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/GetUserByEmailDatabaseRepositoryAdapterTest.java @@ -0,0 +1,84 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.exception.ForbiddenException; +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.GetUserByEmailHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.GetUserPreferencesByUserIdHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.hibernate.UserHibernateModelFactory; +import com.cuoco.factory.hibernate.UserPreferencesHibernateModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetUserByEmailDatabaseRepositoryAdapterTest { + + @Mock + private GetUserByEmailHibernateRepositoryAdapter userRepository; + + @Mock + private GetUserPreferencesByUserIdHibernateRepositoryAdapter preferencesRepository; + + @InjectMocks + private GetUserByEmailDatabaseRepositoryAdapter adapter; + + @Test + void WHEN_execute_with_existing_user_and_preferences_THEN_return_user_with_preferences() { + UserPreferencesHibernateModel preferencesModel = UserPreferencesHibernateModelFactory.create(); + UserHibernateModel userModel = preferencesModel.getUser(); + String email = userModel.getEmail(); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(userModel)); + when(preferencesRepository.findById(userModel.getId())).thenReturn(Optional.of(preferencesModel)); + + User result = adapter.execute(email); + + assertEquals(email, result.getEmail()); + assertNotNull(result.getPreferences()); + verify(userRepository).findByEmail(email); + verify(preferencesRepository).findById(userModel.getId()); + } + + @Test + void WHEN_execute_with_existing_user_but_missing_preferences_THEN_throw_UnprocessableException() { + UserHibernateModel userModel = UserHibernateModelFactory.create(); + String email = userModel.getEmail(); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(userModel)); + when(preferencesRepository.findById(userModel.getId())).thenReturn(Optional.empty()); + + UnprocessableException ex = assertThrows(UnprocessableException.class, () -> adapter.execute(email)); + assertEquals(ErrorDescription.UNEXPECTED_ERROR.getValue(), ex.getDescription()); + + verify(userRepository).findByEmail(email); + verify(preferencesRepository).findById(userModel.getId()); + } + + @Test + void WHEN_execute_with_non_existing_user_THEN_throw_ForbiddenException() { + String email = "notfound@example.com"; + + when(userRepository.findByEmail(email)).thenReturn(Optional.empty()); + + ForbiddenException ex = assertThrows(ForbiddenException.class, () -> adapter.execute(email)); + assertEquals(ErrorDescription.INVALID_CREDENTIALS.getValue(), ex.getDescription()); + + verify(userRepository).findByEmail(email); + verifyNoInteractions(preferencesRepository); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..b02bd9e --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapterTest.java @@ -0,0 +1,104 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; +import com.cuoco.adapter.out.hibernate.repository.CreateUserHibernateRepositoryAdapter; +import com.cuoco.adapter.out.hibernate.repository.CreateUserPreferencesHibernateRepositoryAdapter; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.UserFactory; +import com.cuoco.factory.hibernate.UserHibernateModelFactory; +import com.cuoco.factory.hibernate.UserPreferencesHibernateModelFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UpdateUserDatabaseRepositoryAdapterTest { + + @Mock + private CreateUserHibernateRepositoryAdapter createUserHibernateRepositoryAdapter; + @Mock + private CreateUserPreferencesHibernateRepositoryAdapter createUserPreferencesHibernateRepositoryAdapter; + + private UpdateUserDatabaseRepositoryAdapter updateUserDatabaseRepositoryAdapter; + + @BeforeEach + void setUp() { + updateUserDatabaseRepositoryAdapter = new UpdateUserDatabaseRepositoryAdapter( + createUserHibernateRepositoryAdapter, + createUserPreferencesHibernateRepositoryAdapter + ); + } + + @Test + void shouldUpdateUserSuccessfully() { + User userToUpdate = UserFactory.create(); + userToUpdate.setEmail("test@example.com"); + userToUpdate.setName("Updated Name"); + + UserHibernateModel savedUser = UserHibernateModelFactory.create(); + savedUser.setName("Updated Name"); + + UserPreferencesHibernateModel savedPreferences = UserPreferencesHibernateModelFactory.create(); + + when(createUserHibernateRepositoryAdapter.save(any(UserHibernateModel.class))) + .thenReturn(savedUser); + when(createUserPreferencesHibernateRepositoryAdapter.save(any(UserPreferencesHibernateModel.class))) + .thenReturn(savedPreferences); + + User result = updateUserDatabaseRepositoryAdapter.execute(userToUpdate); + + assertNotNull(result); + verify(createUserHibernateRepositoryAdapter, times(1)).save(any(UserHibernateModel.class)); + verify(createUserPreferencesHibernateRepositoryAdapter, times(1)).save(any(UserPreferencesHibernateModel.class)); + } + + @Test + void shouldUpdateUserWithAllFields() { + User userToUpdate = UserFactory.create(); + userToUpdate.setEmail("test@example.com"); + + UserHibernateModel savedUser = UserHibernateModelFactory.create(); + UserPreferencesHibernateModel savedPreferences = UserPreferencesHibernateModelFactory.create(); + + when(createUserHibernateRepositoryAdapter.save(any(UserHibernateModel.class))) + .thenReturn(savedUser); + when(createUserPreferencesHibernateRepositoryAdapter.save(any(UserPreferencesHibernateModel.class))) + .thenReturn(savedPreferences); + + User result = updateUserDatabaseRepositoryAdapter.execute(userToUpdate); + + assertNotNull(result); + verify(createUserHibernateRepositoryAdapter, times(1)).save(any(UserHibernateModel.class)); + verify(createUserPreferencesHibernateRepositoryAdapter, times(1)).save(any(UserPreferencesHibernateModel.class)); + } + + @Test + void shouldHandleUserWithNullFields() { + User userToUpdate = UserFactory.create(); + userToUpdate.setEmail("test@example.com"); + userToUpdate.setName(null); + + UserHibernateModel savedUser = UserHibernateModelFactory.create(); + UserPreferencesHibernateModel savedPreferences = UserPreferencesHibernateModelFactory.create(); + + when(createUserHibernateRepositoryAdapter.save(any(UserHibernateModel.class))) + .thenReturn(savedUser); + when(createUserPreferencesHibernateRepositoryAdapter.save(any(UserPreferencesHibernateModel.class))) + .thenReturn(savedPreferences); + + User result = updateUserDatabaseRepositoryAdapter.execute(userToUpdate); + + assertNotNull(result); + verify(createUserHibernateRepositoryAdapter, times(1)).save(any(UserHibernateModel.class)); + verify(createUserPreferencesHibernateRepositoryAdapter, times(1)).save(any(UserPreferencesHibernateModel.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapterTest.java new file mode 100644 index 0000000..150cd5e --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/hibernate/UserExistsByEmailDatabaseRepositoryAdapterTest.java @@ -0,0 +1,48 @@ +package com.cuoco.adapter.out.hibernate; + +import com.cuoco.adapter.out.hibernate.repository.ExistsUserByEmailHibernateRepositoryAdapter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class UserExistsByEmailDatabaseRepositoryAdapterTest { + + @Mock + private ExistsUserByEmailHibernateRepositoryAdapter hibernateRepository; + + private ExistsUserByEmailDatabaseRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + adapter = new ExistsUserByEmailDatabaseRepositoryAdapter(hibernateRepository); + } + + @Test + void whenEmailExists_thenReturnTrue() { + String email = "test@example.com"; + when(hibernateRepository.existsByEmail(email)).thenReturn(true); + + Boolean result = adapter.execute(email); + + assertTrue(result); + verify(hibernateRepository).existsByEmail(email); + } + + @Test + void whenEmailDoesNotExist_thenReturnFalse() { + String email = "notfound@example.com"; + when(hibernateRepository.existsByEmail(email)).thenReturn(false); + + Boolean result = adapter.execute(email); + + assertFalse(result); + verify(hibernateRepository).existsByEmail(email); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapterTest.java new file mode 100644 index 0000000..f1a8578 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/CreateRecipeByNameGeminiRestRespositoryAdapterTest.java @@ -0,0 +1,102 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.RecipeResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.CandidateGeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.ParametricDataFactory; +import com.cuoco.factory.gemini.RecipeResponseGeminiModelFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CreateRecipeByNameGeminiRestRespositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + private ObjectMapper objectMapper; + private CreateRecipeByNameGeminiRestRespositoryAdapter adapter; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + adapter = new CreateRecipeByNameGeminiRestRespositoryAdapter(objectMapper, restTemplate); + } + + @Test + void shouldCreateRecipeByNameSuccessfully() throws Exception { + String recipeName = "Pasta Carbonara"; + ParametricData parametricData = ParametricDataFactory.create(); + RecipeResponseGeminiModel expectedRecipe = RecipeResponseGeminiModelFactory.create(); + String expectedResponse = objectMapper.writeValueAsString(List.of(expectedRecipe)); + + GeminiResponseModel geminiResponse = GeminiResponseModel.builder() + .candidates(List.of(CandidateGeminiResponseModel.builder() + .content(ContentGeminiRequestModel.builder() + .parts(List.of(PartGeminiRequestModel.builder().text(expectedResponse).build())) + .build()) + .build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponse); + + Recipe result = adapter.execute(recipeName, parametricData); + + assertNotNull(result); + assertEquals(expectedRecipe.toDomain().getName(), result.getName()); + } + + @Test + void shouldThrowExceptionWhenGeminiResponseIsNull() { + // Given + String recipeName = "Pasta Carbonara"; + ParametricData parametricData = ParametricDataFactory.create(); + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(null); + + // When & Then + assertThrows(NotAvailableException.class, () -> adapter.execute(recipeName, parametricData)); + } + + @Test + void shouldThrowExceptionWhenEmptyRecipeList() throws Exception { + // Given + String recipeName = "Pasta Carbonara"; + ParametricData parametricData = ParametricDataFactory.create(); + String emptyResponse = objectMapper.writeValueAsString(List.of()); + + GeminiResponseModel geminiResponse = GeminiResponseModel.builder() + .candidates(List.of(CandidateGeminiResponseModel.builder() + .content(ContentGeminiRequestModel.builder() + .parts(List.of(PartGeminiRequestModel.builder().text(emptyResponse).build())) + .build()) + .build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponse); + + assertThrows(NotAvailableException.class, () -> adapter.execute(recipeName, parametricData)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..5498813 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,77 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.application.port.out.GetIngredientsFromAudioRepository; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.factory.domain.IngredientFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapterTest { + + private GetIngredientsFromAudioRepository getIngredientsFromAudioRepository; + + private GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setup() { + getIngredientsFromAudioRepository = mock(GetIngredientsFromAudioRepository.class); + adapter = new GetIngredientsFromAudioAsyncGeminiRestRepositoryAdapter(getIngredientsFromAudioRepository); + } + + @Test + void GIVEN_valid_audio_WHEN_execute_THEN_return_ingredients_async() throws Exception { + String audioBase64 = "base64-audio"; + String format = "mp3"; + String language = "en"; + + List expectedIngredients = List.of( + IngredientFactory.create("Tomato", 1.0, "ud"), + IngredientFactory.create("Onion", 2.0, "ud") + ); + + when(getIngredientsFromAudioRepository.execute(audioBase64, format, language)) + .thenReturn(expectedIngredients); + + CompletableFuture> future = adapter.execute(audioBase64, format, language); + + assertNotNull(future); + List result = future.get(); + + assertEquals(expectedIngredients, result); + verify(getIngredientsFromAudioRepository).execute(audioBase64, format, language); + } + + @Test + void GIVEN_repository_throws_exception_WHEN_execute_THEN_throw_UnprocessableException() { + String audioBase64 = "invalid-audio"; + String format = "mp3"; + String language = "en"; + + when(getIngredientsFromAudioRepository.execute(audioBase64, format, language)) + .thenThrow(new RuntimeException("Failed to process audio")); + + CompletableFuture> future = adapter.execute(audioBase64, format, language); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + assertTrue(ex.getCause() instanceof UnprocessableException); + assertEquals(ErrorDescription.AUDIO_FILE_PROCESSING_ERROR.getValue(), + ((UnprocessableException) ex.getCause()).getDescription()); + + verify(getIngredientsFromAudioRepository).execute(audioBase64, format, language); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..ba34eac --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromAudioGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,88 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.UnprocessableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.factory.gemini.GeminiResponseModelFactory; +import com.cuoco.factory.gemini.IngredientResponseGeminiModelFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetIngredientsFromAudioGeminiRestRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private GetIngredientsFromAudioGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setup() { + ReflectionTestUtils.setField(adapter, "url", "https://gemini.api"); + ReflectionTestUtils.setField(adapter, "apiKey", "test-api-key"); + ReflectionTestUtils.setField(adapter, "temperature", 0.7); + ReflectionTestUtils.setField(adapter, "VOICE_PROMPT", "Recognize ingredients from audio: %s"); + } + + @Test + void GIVEN_valid_audio_WHEN_execute_THEN_return_ingredients() throws Exception { + IngredientResponseGeminiModel ingredientResponse = IngredientResponseGeminiModelFactory.create("Tomate"); + List expectedIngredients = List.of(ingredientResponse.toDomain()); + + String responseJson = new ObjectMapper().writeValueAsString(List.of(ingredientResponse)); + GeminiResponseModel responseModel = GeminiResponseModelFactory.create(responseJson); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(responseModel); + + List result = adapter.execute("audioBase64", "mp3", "en"); + + assertNotNull(result); + assertEquals(expectedIngredients.size(), result.size()); + assertEquals(expectedIngredients.get(0).getName(), result.get(0).getName()); + } + + @Test + void GIVEN_invalid_json_WHEN_execute_THEN_throw_UnprocessableException() { + GeminiResponseModel responseModel = GeminiResponseModelFactory.create("INVALID JSON"); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(responseModel); + + UnprocessableException exception = assertThrows(UnprocessableException.class, () -> + adapter.execute("audioBase64", "mp3", "en")); + + assertTrue(exception.getDescription().contains("Error processing voice")); + } + + @Test + void GIVEN_restTemplate_throws_WHEN_execute_THEN_throw_UnprocessableException() { + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenThrow(new RuntimeException("Timeout")); + + UnprocessableException exception = assertThrows(UnprocessableException.class, () -> + adapter.execute("audioBase64", "mp3", "en")); + + assertTrue(exception.getDescription().contains("Error processing voice")); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapterTest.java new file mode 100644 index 0000000..4ee786c --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsFromImageGeminiRestImageRepositoryAdapterTest.java @@ -0,0 +1,96 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.factory.domain.FileModelFactory; +import com.cuoco.factory.gemini.GeminiResponseModelFactory; +import com.cuoco.factory.gemini.IngredientResponseGeminiModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetIngredientsFromImageGeminiRestImageRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private GetIngredientsFromImageGeminiRestImageRepositoryAdapter adapter; + + @BeforeEach + void setup() { + ReflectionTestUtils.setField(adapter, "url", "https://gemini.api"); + ReflectionTestUtils.setField(adapter, "apiKey", "test-api-key"); + ReflectionTestUtils.setField(adapter, "temperature", 0.7); + ReflectionTestUtils.setField(adapter, "PROMPT", "Recognize ingredients from this image:"); + } + + @Test + void GIVEN_valid_image_WHEN_execute_THEN_return_ingredients() throws Exception { + IngredientResponseGeminiModel ingredientResponse = IngredientResponseGeminiModelFactory.create("Tomate"); + + String jsonResponse = new ObjectMapper().writeValueAsString(List.of(ingredientResponse)); + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create(jsonResponse); + + File image = FileModelFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + List result = adapter.execute(List.of(image)); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Tomate", result.get(0).getName()); + assertEquals("image", result.get(0).getSource()); + } + + @Test + void GIVEN_invalid_json_WHEN_execute_THEN_throw_NotAvailableException() { + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create("INVALID_JSON"); + + File image = FileModelFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + NotAvailableException ex = assertThrows(NotAvailableException.class, () -> + adapter.execute(List.of(image))); + + assertEquals(ErrorDescription.NOT_AVAILABLE.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_restTemplate_throws_WHEN_execute_THEN_throw_NotAvailableException() { + File image = FileModelFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenThrow(new RuntimeException("Connection error")); + + NotAvailableException ex = assertThrows(NotAvailableException.class, () -> + adapter.execute(List.of(image))); + + assertEquals(ErrorDescription.NOT_AVAILABLE.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapterTest.java new file mode 100644 index 0000000..6fc4125 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapterTest.java @@ -0,0 +1,119 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.application.usecase.model.File; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.Unit; +import com.cuoco.factory.domain.FileModelFactory; +import com.cuoco.factory.gemini.GeminiResponseModelFactory; +import com.cuoco.factory.gemini.IngredientResponseGeminiModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @Mock + private ObjectMapper objectMapper; + + @InjectMocks + private GetIngredientsGroupedFromImagesGeminiRestFromImagesRepositoryAdapter adapter; + + @BeforeEach + void setup() { + ReflectionTestUtils.setField(adapter, "url", "https://gemini.api"); + ReflectionTestUtils.setField(adapter, "apiKey", "test-api-key"); + ReflectionTestUtils.setField(adapter, "temperature", 0.7); + ReflectionTestUtils.setField(adapter, "PROMPT", "Recognize ingredients from this image:"); + } + + @Test + void GIVEN_valid_images_WHEN_execute_THEN_return_grouped_ingredients() throws Exception { + IngredientResponseGeminiModel ingredientResponse = IngredientResponseGeminiModelFactory.create("Cheese"); + List expected = List.of(ingredientResponse.toDomain()); + + String jsonResponse = new ObjectMapper().writeValueAsString(List.of(ingredientResponse)); + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create(jsonResponse); + + File image1 = FileModelFactory.create("image1.png", "image/png", "base64data1"); + File image2 = FileModelFactory.create("image2.jpg", "image/jpeg", "base64data2"); + + ParametricData parametricData = ParametricData.builder() + .units(List.of(Unit.builder().id(1).build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + Map> result = adapter.execute(List.of(image1, image2), parametricData); + + assertEquals(2, result.size()); + assertTrue(result.containsKey("image1.png")); + assertTrue(result.containsKey("image2.jpg")); + + for (List ingredients : result.values()) { + assertEquals(1, ingredients.size()); + assertEquals("Cheese", ingredients.get(0).getName()); + assertEquals("image", ingredients.get(0).getSource()); + } + } + + @Test + void GIVEN_invalid_json_WHEN_execute_THEN_throw_NotAvailableException() { + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create("INVALID_JSON"); + + File image = FileModelFactory.create(); + ParametricData parametricData = ParametricData.builder() + .units(List.of(Unit.builder().id(1).build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + NotAvailableException ex = assertThrows(NotAvailableException.class, () -> + adapter.execute(List.of(image), parametricData)); + + assertEquals(ErrorDescription.NOT_AVAILABLE.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_restTemplate_throws_WHEN_execute_THEN_throw_NotAvailableException() { + File image = FileModelFactory.create(); + ParametricData parametricData = ParametricData.builder() + .units(List.of(Unit.builder().id(1).build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenThrow(new RuntimeException("Connection error")); + + NotAvailableException ex = assertThrows(NotAvailableException.class, () -> + adapter.execute(List.of(image), parametricData)); + + assertEquals(ErrorDescription.NOT_AVAILABLE.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..2f0bd15 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetMealPrepsFromIngredientsGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,80 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.MealPrepResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.CandidateGeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.factory.domain.MealPrepFactory; +import com.cuoco.factory.gemini.MealPrepResponseGeminiModelFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetMealPrepsFromIngredientsGeminiRestRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + private ObjectMapper objectMapper; + private GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + adapter = new GetMealPrepsFromIngredientsGeminiRestRepositoryAdapter(objectMapper, restTemplate); + } + + @Test + void shouldGenerateMealPrepsSuccessfully() throws Exception { + // Given + MealPrep mealPrep = MealPrepFactory.create(); + List expectedMealPreps = List.of(MealPrepResponseGeminiModelFactory.create()); + String expectedResponse = objectMapper.writeValueAsString(expectedMealPreps); + + GeminiResponseModel geminiResponse = GeminiResponseModel.builder() + .candidates(List.of(CandidateGeminiResponseModel.builder() + .content(ContentGeminiRequestModel.builder() + .parts(List.of(PartGeminiRequestModel.builder().text(expectedResponse).build())) + .build()) + .build())) + .build(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponse); + + // When + List result = adapter.execute(mealPrep); + + // Then + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + void shouldThrowExceptionWhenGeminiResponseIsNull() { + // Given + MealPrep mealPrep = MealPrepFactory.create(); + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(null); + + // When & Then + assertThrows(Exception.class, () -> adapter.execute(mealPrep)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..f704db5 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipeMainImageGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,62 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.utils.ImageUtils; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.RecipeFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.client.RestTemplate; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetRecipeMainImageGeminiRestRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @Mock + private ImageUtils imageUtils; + + private GetRecipeMainImageGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + adapter = new GetRecipeMainImageGeminiRestRepositoryAdapter(restTemplate, imageUtils); + } + + @Test + void shouldGenerateRecipeMainImageSuccessfully() throws Exception { + Recipe recipe = RecipeFactory.create(); + + when(imageUtils.imageExists(any(), any())).thenReturn(false); + when(imageUtils.buildPromptBody(any())).thenReturn(null); + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(GeminiResponseModel.builder().build()); + when(imageUtils.extractImageFromResponse(any())).thenReturn(null); + + boolean result = adapter.execute(recipe); + + assertTrue(result); + } + + @Test + void shouldThrowExceptionWhenGeminiResponseIsNull() { + Recipe recipe = RecipeFactory.create(); + when(imageUtils.imageExists(any(), any())).thenReturn(false); + when(imageUtils.buildPromptBody(any())).thenReturn(null); + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(null); + + assertThrows(Exception.class, () -> adapter.execute(recipe)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..4451d8b --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetRecipesFromIngredientsGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,90 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.adapter.out.rest.gemini.model.RecipeResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.gemini.GeminiResponseModelFactory; +import com.cuoco.factory.gemini.RecipeResponseGeminiModelFactory; +import com.cuoco.shared.model.ErrorDescription; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetRecipesFromIngredientsGeminiRestRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private GetRecipesFromIngredientsGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setup() { + ReflectionTestUtils.setField(adapter, "url", "https://gemini.api"); + ReflectionTestUtils.setField(adapter, "apiKey", "test-api-key"); + ReflectionTestUtils.setField(adapter, "temperature", 0.7); + ReflectionTestUtils.setField(adapter, "BASIC_PROMPT", "Generar recetas para {{INGREDIENTS}}, máximo {{MAX_RECIPES}}."); + ReflectionTestUtils.setField(adapter, "FILTERS_PROMPT", " Filtros: nivel {{COOK_LEVEL}}, tiempo {{COOK_TIME}}, tipos {{FOOD_TYPES}}, dieta {{DIET}}, cantidad {{QUANTITY}}."); + } + + @Test + void GIVEN_valid_ingredients_WHEN_execute_THEN_return_recipes() throws Exception { + Recipe recipe = RecipeFactory.createWithFilters(); + + RecipeResponseGeminiModel recipeResponseModel = RecipeResponseGeminiModelFactory.create(); + String responseJson = new ObjectMapper().writeValueAsString(List.of(recipeResponseModel)); + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create(responseJson); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + List result = adapter.execute(recipe); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(recipeResponseModel.getName(), result.get(0).getName()); + } + + @Test + void GIVEN_null_response_WHEN_execute_THEN_throw_UnprocessableException() { + Recipe recipe = RecipeFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(null); + + NotAvailableException ex = assertThrows(NotAvailableException.class, () -> adapter.execute(recipe)); + assertEquals(ErrorDescription.NOT_AVAILABLE.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_json_WHEN_execute_THEN_throw_NotAvailableException() { + Recipe recipe = RecipeFactory.create(); + + GeminiResponseModel geminiResponseModel = GeminiResponseModelFactory.create("INVALID JSON"); + + when(restTemplate.postForObject(anyString(), any(), eq(GeminiResponseModel.class))) + .thenReturn(geminiResponseModel); + + assertThrows(NotAvailableException.class, () -> adapter.execute(recipe)); + } +} + diff --git a/src/test/java/com/cuoco/adapter/out/rest/gemini/GetStepsImagesGeminiRestRepositoryAdapterTest.java b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetStepsImagesGeminiRestRepositoryAdapterTest.java new file mode 100644 index 0000000..1743628 --- /dev/null +++ b/src/test/java/com/cuoco/adapter/out/rest/gemini/GetStepsImagesGeminiRestRepositoryAdapterTest.java @@ -0,0 +1,90 @@ +package com.cuoco.adapter.out.rest.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.utils.ImageUtils; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import com.cuoco.factory.domain.RecipeFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetStepsImagesGeminiRestRepositoryAdapterTest { + + @Mock + private RestTemplate restTemplate; + + @Mock + private ImageUtils imageUtils; + + @Mock + private GeminiResponseModel geminiResponseModel; + + private GetRecipeStepsImagesGeminiRestRepositoryAdapter adapter; + + @BeforeEach + void setUp() { + adapter = new GetRecipeStepsImagesGeminiRestRepositoryAdapter(restTemplate, imageUtils); + ReflectionTestUtils.setField(adapter, "imageUrl", "https://test-url.com"); + ReflectionTestUtils.setField(adapter, "apiKey", "test-api-key"); + ReflectionTestUtils.setField(adapter, "temperature", 0.7); + } + + @Test + void execute_whenValidRecipe_thenReturnRecipeImages() { + Recipe recipe = RecipeFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), any())).thenReturn(geminiResponseModel); + + List result = adapter.execute(recipe); + + assertNotNull(result); + } + + @Test + void execute_whenNullResponse_thenReturnEmptyList() { + Recipe recipe = RecipeFactory.create(); + + when(restTemplate.postForObject(anyString(), any(), any())).thenReturn(null); + + List result = adapter.execute(recipe); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void execute_whenRecipeWithEmptyInstructions_thenReturnOnlyMainImage() { + Recipe recipeWithEmptyInstructions = RecipeFactory.createWithEmptyInstructions(); + + when(restTemplate.postForObject(anyString(), any(), any())).thenReturn(geminiResponseModel); + + List result = adapter.execute(recipeWithEmptyInstructions); + + assertNotNull(result); + } + + @Test + void execute_whenRecipeWithManySteps_thenReturnMaxFiveStepImages() { + Recipe recipeWithManySteps = RecipeFactory.createWithManySteps(); + + when(restTemplate.postForObject(anyString(), any(), any())).thenReturn(geminiResponseModel); + + List result = adapter.execute(recipeWithManySteps); + + assertNotNull(result); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/AuthenticateUserCommandUnitTest.java b/src/test/java/com/cuoco/application/command/AuthenticateUserCommandUnitTest.java deleted file mode 100644 index 092f20f..0000000 --- a/src/test/java/com/cuoco/application/command/AuthenticateUserCommandUnitTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.AuthenticateUserCommand; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("AuthenticateUserCommand - 5 Unit Tests") -class AuthenticateUserCommandUnitTest { - - @Test - @DisplayName("Test 1: Command should be created with valid JWT token") - void test1_commandShouldBeCreatedWithValidJWTToken() { - // Given - String jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.validtoken"; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(jwtToken); - - // Then - assertNotNull(command); - assertEquals(jwtToken, command.getAuthHeader()); - } - - @Test - @DisplayName("Test 2: Command should handle empty token") - void test2_commandShouldHandleEmptyToken() { - // Given - String emptyToken = ""; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(emptyToken); - - // Then - assertNotNull(command); - assertEquals(emptyToken, command.getAuthHeader()); - } - - @Test - @DisplayName("Test 3: Command should handle Bearer token format") - void test3_commandShouldHandleBearerTokenFormat() { - // Given - String bearerToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.bearertoken"; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(bearerToken); - - // Then - assertNotNull(command); - assertEquals(bearerToken, command.getAuthHeader()); - assertTrue(command.getAuthHeader().startsWith("Bearer ")); - } - - @Test - @DisplayName("Test 4: Command should handle malformed token") - void test4_commandShouldHandleMalformedToken() { - // Given - String malformedToken = "invalid.token.format"; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(malformedToken); - - // Then - assertNotNull(command); - assertEquals(malformedToken, command.getAuthHeader()); - } - - @Test - @DisplayName("Test 5: Command should handle null token") - void test5_commandShouldHandleNullToken() { - // Given - String nullToken = null; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(nullToken); - - // Then - assertNotNull(command); - assertNull(command.getAuthHeader()); - } - - @Test - @DisplayName("Test 6: Command should have proper toString method") - void test6_commandShouldHaveProperToStringMethod() { - // Given - String authHeader = "Bearer jwt-token-123"; - - // When - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - String result = command.toString(); - - // Then - assertNotNull(result); - assertTrue(result.contains("Command")); - assertTrue(result.contains("authHeader")); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/CreateUserCommandCommandTest.java b/src/test/java/com/cuoco/application/command/CreateUserCommandCommandTest.java deleted file mode 100644 index 065a9c9..0000000 --- a/src/test/java/com/cuoco/application/command/CreateUserCommandCommandTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.CreateUserCommand.Command; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.time.LocalDate; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("CreateUserCommand.Command - Unit Tests") -class CreateUserCommandCommandTest { - - @Test - @DisplayName("Command should be created with valid data") - void test_commandCreatedWithValidData() { - Command command = new Command( - "Juan", - "juan@mail.com", - "securePass", - LocalDate.now(), - "premium", - true, - "advanced", - "vegan", - List.of("gluten-free"), - List.of("peanuts") - ); - - assertNotNull(command); - assertEquals("Juan", command.getName()); - assertEquals("premium", command.getPlan()); - assertTrue(command.getIsValid()); - } - - @Test - @DisplayName("Command should accept empty allergies and dietaryNeeds") - void test_commandWithEmptyLists() { - Command command = new Command( - "Ana", - "ana@mail.com", - "1234", - LocalDate.now(), - "basic", - false, - "beginner", - "omnivore", - List.of(), - List.of() - ); - - assertNotNull(command); - assertTrue(command.getDietaryNeeds().isEmpty()); - assertTrue(command.getAllergies().isEmpty()); - } - - @Test - @DisplayName("Command with null fields should still construct") - void test_commandWithNullFields() { - Command command = new Command( - null, null, null, null, - null, null, null, null, - null, null - ); - - assertNotNull(command); - assertNull(command.getName()); - assertNull(command.getDiet()); - } - - @Test - @DisplayName("Command should correctly store a list of dietary needs") - void test_commandWithDietaryNeedsList() { - List needs = List.of("low-carb", "gluten-free"); - Command command = new Command( - "Lucia", - "lucia@example.com", - "luciaPass", - LocalDate.of(2023, 5, 10), - "pro", - true, - "intermediate", - "vegetarian", - needs, - List.of() - ); - - assertEquals(2, command.getDietaryNeeds().size()); - assertTrue(command.getDietaryNeeds().contains("low-carb")); - } - - @Test - @DisplayName("Command with future registration date") - void test_commandWithFutureRegisterDate() { - LocalDate futureDate = LocalDate.now().plusDays(5); - Command command = new Command( - "FutureUser", - "future@example.com", - "future123", - futureDate, - "test-plan", - true, - "expert", - "keto", - List.of(), - List.of() - ); - - assertEquals(futureDate, command.getRegisterDate()); - assertTrue(command.getRegisterDate().isAfter(LocalDate.now())); - } -} diff --git a/src/test/java/com/cuoco/application/command/GetIngredientsFromFileCommandUnitTest.java b/src/test/java/com/cuoco/application/command/GetIngredientsFromFileCommandUnitTest.java deleted file mode 100644 index 9f72312..0000000 --- a/src/test/java/com/cuoco/application/command/GetIngredientsFromFileCommandUnitTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.GetIngredientsFromFileCommand; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.multipart.MultipartFile; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("GetIngredientsFromFileCommand - 5 Unit Tests") -class GetIngredientsFromFileCommandUnitTest { - - @Test - @DisplayName("Test 1: Command should be created with valid files") - void test1_commandShouldBeCreatedWithValidFiles() { - // Given - MockMultipartFile file1 = new MockMultipartFile("file1", "test1.jpg", "image/jpeg", "content1".getBytes()); - MockMultipartFile file2 = new MockMultipartFile("file2", "test2.jpg", "image/jpeg", "content2".getBytes()); - List files = Arrays.asList(file1, file2); - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - // Then - assertNotNull(command); - assertEquals(files, command.getFiles()); - assertEquals(2, command.getFiles().size()); - assertEquals("test1.jpg", command.getFiles().get(0).getOriginalFilename()); - assertEquals("test2.jpg", command.getFiles().get(1).getOriginalFilename()); - } - - @Test - @DisplayName("Test 2: Command should handle empty files list") - void test2_commandShouldHandleEmptyFilesList() { - // Given - List emptyFiles = Collections.emptyList(); - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(emptyFiles); - - // Then - assertNotNull(command); - assertEquals(emptyFiles, command.getFiles()); - assertTrue(command.getFiles().isEmpty()); - } - - @Test - @DisplayName("Test 3: Command should handle single file") - void test3_commandShouldHandleSingleFile() { - // Given - MockMultipartFile singleFile = new MockMultipartFile("file", "single.png", "image/png", "single content".getBytes()); - List files = Arrays.asList(singleFile); - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - // Then - assertNotNull(command); - assertEquals(files, command.getFiles()); - assertEquals(1, command.getFiles().size()); - assertEquals("single.png", command.getFiles().get(0).getOriginalFilename()); - assertEquals("image/png", command.getFiles().get(0).getContentType()); - } - - @Test - @DisplayName("Test 4: Command should handle different file types") - void test4_commandShouldHandleDifferentFileTypes() { - // Given - MockMultipartFile jpegFile = new MockMultipartFile("file1", "image.jpg", "image/jpeg", "jpeg content".getBytes()); - MockMultipartFile pngFile = new MockMultipartFile("file2", "image.png", "image/png", "png content".getBytes()); - MockMultipartFile webpFile = new MockMultipartFile("file3", "image.webp", "image/webp", "webp content".getBytes()); - List files = Arrays.asList(jpegFile, pngFile, webpFile); - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - // Then - assertNotNull(command); - assertEquals(3, command.getFiles().size()); - assertEquals("image/jpeg", command.getFiles().get(0).getContentType()); - assertEquals("image/png", command.getFiles().get(1).getContentType()); - assertEquals("image/webp", command.getFiles().get(2).getContentType()); - } - - @Test - @DisplayName("Test 5: Command should handle null files list") - void test5_commandShouldHandleNullFilesList() { - // Given - List nullFiles = null; - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(nullFiles); - - // Then - assertNotNull(command); - assertNull(command.getFiles()); - } - - @Test - @DisplayName("Test 6: Command should have proper toString method") - void test6_commandShouldHaveProperToStringMethod() { - // Given - MockMultipartFile file = new MockMultipartFile("file", "test.jpg", "image/jpeg", "content".getBytes()); - List files = Arrays.asList(file); - - // When - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - String result = command.toString(); - - // Then - assertNotNull(result); - assertTrue(result.contains("Command")); - assertTrue(result.contains("files")); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/GetIngredientsFromTextCommandUnitTest.java b/src/test/java/com/cuoco/application/command/GetIngredientsFromTextCommandUnitTest.java deleted file mode 100644 index 151e6fa..0000000 --- a/src/test/java/com/cuoco/application/command/GetIngredientsFromTextCommandUnitTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.GetIngredientsFromTextCommand; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("GetIngredientsFromTextCommand - 5 Unit Tests") -class GetIngredientsFromTextCommandUnitTest { - - @Test - @DisplayName("Test 1: Command should be created with valid text") - void test1_commandShouldBeCreatedWithValidText() { - // Given - String text = "tomate, cebolla, ajo"; - String source = "manual"; - - // When - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command(text, source); - - // Then - assertNotNull(command); - assertEquals(text, command.getText()); - assertEquals(source, command.getSource()); - } - - @Test - @DisplayName("Test 2: Command should handle empty text") - void test2_commandShouldHandleEmptyText() { - // Given - String emptyText = ""; - String source = "manual"; - - // When - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command(emptyText, source); - - // Then - assertNotNull(command); - assertEquals(emptyText, command.getText()); - assertEquals(source, command.getSource()); - } - - @Test - @DisplayName("Test 3: Command should handle null source") - void test3_commandShouldHandleNullSource() { - // Given - String text = "ingredientes"; - String nullSource = null; - - // When - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command(text, nullSource); - - // Then - assertNotNull(command); - assertEquals(text, command.getText()); - assertNull(command.getSource()); - } - - @Test - @DisplayName("Test 4: Command should handle long text") - void test4_commandShouldHandleLongText() { - // Given - String longText = "tomate, cebolla, ajo, pimiento rojo, aceite de oliva extra virgen, sal marina, pimienta negra recién molida"; - String source = "recipe"; - - // When - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command(longText, source); - - // Then - assertNotNull(command); - assertEquals(longText, command.getText()); - assertEquals(source, command.getSource()); - } - - @Test - @DisplayName("Test 5: Command should handle special characters") - void test5_commandShouldHandleSpecialCharacters() { - // Given - String textWithSpecialChars = "ñoquis, café, açúcar, jalapeño"; - String source = "international"; - - // When - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command(textWithSpecialChars, source); - - // Then - assertNotNull(command); - assertEquals(textWithSpecialChars, command.getText()); - assertEquals(source, command.getSource()); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/GetIngredientsFromVoiceCommandUnitTest.java b/src/test/java/com/cuoco/application/command/GetIngredientsFromVoiceCommandUnitTest.java deleted file mode 100644 index 96e8d46..0000000 --- a/src/test/java/com/cuoco/application/command/GetIngredientsFromVoiceCommandUnitTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.GetIngredientsFromVoiceCommand; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class GetIngredientsFromVoiceCommandUnitTest { - - @Test - void test1_command_shouldCreateCommandWithAllProperties() { - // Given - String audioBase64 = "base64EncodedAudioData"; - String format = "mp3"; - String language = "es-ES"; - - // When - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - // Then - assertNotNull(command); - assertEquals(audioBase64, command.getAudioBase64()); - assertEquals(format, command.getFormat()); - assertEquals(language, command.getLanguage()); - } - - @Test - void test2_command_shouldHandleDifferentAudioFormats() { - // Given - String audioBase64 = "longBase64AudioString"; - String format = "wav"; - String language = "en-US"; - - // When - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - // Then - assertEquals("wav", command.getFormat()); - assertEquals("en-US", command.getLanguage()); - assertEquals(audioBase64, command.getAudioBase64()); - } - - @Test - void test3_command_shouldHandleEmptyValues() { - // Given - String audioBase64 = ""; - String format = ""; - String language = ""; - - // When - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - // Then - assertNotNull(command); - assertEquals("", command.getAudioBase64()); - assertEquals("", command.getFormat()); - assertEquals("", command.getLanguage()); - } - - @Test - void test4_command_shouldHandleNullValues() { - // Given - String audioBase64 = null; - String format = null; - String language = null; - - // When - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - // Then - assertNotNull(command); - assertNull(command.getAudioBase64()); - assertNull(command.getFormat()); - assertNull(command.getLanguage()); - } - - @Test - void test5_command_shouldHandleComplexAudioBase64() { - // Given - String complexAudioBase64 = "UklGRiQNAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQANAACIhYqFbF1fdJivrJBhNjBcEgAAOaV..."; - String format = "ogg"; - String language = "es-AR"; - - // When - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(complexAudioBase64, format, language); - - // Then - assertEquals(complexAudioBase64, command.getAudioBase64()); - assertEquals("ogg", command.getFormat()); - assertEquals("es-AR", command.getLanguage()); - assertTrue(command.getAudioBase64().length() > 50); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/GetRecipesFromIngredientsCommandUnitTest.java b/src/test/java/com/cuoco/application/command/GetRecipesFromIngredientsCommandUnitTest.java deleted file mode 100644 index aabfa8a..0000000 --- a/src/test/java/com/cuoco/application/command/GetRecipesFromIngredientsCommandUnitTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; -import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.application.usecase.model.RecipeFilter; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class GetRecipesFromIngredientsCommandUnitTest { - - @Test - void test1_commandShouldStoreFiltersAndIngredientsCorrectly() { - // Given - RecipeFilter filter = new RecipeFilter("corto", "facil", Arrays.asList("pasta"), "vegetariano", 4); - List ingredients = Arrays.asList( - new Ingredient("tomate", "imagen", true), - new Ingredient("papa", "texto", false) - ); - - // When - GetRecipesFromIngredientsCommand.Command command = new GetRecipesFromIngredientsCommand.Command(filter, ingredients); - - // Then - assertEquals(filter, command.getFilters()); - assertEquals(ingredients, command.getIngredients()); - assertEquals(2, command.getIngredients().size()); - } - - @Test - void test2_commandShouldHandleNullFilters() { - // Given - List ingredients = Arrays.asList(new Ingredient("tomate", "imagen", true)); - - // When - GetRecipesFromIngredientsCommand.Command command = new GetRecipesFromIngredientsCommand.Command(null, ingredients); - - // Then - assertNull(command.getFilters()); - assertEquals(ingredients, command.getIngredients()); - } - - @Test - void test3_commandShouldHandleEmptyIngredients() { - // Given - RecipeFilter filter = new RecipeFilter("corto", "facil", Arrays.asList("pasta"), "vegetariano", 2); - List emptyIngredients = Arrays.asList(); - - // When - GetRecipesFromIngredientsCommand.Command command = new GetRecipesFromIngredientsCommand.Command(filter, emptyIngredients); - - // Then - assertEquals(filter, command.getFilters()); - assertTrue(command.getIngredients().isEmpty()); - } - - @Test - void test4_commandShouldHaveProperToString() { - // Given - RecipeFilter filter = new RecipeFilter("largo", "dificil", Arrays.asList("carne"), "omnivoro", 6); - List ingredients = Arrays.asList(new Ingredient("carne", "manual", true)); - - // When - GetRecipesFromIngredientsCommand.Command command = new GetRecipesFromIngredientsCommand.Command(filter, ingredients); - - // Then - String result = command.toString(); - assertNotNull(result); - assertTrue(result.contains("Command")); - assertTrue(result.contains("filters")); - assertTrue(result.contains("ingredients")); - } - - @Test - void test5_commandShouldHandleComplexFilters() { - // Given - RecipeFilter complexFilter = new RecipeFilter("medio", "intermedio", - Arrays.asList("italiana", "mexicana", "china"), "vegano", 8); - List multipleIngredients = Arrays.asList( - new Ingredient("quinoa", "manual", true), - new Ingredient("aguacate", "imagen", true), - new Ingredient("tofu", "texto", false) - ); - - // When - GetRecipesFromIngredientsCommand.Command command = new GetRecipesFromIngredientsCommand.Command(complexFilter, multipleIngredients); - - // Then - assertEquals(complexFilter, command.getFilters()); - assertEquals(3, command.getIngredients().size()); - assertEquals("vegano", command.getFilters().getDiet()); - assertEquals(3, command.getFilters().getTypes().size()); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/command/SignInUserCommandUnitTest.java b/src/test/java/com/cuoco/application/command/SignInUserCommandUnitTest.java deleted file mode 100644 index fc300b4..0000000 --- a/src/test/java/com/cuoco/application/command/SignInUserCommandUnitTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.cuoco.application.command; - -import com.cuoco.application.port.in.SignInUserCommand; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -@DisplayName("SignInUserCommand - 5 Unit Tests") -class SignInUserCommandUnitTest { - - @Test - @DisplayName("Test 1: Command should be created with valid credentials") - void test1_validCredentials() { - // Given - String email = "john@example.com"; - String password = "password123"; - - // When - SignInUserCommand.Command command = new SignInUserCommand.Command(email, password); - - // Then - assertNotNull(command); - assertEquals(email, command.getEmail()); - assertEquals(password, command.getPassword()); - } - - @Test - @DisplayName("Test 2: Command should handle empty email") - void test2_emptyEmail() { - // Given - String email = ""; - String password = "password123"; - - // When - SignInUserCommand.Command command = new SignInUserCommand.Command(email, password); - - // Then - assertEquals("", command.getEmail()); - assertEquals("password123", command.getPassword()); - } - - @Test - @DisplayName("Test 3: Command should handle null email") - void test3_nullEmail() { - // Given - String email = null; - String password = "password123"; - - // When - SignInUserCommand.Command command = new SignInUserCommand.Command(email, password); - - // Then - assertNull(command.getEmail()); - assertEquals("password123", command.getPassword()); - } - - @Test - @DisplayName("Test 4: Command should handle special characters in password") - void test4_specialCharactersInPassword() { - // Given - String email = "special@domain.com"; - String password = "P@ssw0rd!@#"; - - // When - SignInUserCommand.Command command = new SignInUserCommand.Command(email, password); - - // Then - assertEquals("special@domain.com", command.getEmail()); - assertEquals("P@ssw0rd!@#", command.getPassword()); - } - - @Test - @DisplayName("Test 5: Command should handle null password") - void test5_nullPassword() { - // Given - String email = "user@example.com"; - String password = null; - - // When - SignInUserCommand.Command command = new SignInUserCommand.Command(email, password); - - // Then - assertEquals("user@example.com", command.getEmail()); - assertNull(command.getPassword()); - } -} diff --git a/src/test/java/com/cuoco/application/controller/AuthenticationControllerAdapterUnitTest.java b/src/test/java/com/cuoco/application/controller/AuthenticationControllerAdapterUnitTest.java deleted file mode 100644 index 508bbe5..0000000 --- a/src/test/java/com/cuoco/application/controller/AuthenticationControllerAdapterUnitTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.cuoco.application.controller; - -import com.cuoco.adapter.in.controller.AuthenticationControllerAdapter; -import com.cuoco.adapter.in.controller.model.AuthRequest; -import com.cuoco.adapter.in.controller.model.AuthResponse; -import com.cuoco.adapter.in.controller.model.UserRequest; -import com.cuoco.application.port.in.CreateUserCommand; -import com.cuoco.application.port.in.SignInUserCommand; -import com.cuoco.application.usecase.model.AuthenticatedUser; -import com.cuoco.application.usecase.model.User; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("AuthenticationControllerAdapter - Unit Tests") -class AuthenticationControllerAdapterUnitTest { - - @Mock - private CreateUserCommand createUserCommand; - - @Mock - private SignInUserCommand signInUserCommand; - - private AuthenticationControllerAdapter controller; - - @BeforeEach - void setUp() { - controller = new AuthenticationControllerAdapter(signInUserCommand, createUserCommand); - } - - @Test - @DisplayName("Test 1: Successful login returns token") - void test1_loginSuccess() { - AuthRequest request = new AuthRequest(); - request.setEmail("john@example.com"); - request.setPassword("password123"); - - User user = new User(1L, "John", "john@example.com", null, null, true, null, LocalDateTime.now(), Collections.emptyList(), Collections.emptyList()); - AuthenticatedUser authenticatedUser = new AuthenticatedUser(user, "jwt-token", Arrays.asList("USER")); - - when(signInUserCommand.execute(any())).thenReturn(authenticatedUser); - - ResponseEntity response = controller.login(request); - - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue(response.getBody() instanceof AuthResponse); - assertEquals("jwt-token", ((AuthResponse) response.getBody()).getToken()); - } - - - @Test - @DisplayName("Test 2: Successful registration returns 201 CREATED") - void test2_registerSuccess() { - UserRequest request = new UserRequest( - "Jane", - "securepass", - "jane@example.com", - "Free", - "Intermediate", - "Vegan", - Arrays.asList("High Protein"), - Arrays.asList("Peanuts", "Shellfish") - ); - - ResponseEntity response = controller.register(request); - - assertEquals(HttpStatus.CREATED, response.getStatusCode()); - verify(createUserCommand, times(1)).execute(any()); - } - - @Test - @DisplayName("Test 3: Login throws exception when credentials are invalid") - void test3_loginThrowsException() { - AuthRequest request = new AuthRequest(); - request.setEmail("invalid@example.com"); - request.setPassword("wrong"); - - when(signInUserCommand.execute(any())).thenThrow(new RuntimeException("Invalid credentials")); - - RuntimeException exception = assertThrows(RuntimeException.class, () -> controller.login(request)); - assertEquals("Invalid credentials", exception.getMessage()); - } - - @Test - @DisplayName("Test 4: Register throws exception on validation failure") - void test4_registerThrowsException() { - UserRequest request = new UserRequest( - "InvalidUser", - "123", - "bad-email", - "Free", - "Beginner", - "None", - Collections.emptyList(), - Collections.emptyList() - ); - - doThrow(new RuntimeException("Validation error")).when(createUserCommand).execute(any()); - - RuntimeException exception = assertThrows(RuntimeException.class, () -> controller.register(request)); - assertEquals("Validation error", exception.getMessage()); - } - - - @Test - @DisplayName("Test 5: Login with null email throws exception") - void test5_loginNullEmail() { - AuthRequest request = new AuthRequest(); - request.setEmail(null); - request.setPassword("pass"); - - when(signInUserCommand.execute(any())).thenThrow(new NullPointerException("Email is null")); - - assertThrows(NullPointerException.class, () -> controller.login(request)); - } - - @Test - @DisplayName("Test 6: Register with missing required fields") - void test6_registerMissingFields() { - UserRequest request = new UserRequest( - "", // name vacío - "", // password vacío - "", // email vacío - "", // plan vacío - "", // cookLevel vacío - "", // diet vacío - null, // dietaryNeeds nulo - null // allergies nulo - ); - - doThrow(new IllegalArgumentException("Missing fields")).when(createUserCommand).execute(any()); - - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> controller.register(request)); - assertEquals("Missing fields", ex.getMessage()); - } -} diff --git a/src/test/java/com/cuoco/application/controller/RecipeControllerAdapterUnitTest.java b/src/test/java/com/cuoco/application/controller/RecipeControllerAdapterUnitTest.java deleted file mode 100644 index d4322c8..0000000 --- a/src/test/java/com/cuoco/application/controller/RecipeControllerAdapterUnitTest.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.cuoco.application.controller; - -import com.cuoco.adapter.in.controller.RecipeControllerAdapter; -import com.cuoco.adapter.out.rest.model.gemini.GeminiResponseMapper; -import com.cuoco.adapter.in.controller.model.FilterRequest; -import com.cuoco.adapter.in.controller.model.IngredientRequest; -import com.cuoco.adapter.in.controller.model.RecipeRequest; -import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class RecipeControllerAdapterUnitTest { - - @Mock - private GetRecipesFromIngredientsCommand getRecipesFromIngredientsCommand; - - @Mock - private GeminiResponseMapper geminiResponseMapper; - - private RecipeControllerAdapter controller; - - @BeforeEach - void setUp() { - controller = new RecipeControllerAdapter(getRecipesFromIngredientsCommand, geminiResponseMapper); - } - - @Test - void test1_shouldReturnRecipesWhenValidRequest() { - // Given - RecipeRequest request = createValidRecipeRequest(); - String geminiResponse = "[{\"id\":\"1\",\"name\":\"Test Recipe\"}]"; - Object parsedResponse = Arrays.asList("parsed recipe"); - - when(getRecipesFromIngredientsCommand.execute(any(GetRecipesFromIngredientsCommand.Command.class))) - .thenReturn(geminiResponse); - when(geminiResponseMapper.parseToJson(anyString())).thenReturn(parsedResponse); - - // When - ResponseEntity response = controller.generate(request); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(parsedResponse, response.getBody()); - verify(getRecipesFromIngredientsCommand).execute(any(GetRecipesFromIngredientsCommand.Command.class)); - verify(geminiResponseMapper).parseToJson(geminiResponse); - } - - @Test - void test2_shouldReturnErrorWhenCommandFails() { - // Given - RecipeRequest request = createValidRecipeRequest(); - when(getRecipesFromIngredientsCommand.execute(any(GetRecipesFromIngredientsCommand.Command.class))) - .thenThrow(new RuntimeException("Command failed")); - - // When - ResponseEntity response = controller.generate(request); - - // Then - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertTrue(response.getBody().toString().contains("Error al generar la receta")); - } - - @Test - void test3_shouldHandleRequestWithoutFilters() { - // Given - RecipeRequest request = new RecipeRequest(); - IngredientRequest ingredient = new IngredientRequest("tomate"); - ingredient.setSource("manual"); - ingredient.setConfirmed(true); - request.setIngredients(Arrays.asList(ingredient)); - // No filters set - - String geminiResponse = "[]"; - when(getRecipesFromIngredientsCommand.execute(any(GetRecipesFromIngredientsCommand.Command.class))) - .thenReturn(geminiResponse); - when(geminiResponseMapper.parseToJson(anyString())).thenReturn(Arrays.asList()); - - // When - ResponseEntity response = controller.generate(request); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - verify(getRecipesFromIngredientsCommand).execute(argThat(command -> - command.getFilters() == null && command.getIngredients().size() == 1 - )); - } - - @Test - void test4_shouldMapRequestToCommandCorrectly() { - // Given - RecipeRequest request = createComplexRecipeRequest(); - when(getRecipesFromIngredientsCommand.execute(any(GetRecipesFromIngredientsCommand.Command.class))) - .thenReturn("[]"); - when(geminiResponseMapper.parseToJson(anyString())).thenReturn(Arrays.asList()); - - // When - controller.generate(request); - - // Then - verify(getRecipesFromIngredientsCommand).execute(argThat(command -> { - return command.getFilters() != null && - command.getFilters().getTime().equals("corto") && - command.getIngredients().size() == 2; - })); - } - - @Test - void test5_shouldHandleMapperFailure() { - // Given - RecipeRequest request = createValidRecipeRequest(); - String geminiResponse = "invalid json"; - - when(getRecipesFromIngredientsCommand.execute(any(GetRecipesFromIngredientsCommand.Command.class))) - .thenReturn(geminiResponse); - when(geminiResponseMapper.parseToJson(anyString())).thenReturn(geminiResponse); // Fallback to string - - // When - ResponseEntity response = controller.generate(request); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(geminiResponse, response.getBody()); - } - - private RecipeRequest createValidRecipeRequest() { - RecipeRequest request = new RecipeRequest(); - IngredientRequest ingredient = new IngredientRequest("tomate"); - ingredient.setSource("imagen"); - ingredient.setConfirmed(true); - request.setIngredients(Arrays.asList(ingredient)); - return request; - } - - private RecipeRequest createComplexRecipeRequest() { - RecipeRequest request = new RecipeRequest(); - - IngredientRequest ingredient1 = new IngredientRequest("pasta"); - ingredient1.setSource("manual"); - ingredient1.setConfirmed(true); - - IngredientRequest ingredient2 = new IngredientRequest("queso"); - ingredient2.setSource("imagen"); - ingredient2.setConfirmed(false); - - request.setIngredients(Arrays.asList(ingredient1, ingredient2)); - - FilterRequest filter = new FilterRequest(); - filter.setTime("corto"); - filter.setDifficulty("facil"); - filter.setQuantity(4); - filter.setPeople(4); - request.setFilters(filter); - - return request; - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/controller/TextControllerAdapterUnitTest.java b/src/test/java/com/cuoco/application/controller/TextControllerAdapterUnitTest.java deleted file mode 100644 index 6a1c8ce..0000000 --- a/src/test/java/com/cuoco/application/controller/TextControllerAdapterUnitTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.cuoco.application.controller; - -import com.cuoco.adapter.in.controller.TextControllerAdapter; -import com.cuoco.adapter.in.controller.model.TextRequest; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.application.port.in.GetIngredientsFromTextCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("TextControllerAdapter - TDD") -class TextControllerAdapterUnitTest { - - @Mock - private GetIngredientsFromTextCommand getIngredientsFromTextCommand; - - @Mock - private IngredientsResponseMapper ingredientsResponseMapper; - - private TextControllerAdapter controller; - - @BeforeEach - void setUp() { - controller = new TextControllerAdapter(getIngredientsFromTextCommand, ingredientsResponseMapper); - } - - @Test - @DisplayName("Test 1: Should process text successfully") - void test1_shouldProcessTextSuccessfully() { - // Given - TextRequest request = new TextRequest(); - request.setText("tomate, cebolla, ajo"); - request.setSource("manual"); - - List domainIngredients = Arrays.asList( - new Ingredient("tomate", "text", false), - new Ingredient("cebolla", "text", false), - new Ingredient("ajo", "text", false) - ); - - IngredientsResponse expectedResponse = new IngredientsResponse(domainIngredients); - - when(getIngredientsFromTextCommand.execute(any(GetIngredientsFromTextCommand.Command.class))) - .thenReturn(domainIngredients); - when(ingredientsResponseMapper.toResponse(domainIngredients)).thenReturn(expectedResponse); - - // When - ResponseEntity response = controller.processText(request); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - - verify(getIngredientsFromTextCommand, times(1)).execute(argThat(command -> { - assertEquals("tomate, cebolla, ajo", command.getText()); - assertEquals("manual", command.getSource()); - return true; - })); - - // Verify Adapter Layer mapping - verify(ingredientsResponseMapper, times(1)).toResponse(domainIngredients); - } - - @Test - @DisplayName("Test 2: Should handle empty text") - void test2_shouldHandleEmptyText() { - // Given - TextRequest request = new TextRequest(); - request.setText(""); - request.setSource("manual"); - - when(getIngredientsFromTextCommand.execute(any())).thenReturn(Collections.emptyList()); - when(ingredientsResponseMapper.toResponse(Collections.emptyList())).thenReturn(new IngredientsResponse(Collections.emptyList())); - - // When - ResponseEntity response = controller.processText(request); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - verify(getIngredientsFromTextCommand, times(1)).execute(any()); - } - - @Test - @DisplayName("Test 3: Should handle text processing exceptions") - void test3_shouldHandleTextProcessingExceptions() { - // Given - TextRequest request = new TextRequest(); - request.setText("invalid%%%text"); - request.setSource("manual"); - - when(getIngredientsFromTextCommand.execute(any())) - .thenThrow(new RuntimeException("Text processing failed")); - - // When - ResponseEntity response = controller.processText(request); - - // Then - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertTrue(response.getBody().toString().contains("Error al procesar el texto")); - verify(getIngredientsFromTextCommand, times(1)).execute(any()); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/controller/UploadControllerAdapterUnitTest.java b/src/test/java/com/cuoco/application/controller/UploadControllerAdapterUnitTest.java deleted file mode 100644 index 35bd748..0000000 --- a/src/test/java/com/cuoco/application/controller/UploadControllerAdapterUnitTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.cuoco.application.controller; - -import com.cuoco.adapter.in.controller.UploadControllerAdapter; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.application.port.in.GetIngredientsFromFileCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.multipart.MultipartFile; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class UploadControllerAdapterUnitTest { - - @Mock - private GetIngredientsFromFileCommand getIngredientsFromFileCommand; - - @Mock - private IngredientsResponseMapper ingredientsResponseMapper; - - @Mock - private MultipartFile multipartFile; - - private UploadControllerAdapter controller; - - @BeforeEach - void setUp() { - controller = new UploadControllerAdapter(getIngredientsFromFileCommand, ingredientsResponseMapper); - } - - @Test - void test1_shouldReturnIngredientsWhenValidFiles() { - // Given - List files = Arrays.asList(multipartFile); - - Map> ingredientsByImage = new HashMap<>(); - ingredientsByImage.put("image1", Arrays.asList( - new Ingredient("tomate", "imagen", true), - new Ingredient("papa", "imagen", true) - )); - - List allIngredients = Arrays.asList( - new Ingredient("tomate", "imagen", true), - new Ingredient("papa", "imagen", true) - ); - IngredientsResponse mockResponse = new IngredientsResponse(allIngredients); - - when(getIngredientsFromFileCommand.executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class))) - .thenReturn(ingredientsByImage); - when(ingredientsResponseMapper.toImageSeparateResponse(ingredientsByImage)) - .thenReturn(mockResponse); - - // When - ResponseEntity response = controller.getIngredients(files); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue(response.getBody() instanceof IngredientsResponse); - verify(getIngredientsFromFileCommand).executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class)); - verify(ingredientsResponseMapper).toImageSeparateResponse(ingredientsByImage); - } - - @Test - void test2_shouldReturnErrorWhenCommandThrowsException() { - // Given - List files = Arrays.asList(multipartFile); - when(getIngredientsFromFileCommand.executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class))) - .thenThrow(new RuntimeException("Processing error")); - - // When - ResponseEntity response = controller.getIngredients(files); - - // Then - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertTrue(response.getBody().toString().contains("Error al procesar la imagen")); - verify(getIngredientsFromFileCommand).executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class)); - verifyNoInteractions(ingredientsResponseMapper); - } - - @Test - void test3_shouldHandleEmptyFilesList() { - // Given - List emptyFiles = Arrays.asList(); - Map> emptyIngredientsByImage = new HashMap<>(); - IngredientsResponse mockResponse = new IngredientsResponse(Collections.emptyList()); - - when(getIngredientsFromFileCommand.executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class))) - .thenReturn(emptyIngredientsByImage); - when(ingredientsResponseMapper.toImageSeparateResponse(emptyIngredientsByImage)) - .thenReturn(mockResponse); - - // When - ResponseEntity response = controller.getIngredients(emptyFiles); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue(response.getBody() instanceof IngredientsResponse); - verify(getIngredientsFromFileCommand).executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class)); - verify(ingredientsResponseMapper).toImageSeparateResponse(emptyIngredientsByImage); - } - - @Test - void test4_shouldCreateCorrectCommandFromFiles() { - // Given - List files = Arrays.asList(multipartFile); - Map> ingredientsByImage = new HashMap<>(); - ingredientsByImage.put("image1", Arrays.asList(new Ingredient("test", "imagen", true))); - - List ingredients = Arrays.asList(new Ingredient("test", "imagen", true)); - IngredientsResponse mockResponse = new IngredientsResponse(ingredients); - - when(getIngredientsFromFileCommand.executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class))) - .thenReturn(ingredientsByImage); - when(ingredientsResponseMapper.toImageSeparateResponse(ingredientsByImage)) - .thenReturn(mockResponse); - - // When - controller.getIngredients(files); - - // Then - verify(getIngredientsFromFileCommand).executeWithSeparation(argThat(command -> - command.getFiles() != null && command.getFiles().size() == 1 - )); - verify(ingredientsResponseMapper).toImageSeparateResponse(ingredientsByImage); - } - - @Test - void test5_shouldReturnIngredientsResponseWithCorrectFormat() { - // Given - List files = Arrays.asList(multipartFile); - - Map> ingredientsByImage = new HashMap<>(); - ingredientsByImage.put("image1", Arrays.asList( - new Ingredient("zanahoria", "imagen", true), - new Ingredient("apio", "imagen", false) - )); - - List ingredients = Arrays.asList( - new Ingredient("zanahoria", "imagen", true), - new Ingredient("apio", "imagen", false) - ); - IngredientsResponse mockResponse = new IngredientsResponse(ingredients); - - when(getIngredientsFromFileCommand.executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class))) - .thenReturn(ingredientsByImage); - when(ingredientsResponseMapper.toImageSeparateResponse(ingredientsByImage)) - .thenReturn(mockResponse); - - // When - ResponseEntity response = controller.getIngredients(files); - - // Then - assertEquals(HttpStatus.OK, response.getStatusCode()); - IngredientsResponse ingredientsResponse = (IngredientsResponse) response.getBody(); - assertNotNull(ingredientsResponse); - assertEquals(2, ingredientsResponse.getIngredients().size()); - verify(getIngredientsFromFileCommand).executeWithSeparation(any(GetIngredientsFromFileCommand.Command.class)); - verify(ingredientsResponseMapper).toImageSeparateResponse(ingredientsByImage); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/controller/VoiceControllerAdapterTest.java b/src/test/java/com/cuoco/application/controller/VoiceControllerAdapterTest.java deleted file mode 100644 index edb494d..0000000 --- a/src/test/java/com/cuoco/application/controller/VoiceControllerAdapterTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.cuoco.application.controller; - -import com.cuoco.adapter.in.controller.VoiceControllerAdapter; -import com.cuoco.adapter.in.controller.helper.AudioFileProcessor; -import com.cuoco.adapter.in.controller.model.IngredientsResponseMapper; -import com.cuoco.adapter.in.controller.model.IngredientsResponse; -import com.cuoco.application.port.in.GetIngredientsFromVoiceCommand; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockMultipartFile; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class VoiceControllerAdapterTest { - - @Mock - private GetIngredientsFromVoiceCommand getIngredientsFromVoiceCommand; - @Mock - private IngredientsResponseMapper ingredientsResponseMapper; - @Mock - private AudioFileProcessor audioFileProcessor; - - private VoiceControllerAdapter controller; - - @BeforeEach - void setUp() { - controller = new VoiceControllerAdapter( - getIngredientsFromVoiceCommand, - ingredientsResponseMapper, - audioFileProcessor - ); - } - - @Test - void test_analyzeVoice_shouldReturnIngredientsWhenValidAudio() throws Exception { - // Given - MockMultipartFile audioFile = new MockMultipartFile( - "audio", "test.mp3", "audio/mp3", "fake audio content".getBytes() - ); - - List ingredients = List.of( - new Ingredient("tomate", "voz", false), - new Ingredient("cebolla", "voz", false) - ); - - IngredientsResponse response = new IngredientsResponse(ingredients); - - // When - when(audioFileProcessor.isValidAudioFile(audioFile)).thenReturn(true); - when(audioFileProcessor.convertToBase64(audioFile)).thenReturn("base64Data"); - when(audioFileProcessor.getAudioFormat(audioFile)).thenReturn("mp3"); - when(getIngredientsFromVoiceCommand.execute(any())).thenReturn(ingredients); - when(ingredientsResponseMapper.toResponse(ingredients)).thenReturn(response); - - ResponseEntity result = controller.analyzeVoice(audioFile, "es-ES"); - - // Then - assertEquals(HttpStatus.OK, result.getStatusCode()); - assertEquals(response, result.getBody()); - verify(getIngredientsFromVoiceCommand).execute(any(GetIngredientsFromVoiceCommand.Command.class)); - } - - @Test - void test_analyzeVoice_shouldReturnBadRequestWhenInvalidAudio() { - // Given - MockMultipartFile invalidFile = new MockMultipartFile( - "audio", "test.txt", "text/plain", "not audio".getBytes() - ); - - // When - when(audioFileProcessor.isValidAudioFile(invalidFile)).thenReturn(false); - when(audioFileProcessor.getSupportedFormatsMessage()).thenReturn("Formato no válido"); - - ResponseEntity result = controller.analyzeVoice(invalidFile, "es-ES"); - - // Then - assertEquals(HttpStatus.BAD_REQUEST, result.getStatusCode()); - assertEquals("Formato no válido", result.getBody()); - verifyNoInteractions(getIngredientsFromVoiceCommand); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseTest.java new file mode 100644 index 0000000..2e06121 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseTest.java @@ -0,0 +1,92 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.UnauthorizedException; +import com.cuoco.application.port.in.AuthenticateUserCommand; +import com.cuoco.application.port.out.GetUserByEmailRepository; +import com.cuoco.application.usecase.model.AuthenticatedUser; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import com.cuoco.factory.domain.UserFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AuthenticateUserUseCaseTest { + + private JwtUtil jwtUtil; + private GetUserByEmailRepository getUserByEmailRepository; + private AuthenticateUserUseCase useCase; + + @BeforeEach + void setup() { + jwtUtil = mock(JwtUtil.class); + getUserByEmailRepository = mock(GetUserByEmailRepository.class); + useCase = new AuthenticateUserUseCase(jwtUtil, getUserByEmailRepository); + } + + @Test + void GIVEN_valid_token_WHEN_execute_THEN_returns_authenticated_user() { + String token = "Bearer valid.jwt.token"; + String email = "user@example.com"; + String authHeader = "Bearer " + token; + User user = UserFactory.create(); + + AuthenticateUserCommand.Command command = AuthenticateUserCommand.Command.builder().authHeader(authHeader).build(); + + when(jwtUtil.extractEmail(token)).thenReturn(email); + when(getUserByEmailRepository.execute(email)).thenReturn(user); + when(jwtUtil.validateToken(token, user)).thenReturn(true); + + AuthenticatedUser result = useCase.execute(command); + + assertNotNull(result); + verify(jwtUtil).extractEmail(token); + verify(getUserByEmailRepository).execute(email); + verify(jwtUtil).validateToken(token, user); + } + + @Test + void GIVEN_invalid_auth_header_WHEN_execute_THEN_throw_unauthorized() { + String authHeader = "InvalidHeader"; + AuthenticateUserCommand.Command command = AuthenticateUserCommand.Command.builder().authHeader(authHeader).build(); + + UnauthorizedException ex = assertThrows(UnauthorizedException.class, () -> useCase.execute(command)); + + assertEquals(ErrorDescription.UNAUTHORIZED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_token_with_null_email_WHEN_execute_THEN_throw_invalid_token() { + String token = "token"; + String authHeader = "Bearer " + token; + + AuthenticateUserCommand.Command command = AuthenticateUserCommand.Command.builder().authHeader(authHeader).build(); + + when(jwtUtil.extractEmail(token)).thenReturn(null); + + UnauthorizedException ex = assertThrows(UnauthorizedException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.INVALID_CREDENTIALS.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_user_or_token_WHEN_execute_THEN_throw_invalid_token() { + String token = "token"; + String email = "user@example.com"; + String authHeader = "Bearer " + token; + + AuthenticateUserCommand.Command command = AuthenticateUserCommand.Command.builder().authHeader(authHeader).build(); + + when(jwtUtil.extractEmail(token)).thenReturn(email); + when(getUserByEmailRepository.execute(email)).thenReturn(null); + + UnauthorizedException ex = assertThrows(UnauthorizedException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.INVALID_CREDENTIALS.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseUnitTest.java deleted file mode 100644 index c2082ad..0000000 --- a/src/test/java/com/cuoco/application/usecase/AuthenticateUserUseCaseUnitTest.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.AuthenticateUserCommand; -import com.cuoco.application.port.out.GetUserByEmailRepository; -import com.cuoco.application.usecase.model.AuthenticatedUser; -import com.cuoco.application.usecase.model.User; -import com.cuoco.shared.utils.JwtUtil; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.time.LocalDateTime; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("AuthenticateUserUseCase - Unit Tests") -class AuthenticateUserUseCaseUnitTest { - - @Mock - private GetUserByEmailRepository getUserByEmailRepository; - - @Mock - private JwtUtil jwtUtil; - - @Mock - private SecurityContext securityContext; - - private AuthenticateUserUseCase useCase; - - @BeforeEach - void setUp() { - useCase = new AuthenticateUserUseCase(jwtUtil, getUserByEmailRepository); - SecurityContextHolder.clearContext(); - } - - @Test - @DisplayName("Test 1: Should authenticate successfully with valid Bearer token") - void test1_shouldAuthenticateSuccessfullyWithValidBearerToken() { - // Given - String authHeader = "Bearer valid-jwt-token-123"; - String email = "john@example.com"; - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - - User expectedUser = new User(1L, "John", email, "password", null, true, null, LocalDateTime.now(), Collections.emptyList(), Collections.emptyList()); - - when(jwtUtil.extractEmail("valid-jwt-token-123")).thenReturn(email); - when(getUserByEmailRepository.execute(email)).thenReturn(expectedUser); - when(jwtUtil.validateToken("valid-jwt-token-123", expectedUser)).thenReturn(true); - - try (MockedStatic securityContextHolderMock = mockStatic(SecurityContextHolder.class)) { - securityContextHolderMock.when(SecurityContextHolder::getContext).thenReturn(securityContext); - when(securityContext.getAuthentication()).thenReturn(null); - - // When - AuthenticatedUser result = useCase.execute(command); - - // Then - assertNotNull(result); - assertNotNull(result.getUser()); - assertEquals(email, result.getUser().getEmail()); - assertEquals("John", result.getUser().getName()); - assertNull(result.getToken()); - assertTrue(result.getRoles().isEmpty()); - - verify(jwtUtil, times(1)).extractEmail("valid-jwt-token-123"); - verify(getUserByEmailRepository, times(1)).execute(email); - verify(jwtUtil, times(1)).validateToken("valid-jwt-token-123", expectedUser); - } - } - - @Test - @DisplayName("Test 2: Should return null when auth header is null or doesn't start with Bearer") - void test2_shouldReturnNullWhenAuthHeaderIsInvalid() { - // Given - null header - AuthenticateUserCommand.Command commandNull = new AuthenticateUserCommand.Command(null); - - // When - AuthenticatedUser resultNull = useCase.execute(commandNull); - - // Then - assertNull(resultNull); - - // Given - invalid prefix - AuthenticateUserCommand.Command commandInvalid = new AuthenticateUserCommand.Command("InvalidPrefix token"); - - // When - AuthenticatedUser resultInvalid = useCase.execute(commandInvalid); - - // Then - assertNull(resultInvalid); - - // Verify no interactions with mocked dependencies - verifyNoInteractions(jwtUtil); - verifyNoInteractions(getUserByEmailRepository); - } - - @Test - @DisplayName("Test 3: Should return null when JWT email extraction fails") - void test3_shouldReturnNullWhenJwtEmailExtractionFails() { - // Given - String authHeader = "Bearer invalid-jwt-token"; - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - - when(jwtUtil.extractEmail("invalid-jwt-token")).thenReturn(null); - - // When - AuthenticatedUser result = useCase.execute(command); - - // Then - assertNull(result); - - verify(jwtUtil, times(1)).extractEmail("invalid-jwt-token"); - verifyNoInteractions(getUserByEmailRepository); - } - - @Test - @DisplayName("Test 4: Should return null when SecurityContext already has authentication") - void test4_shouldReturnNullWhenSecurityContextAlreadyHasAuthentication() { - // Given - String authHeader = "Bearer valid-jwt-token-123"; - String email = "john@example.com"; - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - - when(jwtUtil.extractEmail("valid-jwt-token-123")).thenReturn(email); - - try (MockedStatic securityContextHolderMock = mockStatic(SecurityContextHolder.class)) { - securityContextHolderMock.when(SecurityContextHolder::getContext).thenReturn(securityContext); - when(securityContext.getAuthentication()).thenReturn(mock(org.springframework.security.core.Authentication.class)); - - // When - AuthenticatedUser result = useCase.execute(command); - - // Then - assertNull(result); - - verify(jwtUtil, times(1)).extractEmail("valid-jwt-token-123"); - verifyNoInteractions(getUserByEmailRepository); - } - } - - @Test - @DisplayName("Test 5: Should return null when user is not found") - void test5_shouldReturnNullWhenUserIsNotFound() { - // Given - String authHeader = "Bearer valid-jwt-token-123"; - String email = "nonexistent@example.com"; - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - - when(jwtUtil.extractEmail("valid-jwt-token-123")).thenReturn(email); - when(getUserByEmailRepository.execute(email)).thenReturn(null); - - try (MockedStatic securityContextHolderMock = mockStatic(SecurityContextHolder.class)) { - securityContextHolderMock.when(SecurityContextHolder::getContext).thenReturn(securityContext); - when(securityContext.getAuthentication()).thenReturn(null); - - // When - AuthenticatedUser result = useCase.execute(command); - - // Then - assertNull(result); - - verify(jwtUtil, times(1)).extractEmail("valid-jwt-token-123"); - verify(getUserByEmailRepository, times(1)).execute(email); - verify(jwtUtil, never()).validateToken(any(), any()); - } - } - - @Test - @DisplayName("Test 6: Should return null when token validation fails") - void test6_shouldReturnNullWhenTokenValidationFails() { - // Given - String authHeader = "Bearer invalid-jwt-token-123"; - String email = "john@example.com"; - AuthenticateUserCommand.Command command = new AuthenticateUserCommand.Command(authHeader); - - User user = new User(1L, "John", email, "password", null, true, null, LocalDateTime.now(), Collections.emptyList(), Collections.emptyList()); - - when(jwtUtil.extractEmail("invalid-jwt-token-123")).thenReturn(email); - when(getUserByEmailRepository.execute(email)).thenReturn(user); - when(jwtUtil.validateToken("invalid-jwt-token-123", user)).thenReturn(false); - - try (MockedStatic securityContextHolderMock = mockStatic(SecurityContextHolder.class)) { - securityContextHolderMock.when(SecurityContextHolder::getContext).thenReturn(securityContext); - when(securityContext.getAuthentication()).thenReturn(null); - - // When - AuthenticatedUser result = useCase.execute(command); - - // Then - assertNull(result); - - verify(jwtUtil, times(1)).extractEmail("invalid-jwt-token-123"); - verify(getUserByEmailRepository, times(1)).execute(email); - verify(jwtUtil, times(1)).validateToken("invalid-jwt-token-123", user); - } - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/CreateUserMealPrepUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/CreateUserMealPrepUseCaseTest.java new file mode 100644 index 0000000..9f57afe --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/CreateUserMealPrepUseCaseTest.java @@ -0,0 +1,68 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.ConflictException; +import com.cuoco.application.port.in.CreateUserMealPrepCommand; +import com.cuoco.application.port.out.CreateUserMealPrepRepository; +import com.cuoco.application.port.out.ExistsUserMealPrepRepository; +import com.cuoco.application.port.out.GetMealPrepByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserMealPrep; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CreateUserMealPrepUseCaseTest { + + private UserDomainService userDomainService; + private CreateUserMealPrepRepository createUserMealPrepRepository; + private ExistsUserMealPrepRepository existsUserMealPrepRepository; + private GetMealPrepByIdRepository getMealPrepByIdRepository; + private CreateUserMealPrepUseCase useCase; + + @BeforeEach + void setup() { + userDomainService = mock(UserDomainService.class); + createUserMealPrepRepository = mock(CreateUserMealPrepRepository.class); + existsUserMealPrepRepository = mock(ExistsUserMealPrepRepository.class); + getMealPrepByIdRepository = mock(GetMealPrepByIdRepository.class); + + useCase = new CreateUserMealPrepUseCase(userDomainService, createUserMealPrepRepository, existsUserMealPrepRepository, getMealPrepByIdRepository); + } + + @Test + void GIVEN_not_existing_WHEN_execute_THEN_save_user_meal_prep() { + User user = User.builder().id(1L).build(); + MealPrep mealPrep = MealPrep.builder().id(2L).build(); + CreateUserMealPrepCommand.Command command = CreateUserMealPrepCommand.Command.builder().id(2L).build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(getMealPrepByIdRepository.execute(2L)).thenReturn(mealPrep); + when(existsUserMealPrepRepository.execute(any(UserMealPrep.class))).thenReturn(false); + + useCase.execute(command); + verify(createUserMealPrepRepository, times(1)).execute(any(UserMealPrep.class)); + } + + @Test + void GIVEN_existing_WHEN_execute_THEN_throw_conflict() { + User user = User.builder().id(1L).build(); + MealPrep mealPrep = MealPrep.builder().id(2L).build(); + CreateUserMealPrepCommand.Command command = CreateUserMealPrepCommand.Command.builder().id(2L).build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(getMealPrepByIdRepository.execute(2L)).thenReturn(mealPrep); + when(existsUserMealPrepRepository.execute(any(UserMealPrep.class))).thenReturn(true); + + assertThrows(ConflictException.class, () -> useCase.execute(command)); + verify(createUserMealPrepRepository, never()).execute(any(UserMealPrep.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseTest.java new file mode 100644 index 0000000..facaac2 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseTest.java @@ -0,0 +1,211 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.CreateUserCommand; +import com.cuoco.application.port.out.CreateUserRepository; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetPlanByIdRepository; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import com.cuoco.factory.domain.UserFactory; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Collections; +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CreateUserUseCaseTest { + + private PasswordEncoder passwordEncoder; + private CreateUserRepository createUserRepository; + private ExistsUserByEmailRepository existsUserByEmailRepository; + private GetPlanByIdRepository getPlanByIdRepository; + private GetDietByIdRepository getDietByIdRepository; + private GetCookLevelByIdRepository getCookLevelByIdRepository; + private GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + private GetAllergiesByIdRepository getAllergiesByIdRepository; + private SendConfirmationEmailRepository sendConfirmationEmailRepository; + private JwtUtil jwtUtil; + private CreateUserUseCase useCase; + + @BeforeEach + void setup() { + passwordEncoder = mock(PasswordEncoder.class); + createUserRepository = mock(CreateUserRepository.class); + existsUserByEmailRepository = mock(ExistsUserByEmailRepository.class); + getPlanByIdRepository = mock(GetPlanByIdRepository.class); + getDietByIdRepository = mock(GetDietByIdRepository.class); + getCookLevelByIdRepository = mock(GetCookLevelByIdRepository.class); + getDietaryNeedsByIdRepository = mock(GetDietaryNeedsByIdRepository.class); + getAllergiesByIdRepository = mock(GetAllergiesByIdRepository.class); + sendConfirmationEmailRepository = mock(SendConfirmationEmailRepository.class); + jwtUtil = mock(JwtUtil.class); + + useCase = new CreateUserUseCase( + passwordEncoder, + createUserRepository, + existsUserByEmailRepository, + getPlanByIdRepository, + getDietByIdRepository, + getCookLevelByIdRepository, + getDietaryNeedsByIdRepository, + getAllergiesByIdRepository, + sendConfirmationEmailRepository, + jwtUtil + ); + } + + @Test + void GIVEN_valid_command_WHEN_execute_THEN_return_created_user() { + var user = UserFactory.create(); + var plan = user.getPlan(); + var diet = user.getPreferences().getDiet(); + var cookLevel = user.getPreferences().getCookLevel(); + var dietaryNeedsIds = user.getDietaryNeeds().stream().map(DietaryNeed::getId).toList(); + var allergiesIds = user.getAllergies().stream().map(Allergy::getId).toList(); + + var command = CreateUserCommand.Command.builder() + .name(user.getName()) + .email(user.getEmail()) + .password(user.getPassword()) + .planId(user.getPlan().getId()) + .cookLevelId(user.getPreferences().getCookLevel().getId()) + .dietId(user.getPreferences().getDiet().getId()) + .dietaryNeeds(dietaryNeedsIds) + .allergies(allergiesIds) + .build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(plan); + when(getDietByIdRepository.execute(command.getDietId())).thenReturn(diet); + when(getCookLevelByIdRepository.execute(command.getCookLevelId())).thenReturn(cookLevel); + when(getDietaryNeedsByIdRepository.execute(command.getDietaryNeeds())).thenReturn(user.getDietaryNeeds()); + when(getAllergiesByIdRepository.execute(command.getAllergies())).thenReturn(user.getAllergies()); + when(passwordEncoder.encode(command.getPassword())).thenReturn("encrypted"); + when(createUserRepository.execute(any())).thenReturn(user); + doNothing().when(sendConfirmationEmailRepository).execute(any(), any()); + when(jwtUtil.generateActivationToken(any())).thenReturn("token"); + + User result = useCase.execute(command); + + assertNotNull(result); + assertNull(result.getPassword()); + } + + @Test + void GIVEN_existing_email_WHEN_execute_THEN_throw_bad_request() { + var command = CreateUserCommand.Command.builder() + .email("existing@email.com") + .build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(true); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.USER_DUPLICATED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_plan_id_WHEN_execute_THEN_throw_bad_request() { + var command = CreateUserCommand.Command.builder().email("existing@email.com").planId(1).build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(null); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.PLAN_NOT_EXISTS.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_diet_id_WHEN_execute_THEN_throw_bad_request() { + User user = UserFactory.create(); + var command = CreateUserCommand.Command.builder().email("existing@email.com").planId(1).dietId(1).build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(user.getPlan()); + when(getCookLevelByIdRepository.execute(command.getCookLevelId())).thenReturn(user.getPreferences().getCookLevel()); + when(getDietByIdRepository.execute(command.getDietId())).thenReturn(null); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.DIET_NOT_EXISTS.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_cook_level_id_WHEN_execute_THEN_throw_bad_request() { + User user = UserFactory.create(); + var command = CreateUserCommand.Command.builder() + .email("existing@email.com") + .planId(1) + .dietId(1) + .cookLevelId(1) + .build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(user.getPlan()); + when(getCookLevelByIdRepository.execute(command.getCookLevelId())).thenReturn(null); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.COOK_LEVEL_NOT_EXISTS.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_dietary_needs_WHEN_execute_THEN_throw_bad_request() { + User user = UserFactory.create(); + var command = CreateUserCommand.Command.builder() + .email("existing@email.com") + .planId(1) + .dietId(1) + .cookLevelId(1) + .dietaryNeeds(List.of(1,2)) + .build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(user.getPlan()); + when(getDietByIdRepository.execute(command.getDietId())).thenReturn(user.getPreferences().getDiet()); + when(getCookLevelByIdRepository.execute(command.getCookLevelId())).thenReturn(user.getPreferences().getCookLevel()); + when(getDietaryNeedsByIdRepository.execute(command.getDietaryNeeds())).thenReturn(Collections.emptyList()); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.DIETARY_NEEDS_NOT_EXISTS.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_allergies_WHEN_execute_THEN_throw_bad_request() { + User user = UserFactory.create(); + var command = CreateUserCommand.Command.builder() + .email("existing@email.com") + .planId(1) + .dietId(1) + .cookLevelId(1) + .dietaryNeeds(List.of(1,2,3)) + .allergies(List.of(1,2,3)) + .build(); + + when(existsUserByEmailRepository.execute(command.getEmail())).thenReturn(false); + when(getPlanByIdRepository.execute(any())).thenReturn(user.getPlan()); + when(getDietByIdRepository.execute(command.getDietId())).thenReturn(user.getPreferences().getDiet()); + when(getCookLevelByIdRepository.execute(command.getCookLevelId())).thenReturn(user.getPreferences().getCookLevel()); + when(getDietaryNeedsByIdRepository.execute(command.getDietaryNeeds())).thenReturn(user.getDietaryNeeds()); + when(getAllergiesByIdRepository.execute(command.getAllergies())).thenReturn(Collections.emptyList()); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.ALLERGIES_NOT_EXISTS.getValue(), ex.getDescription()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseUnitTest.java deleted file mode 100644 index af43f21..0000000 --- a/src/test/java/com/cuoco/application/usecase/CreateUserUseCaseUnitTest.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.exception.BadRequestException; -import com.cuoco.application.port.in.CreateUserCommand; -import com.cuoco.application.port.out.*; -import com.cuoco.application.usecase.model.*; -import com.cuoco.shared.model.ErrorDescription; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("CreateUserUseCase - Unit Tests") -class CreateUserUseCaseUnitTest { - - @Mock - private PasswordEncoder passwordEncoder; - - @Mock - private CreateUserRepository createUserRepository; - - @Mock - private UserExistsByEmailRepository userExistsByEmailRepository; - - @Mock - private GetPlanByIdRepository getPlanByIdRepository; - - @Mock - private GetDietByIdRepository getDietByIdRepository; - - @Mock - private GetCookLevelByIdRepository getCookLevelByIdRepository; - - @Mock - private GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; - - @Mock - private GetAllergiesByIdRepository getAllergiesByIdRepository; - - private CreateUserUseCase useCase; - - @BeforeEach - void setUp() { - useCase = new CreateUserUseCase( - passwordEncoder, - createUserRepository, - userExistsByEmailRepository, - getPlanByIdRepository, - getDietByIdRepository, - getCookLevelByIdRepository, - getDietaryNeedsByIdRepository, - getAllergiesByIdRepository - ); - } - - @Test - @DisplayName("Test 1: Should create user successfully with valid data") - void shouldCreateUserSuccessfullyWithValidData() { - // Given - CreateUserCommand.Command command = new CreateUserCommand.Command( - "John Doe", "john@example.com", "password123", - LocalDate.now(), "Basic", true, - "Beginner", "Balanced", - Collections.emptyList(), Collections.emptyList() - ); - - Plan plan = new Plan(1, "Basic"); - Diet diet = new Diet(1, "Balanced"); - CookLevel cookLevel = new CookLevel(1, "Beginner"); - - User expectedUser = new User(1L, "John Doe", "john@example.com", null, plan, true, - new UserPreferences(cookLevel, diet), LocalDateTime.now(), - Collections.emptyList(), Collections.emptyList()); - - when(userExistsByEmailRepository.execute("john@example.com")).thenReturn(false); - when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); - when(getPlanByIdRepository.execute(1)).thenReturn(plan); - when(getDietByIdRepository.execute(1)).thenReturn(diet); - when(getCookLevelByIdRepository.execute(1)).thenReturn(cookLevel); - when(createUserRepository.execute(any(User.class))).thenReturn(expectedUser); - - // When - User result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals("John Doe", result.getName()); - assertEquals("john@example.com", result.getEmail()); - assertNull(result.getPassword()); // Password is cleared - assertTrue(result.getActive()); - - verify(userExistsByEmailRepository, times(1)).execute("john@example.com"); - verify(passwordEncoder, times(1)).encode("password123"); - verify(createUserRepository, times(1)).execute(any(User.class)); - } - - @Test - @DisplayName("Test 2: Should throw BadRequestException when email already exists") - void shouldThrowBadRequestExceptionWhenEmailAlreadyExists() { - // Given - CreateUserCommand.Command command = new CreateUserCommand.Command( - "Jane Doe", "jane@example.com", "pass", - LocalDate.now(), "Plan", true, "Level", "Diet", - Collections.emptyList(), Collections.emptyList() - ); - - when(userExistsByEmailRepository.execute("jane@example.com")).thenReturn(true); - - // When & Then - BadRequestException ex = assertThrows(BadRequestException.class, () -> { - useCase.execute(command); - }); - - assertEquals(ErrorDescription.DUPLICATED.getValue(), ex.getDescription()); - verify(userExistsByEmailRepository, times(1)).execute("jane@example.com"); - verifyNoInteractions(passwordEncoder); - verifyNoInteractions(createUserRepository); - } - - @Test - @DisplayName("Test 3: Should create user with dietary needs successfully") - void shouldCreateUserWithDietaryNeedsSuccessfully() { - // Given - List dietaryNeedDescriptions = Arrays.asList("Low sodium", "High protein"); - CreateUserCommand.Command command = new CreateUserCommand.Command( - "Jane Doe", - "jane@example.com", - "password123", - LocalDate.now(), - "Premium", - true, - "Intermediate", - "Keto", - dietaryNeedDescriptions, - Collections.emptyList() - ); - - Plan plan = new Plan(1, "Basic"); - Diet diet = new Diet(1, "Balanced"); - CookLevel cookLevel = new CookLevel(1, "Beginner"); - - List dietaryNeeds = Arrays.asList( - new DietaryNeed(1, "Low sodium"), - new DietaryNeed(2, "High protein") - ); - - User expectedUser = new User( - 2L, "Jane Doe", "jane@example.com", null, plan, true, - new UserPreferences(cookLevel, diet), LocalDateTime.now(), - dietaryNeeds, Collections.emptyList() - ); - - when(userExistsByEmailRepository.execute("jane@example.com")).thenReturn(false); - when(getDietaryNeedsByIdRepository.execute(dietaryNeedDescriptions)).thenReturn(dietaryNeeds); - when(passwordEncoder.encode("password123")).thenReturn("encodedPassword"); - when(getPlanByIdRepository.execute(1)).thenReturn(plan); - when(getDietByIdRepository.execute(1)).thenReturn(diet); - when(getCookLevelByIdRepository.execute(1)).thenReturn(cookLevel); - when(createUserRepository.execute(any(User.class))).thenReturn(expectedUser); - - // When - User result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals(2L, result.getId()); - assertEquals("Jane Doe", result.getName()); - assertEquals(2, result.getDietaryNeeds().size()); - assertTrue(result.getDietaryNeeds().stream().anyMatch(need -> "Low sodium".equals(need.getDescription()))); - - verify(getDietaryNeedsByIdRepository, times(1)).execute(dietaryNeedDescriptions); - verify(userExistsByEmailRepository, times(1)).execute("jane@example.com"); - verify(createUserRepository, times(1)).execute(any(User.class)); - } - - @Test - @DisplayName("Test 4: Should throw BadRequestException when dietary need does not exist") - void shouldThrowWhenDietaryNeedDoesNotExist() { - // Given - List inputNeeds = Arrays.asList("Unknown", "Another Unknown"); - List foundNeeds = Arrays.asList(new DietaryNeed(1, "Unknown")); // Only 1 found out of 2 - - CreateUserCommand.Command command = new CreateUserCommand.Command( - "Invalid", "fail@example.com", "pass", LocalDate.now(), - "Plan", true, "Level", "Diet", inputNeeds, Collections.emptyList() - ); - - when(userExistsByEmailRepository.execute("fail@example.com")).thenReturn(false); - when(getDietaryNeedsByIdRepository.execute(inputNeeds)).thenReturn(foundNeeds); - - // When & Then - BadRequestException ex = assertThrows(BadRequestException.class, () -> { - useCase.execute(command); - }); - - assertEquals(ErrorDescription.PREFERENCES_NOT_EXISTS.getValue(), ex.getDescription()); - verify(getDietaryNeedsByIdRepository, times(1)).execute(inputNeeds); - verifyNoInteractions(createUserRepository); - } - - @Test - @DisplayName("Test 5: Should throw BadRequestException when allergy does not exist") - void shouldThrowWhenAllergyDoesNotExist() { - // Given - List allergies = Arrays.asList("Non-existent allergy", "Another fake allergy"); - List foundAllergies = Arrays.asList(new Allergy(1, "Non-existent allergy")); // Only 1 found out of 2 - - CreateUserCommand.Command command = new CreateUserCommand.Command( - "Allergic", "allergy@example.com", "pass", LocalDate.now(), - "Plan", true, "Level", "Diet", Collections.emptyList(), allergies - ); - - when(userExistsByEmailRepository.execute("allergy@example.com")).thenReturn(false); - when(getAllergiesByIdRepository.execute(allergies)).thenReturn(foundAllergies); - - // When & Then - BadRequestException ex = assertThrows(BadRequestException.class, () -> { - useCase.execute(command); - }); - - assertEquals(ErrorDescription.ALLERGIES_NOT_EXISTS.getValue(), ex.getDescription()); - verify(getAllergiesByIdRepository, times(1)).execute(allergies); - verifyNoInteractions(createUserRepository); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCaseTest.java new file mode 100644 index 0000000..5390362 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/DeleteUserMealPrepUseCaseTest.java @@ -0,0 +1,38 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.DeleteUserMealPrepCommand; +import com.cuoco.application.port.out.DeleteUserMealPrepRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DeleteUserMealPrepUseCaseTest { + + private UserDomainService userDomainService; + private DeleteUserMealPrepRepository deleteUserMealPrepRepository; + private DeleteUserMealPrepUseCase useCase; + + @BeforeEach + void setup() { + userDomainService = mock(UserDomainService.class); + deleteUserMealPrepRepository = mock(DeleteUserMealPrepRepository.class); + useCase = new DeleteUserMealPrepUseCase(userDomainService, deleteUserMealPrepRepository); + } + + @Test + void GIVEN_command_WHEN_execute_THEN_delete_called_with_user_id_and_meal_prep_id() { + User user = User.builder().id(1L).build(); + DeleteUserMealPrepCommand.Command command = DeleteUserMealPrepCommand.Command.builder().id(2L).build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + + useCase.execute(command); + verify(deleteUserMealPrepRepository, times(1)).execute(1L, 2L); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCaseTest.java new file mode 100644 index 0000000..19b02dd --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/DeleteUserRepositoryUseCaseTest.java @@ -0,0 +1,38 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.DeleteUserRecipeCommand; +import com.cuoco.application.port.out.DeleteUserRecipeRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DeleteUserRepositoryUseCaseTest { + + private UserDomainService userDomainService; + private DeleteUserRecipeRepository deleteUserRecipeRepository; + private DeleteUserRepositoryUseCase useCase; + + @BeforeEach + void setup() { + userDomainService = mock(UserDomainService.class); + deleteUserRecipeRepository = mock(DeleteUserRecipeRepository.class); + useCase = new DeleteUserRepositoryUseCase(userDomainService, deleteUserRecipeRepository); + } + + @Test + void GIVEN_command_WHEN_execute_THEN_delete_called_with_user_id_and_recipe_id() { + User user = User.builder().id(1L).build(); + DeleteUserRecipeCommand.Command command = DeleteUserRecipeCommand.Command.builder().id(2L).build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + + useCase.execute(command); + verify(deleteUserRecipeRepository, times(1)).execute(1L, 2L); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/FindOrGenerateRecipeUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/FindOrGenerateRecipeUseCaseTest.java new file mode 100644 index 0000000..18b72b3 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/FindOrGenerateRecipeUseCaseTest.java @@ -0,0 +1,144 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.RecipeGenerationException; +import com.cuoco.application.port.in.FindOrCreateRecipeCommand; +import com.cuoco.application.port.out.CreateRecipeByNameRepository; +import com.cuoco.application.port.out.CreateRecipeRepository; +import com.cuoco.application.port.out.FindRecipeByNameRepository; +import com.cuoco.application.usecase.domainservice.ParametricDataDomainService; +import com.cuoco.application.usecase.domainservice.RecipeDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.factory.domain.RecipeFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class FindOrGenerateRecipeUseCaseTest { + + @Mock + private ParametricDataDomainService parametricDataDomainService; + + @Mock + private CreateRecipeByNameRepository createRecipeByNameRepository; + + @Mock + private FindRecipeByNameRepository findRecipeByNameRepository; + + @Mock + private CreateRecipeRepository createRecipeRepository; + + @Mock + private RecipeDomainService recipeDomainService; + + private FindOrCreateRecipeUseCase useCase; + + @BeforeEach + void setUp() { + useCase = new FindOrCreateRecipeUseCase( + parametricDataDomainService, + createRecipeByNameRepository, + findRecipeByNameRepository, + createRecipeRepository + ); + } + + @Test + void GIVEN_existing_recipe_name_WHEN_execute_THEN_return_recipe_from_database() { + String recipeName = "Pasta Bolognesa"; + Recipe existingRecipe = RecipeFactory.create(); + existingRecipe.setName(recipeName); + + when(findRecipeByNameRepository.execute(recipeName)).thenReturn(existingRecipe); + + FindOrCreateRecipeCommand.Command command = FindOrCreateRecipeCommand.Command.builder() + .recipeName(recipeName) + .build(); + + Recipe result = useCase.execute(command); + + assertNotNull(result); + assertEquals(recipeName, result.getName()); + assertEquals(existingRecipe.getId(), result.getId()); + + verify(findRecipeByNameRepository).execute(recipeName); + verify(createRecipeByNameRepository, never()).execute(any(), any()); + verify(createRecipeRepository, never()).execute(any()); + } + + @Test + void GIVEN_non_existing_recipe_name_WHEN_execute_THEN_generate_and_save_new_recipe() { + String recipeName = "Pizza Margherita"; + Recipe generatedRecipe = RecipeFactory.create(); + Recipe savedRecipe = RecipeFactory.create(); + savedRecipe.setName(recipeName); + + when(findRecipeByNameRepository.execute(recipeName)).thenReturn(null); + when(createRecipeByNameRepository.execute(any(), any())).thenReturn(generatedRecipe); + when(createRecipeRepository.execute(any())).thenReturn(savedRecipe); + + FindOrCreateRecipeCommand.Command command = FindOrCreateRecipeCommand.Command.builder() + .recipeName(recipeName) + .build(); + + Recipe result = useCase.execute(command); + + assertNotNull(result); + assertEquals(recipeName, result.getName()); + + verify(findRecipeByNameRepository).execute(recipeName); + verify(createRecipeByNameRepository).execute(any(), any()); + verify(createRecipeRepository).execute(any()); + } + + @Test + void GIVEN_non_existing_recipe_and_generation_fails_WHEN_execute_THEN_throw_exception() { + String recipeName = "Impossible Recipe"; + + when(findRecipeByNameRepository.execute(recipeName)).thenReturn(null); + when(createRecipeByNameRepository.execute(any(), any())).thenReturn(null); + + FindOrCreateRecipeCommand.Command command = FindOrCreateRecipeCommand.Command.builder() + .recipeName(recipeName) + .build(); + + RecipeGenerationException exception = assertThrows(RecipeGenerationException.class, () -> useCase.execute(command)); + + assertEquals("Could not generate recipe for: " + recipeName, exception.getDescription()); + + verify(findRecipeByNameRepository).execute(recipeName); + verify(createRecipeByNameRepository).execute(any(), any()); + verify(createRecipeRepository, never()).execute(any()); + } + + @Test + void GIVEN_recipe_name_with_whitespace_WHEN_execute_THEN_search_with_trimmed_name() { + String recipeNameWithSpaces = " Lasagna Bolognesa "; + String trimmedName = recipeNameWithSpaces.trim(); + Recipe existingRecipe = RecipeFactory.create(); + existingRecipe.setName(trimmedName); + + when(findRecipeByNameRepository.execute(recipeNameWithSpaces)).thenReturn(existingRecipe); + + FindOrCreateRecipeCommand.Command command = FindOrCreateRecipeCommand.Command.builder() + .recipeName(recipeNameWithSpaces) + .build(); + + Recipe result = useCase.execute(command); + + assertNotNull(result); + assertEquals(trimmedName, result.getName()); + + verify(findRecipeByNameRepository).execute(recipeNameWithSpaces); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/FindRecipesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/FindRecipesUseCaseTest.java new file mode 100644 index 0000000..dddf069 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/FindRecipesUseCaseTest.java @@ -0,0 +1,56 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.FindRecipesCommand; +import com.cuoco.application.port.out.FindRecipesByFiltersRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.SearchFilters; +import com.cuoco.factory.domain.RecipeFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class FindRecipesUseCaseTest { + + private FindRecipesByFiltersRepository findRecipesByFiltersRepository; + private FindRecipesUseCase useCase; + + @BeforeEach + void setUp() { + findRecipesByFiltersRepository = mock(FindRecipesByFiltersRepository.class); + useCase = new FindRecipesUseCase(findRecipesByFiltersRepository); + } + + @Test + void GIVEN_null_size_and_random_WHEN_execute_THEN_use_default_values() { + List expected = List.of(RecipeFactory.create()); + when(findRecipesByFiltersRepository.execute(any())).thenReturn(expected); + FindRecipesCommand.Command command = FindRecipesCommand.Command.builder().build(); + List result = useCase.execute(command); + assertEquals(expected, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(SearchFilters.class); + verify(findRecipesByFiltersRepository).execute(captor.capture()); + assertEquals(5, captor.getValue().getSize()); + assertEquals(false, captor.getValue().getRandom()); + } + + @Test + void GIVEN_non_null_size_and_random_WHEN_execute_THEN_use_provided_values() { + List expected = List.of(RecipeFactory.create()); + when(findRecipesByFiltersRepository.execute(any())).thenReturn(expected); + FindRecipesCommand.Command command = FindRecipesCommand.Command.builder().size(10).random(true).build(); + List result = useCase.execute(command); + assertEquals(expected, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(SearchFilters.class); + verify(findRecipesByFiltersRepository).execute(captor.capture()); + assertEquals(10, captor.getValue().getSize()); + assertEquals(true, captor.getValue().getRandom()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCaseTest.java new file mode 100644 index 0000000..9ff88f0 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GenerateRecipeImagesUseCaseTest.java @@ -0,0 +1,109 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GenerateRecipeImagesCommand; +import com.cuoco.application.port.out.GetRecipeStepsImagesRepository; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.Step; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.domain.RecipeImageFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GenerateRecipeImagesUseCaseTest { + + @Mock + private GetRecipeStepsImagesRepository getRecipeStepsImagesRepository; + + private GenerateRecipeImagesUseCase generateRecipeImagesUseCase; + + @BeforeEach + void setUp() { + generateRecipeImagesUseCase = new GenerateRecipeImagesUseCase(getRecipeStepsImagesRepository); + } + + @Test + void execute_whenValidRecipe_thenReturnGeneratedImages() { + Recipe recipe = RecipeFactory.create(); + List expectedImages = List.of( + RecipeImageFactory.createMainRecipeImage(), + RecipeImageFactory.createStepRecipeImage() + ); + + GenerateRecipeImagesCommand.Command command = GenerateRecipeImagesCommand.Command.builder() + .recipe(recipe) + .build(); + + when(getRecipeStepsImagesRepository.execute(any(Recipe.class))) + .thenReturn(expectedImages); + + List result = generateRecipeImagesUseCase.execute(command); + + assertNotNull(result); + assertEquals(2, result.size()); + verify(getRecipeStepsImagesRepository).execute(recipe); + } + + @Test + void execute_whenRepositoryReturnsNull_thenReturnEmptyList() { + Recipe recipe = RecipeFactory.create(); + GenerateRecipeImagesCommand.Command command = GenerateRecipeImagesCommand.Command.builder() + .recipe(recipe) + .build(); + + when(getRecipeStepsImagesRepository.execute(any(Recipe.class))) + .thenReturn(null); + + List result = generateRecipeImagesUseCase.execute(command); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(getRecipeStepsImagesRepository).execute(recipe); + } + + @Test + void execute_whenRepositoryThrowsException_thenReturnEmptyList() { + Recipe recipe = RecipeFactory.create(); + GenerateRecipeImagesCommand.Command command = GenerateRecipeImagesCommand.Command.builder() + .recipe(recipe) + .build(); + + when(getRecipeStepsImagesRepository.execute(any(Recipe.class))) + .thenThrow(new RuntimeException("Test exception")); + + List result = generateRecipeImagesUseCase.execute(command); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(getRecipeStepsImagesRepository).execute(recipe); + } + + @Test + void execute_whenRepositoryReturnsEmptyList_thenReturnEmptyList() { + Recipe recipe = RecipeFactory.create(); + GenerateRecipeImagesCommand.Command command = GenerateRecipeImagesCommand.Command.builder() + .recipe(recipe) + .build(); + + when(getRecipeStepsImagesRepository.execute(any(Recipe.class))) + .thenReturn(List.of()); + + List result = generateRecipeImagesUseCase.execute(command); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(getRecipeStepsImagesRepository).execute(recipe); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetAllAllergiesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllAllergiesUseCaseTest.java new file mode 100644 index 0000000..4552f7d --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllAllergiesUseCaseTest.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllAllergiesRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllAllergiesUseCaseTest { + + private GetAllAllergiesRepository getAllAllergiesRepository; + + private GetAllAllergiesUseCase useCase; + + @BeforeEach + void setup() { + getAllAllergiesRepository = mock(GetAllAllergiesRepository.class); + useCase = new GetAllAllergiesUseCase(getAllAllergiesRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllAllergiesRepository.execute()).thenReturn(List.of()); + + useCase.execute(); + + verify(getAllAllergiesRepository, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetAllCookLevelsUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllCookLevelsUseCaseTest.java new file mode 100644 index 0000000..e9e6bb0 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllCookLevelsUseCaseTest.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllCookLevelsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllCookLevelsUseCaseTest { + + private GetAllCookLevelsRepository getAllCookLevelsRepository; + + private GetAllCookLevelsUseCase useCase; + + @BeforeEach + void setup() { + getAllCookLevelsRepository = mock(GetAllCookLevelsRepository.class); + useCase = new GetAllCookLevelsUseCase(getAllCookLevelsRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllCookLevelsRepository.execute()).thenReturn(List.of()); + + useCase.execute(); + + verify(getAllCookLevelsRepository, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCaseTest.java new file mode 100644 index 0000000..3935037 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllDietaryNeedsUseCaseTest.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllDietaryNeedsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class GetAllDietaryNeedsUseCaseTest { + + private GetAllDietaryNeedsRepository getAllDietaryNeedsRepository; + + private GetAllDietaryNeedsUseCase useCase; + + @BeforeEach + void setup() { + getAllDietaryNeedsRepository = mock(GetAllDietaryNeedsRepository.class); + useCase = new GetAllDietaryNeedsUseCase(getAllDietaryNeedsRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllDietaryNeedsRepository.execute()).thenReturn(List.of()); + + useCase.execute(); + + verify(getAllDietaryNeedsRepository, times(1)).execute(); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetAllDietsUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllDietsUseCaseTest.java new file mode 100644 index 0000000..b93dc4f --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllDietsUseCaseTest.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllDietsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class GetAllDietsUseCaseTest { + + private GetAllDietsRepository getAllDietsRepository; + + private GetAllDietsUseCase useCase; + + @BeforeEach + void setup() { + getAllDietsRepository = mock(GetAllDietsRepository.class); + useCase = new GetAllDietsUseCase(getAllDietsRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllDietsRepository.execute()).thenReturn(List.of()); + + useCase.execute(); + + verify(getAllDietsRepository, times(1)).execute(); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetAllPlansUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllPlansUseCaseTest.java new file mode 100644 index 0000000..d6e0267 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllPlansUseCaseTest.java @@ -0,0 +1,34 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllPlansRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class GetAllPlansUseCaseTest { + + private GetAllPlansRepository getAllPlansRepository; + + private GetAllPlansUseCase useCase; + + @BeforeEach + void setup() { + getAllPlansRepository = mock(GetAllPlansRepository.class); + useCase = new GetAllPlansUseCase(getAllPlansRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllPlansRepository.execute()).thenReturn(List.of()); + + useCase.execute(); + + verify(getAllPlansRepository, times(1)).execute(); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCaseTest.java new file mode 100644 index 0000000..052fd0c --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetAllPreparationTimesUseCaseTest.java @@ -0,0 +1,31 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllPreparationTimesRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetAllPreparationTimesUseCaseTest { + + private GetAllPreparationTimesRepository getAllPreparationTimesRepository; + private GetAllPreparationTimesUseCase useCase; + + @BeforeEach + void setup() { + getAllPreparationTimesRepository = mock(GetAllPreparationTimesRepository.class); + useCase = new GetAllPreparationTimesUseCase(getAllPreparationTimesRepository); + } + + @Test + void GIVEN_no_params_WHEN_execute_THEN_repository_called_once() { + when(getAllPreparationTimesRepository.execute()).thenReturn(List.of()); + useCase.execute(); + verify(getAllPreparationTimesRepository, times(1)).execute(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCaseTest.java new file mode 100644 index 0000000..dc594bb --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioAsyncUseCaseTest.java @@ -0,0 +1,104 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.GetIngredientsFromAudioAsyncCommand; +import com.cuoco.application.port.out.GetIngredientsFromAudioAsyncRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GetIngredientsFromAudioAsyncUseCaseTest { + + private GetIngredientsFromAudioAsyncRepository getIngredientsFromAudioAsyncRepository; + private FileDomainService fileDomainService; + private MultipartFile multipartFile; + + private GetIngredientsFromAudioAsyncUseCase useCase; + + @BeforeEach + void setup() { + getIngredientsFromAudioAsyncRepository = mock(GetIngredientsFromAudioAsyncRepository.class); + fileDomainService = mock(FileDomainService.class); + multipartFile = mock(MultipartFile.class); + + useCase = new GetIngredientsFromAudioAsyncUseCase(getIngredientsFromAudioAsyncRepository, fileDomainService); + } + + @Test + void GIVEN_valid_audio_WHEN_execute_THEN_return_ingredients_list() throws Exception { + String base64 = "base64string"; + String format = "mp3"; + + List ingredients = List.of( + Ingredient.builder().name("Ingredient 1").build(), + Ingredient.builder().name("Ingredient 2").build() + ); + + GetIngredientsFromAudioAsyncCommand.Command command = GetIngredientsFromAudioAsyncCommand.Command.builder() + .audioFile(multipartFile) + .language("en") + .build(); + + when(multipartFile.isEmpty()).thenReturn(false); + when(fileDomainService.isValidAudioFile(multipartFile)).thenReturn(false); + when(fileDomainService.convertToBase64(multipartFile)).thenReturn(base64); + when(fileDomainService.getAudioFormat(multipartFile)).thenReturn(format); + when(getIngredientsFromAudioAsyncRepository.execute(base64, format, "en")) + .thenReturn(CompletableFuture.completedFuture(ingredients)); + + CompletableFuture> futureResult = useCase.execute(command); + + List result = futureResult.get(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Ingredient 1", result.get(0).getName()); + } + + @Test + void GIVEN_null_audio_file_WHEN_execute_THEN_throw_bad_request() { + GetIngredientsFromAudioAsyncCommand.Command command = GetIngredientsFromAudioAsyncCommand.Command.builder() + .audioFile(null) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_empty_audio_file_WHEN_execute_THEN_throw_bad_request() { + when(multipartFile.isEmpty()).thenReturn(true); + + GetIngredientsFromAudioAsyncCommand.Command command = GetIngredientsFromAudioAsyncCommand.Command.builder() + .audioFile(multipartFile) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_audio_format_WHEN_execute_THEN_throw_bad_request() { + when(multipartFile.isEmpty()).thenReturn(false); + when(fileDomainService.isValidAudioFile(multipartFile)).thenReturn(true); + + GetIngredientsFromAudioAsyncCommand.Command command = GetIngredientsFromAudioAsyncCommand.Command.builder() + .audioFile(multipartFile) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.INVALID_AUDIO_FILE_EXTENSION.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCaseTest.java new file mode 100644 index 0000000..6d53c4c --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromAudioUseCaseTest.java @@ -0,0 +1,100 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.BadRequestException; +import com.cuoco.application.port.in.GetIngredientsFromAudioCommand; +import com.cuoco.application.port.out.GetIngredientsFromAudioRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GetIngredientsFromAudioUseCaseTest { + + private GetIngredientsFromAudioRepository getIngredientsFromAudioRepository; + private FileDomainService fileDomainService; + private MultipartFile multipartFile; + + private GetIngredientsFromAudioUseCase useCase; + + @BeforeEach + void setup() { + getIngredientsFromAudioRepository = mock(GetIngredientsFromAudioRepository.class); + fileDomainService = mock(FileDomainService.class); + multipartFile = mock(MultipartFile.class); + + useCase = new GetIngredientsFromAudioUseCase(getIngredientsFromAudioRepository, fileDomainService); + } + + @Test + void GIVEN_valid_audio_WHEN_execute_THEN_return_ingredients_list() { + String base64 = "base64string"; + String format = "mp3"; + + List ingredients = List.of( + Ingredient.builder().name("Ingredient 1").build(), + Ingredient.builder().name("Ingredient 2").build() + ); + + GetIngredientsFromAudioCommand.Command command = GetIngredientsFromAudioCommand.Command.builder() + .audioFile(multipartFile) + .language("en") + .build(); + + when(multipartFile.isEmpty()).thenReturn(false); + when(fileDomainService.isValidAudioFile(multipartFile)).thenReturn(false); + when(fileDomainService.convertToBase64(multipartFile)).thenReturn(base64); + when(fileDomainService.getAudioFormat(multipartFile)).thenReturn(format); + when(getIngredientsFromAudioRepository.execute(base64, format, "en")).thenReturn(ingredients); + + List result = useCase.execute(command); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Ingredient 1", result.get(0).getName()); + } + + @Test + void GIVEN_null_audio_file_WHEN_execute_THEN_throw_bad_request() { + GetIngredientsFromAudioCommand.Command command = GetIngredientsFromAudioCommand.Command.builder() + .audioFile(null) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_empty_audio_file_WHEN_execute_THEN_throw_bad_request() { + when(multipartFile.isEmpty()).thenReturn(true); + + GetIngredientsFromAudioCommand.Command command = GetIngredientsFromAudioCommand.Command.builder() + .audioFile(multipartFile) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.AUDIO_FILE_IS_REQUIRED.getValue(), ex.getDescription()); + } + + @Test + void GIVEN_invalid_audio_format_WHEN_execute_THEN_throw_bad_request() { + when(multipartFile.isEmpty()).thenReturn(false); + when(fileDomainService.isValidAudioFile(multipartFile)).thenReturn(true); + + GetIngredientsFromAudioCommand.Command command = GetIngredientsFromAudioCommand.Command.builder() + .audioFile(multipartFile) + .build(); + + BadRequestException ex = assertThrows(BadRequestException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.INVALID_AUDIO_FILE_EXTENSION.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCaseUnitTest.java deleted file mode 100644 index 70f218b..0000000 --- a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromFileUseCaseUnitTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetIngredientsFromFileCommand; -import com.cuoco.application.port.out.GetIngredientsFromRepository; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.multipart.MultipartFile; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("GetIngredientsFromFileUseCase - 5 Unit Tests") -class GetIngredientsFromFileUseCaseUnitTest { - - @Mock - private GetIngredientsFromRepository repository; - - private GetIngredientsFromFileUseCase useCase; - - @BeforeEach - void setUp() { - useCase = new GetIngredientsFromFileUseCase(repository); - } - - @Test - @DisplayName("Test 1: Should return ingredients when repository succeeds") - void test1_shouldReturnIngredientsWhenRepositorySucceeds() { - // Given - MockMultipartFile file = new MockMultipartFile("file", "test.jpg", "image/jpeg", "content".getBytes()); - List files = List.of(file); - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - List expectedIngredients = Arrays.asList( - new Ingredient("tomate", "imagen", false), - new Ingredient("cebolla", "imagen", false) - ); - - when(repository.execute(files)).thenReturn(expectedIngredients); - - // When - List result = useCase.execute(command); - - // Then - assertEquals(2, result.size()); - assertEquals("tomate", result.get(0).getName()); - assertEquals("cebolla", result.get(1).getName()); - verify(repository).execute(files); - } - - @Test - @DisplayName("Test 2: Should handle empty files list") - void test2_shouldHandleEmptyFilesList() { - // Given - List emptyFiles = List.of(); - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(emptyFiles); - - when(repository.execute(emptyFiles)).thenReturn(List.of()); - - // When - List result = useCase.execute(command); - - // Then - assertTrue(result.isEmpty()); - verify(repository).execute(emptyFiles); - } - - @Test - @DisplayName("Test 3: Should propagate repository exceptions") - void test3_shouldPropagateRepositoryExceptions() { - // Given - MockMultipartFile file = new MockMultipartFile("file", "error.jpg", "image/jpeg", "content".getBytes()); - List files = List.of(file); - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - when(repository.execute(files)).thenThrow(new RuntimeException("Repository error")); - - // When & Then - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - useCase.execute(command); - }); - - assertEquals("Repository error", exception.getMessage()); - verify(repository).execute(files); - } - - @Test - @DisplayName("Test 4: Should pass correct files to repository") - void test4_shouldPassCorrectFilesToRepository() { - // Given - MockMultipartFile file1 = new MockMultipartFile("file1", "test1.jpg", "image/jpeg", "content1".getBytes()); - MockMultipartFile file2 = new MockMultipartFile("file2", "test2.jpg", "image/jpeg", "content2".getBytes()); - List files = List.of(file1, file2); - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - when(repository.execute(any())).thenReturn(List.of()); - - // When - useCase.execute(command); - - // Then - verify(repository).execute(argThat(fileList -> { - assertEquals(2, fileList.size()); - assertEquals("test1.jpg", fileList.get(0).getOriginalFilename()); - assertEquals("test2.jpg", fileList.get(1).getOriginalFilename()); - return true; - })); - } - - @Test - @DisplayName("Test 5: Should return correct ingredient structure") - void test5_shouldReturnCorrectIngredientStructure() { - // Given - MockMultipartFile file = new MockMultipartFile("file", "test.jpg", "image/jpeg", "content".getBytes()); - List files = List.of(file); - GetIngredientsFromFileCommand.Command command = new GetIngredientsFromFileCommand.Command(files); - - List repositoryResult = Arrays.asList( - new Ingredient("zanahoria", "imagen", true), - new Ingredient("apio", "imagen", false), - new Ingredient("pimiento", "imagen", true) - ); - - when(repository.execute(files)).thenReturn(repositoryResult); - - // When - List result = useCase.execute(command); - - // Then - assertEquals(3, result.size()); - - // Verify first ingredient - assertEquals("zanahoria", result.get(0).getName()); - assertEquals("imagen", result.get(0).getSource()); - assertTrue(result.get(0).isConfirmed()); - - // Verify second ingredient - assertEquals("apio", result.get(1).getName()); - assertEquals("imagen", result.get(1).getSource()); - assertFalse(result.get(1).isConfirmed()); - - // Verify third ingredient - assertEquals("pimiento", result.get(2).getName()); - assertEquals("imagen", result.get(2).getSource()); - assertTrue(result.get(2).isConfirmed()); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCaseTest.java new file mode 100644 index 0000000..9513ff3 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromImagesUseCaseTest.java @@ -0,0 +1,83 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetIngredientsFromImagesCommand; +import com.cuoco.application.port.out.GetIngredientsFromImageRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GetIngredientsFromImagesUseCaseTest { + + private GetIngredientsFromImageRepository getIngredientsFromImageRepository; + private FileDomainService fileDomainService; + private MultipartFile imageFile1; + private MultipartFile imageFile2; + + private GetIngredientsFromImagesUseCase useCase; + + @BeforeEach + void setup() { + getIngredientsFromImageRepository = mock(GetIngredientsFromImageRepository.class); + fileDomainService = mock(FileDomainService.class); + imageFile1 = mock(MultipartFile.class); + imageFile2 = mock(MultipartFile.class); + + useCase = new GetIngredientsFromImagesUseCase(getIngredientsFromImageRepository, fileDomainService); + } + + @Test + void GIVEN_valid_images_WHEN_execute_THEN_return_ingredients_list() { + List imageFiles = List.of(imageFile1, imageFile2); + + when(fileDomainService.getFileName(imageFile1)).thenReturn("file1.jpg"); + when(fileDomainService.convertToBase64(imageFile1)).thenReturn("base641"); + when(fileDomainService.getMimeType(imageFile1)).thenReturn("image/jpeg"); + + when(fileDomainService.getFileName(imageFile2)).thenReturn("file2.png"); + when(fileDomainService.convertToBase64(imageFile2)).thenReturn("base642"); + when(fileDomainService.getMimeType(imageFile2)).thenReturn("image/png"); + + List ingredients = List.of( + Ingredient.builder().name("Ingredient 1").build(), + Ingredient.builder().name("Ingredient 2").build() + ); + + when(getIngredientsFromImageRepository.execute(anyList())).thenReturn(ingredients); + + GetIngredientsFromImagesCommand.Command command = GetIngredientsFromImagesCommand.Command.builder() + .images(imageFiles) + .build(); + + List result = useCase.execute(command); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Ingredient 1", result.get(0).getName()); + assertEquals("Ingredient 2", result.get(1).getName()); + } + + @Test + void GIVEN_empty_image_list_WHEN_execute_THEN_return_empty_ingredient_list() { + GetIngredientsFromImagesCommand.Command command = GetIngredientsFromImagesCommand.Command.builder() + .images(List.of()) + .build(); + + when(getIngredientsFromImageRepository.execute(anyList())).thenReturn(List.of()); + + List result = useCase.execute(command); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseTest.java new file mode 100644 index 0000000..4674a24 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseTest.java @@ -0,0 +1,49 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetIngredientsFromTextCommand; +import com.cuoco.application.port.out.GetIngredientsFromTextRepository; +import com.cuoco.application.usecase.model.Ingredient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetIngredientsFromTextUseCaseTest { + + private GetIngredientsFromTextRepository getIngredientsFromTextRepository; + private GetIngredientsFromTextUseCase useCase; + + @BeforeEach + void setup() { + getIngredientsFromTextRepository = mock(GetIngredientsFromTextRepository.class); + useCase = new GetIngredientsFromTextUseCase(getIngredientsFromTextRepository); + } + + @Test + void GIVEN_valid_text_WHEN_execute_THEN_return_ingredient_list() { + String text = "tomate, cebolla"; + List expectedIngredients = List.of( + Ingredient.builder().name("tomate").build(), + Ingredient.builder().name("cebolla").build() + ); + + when(getIngredientsFromTextRepository.execute(text)).thenReturn(expectedIngredients); + + GetIngredientsFromTextCommand.Command command = GetIngredientsFromTextCommand.Command.builder() + .text(text) + .build(); + + List result = useCase.execute(command); + + assertEquals(2, result.size()); + assertEquals("tomate", result.get(0).getName()); + assertEquals("cebolla", result.get(1).getName()); + verify(getIngredientsFromTextRepository, times(1)).execute(text); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseUnitTest.java deleted file mode 100644 index 0d5d927..0000000 --- a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromTextUseCaseUnitTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetIngredientsFromTextCommand; -import com.cuoco.application.port.out.GetIngredientsFromTextRepository; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("GetIngredientsFromTextUseCase - Simple TDD") -class GetIngredientsFromTextUseCaseUnitTest { - - @Mock - private GetIngredientsFromTextRepository repository; - - private GetIngredientsFromTextUseCase useCase; - - @BeforeEach - void setUp() { - useCase = new GetIngredientsFromTextUseCase(repository); - } - - @Test - @DisplayName("Test 1: Should parse simple comma-separated text") - void test1_shouldParseSimpleCommaSeparatedText() { - // Given - SIMPLE: solo split por comas - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command("tomate, cebolla, ajo", "manual"); - - List expected = Arrays.asList( - new Ingredient("tomate", "text", false), - new Ingredient("cebolla", "text", false), - new Ingredient("ajo", "text", false) - ); - - when(repository.execute("tomate, cebolla, ajo")).thenReturn(expected); - - // When - List result = useCase.execute(command); - - // Then - assertEquals(3, result.size()); - assertEquals("tomate", result.get(0).getName()); - assertEquals("text", result.get(0).getSource()); - assertFalse(result.get(0).isConfirmed()); - - verify(repository, times(1)).execute("tomate, cebolla, ajo"); - } - - @Test - @DisplayName("Test 2: Should handle empty text") - void test2_shouldHandleEmptyText() { - // Given - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command("", "manual"); - - when(repository.execute("")).thenReturn(Arrays.asList()); - - // When - List result = useCase.execute(command); - - // Then - assertTrue(result.isEmpty()); - verify(repository, times(1)).execute(""); - } - - @Test - @DisplayName("Test 3: Should handle single ingredient") - void test3_shouldHandleSingleIngredient() { - // Given - GetIngredientsFromTextCommand.Command command = - new GetIngredientsFromTextCommand.Command("tomate", "manual"); - - List expected = Arrays.asList( - new Ingredient("tomate", "text", false) - ); - - when(repository.execute("tomate")).thenReturn(expected); - - // When - List result = useCase.execute(command); - - // Then - assertEquals(1, result.size()); - assertEquals("tomate", result.get(0).getName()); - verify(repository, times(1)).execute("tomate"); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCaseUnitTest.java deleted file mode 100644 index 20f7731..0000000 --- a/src/test/java/com/cuoco/application/usecase/GetIngredientsFromVoiceUseCaseUnitTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetIngredientsFromVoiceCommand; -import com.cuoco.application.port.out.GetIngredientsFromVoiceRepository; -import com.cuoco.application.usecase.model.Ingredient; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class GetIngredientsFromVoiceUseCaseUnitTest { - - @Mock - private GetIngredientsFromVoiceRepository getIngredientsFromVoiceRepository; - - private GetIngredientsFromVoiceUseCase getIngredientsFromVoiceUseCase; - - @BeforeEach - void setUp() { - getIngredientsFromVoiceUseCase = new GetIngredientsFromVoiceUseCase(getIngredientsFromVoiceRepository); - } - - @Test - void test1_execute_shouldProcessVoiceAndReturnIngredients() { - // Given - String audioBase64 = "base64AudioData"; - String format = "mp3"; - String language = "es-ES"; - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - List expectedIngredients = List.of( - new Ingredient("tomate", "voz", false), - new Ingredient("cebolla", "voz", false), - new Ingredient("ajo", "voz", false) - ); - - when(getIngredientsFromVoiceRepository.processVoice(audioBase64, format, language)) - .thenReturn(expectedIngredients); - - // When - List result = getIngredientsFromVoiceUseCase.execute(command); - - // Then - assertEquals(expectedIngredients, result); - assertEquals(3, result.size()); - assertEquals("tomate", result.get(0).getName()); - assertEquals("voz", result.get(0).getSource()); - assertFalse(result.get(0).isConfirmed()); - - verify(getIngredientsFromVoiceRepository).processVoice(audioBase64, format, language); - } - - @Test - void test2_execute_shouldHandleEmptyIngredientsResponse() { - // Given - String audioBase64 = "base64AudioData"; - String format = "wav"; - String language = "es-ES"; - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - List emptyIngredients = List.of(); - - when(getIngredientsFromVoiceRepository.processVoice(audioBase64, format, language)) - .thenReturn(emptyIngredients); - - // When - List result = getIngredientsFromVoiceUseCase.execute(command); - - // Then - assertNotNull(result); - assertTrue(result.isEmpty()); - - verify(getIngredientsFromVoiceRepository).processVoice(audioBase64, format, language); - } - - @Test - void test3_executeAsync_shouldProcessVoiceAsynchronously() { - // Given - String audioBase64 = "base64AudioData"; - String format = "ogg"; - String language = "en-US"; - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - List expectedIngredients = List.of( - new Ingredient("tomato", "voz", false), - new Ingredient("onion", "voz", false) - ); - - CompletableFuture> futureIngredients = CompletableFuture.completedFuture(expectedIngredients); - - when(getIngredientsFromVoiceRepository.processVoiceAsync(audioBase64, format, language)) - .thenReturn(futureIngredients); - - // When - CompletableFuture> result = getIngredientsFromVoiceUseCase.executeAsync(command); - - // Then - assertNotNull(result); - assertTrue(result.isDone()); - assertEquals(expectedIngredients, result.join()); - - verify(getIngredientsFromVoiceRepository).processVoiceAsync(audioBase64, format, language); - } - - @Test - void test4_execute_shouldHandleDifferentAudioFormats() { - // Given - String audioBase64 = "base64AudioData"; - String format = "flac"; - String language = "es-AR"; - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - List expectedIngredients = List.of( - new Ingredient("palta", "voz", false), - new Ingredient("choclo", "voz", false) - ); - - when(getIngredientsFromVoiceRepository.processVoice(audioBase64, format, language)) - .thenReturn(expectedIngredients); - - // When - List result = getIngredientsFromVoiceUseCase.execute(command); - - // Then - assertEquals(expectedIngredients, result); - assertEquals("palta", result.get(0).getName()); - assertEquals("choclo", result.get(1).getName()); - - verify(getIngredientsFromVoiceRepository).processVoice(audioBase64, format, language); - } - - @Test - void test5_executeAsync_shouldHandleRepositoryException() { - // Given - String audioBase64 = "base64AudioData"; - String format = "mp3"; - String language = "es-ES"; - GetIngredientsFromVoiceCommand.Command command = - new GetIngredientsFromVoiceCommand.Command(audioBase64, format, language); - - CompletableFuture> failedFuture = new CompletableFuture<>(); - failedFuture.completeExceptionally(new RuntimeException("Repository error")); - - when(getIngredientsFromVoiceRepository.processVoiceAsync(audioBase64, format, language)) - .thenReturn(failedFuture); - - // When - CompletableFuture> result = getIngredientsFromVoiceUseCase.executeAsync(command); - - // Then - assertNotNull(result); - assertTrue(result.isCompletedExceptionally()); - - verify(getIngredientsFromVoiceRepository).processVoiceAsync(audioBase64, format, language); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCaseTest.java new file mode 100644 index 0000000..81cd886 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetIngredientsGroupedFromImagesUseCaseTest.java @@ -0,0 +1,95 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetIngredientsGroupedFromImagesCommand; +import com.cuoco.application.port.out.GetAllUnitsRepository; +import com.cuoco.application.port.out.GetIngredientsGroupedFromImagesRepository; +import com.cuoco.application.usecase.domainservice.FileDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Unit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GetIngredientsGroupedFromImagesUseCaseTest { + + private GetIngredientsGroupedFromImagesRepository getIngredientsGroupedFromImagesRepository; + private GetAllUnitsRepository getAllUnitsRepository; + private FileDomainService fileDomainService; + private MultipartFile imageFile1; + private MultipartFile imageFile2; + + private GetIngredientsGroupedFromImagesUseCase useCase; + + @BeforeEach + void setup() { + getAllUnitsRepository = mock(GetAllUnitsRepository.class); + getIngredientsGroupedFromImagesRepository = mock(GetIngredientsGroupedFromImagesRepository.class); + fileDomainService = mock(FileDomainService.class); + imageFile1 = mock(MultipartFile.class); + imageFile2 = mock(MultipartFile.class); + + useCase = new GetIngredientsGroupedFromImagesUseCase( + getIngredientsGroupedFromImagesRepository, + getAllUnitsRepository, + fileDomainService + ); + } + + @Test + void GIVEN_valid_images_WHEN_execute_THEN_return_ingredients_grouped_by_filename() { + List imageFiles = List.of(imageFile1, imageFile2); + + when(fileDomainService.getFileName(imageFile1)).thenReturn("image1.jpg"); + when(fileDomainService.convertToBase64(imageFile1)).thenReturn("base641"); + when(fileDomainService.getMimeType(imageFile1)).thenReturn("image/jpeg"); + + when(fileDomainService.getFileName(imageFile2)).thenReturn("image2.jpg"); + when(fileDomainService.convertToBase64(imageFile2)).thenReturn("base642"); + when(fileDomainService.getMimeType(imageFile2)).thenReturn("image/jpeg"); + + Map> expectedMap = Map.of( + "image1.jpg", List.of(Ingredient.builder().name("Tomato").build()), + "image2.jpg", List.of(Ingredient.builder().name("Lettuce").build()) + ); + + when(getAllUnitsRepository.execute()).thenReturn(List.of(Unit.builder().id(1).build())); + when(getIngredientsGroupedFromImagesRepository.execute(anyList(), any())).thenReturn(expectedMap); + + GetIngredientsGroupedFromImagesCommand.Command command = GetIngredientsGroupedFromImagesCommand.Command.builder() + .images(imageFiles) + .build(); + + Map> result = useCase.execute(command); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.containsKey("image1.jpg")); + assertEquals("Tomato", result.get("image1.jpg").get(0).getName()); + } + + @Test + void GIVEN_empty_image_list_WHEN_execute_THEN_return_empty_map() { + GetIngredientsGroupedFromImagesCommand.Command command = GetIngredientsGroupedFromImagesCommand.Command.builder() + .images(List.of()) + .build(); + + when(getAllUnitsRepository.execute()).thenReturn(List.of(Unit.builder().id(1).build())); + when(getIngredientsGroupedFromImagesRepository.execute(anyList(), any())).thenReturn(Map.of()); + + Map> result = useCase.execute(command); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetMealPrepByIdUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetMealPrepByIdUseCaseTest.java new file mode 100644 index 0000000..98b8cef --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetMealPrepByIdUseCaseTest.java @@ -0,0 +1,35 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetMealPrepByIdRepository; +import com.cuoco.application.usecase.model.MealPrep; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetMealPrepByIdUseCaseTest { + + private GetMealPrepByIdRepository getMealPrepByIdRepository; + private GetMealPrepByIdUseCase useCase; + + @BeforeEach + void setup() { + getMealPrepByIdRepository = mock(GetMealPrepByIdRepository.class); + useCase = new GetMealPrepByIdUseCase(getMealPrepByIdRepository); + } + + @Test + void GIVEN_id_WHEN_execute_THEN_return_meal_prep() { + MealPrep mealPrep = MealPrep.builder().id(1L).build(); + + when(getMealPrepByIdRepository.execute(1L)).thenReturn(mealPrep); + MealPrep result = useCase.execute(1L); + + assertEquals(mealPrep, result); + verify(getMealPrepByIdRepository, times(1)).execute(1L); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseTest.java new file mode 100644 index 0000000..5d2688f --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseTest.java @@ -0,0 +1,113 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetMealTypeByIdRepository; +import com.cuoco.application.port.out.GetPreparationTimeByIdRepository; +import com.cuoco.application.usecase.domainservice.RecipeDomainService; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.IngredientFactory; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.domain.UserFactory; +import com.cuoco.shared.utils.PlanConstants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GetRecipesFromIngredientsUseCaseTest { + + private RecipeDomainService recipeDomainService; + private GetPreparationTimeByIdRepository getPreparationTimeByIdRepository; + private GetCookLevelByIdRepository getCookLevelByIdRepository; + private GetMealTypeByIdRepository getMealTypeByIdRepository; + private GetDietByIdRepository getDietByIdRepository; + private GetAllergiesByIdRepository getAllergiesByIdRepository; + private GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + + private GetRecipesFromIngredientsUseCase useCase; + + @BeforeEach + void setUp() { + recipeDomainService = mock(RecipeDomainService.class); + getPreparationTimeByIdRepository = mock(GetPreparationTimeByIdRepository.class); + getCookLevelByIdRepository = mock(GetCookLevelByIdRepository.class); + getMealTypeByIdRepository = mock(GetMealTypeByIdRepository.class); + getDietByIdRepository = mock(GetDietByIdRepository.class); + getAllergiesByIdRepository = mock(GetAllergiesByIdRepository.class); + getDietaryNeedsByIdRepository = mock(GetDietaryNeedsByIdRepository.class); + + useCase = new GetRecipesFromIngredientsUseCase( + recipeDomainService, + getPreparationTimeByIdRepository, + getCookLevelByIdRepository, + getMealTypeByIdRepository, + getDietByIdRepository, + getAllergiesByIdRepository, + getDietaryNeedsByIdRepository + ); + + ReflectionTestUtils.setField(useCase, "FREE_USER_RECIPES_SIZE", 3); + ReflectionTestUtils.setField(useCase, "PRO_USER_RECIPES_SIZE", 5); + + User user = UserFactory.create(); + user.setPlan(Plan.builder().id(PlanConstants.FREE.getValue()).build()); + + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, List.of()); + SecurityContextHolder.getContext().setAuthentication(auth); + } + + @Test + void GIVEN_valid_ingredients_WHEN_execute_THEN_return_recipes() { + List ingredients = List.of(IngredientFactory.create()); + List expectedRecipes = List.of( + RecipeFactory.create(), + RecipeFactory.create(), + RecipeFactory.create() + ); + + when(recipeDomainService.getOrCreate(any())).thenReturn(expectedRecipes); + + GetRecipesFromIngredientsCommand.Command command = GetRecipesFromIngredientsCommand.Command.builder() + .ingredients(ingredients) + .build(); + + List result = useCase.execute(command); + + assertEquals(expectedRecipes, result); + verify(recipeDomainService).getOrCreate(any()); + } + + @Test + void GIVEN_empty_ingredients_WHEN_execute_THEN_throw_exception() { + List ingredients = List.of(); + + GetRecipesFromIngredientsCommand.Command command = GetRecipesFromIngredientsCommand.Command.builder() + .ingredients(ingredients) + .build(); + + when(recipeDomainService.getOrCreate(any())).thenReturn(List.of()); + + useCase.execute(command); + + verify(recipeDomainService).getOrCreate(any()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseUnitTest.java b/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseUnitTest.java deleted file mode 100644 index aa6610d..0000000 --- a/src/test/java/com/cuoco/application/usecase/GetRecipesFromIngredientsUseCaseUnitTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.cuoco.application.usecase; - -import com.cuoco.application.port.in.GetRecipesFromIngredientsCommand; -import com.cuoco.application.port.out.GetRecipesFromIngredientsRepository; -import com.cuoco.application.usecase.model.Ingredient; -import com.cuoco.application.usecase.model.RecipeFilter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -@DisplayName("GetRecipesFromIngredientsUseCase - 5 Unit Tests") -class GetRecipesFromIngredientsUseCaseUnitTest { - - @Mock - private GetRecipesFromIngredientsRepository repository; - - private GetRecipesFromIngredientsUseCase useCase; - - @BeforeEach - void setUp() { - useCase = new GetRecipesFromIngredientsUseCase(repository); - } - - @Test - @DisplayName("Test 1: Should return recipes when valid ingredients provided") - void test1_shouldReturnRecipesWhenValidIngredientsProvided() { - // Given - List ingredients = Arrays.asList( - new Ingredient("tomate", "manual", true), - new Ingredient("cebolla", "manual", true) - ); - RecipeFilter filter = new RecipeFilter("30 min", "Fácil", Arrays.asList("Italiana"), "Vegetariana", 4); - GetRecipesFromIngredientsCommand.Command command = - new GetRecipesFromIngredientsCommand.Command(filter, ingredients); - - String expectedRecipes = "Recipe 1: Pasta italiana\nRecipe 2: Ensalada de tomate"; - when(repository.execute(ingredients)).thenReturn(expectedRecipes); - - // When - String result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals(expectedRecipes, result); - verify(repository, times(1)).execute(ingredients); - } - - @Test - @DisplayName("Test 2: Should handle empty ingredients list") - void test2_shouldHandleEmptyIngredientsList() { - // Given - List emptyIngredients = Collections.emptyList(); - RecipeFilter filter = new RecipeFilter("15 min", "Fácil", Arrays.asList("Rápida"), null, 2); - GetRecipesFromIngredientsCommand.Command command = - new GetRecipesFromIngredientsCommand.Command(filter, emptyIngredients); - - String expectedMessage = "No se pueden generar recetas sin ingredientes"; - when(repository.execute(emptyIngredients)).thenReturn(expectedMessage); - - // When - String result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals(expectedMessage, result); - verify(repository, times(1)).execute(emptyIngredients); - } - - @Test - @DisplayName("Test 3: Should propagate repository exceptions") - void test3_shouldPropagateRepositoryExceptions() { - // Given - List ingredients = Arrays.asList( - new Ingredient("ajo", "voice", false) - ); - GetRecipesFromIngredientsCommand.Command command = - new GetRecipesFromIngredientsCommand.Command(null, ingredients); - - RuntimeException expectedException = new RuntimeException("API connection failed"); - when(repository.execute(ingredients)).thenThrow(expectedException); - - // When & Then - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - useCase.execute(command); - }); - - assertEquals("API connection failed", exception.getMessage()); - verify(repository, times(1)).execute(ingredients); - } - - @Test - @DisplayName("Test 4: Should handle null filter correctly") - void test4_shouldHandleNullFilterCorrectly() { - // Given - List ingredients = Arrays.asList( - new Ingredient("arroz", "camera", true), - new Ingredient("pollo", "text", true) - ); - GetRecipesFromIngredientsCommand.Command command = - new GetRecipesFromIngredientsCommand.Command(null, ingredients); - - String expectedRecipes = "Recipe without specific filters"; - when(repository.execute(ingredients)).thenReturn(expectedRecipes); - - // When - String result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals(expectedRecipes, result); - verify(repository, times(1)).execute(ingredients); - } - - @Test - @DisplayName("Test 5: Should handle mixed ingredient sources and confirmation status") - void test5_shouldHandleMixedIngredientSourcesAndConfirmationStatus() { - // Given - List mixedIngredients = Arrays.asList( - new Ingredient("tomate", "camera", true), - new Ingredient("cebolla", "voice", false), - new Ingredient("ajo", "text", true), - new Ingredient("aceite", "manual", false) - ); - RecipeFilter complexFilter = new RecipeFilter("45 min", "Intermedio", - Arrays.asList("Mediterránea", "Saludable"), "Omnívora", 6); - GetRecipesFromIngredientsCommand.Command command = - new GetRecipesFromIngredientsCommand.Command(complexFilter, mixedIngredients); - - String expectedRecipes = "Complex recipe with mixed ingredients"; - when(repository.execute(mixedIngredients)).thenReturn(expectedRecipes); - - // When - String result = useCase.execute(command); - - // Then - assertNotNull(result); - assertEquals(expectedRecipes, result); - verify(repository, times(1)).execute(argThat(ingredientList -> { - assertEquals(4, ingredientList.size()); - assertEquals("tomate", ingredientList.get(0).getName()); - assertEquals("camera", ingredientList.get(0).getSource()); - assertTrue(ingredientList.get(0).isConfirmed()); - return true; - })); - } -} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/GetUserRecipesUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/GetUserRecipesUseCaseTest.java new file mode 100644 index 0000000..e8d0389 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/GetUserRecipesUseCaseTest.java @@ -0,0 +1,66 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.out.GetAllUserRecipesByUserIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.domain.UserFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class GetUserRecipesUseCaseTest { + + private UserDomainService userDomainService; + private GetAllUserRecipesByUserIdRepository repository; + private GetAllUserRecipesUseCase useCase; + + @BeforeEach + void setUp() { + userDomainService = mock(UserDomainService.class); + repository = mock(GetAllUserRecipesByUserIdRepository.class); + useCase = new GetAllUserRecipesUseCase(userDomainService, repository); + } + + @Test + void shouldReturnUserRecipesWhenUserIsAuthenticated() { + User user = UserFactory.create(); + List userRecipes = prepareUserRecipes(); + List expectedRecipes = userRecipes.stream().map(UserRecipe::getRecipe).toList(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(repository.execute(user.getId())).thenReturn(userRecipes); + + List result = useCase.execute(); + + assertEquals(expectedRecipes, result); + verify(repository).execute(user.getId()); + } + + private List prepareUserRecipes() { + User user = UserFactory.create(); + Recipe recipe1 = RecipeFactory.create(); + Recipe recipe2 = RecipeFactory.create(); + + UserRecipe userRecipe1 = UserRecipe.builder() + .user(user) + .recipe(recipe1) + .build(); + + UserRecipe userRecipe2 = UserRecipe.builder() + .user(user) + .recipe(recipe2) + .build(); + + return Arrays.asList(userRecipe1, userRecipe2); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/SignInUserUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/SignInUserUseCaseTest.java new file mode 100644 index 0000000..e01a232 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/SignInUserUseCaseTest.java @@ -0,0 +1,89 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.ForbiddenException; +import com.cuoco.application.port.in.SignInUserCommand; +import com.cuoco.application.port.out.GetUserByEmailRepository; +import com.cuoco.application.usecase.model.AuthenticatedUser; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; +import com.cuoco.shared.model.ErrorDescription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SignInUserUseCaseTest { + + private GetUserByEmailRepository getUserByEmailRepository; + private JwtUtil jwtUtil; + private PasswordEncoder passwordEncoder; + + private SignInUserUseCase useCase; + + @BeforeEach + void setup() { + getUserByEmailRepository = mock(GetUserByEmailRepository.class); + jwtUtil = mock(JwtUtil.class); + passwordEncoder = mock(PasswordEncoder.class); + + useCase = new SignInUserUseCase(getUserByEmailRepository, jwtUtil, passwordEncoder); + } + + @Test + void GIVEN_valid_credentials_WHEN_execute_THEN_return_authenticated_user() { + String email = "test@example.com"; + String rawPassword = "password123"; + String encodedPassword = "$2a$10$encodedPasswordHash"; + + User user = User.builder() + .email(email) + .password(encodedPassword) + .build(); + + when(getUserByEmailRepository.execute(email)).thenReturn(user); + when(passwordEncoder.matches(rawPassword, encodedPassword)).thenReturn(true); + when(jwtUtil.generateToken(any(User.class))).thenReturn("jwt-token"); + + SignInUserCommand.Command command = SignInUserCommand.Command.builder() + .email(email) + .password(rawPassword) + .build(); + + AuthenticatedUser result = useCase.execute(command); + + assertNotNull(result); + assertEquals("jwt-token", result.getToken()); + assertEquals(email, result.getUser().getEmail()); + assertNull(result.getUser().getPassword()); + } + + @Test + void GIVEN_invalid_password_WHEN_execute_THEN_throw_forbidden_exception() { + String email = "test@example.com"; + String rawPassword = "wrong-password"; + String encodedPassword = "$2a$10$encodedPasswordHash"; + + User user = User.builder() + .email(email) + .password(encodedPassword) + .build(); + + when(getUserByEmailRepository.execute(email)).thenReturn(user); + when(passwordEncoder.matches(rawPassword, encodedPassword)).thenReturn(false); + + SignInUserCommand.Command command = SignInUserCommand.Command.builder() + .email(email) + .password(rawPassword) + .build(); + + ForbiddenException ex = assertThrows(ForbiddenException.class, () -> useCase.execute(command)); + assertEquals(ErrorDescription.INVALID_CREDENTIALS.getValue(), ex.getDescription()); + } +} diff --git a/src/test/java/com/cuoco/application/usecase/UpdateUserProfileUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/UpdateUserProfileUseCaseTest.java new file mode 100644 index 0000000..4b3d914 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/UpdateUserProfileUseCaseTest.java @@ -0,0 +1,217 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.port.in.UpdateUserProfileCommand; +import com.cuoco.application.port.out.GetAllergiesByIdRepository; +import com.cuoco.application.port.out.GetCookLevelByIdRepository; +import com.cuoco.application.port.out.GetDietByIdRepository; +import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.User; +import com.cuoco.factory.domain.UserFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UpdateUserProfileUseCaseTest { + + @Mock + private UserDomainService userDomainService; + @Mock + private GetUserByIdRepository getUserByIdRepository; + @Mock + private UpdateUserRepository updateUserRepository; + @Mock + private GetDietByIdRepository getDietByIdRepository; + @Mock + private GetCookLevelByIdRepository getCookLevelByIdRepository; + @Mock + private GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; + @Mock + private GetAllergiesByIdRepository getAllergiesByIdRepository; + + private UpdateUserProfileUseCase updateUserProfileUseCase; + + @BeforeEach + void setUp() { + updateUserProfileUseCase = new UpdateUserProfileUseCase( + userDomainService, + getUserByIdRepository, + updateUserRepository, + getDietByIdRepository, + getCookLevelByIdRepository, + getDietaryNeedsByIdRepository, + getAllergiesByIdRepository + ); + } + + @Test + void shouldUpdateUserProfileSuccessfully() { + String userName = "Updated Name"; + User currentUser = UserFactory.create(); + User existingUser = UserFactory.create(); + + UpdateUserProfileCommand.Command command = UpdateUserProfileCommand.Command.builder() + .name(userName) + .planId(1) + .cookLevelId(1) + .dietId(1) + .dietaryNeeds(List.of(1, 2)) + .allergies(List.of(1)) + .build(); + + CookLevel mockCookLevel = CookLevel.builder().id(1).description("Beginner").build(); + Diet mockDiet = Diet.builder().id(1).description("Vegetarian").build(); + List mockDietaryNeeds = List.of( + DietaryNeed.builder().id(1).description("Low Sodium").build(), + DietaryNeed.builder().id(2).description("High Protein").build() + ); + List mockAllergies = List.of( + Allergy.builder().id(1).description("Nuts").build() + ); + + when(userDomainService.getCurrentUser()).thenReturn(currentUser); + when(getUserByIdRepository.execute(currentUser.getId())).thenReturn(existingUser); + when(getCookLevelByIdRepository.execute(1)).thenReturn(mockCookLevel); + when(getDietByIdRepository.execute(1)).thenReturn(mockDiet); + when(getDietaryNeedsByIdRepository.execute(List.of(1, 2))).thenReturn(mockDietaryNeeds); + when(getAllergiesByIdRepository.execute(List.of(1))).thenReturn(mockAllergies); + + User expectedUser = UserFactory.create(); + expectedUser.setName(userName); + when(updateUserRepository.execute(any(User.class))).thenReturn(expectedUser); + + User result = updateUserProfileUseCase.execute(command); + + assertNotNull(result); + assertEquals(userName, result.getName()); + + verify(updateUserRepository, times(1)).execute(any(User.class)); + verify(getCookLevelByIdRepository, times(1)).execute(1); + verify(getDietByIdRepository, times(1)).execute(1); + verify(getDietaryNeedsByIdRepository, times(1)).execute(List.of(1, 2)); + verify(getAllergiesByIdRepository, times(1)).execute(List.of(1)); + } + + @Test + void shouldPassCorrectUserDataToRepository() { + String userName = "Test User"; + User currentUser = UserFactory.create(); + User existingUser = UserFactory.create(); + + UpdateUserProfileCommand.Command command = UpdateUserProfileCommand.Command.builder() + .name(userName) + .build(); + + User expectedUser = UserFactory.create(); + when(userDomainService.getCurrentUser()).thenReturn(currentUser); + when(getUserByIdRepository.execute(currentUser.getId())).thenReturn(existingUser); + when(updateUserRepository.execute(any(User.class))).thenReturn(expectedUser); + + updateUserProfileUseCase.execute(command); + + verify(updateUserRepository).execute(argThat(user -> + user.getName().equals(userName) + )); + } + + @Test + void shouldMapAllFieldsFromCommandToUser() { + String userName = "Test User"; + Integer cookLevelId = 3; + Integer dietId = 1; + List dietaryNeeds = List.of(1, 2, 3); + List allergies = List.of(4, 5); + + User currentUser = UserFactory.create(); + User existingUser = UserFactory.create(); + + UpdateUserProfileCommand.Command command = UpdateUserProfileCommand.Command.builder() + .name(userName) + .cookLevelId(cookLevelId) + .dietId(dietId) + .dietaryNeeds(dietaryNeeds) + .allergies(allergies) + .build(); + + when(userDomainService.getCurrentUser()).thenReturn(currentUser); + when(getUserByIdRepository.execute(currentUser.getId())).thenReturn(existingUser); + when(getCookLevelByIdRepository.execute(cookLevelId)).thenReturn(CookLevel.builder().id(cookLevelId).build()); + when(getDietByIdRepository.execute(dietId)).thenReturn(Diet.builder().id(dietId).build()); + when(getDietaryNeedsByIdRepository.execute(dietaryNeeds)).thenReturn(List.of( + DietaryNeed.builder().id(1).build(), + DietaryNeed.builder().id(2).build(), + DietaryNeed.builder().id(3).build() + )); + when(getAllergiesByIdRepository.execute(allergies)).thenReturn(List.of( + Allergy.builder().id(4).build(), + Allergy.builder().id(5).build() + )); + + User expectedUser = UserFactory.create(); + when(updateUserRepository.execute(any(User.class))).thenReturn(expectedUser); + + updateUserProfileUseCase.execute(command); + + verify(updateUserRepository).execute(argThat(user -> + user.getName().equals(userName) && + user.getPreferences() != null && + user.getPreferences().getCookLevel() != null && + user.getPreferences().getCookLevel().getId().equals(cookLevelId) && + user.getPreferences().getDiet() != null && + user.getPreferences().getDiet().getId().equals(dietId) && + user.getDietaryNeeds() != null && user.getDietaryNeeds().size() == 3 && + user.getAllergies() != null && user.getAllergies().size() == 2 + )); + } + + @Test + void shouldHandleNullFieldsInCommand() { + User currentUser = UserFactory.create(); + User existingUser = UserFactory.create(); + + UpdateUserProfileCommand.Command command = UpdateUserProfileCommand.Command.builder() + .name(null) + .planId(null) + .cookLevelId(null) + .dietId(null) + .dietaryNeeds(null) + .allergies(null) + .build(); + + User expectedUser = UserFactory.create(); + when(userDomainService.getCurrentUser()).thenReturn(currentUser); + when(getUserByIdRepository.execute(currentUser.getId())).thenReturn(existingUser); + when(updateUserRepository.execute(any(User.class))).thenReturn(expectedUser); + + User result = updateUserProfileUseCase.execute(command); + + assertNotNull(result); + verify(updateUserRepository, times(1)).execute(any(User.class)); + verify(getCookLevelByIdRepository, never()).execute(anyInt()); + verify(getDietByIdRepository, never()).execute(anyInt()); + verify(getDietaryNeedsByIdRepository, never()).execute(anyList()); + verify(getAllergiesByIdRepository, never()).execute(anyList()); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/UserRecipeUseCaseTest.java b/src/test/java/com/cuoco/application/usecase/UserRecipeUseCaseTest.java new file mode 100644 index 0000000..30cdb95 --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/UserRecipeUseCaseTest.java @@ -0,0 +1,102 @@ +package com.cuoco.application.usecase; + +import com.cuoco.application.exception.ConflictException; +import com.cuoco.application.port.in.CreateUserRecipeCommand; +import com.cuoco.application.port.out.CreateUserRecipeRepository; +import com.cuoco.application.port.out.ExistsUserRecipeByUserIdAndRecipeIdRepository; +import com.cuoco.application.port.out.GetRecipeByIdRepository; +import com.cuoco.application.usecase.domainservice.UserDomainService; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserRecipe; +import com.cuoco.factory.domain.RecipeFactory; +import com.cuoco.factory.domain.UserFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UserRecipeUseCaseTest { + + private UserDomainService userDomainService; + private CreateUserRecipeRepository createUserRecipeRepository; + private ExistsUserRecipeByUserIdAndRecipeIdRepository existsUserRecipeByUserIdAndRecipeIdRepository; + private GetRecipeByIdRepository getRecipeByIdRepository; + + private CreateUserRecipeUseCase useCase; + + @BeforeEach + public void setUp() { + userDomainService = mock(UserDomainService.class); + createUserRecipeRepository = mock(CreateUserRecipeRepository.class); + existsUserRecipeByUserIdAndRecipeIdRepository = mock(ExistsUserRecipeByUserIdAndRecipeIdRepository.class); + getRecipeByIdRepository = mock(GetRecipeByIdRepository.class); + + useCase = new CreateUserRecipeUseCase( + userDomainService, + createUserRecipeRepository, + existsUserRecipeByUserIdAndRecipeIdRepository, + getRecipeByIdRepository + ); + } + + @Test + public void shouldSaveRecipeIfNotExists() { + User user = UserFactory.create(); + Long recipeId = 1L; + Recipe recipe = RecipeFactory.create(); + CreateUserRecipeCommand.Command command = CreateUserRecipeCommand.Command.builder() + .recipeId(recipeId) + .build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(getRecipeByIdRepository.execute(recipeId)).thenReturn(recipe); + when(existsUserRecipeByUserIdAndRecipeIdRepository.execute(any(UserRecipe.class))).thenReturn(false); + doNothing().when(createUserRecipeRepository).execute(any(UserRecipe.class)); + + useCase.execute(command); + + verify(createUserRecipeRepository).execute(any(UserRecipe.class)); + } + + @Test + public void shouldThrowConflictExceptionIfRecipeAlreadySaved() { + User user = UserFactory.create(); + Long recipeId = 1L; + Recipe recipe = RecipeFactory.create(); + CreateUserRecipeCommand.Command command = CreateUserRecipeCommand.Command.builder() + .recipeId(recipeId) + .build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(getRecipeByIdRepository.execute(recipeId)).thenReturn(recipe); + when(existsUserRecipeByUserIdAndRecipeIdRepository.execute(any(UserRecipe.class))).thenReturn(true); + + assertThrows(ConflictException.class, () -> useCase.execute(command)); + verify(createUserRecipeRepository, never()).execute(any()); + } + + @Test + public void shouldThrowExceptionIfSaveFails() { + User user = UserFactory.create(); + Long recipeId = 1L; + Recipe recipe = RecipeFactory.create(); + CreateUserRecipeCommand.Command command = CreateUserRecipeCommand.Command.builder() + .recipeId(recipeId) + .build(); + + when(userDomainService.getCurrentUser()).thenReturn(user); + when(getRecipeByIdRepository.execute(recipeId)).thenReturn(recipe); + when(existsUserRecipeByUserIdAndRecipeIdRepository.execute(any(UserRecipe.class))).thenReturn(false); + doThrow(new RuntimeException("Error")).when(createUserRecipeRepository).execute(any(UserRecipe.class)); + + assertThrows(RuntimeException.class, () -> useCase.execute(command)); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/application/usecase/domainservice/FileDomainServiceTest.java b/src/test/java/com/cuoco/application/usecase/domainservice/FileDomainServiceTest.java new file mode 100644 index 0000000..8f160da --- /dev/null +++ b/src/test/java/com/cuoco/application/usecase/domainservice/FileDomainServiceTest.java @@ -0,0 +1,142 @@ +package com.cuoco.application.usecase.domainservice; + +import com.cuoco.application.exception.UnprocessableException; +import com.cuoco.shared.utils.AudioConstants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FileDomainServiceTest { + + private FileDomainService fileDomainService; + + @BeforeEach + void setUp() { + fileDomainService = new FileDomainService(); + } + + @Test + void getFileName_whenOriginalFilenameNotNull_thenReturnOriginalFilename() { + MultipartFile file = new MockMultipartFile("file", "test.mp3", "audio/mpeg", new byte[]{}); + String result = fileDomainService.getFileName(file); + assertEquals("test.mp3", result); + } + + @Test + void getFileName_whenOriginalFilenameNull_thenReturnUnknownWithTimestamp() { + MultipartFile file = new MockMultipartFile("file", null, "audio/mpeg", new byte[]{}); + String result = fileDomainService.getFileName(file); + assertTrue(result.startsWith("unknown_")); + } + + @Test + void getMimeType_whenContentTypeNotNull_thenReturnContentType() { + MultipartFile file = new MockMultipartFile("file", "file.mp3", "audio/mpeg", new byte[]{}); + String result = fileDomainService.getMimeType(file); + assertEquals("audio/mpeg", result); + } + + @Test + void getMimeType_whenContentTypeNull_thenReturnDefault() { + MultipartFile file = new MockMultipartFile("file", "file.jpg", null, new byte[]{}); + String result = fileDomainService.getMimeType(file); + assertEquals("image/jpeg", result); + } + + @Test + void isValidAudioFile_whenContentTypeStartsWithAudioFolder_thenReturnFalse() { + MultipartFile file = new MockMultipartFile("file", "file.mp3", "audio/file", new byte[]{}); + boolean result = fileDomainService.isValidAudioFile(file); + assertFalse(result); + } + + @Test + void isValidAudioFile_whenContentTypeNullAndFilenameHasSupportedExtension_thenReturnFalse() { + MultipartFile file = new MockMultipartFile("file", "song.mp3", null, new byte[]{}); + boolean result = fileDomainService.isValidAudioFile(file); + assertTrue(result); + } + + @Test + void isValidAudioFile_whenContentTypeNullAndFilenameHasUnsupportedExtension_thenReturnTrue() { + MultipartFile file = new MockMultipartFile("file", "file.txt", null, new byte[]{}); + boolean result = fileDomainService.isValidAudioFile(file); + assertTrue(result); + } + + @Test + void isValidAudioFile_whenContentTypeNullAndFilenameNull_thenReturnTrue() { + MultipartFile file = new MockMultipartFile("file", null, null, new byte[]{}); + boolean result = fileDomainService.isValidAudioFile(file); + assertTrue(result); + } + + @Test + void getAudioFormat_whenContentTypeHasSlash_thenReturnSubstringAfterSlash() { + MultipartFile file = new MockMultipartFile("file", "file.mp3", "audio/mpeg", new byte[]{}); + String result = fileDomainService.getAudioFormat(file); + assertEquals("mpeg", result); + } + + @Test + void getAudioFormat_whenContentTypeNullAndFilenameHasDot_thenReturnExtensionLowerCase() { + MultipartFile file = new MockMultipartFile("file", "Song.WAV", null, new byte[]{}); + String result = fileDomainService.getAudioFormat(file); + assertEquals("wav", result); + } + + @Test + void getAudioFormat_whenContentTypeAndFilenameNull_thenReturnDefaultMP3() { + MultipartFile file = new MockMultipartFile("file", null, null, new byte[]{}); + String result = fileDomainService.getAudioFormat(file); + assertEquals(AudioConstants.MP3, result); + } + + @Test + void convertToBase64_whenFileBytes_thenReturnBase64String() { + byte[] content = "test content".getBytes(StandardCharsets.UTF_8); + MultipartFile file = new MockMultipartFile("file", "file.txt", "text/plain", content); + + String base64 = fileDomainService.convertToBase64(file); + assertEquals(Base64.getEncoder().encodeToString(content), base64); + } + + @Test + void convertToBase64_whenExceptionThrown_thenThrowUnprocessableException() { + MultipartFile file = new MultipartFile() { + @Override + public String getName() { return "file"; } + @Override + public String getOriginalFilename() { return "file.txt"; } + @Override + public String getContentType() { return "text/plain"; } + @Override + public boolean isEmpty() { return false; } + @Override + public long getSize() { return 0; } + @Override + public byte[] getBytes() { + try { + throw new Exception("fail"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override + public java.io.InputStream getInputStream() { return null; } + @Override + public void transferTo(java.io.File dest) { } + }; + + assertThrows(UnprocessableException.class, () -> fileDomainService.convertToBase64(file)); + } +} diff --git a/src/test/java/com/cuoco/archunit/CodingRulesTest.java b/src/test/java/com/cuoco/archunit/CodingRulesTest.java index c18e1c8..74b88f2 100644 --- a/src/test/java/com/cuoco/archunit/CodingRulesTest.java +++ b/src/test/java/com/cuoco/archunit/CodingRulesTest.java @@ -21,11 +21,19 @@ public class CodingRulesTest { @ArchTest static final ArchRule use_cases_should_respect_naming_convention = ArchRuleDefinition.classes() - .that().resideInAPackage("..usecase..") + .that().resideInAPackage("..application.usecase..") .and(DescribedPredicate.not(Predicates.resideInAnyPackage("..usecase.model.."))) + .and(DescribedPredicate.not(Predicates.resideInAnyPackage("..usecase.domainservice.."))) .and().haveNameNotMatching(".*\\$.*") .should().haveSimpleNameEndingWith("UseCase"); + @ArchTest + static final ArchRule domain_services_should_respect_naming_convention = + ArchRuleDefinition.classes() + .that().resideInAPackage("..usecase.domainservice..") + .and().haveNameNotMatching(".*\\$.*") + .should().haveSimpleNameEndingWith("DomainService"); + @ArchTest static final ArchRule in_ports_should_respect_naming_convention = ArchRuleDefinition.classes() diff --git a/src/test/java/com/cuoco/factory/domain/AllergyFactory.java b/src/test/java/com/cuoco/factory/domain/AllergyFactory.java new file mode 100644 index 0000000..60e10dc --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/AllergyFactory.java @@ -0,0 +1,4 @@ +package com.cuoco.factory.domain; + +public class AllergyFactory { +} diff --git a/src/test/java/com/cuoco/factory/domain/CalendarFactory.java b/src/test/java/com/cuoco/factory/domain/CalendarFactory.java new file mode 100644 index 0000000..72d912c --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/CalendarFactory.java @@ -0,0 +1,27 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.Calendar; +import com.cuoco.application.usecase.model.CalendarRecipe; +import com.cuoco.application.usecase.model.Day; +import com.cuoco.application.usecase.model.MealType; + +import java.util.List; + +public class CalendarFactory { + + public static Calendar create() { + return Calendar.builder() + .day(Day.builder() + .id(1) + .description("Monday") + .build()) + .recipes(List.of(CalendarRecipe.builder() + .recipe(RecipeFactory.create()) + .mealType(MealType.builder() + .id(1) + .description("Lunch") + .build()) + .build())) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/domain/CookLevelFactory.java b/src/test/java/com/cuoco/factory/domain/CookLevelFactory.java new file mode 100644 index 0000000..9f67e15 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/CookLevelFactory.java @@ -0,0 +1,4 @@ +package com.cuoco.factory.domain; + +public class CookLevelFactory { +} diff --git a/src/test/java/com/cuoco/factory/domain/DietaryNeedFactory.java b/src/test/java/com/cuoco/factory/domain/DietaryNeedFactory.java new file mode 100644 index 0000000..616c931 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/DietaryNeedFactory.java @@ -0,0 +1,4 @@ +package com.cuoco.factory.domain; + +public class DietaryNeedFactory { +} diff --git a/src/test/java/com/cuoco/factory/domain/FileModelFactory.java b/src/test/java/com/cuoco/factory/domain/FileModelFactory.java new file mode 100644 index 0000000..b0d5967 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/FileModelFactory.java @@ -0,0 +1,25 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.File; + +public class FileModelFactory { + + public static File create() { + return File.builder() + .fileName("test.png") + .format("image/png") + .mimeType("image/png") + .fileBase64("base64string") + .build(); + } + + public static File create(String fileName, String format, String base64) { + return File.builder() + .fileName(fileName != null ? fileName : "test.png") + .format(format != null ? format: "image/png") + .mimeType("image/png") + .fileBase64(base64 != null ? base64 : "base64string") + .build(); + } + +} diff --git a/src/test/java/com/cuoco/factory/domain/IngredientFactory.java b/src/test/java/com/cuoco/factory/domain/IngredientFactory.java new file mode 100644 index 0000000..f91e4cb --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/IngredientFactory.java @@ -0,0 +1,39 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.Unit; + +public class IngredientFactory { + + public static Ingredient create() { + return Ingredient.builder() + .name("Ingredient 1") + .quantity(1.0) + .unit(Unit.builder() + .id(1) + .description("Gram") + .symbol("gr") + .build() + ) + .optional(true) + .source("text") + .confirmed(false) + .build(); + } + + public static Ingredient create(String name, Double quantity, String unitSymbol) { + return Ingredient.builder() + .name(name != null ? name : "Ingredient 1") + .quantity(quantity != null ? quantity : 1.0) + .unit(Unit.builder() + .id(1) + .description("Gram") + .symbol(unitSymbol != null ? unitSymbol : "gr") + .build() + ) + .optional(true) + .source("text") + .confirmed(false) + .build(); + } +} diff --git a/src/test/java/com/cuoco/factory/domain/MealPrepFactory.java b/src/test/java/com/cuoco/factory/domain/MealPrepFactory.java new file mode 100644 index 0000000..c21af55 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/MealPrepFactory.java @@ -0,0 +1,28 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.MealPrep; +import com.cuoco.application.usecase.model.Step; + +import java.util.List; + +public class MealPrepFactory { + + public static MealPrep create() { + return MealPrep.builder() + .id(1L) + .title("Test Meal Prep") + .estimatedCookingTime("30 minutes") + .servings(4) + .freeze(true) + .steps(List.of(Step.builder() + .id(1L) + .title("Test Step") + .number(1) + .description("Test Step Description") + .time("10 minutes") + .build())) + .recipes(List.of(RecipeFactory.create())) + .ingredients(List.of(IngredientFactory.create())) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/domain/ParametricDataFactory.java b/src/test/java/com/cuoco/factory/domain/ParametricDataFactory.java new file mode 100644 index 0000000..57634d6 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/ParametricDataFactory.java @@ -0,0 +1,27 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.application.usecase.model.Unit; + +import java.util.List; + +public class ParametricDataFactory { + + public static ParametricData create() { + return ParametricData.builder() + .units(List.of(Unit.builder().id(1).description("Cup").symbol("cup").build())) + .preparationTimes(List.of(PreparationTime.builder().id(1).description("15 minutes").build())) + .cookLevels(List.of(CookLevel.builder().id(1).description("Easy").build())) + .diets(List.of(Diet.builder().id(1).description("Vegetarian").build())) + .mealTypes(List.of(MealType.builder().id(1).description("Lunch").build())) + .allergies(List.of(Allergy.builder().id(1).description("Nuts").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Gluten Free").build())) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/domain/RecipeFactory.java b/src/test/java/com/cuoco/factory/domain/RecipeFactory.java new file mode 100644 index 0000000..9ae5a15 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/RecipeFactory.java @@ -0,0 +1,173 @@ +package com.cuoco.factory.domain; + +import com.cuoco.adapter.in.controller.model.IngredientRequest; +import com.cuoco.adapter.in.controller.model.RecipeRequest; +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Filters; +import com.cuoco.application.usecase.model.Ingredient; +import com.cuoco.application.usecase.model.MealType; +import com.cuoco.application.usecase.model.ParametricData; +import com.cuoco.application.usecase.model.PreparationTime; +import com.cuoco.application.usecase.model.Recipe; +import com.cuoco.application.usecase.model.RecipeConfiguration; +import com.cuoco.application.usecase.model.Step; +import com.cuoco.application.usecase.model.Unit; + +import java.util.List; + +public class RecipeFactory { + + public static Recipe create() { + return Recipe.builder() + .id(1L) + .name("Test Recipe") + .subtitle("Test Subtitle") + .description("Test Description") + .favorite(false) + .steps(List.of(Step.builder() + .id(1L) + .title("Test Step") + .number(1) + .description("Test Step Description") + .time("10 minutes") + .build())) + .image("test-image.jpg") + .preparationTime(PreparationTime.builder().id(1).description("30 minutes").build()) + .cookLevel(CookLevel.builder().id(1).description("Beginner").build()) + .diet(Diet.builder().id(1).description("Vegetarian").build()) + .mealTypes(List.of(MealType.builder().id(1).description("Lunch").build())) + .allergies(List.of(Allergy.builder().id(1).description("Nuts").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Gluten Free").build())) + .ingredients(List.of(Ingredient.builder() + .id(1L) + .name("Test Ingredient") + .quantity(1.0) + .unit(Unit.builder().id(1).description("Cup").symbol("cup").build()) + .build())) + .images(List.of(Step.builder() + .id(1L) + .title("Test Image") + .number(1) + .description("Test Image Description") + .time("5 minutes") + .build())) + .filters(Filters.builder() + .useProfilePreferences(true) + .enable(true) + .preparationTime(PreparationTime.builder().id(1).description("30 minutes").build()) + .cookLevel(CookLevel.builder().id(1).description("Beginner").build()) + .diet(Diet.builder().id(1).description("Vegetarian").build()) + .mealTypes(List.of(MealType.builder().id(1).description("Lunch").build())) + .allergies(List.of(Allergy.builder().id(1).description("Nuts").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Gluten Free").build())) + .freeze(false) + .build()) + .configuration(RecipeConfiguration.builder() + .size(4) + .notInclude(List.of()) + .parametricData(ParametricData.builder().build()) + .build()) + .build(); + } + + public static Recipe createWithName(String name) { + return Recipe.builder() + .id(1L) + .name(name) + .subtitle("Test Subtitle") + .description("Test Description") + .favorite(false) + .steps(List.of(Step.builder() + .id(1L) + .title("Test Step") + .number(1) + .description("Test Step Description") + .time("10 minutes") + .build())) + .image("test-image.jpg") + .preparationTime(PreparationTime.builder().id(1).description("30 minutes").build()) + .cookLevel(CookLevel.builder().id(1).description("Beginner").build()) + .diet(Diet.builder().id(1).description("Vegetarian").build()) + .mealTypes(List.of(MealType.builder().id(1).description("Lunch").build())) + .allergies(List.of(Allergy.builder().id(1).description("Nuts").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Gluten Free").build())) + .ingredients(List.of(Ingredient.builder() + .id(1L) + .name("Test Ingredient") + .quantity(1.0) + .unit(Unit.builder().id(1).description("Cup").symbol("cup").build()) + .build())) + .images(List.of(Step.builder() + .id(1L) + .title("Test Image") + .number(1) + .description("Test Image Description") + .time("5 minutes") + .build())) + .filters(Filters.builder() + .useProfilePreferences(true) + .enable(true) + .preparationTime(PreparationTime.builder().id(1).description("30 minutes").build()) + .cookLevel(CookLevel.builder().id(1).description("Beginner").build()) + .diet(Diet.builder().id(1).description("Vegetarian").build()) + .mealTypes(List.of(MealType.builder().id(1).description("Lunch").build())) + .allergies(List.of(Allergy.builder().id(1).description("Nuts").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Gluten Free").build())) + .freeze(false) + .build()) + .configuration(RecipeConfiguration.builder() + .size(4) + .notInclude(List.of()) + .parametricData(ParametricData.builder().build()) + .build()) + .build(); + } + + public static Recipe createWithFilters() { + Recipe recipe = create(); + Filters filters = Filters.builder() + .useProfilePreferences(true) + .enable(true) + .preparationTime(PreparationTime.builder().id(1).description("15 minutes").build()) + .cookLevel(CookLevel.builder().id(1).description("Easy").build()) + .diet(Diet.builder().id(1).description("Vegan").build()) + .mealTypes(List.of(MealType.builder().id(1).description("Breakfast").build())) + .allergies(List.of(Allergy.builder().id(1).description("Dairy").build())) + .dietaryNeeds(List.of(DietaryNeed.builder().id(1).description("Low Sodium").build())) + .freeze(true) + .build(); + recipe.setFilters(filters); + return recipe; + } + + public static Recipe createWithEmptyInstructions() { + Recipe recipe = create(); + recipe.setSteps(List.of()); + return recipe; + } + + public static Recipe createWithManySteps() { + Recipe recipe = create(); + List manySteps = List.of( + Step.builder().id(1L).title("Step 1").number(1).description("First step").time("5 minutes").build(), + Step.builder().id(2L).title("Step 2").number(2).description("Second step").time("10 minutes").build(), + Step.builder().id(3L).title("Step 3").number(3).description("Third step").time("15 minutes").build() + ); + recipe.setSteps(manySteps); + return recipe; + } + + public static RecipeRequest getRecipeRequest() { + Recipe recipe = create(); + return RecipeRequest.builder() + .ingredients(recipe.getIngredients().stream().map(ingredient -> + IngredientRequest.builder() + .name(ingredient.getName()) + .build()) + .toList()) + .build(); + } +} diff --git a/src/test/java/com/cuoco/factory/domain/RecipeImageFactory.java b/src/test/java/com/cuoco/factory/domain/RecipeImageFactory.java new file mode 100644 index 0000000..8e996de --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/RecipeImageFactory.java @@ -0,0 +1,36 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.Step; + +public class RecipeImageFactory { + + public static Step createMainImage() { + return Step.builder() + .id(1L) + .title("Main Recipe Image") + .number(1) + .description("Main image for the recipe") + .time("5 minutes") + .imageName("test-recipe-main.jpg") + .build(); + } + + public static Step createStepImage(Integer stepNumber) { + return Step.builder() + .id((long) stepNumber) + .title("Step " + stepNumber + " Image") + .number(stepNumber) + .description("Image for step " + stepNumber) + .time("2 minutes") + .imageName("step-" + stepNumber + ".jpg") + .build(); + } + + public static Step createMainRecipeImage() { + return createMainImage(); + } + + public static Step createStepRecipeImage() { + return createStepImage(1); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/domain/UserFactory.java b/src/test/java/com/cuoco/factory/domain/UserFactory.java new file mode 100644 index 0000000..1327353 --- /dev/null +++ b/src/test/java/com/cuoco/factory/domain/UserFactory.java @@ -0,0 +1,45 @@ +package com.cuoco.factory.domain; + +import com.cuoco.application.usecase.model.Allergy; +import com.cuoco.application.usecase.model.CookLevel; +import com.cuoco.application.usecase.model.Diet; +import com.cuoco.application.usecase.model.DietaryNeed; +import com.cuoco.application.usecase.model.Plan; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.usecase.model.UserPreferences; + +import java.time.LocalDateTime; +import java.util.List; + +public class UserFactory { + + public static User create() { + return User.builder() + .id(1L) + .name("Test User") + .email("test@example.com") + .password("password123") + .plan( + Plan.builder().id(1).description("plan a").build() + ) + .active(true) + .preferences( + UserPreferences.builder() + .diet(Diet.builder().id(1).description("diet").build()) + .cookLevel(CookLevel.builder().id(1).description("cookLevel").build()) + .build() + ) + .createdAt(LocalDateTime.now()) + .dietaryNeeds(List.of( + DietaryNeed.builder().id(1).description("dietary need 1").build(), + DietaryNeed.builder().id(2).description("dietary need 2").build(), + DietaryNeed.builder().id(3).description("dietary need 3").build() + )) + .allergies(List.of( + Allergy.builder().id(1).description("allergy 1").build(), + Allergy.builder().id(2).description("allergy 2").build(), + Allergy.builder().id(3).description("allergy 3").build() + )) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/gemini/GeminiResponseModelFactory.java b/src/test/java/com/cuoco/factory/gemini/GeminiResponseModelFactory.java new file mode 100644 index 0000000..8e5041a --- /dev/null +++ b/src/test/java/com/cuoco/factory/gemini/GeminiResponseModelFactory.java @@ -0,0 +1,30 @@ +package com.cuoco.factory.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.wrapper.CandidateGeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.ContentGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.GeminiResponseModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.InlineDataGeminiRequestModel; +import com.cuoco.adapter.out.rest.gemini.model.wrapper.PartGeminiRequestModel; + +import java.util.List; + +public class GeminiResponseModelFactory { + + public static GeminiResponseModel create(String jsonResponse) { + return GeminiResponseModel.builder() + .candidates(List.of(CandidateGeminiResponseModel.builder().content(getContent(jsonResponse)).build())) + .build(); + } + + public static ContentGeminiRequestModel getContent(String jsonResponse) { + return ContentGeminiRequestModel.builder() + .parts(List.of( + PartGeminiRequestModel.builder() + .inlineData(InlineDataGeminiRequestModel.builder().data("DATA").mimeType("MIMETYPE").build()) + .text(jsonResponse != null ? jsonResponse : "RESPONSE TEXT") + .build() + )) + .build(); + } + +} diff --git a/src/test/java/com/cuoco/factory/gemini/IngredientResponseGeminiModelFactory.java b/src/test/java/com/cuoco/factory/gemini/IngredientResponseGeminiModelFactory.java new file mode 100644 index 0000000..10ba9bc --- /dev/null +++ b/src/test/java/com/cuoco/factory/gemini/IngredientResponseGeminiModelFactory.java @@ -0,0 +1,25 @@ +package com.cuoco.factory.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.application.usecase.model.Unit; + +public class IngredientResponseGeminiModelFactory { + + public static IngredientResponseGeminiModel create() { + return IngredientResponseGeminiModel.builder() + .name("Test Ingredient") + .quantity(1.0) + .unit(Unit.builder().id(1).description("Cup").symbol("cup").build()) + .optional(false) + .build(); + } + + public static IngredientResponseGeminiModel create(String name) { + return IngredientResponseGeminiModel.builder() + .name(name) + .quantity(1.0) + .unit(Unit.builder().id(1).description("Cup").symbol("cup").build()) + .optional(false) + .build(); + } +} diff --git a/src/test/java/com/cuoco/factory/gemini/MealPrepResponseGeminiModelFactory.java b/src/test/java/com/cuoco/factory/gemini/MealPrepResponseGeminiModelFactory.java new file mode 100644 index 0000000..c4de195 --- /dev/null +++ b/src/test/java/com/cuoco/factory/gemini/MealPrepResponseGeminiModelFactory.java @@ -0,0 +1,18 @@ +package com.cuoco.factory.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.MealPrepResponseGeminiModel; + +import java.util.List; + +public class MealPrepResponseGeminiModelFactory { + + public static MealPrepResponseGeminiModel create() { + return MealPrepResponseGeminiModel.builder() + .title("Test Meal Prep") + .estimatedCookingTime("30 minutes") + .servings(4) + .freeze(true) + .recipeIds(List.of(1L)) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/gemini/RecipeResponseGeminiModelFactory.java b/src/test/java/com/cuoco/factory/gemini/RecipeResponseGeminiModelFactory.java new file mode 100644 index 0000000..fc59155 --- /dev/null +++ b/src/test/java/com/cuoco/factory/gemini/RecipeResponseGeminiModelFactory.java @@ -0,0 +1,31 @@ +package com.cuoco.factory.gemini; + +import com.cuoco.adapter.out.rest.gemini.model.IngredientResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.RecipeResponseGeminiModel; +import com.cuoco.adapter.out.rest.gemini.model.StepResponseGeminiModel; +import com.cuoco.application.usecase.model.Unit; + +import java.util.List; + +public class RecipeResponseGeminiModelFactory { + + public static RecipeResponseGeminiModel create() { + return RecipeResponseGeminiModel.builder() + .id("1") + .name("Test Recipe") + .subtitle("Test Subtitle") + .description("Test Description") + .ingredients(List.of(IngredientResponseGeminiModel.builder() + .name("Test Ingredient") + .quantity(1.0) + .unit(Unit.builder().id(1).description("Cup").symbol("cup").build()) + .optional(false) + .build())) + .steps(List.of(StepResponseGeminiModel.builder() + .title("Test Step") + .description("Test Step Description") + .time("10 minutes") + .build())) + .build(); + } +} diff --git a/src/test/java/com/cuoco/factory/hibernate/AllergyHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/AllergyHibernateModelFactory.java new file mode 100644 index 0000000..19eea45 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/AllergyHibernateModelFactory.java @@ -0,0 +1,25 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; + +public class AllergyHibernateModelFactory { + + public static AllergyHibernateModel create() { + + return AllergyHibernateModel.builder() + .id(1) + .description("Allergy") + .build(); + + } + + public static AllergyHibernateModel create(Integer id, String description) { + + return AllergyHibernateModel.builder() + .id(id) + .description(description) + .build(); + + } + +} diff --git a/src/test/java/com/cuoco/factory/hibernate/CookLevelHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/CookLevelHibernateModelFactory.java new file mode 100644 index 0000000..b64c782 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/CookLevelHibernateModelFactory.java @@ -0,0 +1,24 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; + +public class CookLevelHibernateModelFactory { + + public static CookLevelHibernateModel create() { + + return CookLevelHibernateModel.builder() + .id(1) + .description("Medium") + .build(); + + } + + public static CookLevelHibernateModel create(Integer id, String description) { + + return CookLevelHibernateModel.builder() + .id(id) + .description(description) + .build(); + + } +} diff --git a/src/test/java/com/cuoco/factory/hibernate/DietHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/DietHibernateModelFactory.java new file mode 100644 index 0000000..437a7e3 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/DietHibernateModelFactory.java @@ -0,0 +1,25 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; + +public class DietHibernateModelFactory { + + public static DietHibernateModel create() { + + return DietHibernateModel.builder() + .id(1) + .description("Dieta") + .build(); + + } + + public static DietHibernateModel create(Integer id, String description) { + + return DietHibernateModel.builder() + .id(id) + .description(description) + .build(); + + } + +} diff --git a/src/test/java/com/cuoco/factory/hibernate/DietaryNeedHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/DietaryNeedHibernateModelFactory.java new file mode 100644 index 0000000..cc32b7b --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/DietaryNeedHibernateModelFactory.java @@ -0,0 +1,25 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; + +public class DietaryNeedHibernateModelFactory { + + public static DietaryNeedHibernateModel create() { + + return DietaryNeedHibernateModel.builder() + .id(1) + .description("Dieta") + .build(); + + } + + public static DietaryNeedHibernateModel create(Integer id, String description) { + + return DietaryNeedHibernateModel.builder() + .id(id) + .description(description) + .build(); + + } + +} diff --git a/src/test/java/com/cuoco/factory/hibernate/MealPrepHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/MealPrepHibernateModelFactory.java new file mode 100644 index 0000000..c1a7169 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/MealPrepHibernateModelFactory.java @@ -0,0 +1,83 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealPrepStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; +import com.cuoco.application.usecase.model.Step; + +import java.util.List; + +public class MealPrepHibernateModelFactory { + + public static MealPrepHibernateModel create() { + return MealPrepHibernateModel.builder() + .id(1L) + .title("Test Meal Prep") + .estimatedCookingTime("30 minutes") + .steps(List.of( + MealPrepStepsHibernateModel.builder() + .id(2L) + .title("step 1") + .imageName("image1") + .description("description1") + .build() + )) + .recipes(List.of( + RecipeHibernateModel.builder() + .id(1L) + .name("Recipe 1") + .description("description") + .ingredients(List.of( + RecipeIngredientsHibernateModel.builder() + .ingredient( + IngredientHibernateModel.builder() + .id(1L) + .name("Harina") + .unit(UnitHibernateModel.builder().id(1).symbol("gr").build()) + .build() + ) + .build() + )) + .steps(List.of( + RecipeStepsHibernateModel.builder() + .id(2L) + .title("step 1") + .imageName("image1") + .description("description1") + .build() + )) + .preparationTime(PreparationTimeHibernateModel.builder().id(1).description("description").build()) + .diet(DietHibernateModel.builder().id(1).description("description").build()) + .cookLevel(CookLevelHibernateModel.builder().id(1).description("description").build()) + .dietaryNeeds(List.of(DietaryNeedHibernateModel.builder().id(1).description("description").build())) + .allergies(List.of(AllergyHibernateModel.builder().id(1).description("description").build())) + .mealTypes(List.of(MealTypeHibernateModel.builder().id(1).description("description").build())) + .build() + )) + .ingredients(List.of( + MealPrepIngredientsHibernateModel.builder() + .ingredient( + IngredientHibernateModel.builder() + .id(1L) + .name("Harina") + .unit(UnitHibernateModel.builder().id(1).symbol("gr").build()) + .build() + ) + .build() + )) + .servings(4) + .freeze(true) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/hibernate/PlanHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/PlanHibernateModelFactory.java new file mode 100644 index 0000000..8f69a44 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/PlanHibernateModelFactory.java @@ -0,0 +1,24 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; + +public class PlanHibernateModelFactory { + + public static PlanHibernateModel create() { + + return PlanHibernateModel.builder() + .id(1) + .description("Free") + .build(); + + } + + public static PlanHibernateModel create(Integer id, String description) { + + return PlanHibernateModel.builder() + .id(id) + .description(description) + .build(); + + } +} diff --git a/src/test/java/com/cuoco/factory/hibernate/RecipeHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/RecipeHibernateModelFactory.java new file mode 100644 index 0000000..0995469 --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/RecipeHibernateModelFactory.java @@ -0,0 +1,51 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.AllergyHibernateModel; +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietaryNeedHibernateModel; +import com.cuoco.adapter.out.hibernate.model.IngredientHibernateModel; +import com.cuoco.adapter.out.hibernate.model.MealTypeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.PreparationTimeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeIngredientsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.RecipeStepsHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UnitHibernateModel; + +import java.util.List; + +public class RecipeHibernateModelFactory { + + public static RecipeHibernateModel create() { + return RecipeHibernateModel.builder() + .id(1L) + .name("Recipe 1") + .description("description") + .ingredients(List.of( + RecipeIngredientsHibernateModel.builder() + .ingredient( + IngredientHibernateModel.builder() + .id(1L) + .name("Harina") + .unit(UnitHibernateModel.builder().id(1).symbol("gr").build()) + .build() + ) + .build() + )) + .steps(List.of( + RecipeStepsHibernateModel.builder() + .id(2L) + .title("step 1") + .imageName("image1") + .description("description1") + .build() + )) + .preparationTime(PreparationTimeHibernateModel.builder().id(1).description("description").build()) + .diet(DietHibernateModel.builder().id(1).description("description").build()) + .cookLevel(CookLevelHibernateModel.builder().id(1).description("description").build()) + .dietaryNeeds(List.of(DietaryNeedHibernateModel.builder().id(1).description("description").build())) + .allergies(List.of(AllergyHibernateModel.builder().id(1).description("description").build())) + .mealTypes(List.of(MealTypeHibernateModel.builder().id(1).description("description").build())) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/cuoco/factory/hibernate/UserHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/UserHibernateModelFactory.java new file mode 100644 index 0000000..4b8b3fa --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/UserHibernateModelFactory.java @@ -0,0 +1,23 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.PlanHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserHibernateModel; + +import java.time.LocalDateTime; + +public class UserHibernateModelFactory { + + public static UserHibernateModel create() { + return UserHibernateModel.builder() + .id(1L) + .name("Name") + .email("email@email.com") + .password("password") + .plan(PlanHibernateModel.builder().id(1).description("Plan 1").build()) + .active(true) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + } + +} diff --git a/src/test/java/com/cuoco/factory/hibernate/UserPreferencesHibernateModelFactory.java b/src/test/java/com/cuoco/factory/hibernate/UserPreferencesHibernateModelFactory.java new file mode 100644 index 0000000..eb9d6bd --- /dev/null +++ b/src/test/java/com/cuoco/factory/hibernate/UserPreferencesHibernateModelFactory.java @@ -0,0 +1,18 @@ +package com.cuoco.factory.hibernate; + +import com.cuoco.adapter.out.hibernate.model.CookLevelHibernateModel; +import com.cuoco.adapter.out.hibernate.model.DietHibernateModel; +import com.cuoco.adapter.out.hibernate.model.UserPreferencesHibernateModel; + +public class UserPreferencesHibernateModelFactory { + + public static UserPreferencesHibernateModel create() { + return UserPreferencesHibernateModel.builder() + .id(1L) + .user(UserHibernateModelFactory.create()) + .cookLevel(CookLevelHibernateModel.builder().id(1).description("Cook Level").build()) + .diet(DietHibernateModel.builder().id(1).description("Diet").build()) + .build(); + } + +}