diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/refrigerator/.gitignore b/refrigerator/.gitignore index 2b88fe8..68fd73d 100644 --- a/refrigerator/.gitignore +++ b/refrigerator/.gitignore @@ -713,3 +713,6 @@ FodyWeavers.xsd # End of https://www.toptal.com/developers/gitignore/api/macos,windows,git,java,intellij,visualstudio,eclipse,gradle,netbeans /src/main/resources/application.yml + +.idea/ +*.iml diff --git a/refrigerator/.idea/dataSources.xml b/refrigerator/.idea/dataSources.xml new file mode 100644 index 0000000..b572de5 --- /dev/null +++ b/refrigerator/.idea/dataSources.xml @@ -0,0 +1,45 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306 + + + + + + $ProjectFileDir$ + + + mysql.8 + true + true + $PROJECT_DIR$/src/main/resources/application.yml + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/refrigerator + + + + + + $ProjectFileDir$ + + + mysql.8 + true + true + $PROJECT_DIR$/src/main/resources/application.yml + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/test_refrigerator + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/refrigerator/.idea/modules.xml b/refrigerator/.idea/modules.xml new file mode 100644 index 0000000..bdc72c3 --- /dev/null +++ b/refrigerator/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/refrigerator/.idea/modules/refrigerator.main.iml b/refrigerator/.idea/modules/refrigerator.main.iml new file mode 100644 index 0000000..397c268 --- /dev/null +++ b/refrigerator/.idea/modules/refrigerator.main.iml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/refrigerator/build.gradle b/refrigerator/build.gradle index 4fec5bf..156aa6d 100644 --- a/refrigerator/build.gradle +++ b/refrigerator/build.gradle @@ -31,13 +31,19 @@ dependencies { developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + runtimeOnly 'com.mysql:mysql-connector-j' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.modelmapper:modelmapper:3.1.0' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-mail' } - tasks.named('test') { useJUnitPlatform() } diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientBookmark.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientBookmark.java new file mode 100644 index 0000000..b609706 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientBookmark.java @@ -0,0 +1,24 @@ +package moja.refrigerator.aggregate.ingredient; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; + +@Table(name = "tbl_ingredient_bookmark") +@Entity +@Data +public class IngredientBookmark { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ingredient_bookmark_pk") + private long ingredientBookmarkPk; + + @ManyToOne + @JoinColumn(name = "user_pk") + private User user; + + @ManyToOne + @JoinColumn(name = "ingredient_my_refrigerator_pk") + private IngredientMyRefrigerator ingredientMyRefrigerator; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientCategory.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientCategory.java new file mode 100644 index 0000000..56de8ce --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientCategory.java @@ -0,0 +1,18 @@ +package moja.refrigerator.aggregate.ingredient; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "tbl_ingredient_category") +@Data +public class IngredientCategory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ingredient_category_pk") + private int ingredientCategoryPk; + + @Column(name = "ingredient_category") + private String ingredientCategory; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientManagement.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientManagement.java new file mode 100644 index 0000000..dfd453f --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientManagement.java @@ -0,0 +1,29 @@ +package moja.refrigerator.aggregate.ingredient; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "tbl_ingredient_management") +public class IngredientManagement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ingredient_management_pk") + private long ingredientManagementPk; + + @Column(name = "ingredient_name") + private String ingredientName; + + @Column(name = "season_date") + private int seasonDate; + + @JoinColumn(name = "ingredient_category") + @ManyToOne + private IngredientCategory ingredientCategory; + + @JoinColumn(name = "ingredient_storage") + @ManyToOne + private IngredientStorage ingredientStorage; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientMyRefrigerator.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientMyRefrigerator.java new file mode 100644 index 0000000..c0dc51f --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientMyRefrigerator.java @@ -0,0 +1,34 @@ +package moja.refrigerator.aggregate.ingredient; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; + +@Data +@Entity +@Table(name = "tbl_ingredient_my_refrigerator") +public class IngredientMyRefrigerator { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ingredient_my_refrigerator_pk") + private long ingredientMyRefrigeratorPk; + + @ManyToOne + @JoinColumn(name = "user_pk") + private User user; + + @ManyToOne + @JoinColumn(name = "ingredient_management_pk") + private IngredientManagement ingredientManagement; + + @Column(name = "expiration_date") + private String expirationDate; + + @Column(name = "registration_date") + private String registrationDate; + + @Column(name = "ingredient_amount") + private float ingredientAmount; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientStorage.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientStorage.java new file mode 100644 index 0000000..2dc7828 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/ingredient/IngredientStorage.java @@ -0,0 +1,17 @@ +package moja.refrigerator.aggregate.ingredient; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name="tbl_ingredient_storage") +public class IngredientStorage { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ingredient_storage_pk") + private int ingredientStoragePk; + + @Column(name = "ingredient_storage") + private String ingredientStorage; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Comment.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Comment.java new file mode 100644 index 0000000..694fc8a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Comment.java @@ -0,0 +1,30 @@ +package moja.refrigerator.aggregate.recipe; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; + +@Data +@Entity +@Table(name = "tbl_comment") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "comment_pk") + private long commentPk ; + + @Column(name = "comment_create_time") + private String commentCreateTime ; + @Column(name = "comment_update_time") + private String commentUpdateTime ; + @Column(name = "comment_contents") + private String commentContents ; + + @JoinColumn(name = "user") + @ManyToOne + private User user ; + + @JoinColumn(name = "recipe") + @ManyToOne + private Recipe recipe ; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Recipe.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Recipe.java new file mode 100644 index 0000000..575058e --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/Recipe.java @@ -0,0 +1,66 @@ +package moja.refrigerator.aggregate.recipe; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +@Entity +@Table(name = "tbl_recipe") +public class Recipe { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recipe_pk") + private long recipePk; + + @Column(name = "recipe_name") + private String recipeName; + + @Column(name = "recipe_cooking_time") + private int recipeCookingTime; + + @Column(name = "recipe_content",columnDefinition = "TEXT") // 길이 제한을 해제하기 위해 text로 설정 + private String recipeContent; + + @CreationTimestamp + @Column(name = "recipe_create_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd", timezone = "Asia/Seoul") + private LocalDateTime recipeCreateTime; + + @UpdateTimestamp + @Column(name = "recipe_update_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd", timezone = "Asia/Seoul") + private LocalDateTime recipeUpdateTime; + + @Column(name = "recipe_difficulty") + private int recipeDifficulty; + + @Column(name = "recipe_views") + private long recipeViews = 0; + + @JoinColumn(name = "user") + @ManyToOne + private User user; + + @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true,fetch = FetchType.LAZY) // 1개의 레시피에 여러 이미지가 들어갈 수 있으니까 수정 + @JsonManagedReference + private List recipeSource = new ArrayList<>() ; // 여러 Source가 들어갈 수 있으니까 list로 수정 + + + @JoinColumn(name = "recipe_category") + @ManyToOne + private RecipeCategory recipeCategory; + + @OneToMany(mappedBy = "recipe") + private List recipeIngredients; +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeCategory.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeCategory.java new file mode 100644 index 0000000..c5561ea --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeCategory.java @@ -0,0 +1,17 @@ +package moja.refrigerator.aggregate.recipe; + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name="tbl_recipe_category") +public class RecipeCategory { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recipe_category_pk") + private int recipeCategoryPK; + + @Column(name = "recipe_category") + private String recipeCategory; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeIngredient.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeIngredient.java new file mode 100644 index 0000000..7790098 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeIngredient.java @@ -0,0 +1,28 @@ +package moja.refrigerator.aggregate.recipe; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.ingredient.IngredientManagement; +import moja.refrigerator.aggregate.ingredient.IngredientMyRefrigerator; + +@Data +@Entity +@Table(name = "tbl_recipe_ingredient") +public class RecipeIngredient { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recipe_ingredient_pk") + private long recipeIngredientPk ; + + @Column(name = "ingredient_is_necessary") + private boolean ingredientIsNecessary ; + + @JoinColumn(name = "ingredient_management") + @ManyToOne + private IngredientManagement ingredientManagement; +// private IngredientMyRefrigerator ingredientMyRefrigerator ; + + @JoinColumn(name = "recipe") + @ManyToOne + private Recipe recipe; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLike.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLike.java new file mode 100644 index 0000000..402462a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLike.java @@ -0,0 +1,24 @@ +package moja.refrigerator.aggregate.recipe; + + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; + +@Data +@Entity +@Table(name="tbl_recipe_like") +public class RecipeLike { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "like_pk") + private long likePk; + + @JoinColumn(name = "user") + @ManyToOne + private User user; + + @JoinColumn(name = "recipe") + @ManyToOne + private Recipe recipe; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLikeDislike.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLikeDislike.java new file mode 100644 index 0000000..8fe52a5 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeLikeDislike.java @@ -0,0 +1,27 @@ +package moja.refrigerator.aggregate.recipe; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.user.User; + +@Entity +@Table(name = "tbl_recipe_like_dislike") +@Data +public class RecipeLikeDislike { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "like_dislike_pk") + private Long id; + + @ManyToOne + @JoinColumn(name = "recipe_pk") + private Recipe recipe; + + @ManyToOne + @JoinColumn(name = "user_pk") + private User user; + + @Column(name = "like_status") + private Boolean likeStatus; +} + diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSource.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSource.java new file mode 100644 index 0000000..c69c19b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSource.java @@ -0,0 +1,44 @@ +package moja.refrigerator.aggregate.recipe; + + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name="tbl_recipe_source") +public class RecipeSource { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recipe_source_pk") + private long recipeSourcePk; + + @Column(name = "recipe_source_save") // 저장위치 + private String recipeSourceSave; + + @Column(name = "recipe_source_create_time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd", timezone = "Asia/Seoul") + private LocalDateTime recipeSourceCreateTime = LocalDateTime.now(); + + @Column(name = "recipe_source_file_name",nullable = false) // 저장 파일 명 + private String recipeSourceFileName; + + @Column(name = "recipe_source_servername") // 서버 저장한 이름 추가. + private String recipeSourceServername; + + @JoinColumn(name = "recipe_source_type") // 자료타입 동영상 or 사진 + @ManyToOne + private RecipeSourceType recipeSourceType; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="recipe_pk") + @JsonBackReference + private Recipe recipe; + +} + diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSourceType.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSourceType.java new file mode 100644 index 0000000..e4a798a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/RecipeSourceType.java @@ -0,0 +1,19 @@ +package moja.refrigerator.aggregate.recipe; + + +import jakarta.persistence.*; +import lombok.Data; + +@Table(name = "tbl_recipe_source_type") +@Entity +@Data +public class RecipeSourceType { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "recipe_source_type_pk") + private int recipeSourceTypePk; + + @Column(name = "recipe_source_type",length = 50) + private String recipeSourceType; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/ReplacableIngredient.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/ReplacableIngredient.java new file mode 100644 index 0000000..260fc7b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/recipe/ReplacableIngredient.java @@ -0,0 +1,23 @@ +package moja.refrigerator.aggregate.recipe; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.ingredient.IngredientManagement; + +@Data +@Entity +@Table(name = "tbl_replacable_ingredient") +public class ReplacableIngredient { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "replaceable_ingredient_pk") + private long replaceableIngredientPk ; + + @JoinColumn(name = "ingredient_management") + @ManyToOne + private IngredientManagement ingredientManagement; + + @JoinColumn(name = "recipe_ingredient") + @ManyToOne + private RecipeIngredient recipeIngredient ; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/user/Follow.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/Follow.java new file mode 100644 index 0000000..80eaa90 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/Follow.java @@ -0,0 +1,22 @@ +package moja.refrigerator.aggregate.user; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "tbl_follow") +@Data +public class Follow { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "follow_pk") + private long followPk; + + @ManyToOne + @JoinColumn(name = "follower") + private User follower; + + @ManyToOne + @JoinColumn(name = "following") + private User following; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java new file mode 100644 index 0000000..fc72650 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java @@ -0,0 +1,16 @@ +package moja.refrigerator.aggregate.user; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "tbl_token_blacklist") +@Data +public class TokenBlacklist { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long blacklistPk; + + @Column(nullable = false) + private String blacklistToken; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/user/User.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/User.java new file mode 100644 index 0000000..03eb039 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/User.java @@ -0,0 +1,36 @@ +package moja.refrigerator.aggregate.user; + +import jakarta.persistence.*; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDate; + +@Entity +@Table(name = "tbl_user") +@Data +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_pk") + private long userPk; + + @Column(name = "user_id", nullable = false, unique = true) + private String userId; + + @Column(name = "user_pw", nullable = false) + private String userPw; + + @Column(name = "user_email", nullable = false, unique = true) + private String userEmail; + + @Column(name = "user_nickname", nullable = false, unique = true) + private String userNickname; + + @Column(name = "join_date", nullable = false, updatable = false) + @CreationTimestamp + private LocalDate joinDate; + + @Column(name = "user_role") + private String userRole = "ROLE_USER"; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/config/AwsS3Config.java b/refrigerator/src/main/java/moja/refrigerator/config/AwsS3Config.java new file mode 100644 index 0000000..83707b2 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/config/AwsS3Config.java @@ -0,0 +1,35 @@ +package moja.refrigerator.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AwsS3Config { + + // S3를 등록한 사람이 전달받은 접속하기 위한 key 값 + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + // S3를 등록한 사람이 전달받은 접속하기 위한 secret key 값 + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + // S3를 등록한 사람이 S3를 사용할 지역 + @Value("${cloud.aws.region.static}") + private String region; + + // 전달받은 Accesskey 와 SecretKey 로 아마존 서비스 실행 준비 + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java new file mode 100644 index 0000000..f42e6c7 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java @@ -0,0 +1,94 @@ +package moja.refrigerator.config; + +import jakarta.servlet.http.HttpServletResponse; +import moja.refrigerator.jwt.JWTFilter; +import moja.refrigerator.jwt.JWTUtil; +import moja.refrigerator.jwt.LoginFilter; +import moja.refrigerator.jwt.LogoutFilter; +import moja.refrigerator.repository.user.TokenBlacklistRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + // AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입 + private final AuthenticationConfiguration authenticationConfiguration; + // JWTUtil 주입 + private final JWTUtil jwtUtil; + private final LogoutFilter logoutFilter; + private final TokenBlacklistRepository tokenBlacklistRepository; + + public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, LogoutFilter logoutFilter, TokenBlacklistRepository tokenBlacklistRepository) { + this.authenticationConfiguration = authenticationConfiguration; + this.jwtUtil = jwtUtil; + this.logoutFilter = logoutFilter; + this.tokenBlacklistRepository = tokenBlacklistRepository; + } + + @Bean + // 비밀번호 암호화를 위한 인코더 + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + // AuthenticationManager Bean 등록 + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + return configuration.getAuthenticationManager(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // csrf 보안 비활성화 + http + .csrf((auth) -> auth.disable()); + + // 기본 로그인 폼 비활성화 + http + .formLogin((auth) -> auth.disable()); + + // HTTP Basic 인증 비활성화 + http + .httpBasic((auth) -> auth.disable()); + + // URL 별 접근 권한 설정 + http + .authorizeHttpRequests((auth) -> auth + .requestMatchers("/**").permitAll() // 이 경로들은 모두 접근 가능 + .requestMatchers("/admin").hasRole("ADMIN") // admin 경로는 ADMIN 역할을 가진 사용자만 + .anyRequest().authenticated()); // 나머지는 인증된 사용자만 + + // 세션 관리 설정 + http + .sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // JWT 사용을 위한 세션리스 설정 + + // 로그인 필터 추가 + http + .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); + + http + .addFilterAfter(new JWTFilter(jwtUtil, tokenBlacklistRepository), LoginFilter.class); + + http + .logout(logout -> logout + .logoutUrl("/logout") + .addLogoutHandler(logoutFilter) + .logoutSuccessHandler((request, response, authentication) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("로그아웃 되었습니다."); + })); + + return http.build(); + } +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/ingredient/IngredientController.java b/refrigerator/src/main/java/moja/refrigerator/controller/ingredient/IngredientController.java new file mode 100644 index 0000000..0333c74 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/controller/ingredient/IngredientController.java @@ -0,0 +1,101 @@ +package moja.refrigerator.controller.ingredient; + +import jakarta.persistence.EntityNotFoundException; +import moja.refrigerator.dto.ingredient.request.*; +import moja.refrigerator.dto.ingredient.response.*; +import moja.refrigerator.service.ingredient.IngredientService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +@RestController +@RequestMapping("/ingredient") +public class IngredientController { + private IngredientService ingredientService; + + @Autowired + public IngredientController(IngredientService ingredientService) { + this.ingredientService = ingredientService; + } + + // 재료 등록 + @PostMapping + public void createIngredient( + @RequestBody IngredientCreateRequest request, + @RequestParam Long userPk, + @RequestParam Long ingredientManagementPk) { + ingredientService.createIngredient(request, userPk, ingredientManagementPk); + } + + // 재료 조회 + @GetMapping + public List getIngredient(@RequestParam Long userPk) { + return ingredientService.getIngredient(userPk); + } + + // 재료 정보 수정 + @PutMapping + public void updateIngredient(@RequestBody IngredientUpdateRequest request) { + ingredientService.updateIngredient(request); + } + + // 재료 삭제 + @DeleteMapping + public ResponseEntity deleteIngredient(@RequestBody IngredientDeleteRequest request) { + try { + ingredientService.deleteIngredient(request); + return ResponseEntity.ok("재료가 성공적으로 삭제되었습니다."); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } catch (EntityNotFoundException e) { + return ResponseEntity.notFound().build(); + } + } + + @PostMapping("/bookmark/regist") + public ResponseEntity createIngredientBookmark( + @RequestBody RequestRegistIngredientBookmark requestBookmark + ) { + ResponseRegistIngredientBookmark responseBookmark = + ingredientService.createIngredientBookmark(requestBookmark); + + return ResponseEntity.status(HttpStatus.CREATED).body(responseBookmark); + } + + @GetMapping("/bookmark") + public ResponseEntity> getUsersIngredientBookmarkList( + @RequestBody RequestIngredientBookmarkLists requestBookmarkLists + ) { + List responseBookmarkLists = + ingredientService.getUsersIngredientBookmarkLists(requestBookmarkLists); + + return ResponseEntity.status(HttpStatus.OK).body(responseBookmarkLists); + } + + @DeleteMapping("/bookmark/delete") + public ResponseEntity deleteIngredientBookmark( + @RequestBody RequestDeleteIngredientBookmark requestDeleteBookmark + ) { + + ResponseDeleteIngredientBookmark responseDeleteIngredientBookmark = + ingredientService.deleteIngredientBookmark(requestDeleteBookmark); + + System.out.println(responseDeleteIngredientBookmark.getMessage()); + return ResponseEntity.status(HttpStatus.OK).body(responseDeleteIngredientBookmark); + } + + @GetMapping("/alert") + public ResponseEntity> alertExpirationDate( + @RequestBody RequestAlertExpirationDate requestAlertExpirationDate + ) { + List responseAlertExpirationDate = + ingredientService.alertExpirationDate(requestAlertExpirationDate); + + return ResponseEntity.status(HttpStatus.OK).body(responseAlertExpirationDate); + } + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/recipe/RecipeController.java b/refrigerator/src/main/java/moja/refrigerator/controller/recipe/RecipeController.java new file mode 100644 index 0000000..03ecb0a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/controller/recipe/RecipeController.java @@ -0,0 +1,82 @@ +package moja.refrigerator.controller.recipe; + +import moja.refrigerator.aggregate.recipe.Recipe; +import moja.refrigerator.dto.recipe.request.RecipeCreateRequest; +import moja.refrigerator.dto.recipe.request.RecipeLikeRequest; +import moja.refrigerator.dto.recipe.request.RecipeUpdateRequest; +import moja.refrigerator.dto.recipe.response.RecipeDetailResponse; +import moja.refrigerator.dto.recipe.response.RecipeLikeResponse; +import moja.refrigerator.dto.recipe.response.RecipeRecommendResponse; +import moja.refrigerator.dto.recipe.response.RecipeResponse; +import moja.refrigerator.service.recipe.RecipeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/recipe") +public class RecipeController { + private RecipeService recipeService; + + @Autowired + public RecipeController(RecipeService recipeService) { + this.recipeService = recipeService; + } + + @PostMapping + public void createRecipe( + @RequestPart RecipeCreateRequest request + , @RequestPart (required =false) List files + ){ + recipeService.createRecipe(request + , files + ); + } + + @GetMapping + public List getAllRecipes(){ + return recipeService.getAllRecipes(); + } + + @GetMapping("/{id}") + public RecipeDetailResponse getRecipe(@PathVariable long id){ return recipeService.getRecipe(id);} + + @DeleteMapping + public void deleteRecipe(@RequestParam long recipePk){ + recipeService.deleteRecipe(recipePk); + } + + @PutMapping + public void updateRecipe( + @RequestPart RecipeUpdateRequest request + ,@RequestPart (required =false) List files + ){ + recipeService.updateRecipe(request,files); + } + + @GetMapping("/recommend") + public List getRecommendedRecipes(@RequestParam Long userPk) { + return recipeService.getRecommendedRecipes(userPk); + } + + @GetMapping("/random") + public ResponseEntity getRandomRecipe() { + try { + RecipeRecommendResponse randomRecipe = recipeService.getRandomRecipe(); + return ResponseEntity.ok(randomRecipe); + } catch (IllegalStateException e) { + return ResponseEntity.notFound().build(); + } + } + + @PostMapping("/reaction") + public ResponseEntity toggleLikeDislike( + @RequestBody RecipeLikeRequest request + ) { + return ResponseEntity.ok(recipeService.toggleLikeDislike(request)); + } + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/user/AdminController.java b/refrigerator/src/main/java/moja/refrigerator/controller/user/AdminController.java new file mode 100644 index 0000000..f88b295 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/controller/user/AdminController.java @@ -0,0 +1,12 @@ +package moja.refrigerator.controller.user; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class AdminController { + @GetMapping("/admin") + public String getAdminPage() { + return "admin Controller"; + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java new file mode 100644 index 0000000..c841068 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java @@ -0,0 +1,63 @@ +package moja.refrigerator.controller.user; + +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; +import moja.refrigerator.dto.user.request.UserCreateRequest; +import moja.refrigerator.dto.user.request.UserUpdateRequest; +import moja.refrigerator.service.user.FollowService; +import moja.refrigerator.service.user.UserService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +public class UserController { + private final UserService userService; + private final FollowService followService; + + public UserController(UserService userService, FollowService followService) { + this.userService = userService; + this.followService = followService; + } + + // 토큰 검증 로직 확인용 + @GetMapping("/") + public String getMainPage() { + return "user Controller"; + } + + // 회원 가입 처리 + @PostMapping("/auth/join") + public ResponseEntity join(@RequestBody UserCreateRequest request) { + userService.createUser(request); + return ResponseEntity.status(HttpStatus.CREATED).body("회원 가입이 완료되었습니다."); + } + + // 회원 정보 수정 + @PutMapping("/update") + public ResponseEntity update(@RequestBody UserUpdateRequest request) { + userService.updateUser(request); + return ResponseEntity.ok().body("회원 정보가 수정되었습니다."); + } + + // 비밀번호 재발급 + @PostMapping("/password/reset") + public ResponseEntity resetPassword(@RequestBody PasswordResetRequest request) { + userService.resetPassword(request); + return ResponseEntity.ok().body("임시 비밀번호가 이메일로 발송되었습니다."); + } + + // 비밀번호 재설정 + @PutMapping("/password/update") + public ResponseEntity updatePassword(@RequestBody PasswordUpdateRequest request) { + userService.updatePassword(request); + return ResponseEntity.ok().body("비밀번호가 변경되었습니다."); + } + + // 팔로우 및 언팔로우 + @PostMapping("/follow/{userPk}") + public ResponseEntity toggleFollow(@PathVariable Long userPk) { + followService.toggleFollow(userPk); + return ResponseEntity.ok().body("팔로우 상태가 변경되었습니다."); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientCreateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientCreateRequest.java new file mode 100644 index 0000000..e78f743 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientCreateRequest.java @@ -0,0 +1,12 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class IngredientCreateRequest { + + private float ingredientAmount; + private String expirationDate; + private String registrationDate; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientDeleteRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientDeleteRequest.java new file mode 100644 index 0000000..063da41 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientDeleteRequest.java @@ -0,0 +1,11 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class IngredientDeleteRequest { + + private Long ingredientMyRefrigeratorPk; + private float deleteAmount; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientUpdateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientUpdateRequest.java new file mode 100644 index 0000000..ad2ee5f --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/IngredientUpdateRequest.java @@ -0,0 +1,13 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class IngredientUpdateRequest { + + private long ingredientMyRefrigeratorPk; + private float ingredientAmount; + private String expirationDate; + private String registrationDate; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestAlertExpirationDate.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestAlertExpirationDate.java new file mode 100644 index 0000000..dec0816 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestAlertExpirationDate.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class RequestAlertExpirationDate { + private long userPk; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestDeleteIngredientBookmark.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestDeleteIngredientBookmark.java new file mode 100644 index 0000000..0d7732d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestDeleteIngredientBookmark.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class RequestDeleteIngredientBookmark { + private long ingredientBookmarkPk; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestIngredientBookmarkLists.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestIngredientBookmarkLists.java new file mode 100644 index 0000000..7bafd4a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestIngredientBookmarkLists.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class RequestIngredientBookmarkLists { + private long userPk; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestRegistIngredientBookmark.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestRegistIngredientBookmark.java new file mode 100644 index 0000000..61cb5ac --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/request/RequestRegistIngredientBookmark.java @@ -0,0 +1,9 @@ +package moja.refrigerator.dto.ingredient.request; + +import lombok.Data; + +@Data +public class RequestRegistIngredientBookmark { + private long userPk; + private long ingredientMyRefrigeratorPk; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/IngredientResponse.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/IngredientResponse.java new file mode 100644 index 0000000..bda5d0b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/IngredientResponse.java @@ -0,0 +1,20 @@ +package moja.refrigerator.dto.ingredient.response; + +import lombok.Data; + +@Data +public class IngredientResponse { + + private int number; + private long ingredientMyRefrigeratorPk; + private String ingredientName; + private float ingredientAmount; + private String expirationDate; + private long remainExpirationDate; + private int seasonDate; + private String ingredientStorage; + + // ModelMapper 를 위한 기본 생성자 생성 + public IngredientResponse() {} + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseAlertExpirationDate.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseAlertExpirationDate.java new file mode 100644 index 0000000..e69430d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseAlertExpirationDate.java @@ -0,0 +1,21 @@ +package moja.refrigerator.dto.ingredient.response; + +import lombok.Data; +import moja.refrigerator.aggregate.ingredient.IngredientManagement; + +@Data +public class ResponseAlertExpirationDate { + private long ingredientMyRefrigeratorPk; + private IngredientManagement ingredientManagement; + private float ingredientAmount; + private String expirationDate; + private int remainDate; + + public ResponseAlertExpirationDate(long ingredientMyRefrigeratorPk, IngredientManagement ingredientManagement, float ingredientAmount, String expirationDate, int remainDate) { + this.ingredientMyRefrigeratorPk = ingredientMyRefrigeratorPk; + this.ingredientManagement = ingredientManagement; + this.ingredientAmount = ingredientAmount; + this.expirationDate = expirationDate; + this.remainDate = remainDate; + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseDeleteIngredientBookmark.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseDeleteIngredientBookmark.java new file mode 100644 index 0000000..6feed8e --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseDeleteIngredientBookmark.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.ingredient.response; + +import lombok.Data; + +@Data +public class ResponseDeleteIngredientBookmark { + String message; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseRegistIngredientBookmark.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseRegistIngredientBookmark.java new file mode 100644 index 0000000..439616d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseRegistIngredientBookmark.java @@ -0,0 +1,14 @@ +package moja.refrigerator.dto.ingredient.response; + +import lombok.Data; +import moja.refrigerator.aggregate.ingredient.IngredientBookmark; +import moja.refrigerator.aggregate.ingredient.IngredientManagement; +import moja.refrigerator.aggregate.ingredient.IngredientMyRefrigerator; +import moja.refrigerator.aggregate.user.User; + +@Data +public class ResponseRegistIngredientBookmark { + private long ingredientBookmarkPk; + private User user; + private IngredientMyRefrigerator ingredientMyRefrigerator; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseUsersIngredientBookmarkLists.java b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseUsersIngredientBookmarkLists.java new file mode 100644 index 0000000..c279d91 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/ingredient/response/ResponseUsersIngredientBookmarkLists.java @@ -0,0 +1,10 @@ +package moja.refrigerator.dto.ingredient.response; + +import lombok.Data; +import moja.refrigerator.aggregate.ingredient.IngredientManagement; +import moja.refrigerator.aggregate.ingredient.IngredientMyRefrigerator; + +@Data +public class ResponseUsersIngredientBookmarkLists { + private IngredientMyRefrigerator ingredientMyRefrigerator; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/RecipeMatchResult.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/RecipeMatchResult.java new file mode 100644 index 0000000..210e602 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/RecipeMatchResult.java @@ -0,0 +1,15 @@ +package moja.refrigerator.dto.recipe; + +import lombok.AllArgsConstructor; +import lombok.Data; +import moja.refrigerator.aggregate.recipe.Recipe; + +@Data +@AllArgsConstructor +public class RecipeMatchResult { + private Recipe recipe; + private boolean matched; + private double matchRate; + private long remainExpirationDays; + private String urgentIngredientName; +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeCreateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeCreateRequest.java new file mode 100644 index 0000000..ef8640d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeCreateRequest.java @@ -0,0 +1,22 @@ +package moja.refrigerator.dto.recipe.request; + +import lombok.Data; +import moja.refrigerator.aggregate.recipe.RecipeCategory; +import moja.refrigerator.aggregate.recipe.RecipeSource; +import moja.refrigerator.aggregate.user.User; + +@Data +public class RecipeCreateRequest { + private String recipeName; + private int recipeCookingTime; + private int recipeDifficulty; + private String recipeContent; +// private long recipeSource; + private int recipeCategoryPk; + private long userPk; + +// private long recipePk; // 자동 추가 +// private String recipeCreateTime; //자동 추가 +// private String recipeUpdateTime; //자동 추가 +// private long recipeViews; // 조회 시 올리는 것으로 +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeLikeRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeLikeRequest.java new file mode 100644 index 0000000..4deff9c --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeLikeRequest.java @@ -0,0 +1,10 @@ +package moja.refrigerator.dto.recipe.request; + +import lombok.Data; + +@Data +public class RecipeLikeRequest { + private Long recipePk; + private Long userPk; + private Boolean likeStatus; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeSourceCreateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeSourceCreateRequest.java new file mode 100644 index 0000000..68b71d4 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeSourceCreateRequest.java @@ -0,0 +1,15 @@ +package moja.refrigerator.dto.recipe.request; + +import jakarta.persistence.*; +import lombok.Data; +import moja.refrigerator.aggregate.recipe.RecipeSourceType; + +@Data +public class RecipeSourceCreateRequest { + private String recipeSourceFileName; + private String recipeSourceSave; + private RecipeSourceType recipeSourceType; + +// private long recipeSourcePk; +// private String recipeSourceCreateTime; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeUpdateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeUpdateRequest.java new file mode 100644 index 0000000..dd7c2d9 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/request/RecipeUpdateRequest.java @@ -0,0 +1,17 @@ +package moja.refrigerator.dto.recipe.request; + + +import lombok.Data; + +@Data +public class RecipeUpdateRequest { + private long recipePk; + private String recipeName; + private int recipeCookingTime; + private int recipeDifficulty; + + private String recipeSource; + private String recipeCategory; + private String userPk; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeDetailResponse.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeDetailResponse.java new file mode 100644 index 0000000..0fcf203 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeDetailResponse.java @@ -0,0 +1,25 @@ +package moja.refrigerator.dto.recipe.response; + +import java.time.LocalDateTime; +import java.util.List; + +import lombok.Data; +import moja.refrigerator.dto.ingredient.response.IngredientResponse; + +@Data +public class RecipeDetailResponse { + private long recipePk; + private String recipeName; + private int recipeCookingTime; + private int recipeDifficulty; + private long recipeViews; + + private LocalDateTime recipeCreateTime;; + private LocalDateTime recipeUpdateTime; + + private String recipeSource; + private String userPk; + private String recipeCategory; + + private List ingredients; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeIngredientInfo.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeIngredientInfo.java new file mode 100644 index 0000000..fecc03b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeIngredientInfo.java @@ -0,0 +1,9 @@ +package moja.refrigerator.dto.recipe.response; + +import lombok.Data; + +@Data +public class RecipeIngredientInfo { + private String ingredientName; + private boolean isNecessary; +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeLikeResponse.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeLikeResponse.java new file mode 100644 index 0000000..a104acf --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeLikeResponse.java @@ -0,0 +1,12 @@ +package moja.refrigerator.dto.recipe.response; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class RecipeLikeResponse { + private long likesCount; + private long dislikesCount; + private Boolean userReaction; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeRecommendResponse.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeRecommendResponse.java new file mode 100644 index 0000000..efdb622 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeRecommendResponse.java @@ -0,0 +1,17 @@ +package moja.refrigerator.dto.recipe.response; + +import lombok.Data; + +import java.util.List; + +@Data +public class RecipeRecommendResponse { + private long recipePk; + private String recipeName; + private String recipeContent; + private int recipeCookingTime; + private double matchRate; + private long remainExpirationDays; + private String urgentIngredientName; + private List ingredients; +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeResponse.java b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeResponse.java new file mode 100644 index 0000000..5686857 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/recipe/response/RecipeResponse.java @@ -0,0 +1,28 @@ +package moja.refrigerator.dto.recipe.response; + +import lombok.Data; +import moja.refrigerator.aggregate.recipe.RecipeCategory; +import moja.refrigerator.aggregate.recipe.RecipeSource; +import moja.refrigerator.aggregate.user.User; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class RecipeResponse { + + private long recipePk; + private String recipeName; + private int recipeCookingTime; + private int recipeDifficulty; + private long recipeViews; + private String recipeContent; + + private LocalDateTime recipeCreateTime;; + private LocalDateTime recipeUpdateTime; + + private List recipeSource; + private User user; + private RecipeCategory recipeCategory; + +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/CustomUserDetails.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/CustomUserDetails.java new file mode 100644 index 0000000..2fb2d20 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/CustomUserDetails.java @@ -0,0 +1,53 @@ +package moja.refrigerator.dto.user; + +import moja.refrigerator.aggregate.user.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public class CustomUserDetails implements UserDetails { + private final User user; + + public CustomUserDetails(User user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + Collection collection = new ArrayList<>(); + collection.add(() -> user.getUserRole()); + return collection; + } + + @Override + public String getPassword() { + return user.getUserPw(); + } + + @Override + public String getUsername() { + return user.getUserId(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java new file mode 100644 index 0000000..89b2c75 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordResetRequest.java @@ -0,0 +1,8 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class PasswordResetRequest { + private String userEmail; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java new file mode 100644 index 0000000..3b6762a --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/PasswordUpdateRequest.java @@ -0,0 +1,9 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class PasswordUpdateRequest { + private String currentPw; + private String newPw; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserCreateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserCreateRequest.java new file mode 100644 index 0000000..de6ebb5 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserCreateRequest.java @@ -0,0 +1,11 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class UserCreateRequest { + private String userId; + private String userPw; + private String userEmail; + private String userNickname; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserUpdateRequest.java b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserUpdateRequest.java new file mode 100644 index 0000000..22289eb --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/dto/user/request/UserUpdateRequest.java @@ -0,0 +1,9 @@ +package moja.refrigerator.dto.user.request; + +import lombok.Data; + +@Data +public class UserUpdateRequest { + private String userEmail; + private String userNickname; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/exception/common/BusinessException.java b/refrigerator/src/main/java/moja/refrigerator/exception/common/BusinessException.java new file mode 100644 index 0000000..e012311 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/exception/common/BusinessException.java @@ -0,0 +1,7 @@ +package moja.refrigerator.exception.common; + +public class BusinessException extends RuntimeException { + public BusinessException(String message) { + super(message); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/exception/handler/GlobalExceptionHandler.java b/refrigerator/src/main/java/moja/refrigerator/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..445d8e1 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,16 @@ +package moja.refrigerator.exception.handler; + +import moja.refrigerator.exception.user.DuplicateUserException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(DuplicateUserException.class) + public ResponseEntity handleDuplicateUser(DuplicateUserException e) { + return ResponseEntity.status(HttpStatus.CONFLICT) + .body(e.getMessage()); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/exception/user/DuplicateUserException.java b/refrigerator/src/main/java/moja/refrigerator/exception/user/DuplicateUserException.java new file mode 100644 index 0000000..b9937bf --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/exception/user/DuplicateUserException.java @@ -0,0 +1,9 @@ +package moja.refrigerator.exception.user; + +import moja.refrigerator.exception.common.BusinessException; + +public class DuplicateUserException extends BusinessException { + public DuplicateUserException(String message) { + super(message); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java new file mode 100644 index 0000000..a0b4677 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java @@ -0,0 +1,73 @@ +package moja.refrigerator.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.dto.user.CustomUserDetails; +import moja.refrigerator.repository.user.TokenBlacklistRepository; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class JWTFilter extends OncePerRequestFilter { + private final JWTUtil jwtUtil; + private final TokenBlacklistRepository tokenBlacklistRepository; + + public JWTFilter(JWTUtil jwtUtil, TokenBlacklistRepository tokenBlacklistRepository) { + this.jwtUtil = jwtUtil; + this.tokenBlacklistRepository = tokenBlacklistRepository; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // 헤더에서 토큰 추출 + String authorization = request.getHeader("Authorization"); + + // Authorization 헤더 검증 + if (authorization == null || !authorization.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + String token = authorization.split(" ")[1]; + + // 블랙리스트 체크 + if (tokenBlacklistRepository.existsByBlacklistToken(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + // 토큰 소멸 시간 검증 + if (jwtUtil.isExpired(token)) { + // 만료된 토큰이면 그냥 통과 + filterChain.doFilter(request, response); + return; + } + + // 토큰에서 정보 추출 + String username = jwtUtil.getUsername(token); + String role = jwtUtil.getRole(token); + + // User를 생성하여 값 set + User user = new User(); + user.setUserId(username); + user.setUserPw("temppassword"); + user.setUserRole(role); + + // UserDetails에 회원 정보 객체 담기 + CustomUserDetails customUserDetails = new CustomUserDetails(user); + + // 스프링 시큐리티 인증 토큰 생성 + Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + + // 세션에 사용자 등록 + SecurityContextHolder.getContext().setAuthentication(authToken); + + filterChain.doFilter(request, response); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/JWTUtil.java b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTUtil.java new file mode 100644 index 0000000..df05cc4 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTUtil.java @@ -0,0 +1,46 @@ +package moja.refrigerator.jwt; + +import io.jsonwebtoken.Jwts; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + private SecretKey secretKey; + + // application.yml에서 jwt secret key를 가져옴 + public JWTUtil(@Value("${spring.jwt.secret}") String secret) { + this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); + } + + // 토큰에서 username 추출 + public String getUsername(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); + } + + // 토큰에서 role(권한) 추출 + public String getRole(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); + } + + // 토큰 만료 여부 확인 + public Boolean isExpired(String token) { + return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } + + // 토큰 생성 + public String createJwt(String username, String role, Long expiredMs) { + return Jwts.builder() + .claim("username", username) + .claim("role", role) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredMs)) + .signWith(secretKey) + .compact(); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/LoginFilter.java b/refrigerator/src/main/java/moja/refrigerator/jwt/LoginFilter.java new file mode 100644 index 0000000..17f3ed5 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/LoginFilter.java @@ -0,0 +1,63 @@ +package moja.refrigerator.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import moja.refrigerator.dto.user.CustomUserDetails; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.util.Collection; +import java.util.Iterator; + +public class LoginFilter extends UsernamePasswordAuthenticationFilter { + private final AuthenticationManager authenticationManager; + private final JWTUtil jwtUtil; + + public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) { + this.authenticationManager = authenticationManager; + this.jwtUtil = jwtUtil; + } + + // 로그인 시도 처리 + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { + // 클라이언트 요청에서 username, password 추출 + String username = obtainUsername(request); + String password = obtainPassword(request); + + // 이 정보를 토큰으로 만듦 (아직 인증되지 않은 상태) + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); + + // AuthenticationManager에게 검증 요청 + return authenticationManager.authenticate(authToken); + } + + // 로그인 성공 처리 - JWT 토큰 발급 + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { + CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal(); + + String username = customUserDetails.getUsername(); + + Collection authorities = authentication.getAuthorities(); + Iterator iterator = authorities.iterator(); + GrantedAuthority auth = iterator.next(); + + String role = auth.getAuthority(); + + String token = jwtUtil.createJwt(username, role, 60*60*10*1000L); + + response.addHeader("Authorization", "Bearer " + token); + } + + // 로그인 실패 처리 + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + response.setStatus(401); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java b/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java new file mode 100644 index 0000000..971f73d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java @@ -0,0 +1,34 @@ +package moja.refrigerator.jwt; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import moja.refrigerator.aggregate.user.TokenBlacklist; +import moja.refrigerator.repository.user.TokenBlacklistRepository; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; + +@Component +public class LogoutFilter implements LogoutHandler { + private final TokenBlacklistRepository tokenBlacklistRepository; + + public LogoutFilter(TokenBlacklistRepository tokenBlacklistRepository) { + this.tokenBlacklistRepository = tokenBlacklistRepository; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + String authorization = request.getHeader("Authorization"); + + if (authorization == null || !authorization.startsWith("Bearer ")) { + throw new IllegalArgumentException("토큰이 유효하지 않습니다."); + } + + String token = authorization.split(" ")[1]; + + // 토큰을 블랙리스트에 추가 + TokenBlacklist blacklist = new TokenBlacklist(); + blacklist.setBlacklistToken(token); + tokenBlacklistRepository.save(blacklist); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientBookmarkRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientBookmarkRepository.java new file mode 100644 index 0000000..987536f --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientBookmarkRepository.java @@ -0,0 +1,10 @@ +package moja.refrigerator.repository.ingredient; + +import moja.refrigerator.aggregate.ingredient.IngredientBookmark; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface IngredientBookmarkRepository extends JpaRepository { + List findAllByUser_UserPk(long userPk); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientCategoryRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientCategoryRepository.java new file mode 100644 index 0000000..6ff4511 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientCategoryRepository.java @@ -0,0 +1,9 @@ +package moja.refrigerator.repository.ingredient; + +import moja.refrigerator.aggregate.ingredient.IngredientCategory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface IngredientCategoryRepository extends JpaRepository { +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientManagementRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientManagementRepository.java new file mode 100644 index 0000000..4278b26 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientManagementRepository.java @@ -0,0 +1,11 @@ +package moja.refrigerator.repository.ingredient; + +import moja.refrigerator.aggregate.ingredient.IngredientManagement; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface IngredientManagementRepository extends JpaRepository { + +} + diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientMyRefrigeratorRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientMyRefrigeratorRepository.java new file mode 100644 index 0000000..19561f3 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientMyRefrigeratorRepository.java @@ -0,0 +1,13 @@ +package moja.refrigerator.repository.ingredient; + +import moja.refrigerator.aggregate.ingredient.IngredientMyRefrigerator; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface IngredientMyRefrigeratorRepository extends JpaRepository { + // 사용자 PK로 해당 사용자의 냉장고 재료 목록 조회 + List findByUserUserPk(Long userPk); +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientStorageRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientStorageRepository.java new file mode 100644 index 0000000..67e6c36 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/ingredient/IngredientStorageRepository.java @@ -0,0 +1,9 @@ +package moja.refrigerator.repository.ingredient; + +import moja.refrigerator.aggregate.ingredient.IngredientStorage; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface IngredientStorageRepository extends JpaRepository { +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeCategoryRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeCategoryRepository.java new file mode 100644 index 0000000..43b04aa --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeCategoryRepository.java @@ -0,0 +1,12 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.RecipeCategory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RecipeCategoryRepository extends JpaRepository { + Optional findByRecipeCategory(String RecipeCategory); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeIngredientRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeIngredientRepository.java new file mode 100644 index 0000000..e717f33 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeIngredientRepository.java @@ -0,0 +1,13 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.Recipe; +import moja.refrigerator.aggregate.recipe.RecipeIngredient; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface RecipeIngredientRepository extends JpaRepository { + List findByRecipe(Recipe recipe); +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeLikeDislikeRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeLikeDislikeRepository.java new file mode 100644 index 0000000..7510915 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeLikeDislikeRepository.java @@ -0,0 +1,14 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.Recipe; +import moja.refrigerator.aggregate.recipe.RecipeLikeDislike; +import moja.refrigerator.aggregate.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface RecipeLikeDislikeRepository extends JpaRepository { + Optional findByRecipeAndUser(Recipe recipe, User user); + long countByRecipeAndLikeStatus(Recipe recipe, Boolean likeStatus); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeRepository.java new file mode 100644 index 0000000..aa514db --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeRepository.java @@ -0,0 +1,13 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.Recipe; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RecipeRepository extends JpaRepository { + Optional findByRecipePk(long recipePk); +} + diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceRepository.java new file mode 100644 index 0000000..d0db45e --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceRepository.java @@ -0,0 +1,10 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.RecipeSource; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RecipeSourceRepository extends JpaRepository { + Optional findByRecipeSourceFileName(String recipeSourceFileName); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceTypeRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceTypeRepository.java new file mode 100644 index 0000000..4948bec --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/RecipeSourceTypeRepository.java @@ -0,0 +1,9 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.RecipeSourceType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RecipeSourceTypeRepository extends JpaRepository { +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/recipe/ReplacableIngredientRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/ReplacableIngredientRepository.java new file mode 100644 index 0000000..50a497b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/recipe/ReplacableIngredientRepository.java @@ -0,0 +1,13 @@ +package moja.refrigerator.repository.recipe; + +import moja.refrigerator.aggregate.recipe.RecipeIngredient; +import moja.refrigerator.aggregate.recipe.ReplacableIngredient; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ReplacableIngredientRepository extends JpaRepository { + List findByRecipeIngredient(RecipeIngredient recipeIngredient); +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/user/FollowRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/user/FollowRepository.java new file mode 100644 index 0000000..a568240 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/user/FollowRepository.java @@ -0,0 +1,12 @@ +package moja.refrigerator.repository.user; + +import moja.refrigerator.aggregate.user.Follow; +import moja.refrigerator.aggregate.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FollowRepository extends JpaRepository { + boolean existsByFollowerAndFollowing(User follower, User following); + void deleteByFollowerAndFollowing(User follower, User following); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java new file mode 100644 index 0000000..6d82bd7 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java @@ -0,0 +1,8 @@ +package moja.refrigerator.repository.user; + +import moja.refrigerator.aggregate.user.TokenBlacklist; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TokenBlacklistRepository extends JpaRepository { + boolean existsByBlacklistToken(String blacklistToken); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java new file mode 100644 index 0000000..1742c4b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/user/UserRepository.java @@ -0,0 +1,17 @@ +package moja.refrigerator.repository.user; + +import moja.refrigerator.aggregate.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + boolean existsByUserId(String userId); + boolean existsByUserEmail(String userEmail); + boolean existsByUserNickname(String userNickname); + Optional findByUserPk(long userPk); + Optional findByUserId(String userId); + Optional findByUserEmail(String userEmail); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java new file mode 100644 index 0000000..c189c53 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailService.java @@ -0,0 +1,5 @@ +package moja.refrigerator.service.email; + +public interface EmailService { + void sendTempPassword(String name, String email, String tempPassword); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java new file mode 100644 index 0000000..ec915b3 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/email/EmailServiceImpl.java @@ -0,0 +1,38 @@ +package moja.refrigerator.service.email; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EmailServiceImpl implements EmailService { + private final JavaMailSender mailSender; + + public EmailServiceImpl(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + @Value("${spring.mail.username}") + private String senderEmail; + + @Override + @Transactional + public void sendTempPassword(String name, String email, String tempPassword) { + SimpleMailMessage message = new SimpleMailMessage(); + + message.setTo(email); + message.setFrom(senderEmail); + message.setSubject("[ReciPick] 임시 비밀번호 발급"); + message.setText(String.format(""" + 안녕하세요. %s님! 임시 비밀번호가 발급되었습니다. + + 임시 비밀번호: %s + + 보안을 위해 로그인 후 반드시 비밀번호를 변경해 주세요. + """, name, tempPassword)); + + mailSender.send(message); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientService.java b/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientService.java new file mode 100644 index 0000000..882ac06 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientService.java @@ -0,0 +1,25 @@ +package moja.refrigerator.service.ingredient; + +import moja.refrigerator.dto.ingredient.request.*; +import moja.refrigerator.dto.ingredient.response.*; + +import java.util.List; + +public interface IngredientService { + + ResponseRegistIngredientBookmark createIngredientBookmark(RequestRegistIngredientBookmark requestBookmark); + + void createIngredient(IngredientCreateRequest request, Long userPk, Long ingredientManagementPk); // 재료 등록 메서드 + + List getIngredient(Long userPk); // 재료 조회 메서드 + void updateIngredient(IngredientUpdateRequest request); + + void deleteIngredient(IngredientDeleteRequest request); + + List getUsersIngredientBookmarkLists( + RequestIngredientBookmarkLists requestBookmarkLists); + + ResponseDeleteIngredientBookmark deleteIngredientBookmark(RequestDeleteIngredientBookmark requestDeleteBookmark); + + List alertExpirationDate(RequestAlertExpirationDate requestAlertExpirationDate); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientServiceImpl.java new file mode 100644 index 0000000..9842b63 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/ingredient/IngredientServiceImpl.java @@ -0,0 +1,220 @@ +package moja.refrigerator.service.ingredient; + +import jakarta.persistence.EntityNotFoundException; +import moja.refrigerator.aggregate.ingredient.*; +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.repository.ingredient.*; + +import moja.refrigerator.dto.ingredient.request.*; +import moja.refrigerator.dto.ingredient.response.*; + +import moja.refrigerator.repository.user.UserRepository; +import org.modelmapper.ModelMapper; +import org.modelmapper.convention.MatchingStrategies; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Service +public class IngredientServiceImpl implements IngredientService{ + + private IngredientManagementRepository ingredientManagementRepository; + private IngredientBookmarkRepository ingredientBookmarkRepository; + private IngredientMyRefrigeratorRepository ingredientMyRefrigeratorRepository; + private UserRepository userRepository; + private ModelMapper mapper; + + @Autowired + public IngredientServiceImpl(IngredientManagementRepository ingredientManagementRepository, + IngredientBookmarkRepository ingredientBookmarkRepository, + IngredientMyRefrigeratorRepository ingredientMyRefrigeratorRepository, + UserRepository userRepository, + ModelMapper mapper) { + this.ingredientManagementRepository = ingredientManagementRepository; + this.ingredientBookmarkRepository = ingredientBookmarkRepository; + this.ingredientMyRefrigeratorRepository = ingredientMyRefrigeratorRepository; + this.userRepository = userRepository; + this.mapper = mapper; + } + + @Override + @Transactional + public void createIngredient(IngredientCreateRequest request, Long userPk, Long ingredientManagementPk) { + IngredientMyRefrigerator myRefrigerator = mapper.map(request, IngredientMyRefrigerator.class); + + User user = userRepository.findById(userPk) + .orElseThrow(() -> new IllegalArgumentException("회원을 찾을 수 없습니다.")); + + IngredientManagement ingredientManagement = ingredientManagementRepository.findById(ingredientManagementPk) + .orElseThrow(() -> new IllegalArgumentException("재료를 찾을 수 없습니다.")); + + myRefrigerator.setUser(user); + myRefrigerator.setIngredientManagement(ingredientManagement); + + // 재료를 JpaRepository 의 save() 메소드로 DB에 저장 ! + ingredientMyRefrigeratorRepository.save(myRefrigerator); + } + + @Transactional(readOnly = true) + public List getIngredient(Long userPk) { + List ingredients = ingredientMyRefrigeratorRepository.findByUserUserPk(userPk); + + LocalDate currentDate = LocalDate.now(); + AtomicInteger counter = new AtomicInteger(1); + + return ingredients.stream() + .map(ingredient -> { + IngredientResponse response = mapper.map(ingredient, IngredientResponse.class); + response.setNumber(counter.getAndIncrement()); + response.setIngredientName(ingredient.getIngredientManagement().getIngredientName()); + response.setSeasonDate(ingredient.getIngredientManagement().getSeasonDate()); + response.setIngredientStorage(ingredient.getIngredientManagement().getIngredientStorage().getIngredientStorage()); + + // 현재 날짜 기준, 유통기한 남은 일수 계산 + LocalDate expirationDate = LocalDate.parse(ingredient.getExpirationDate()); + long remainExpirationDate = ChronoUnit.DAYS.between(currentDate, expirationDate); + response.setRemainExpirationDate(remainExpirationDate); + + return response; + }) + // 남은 일수 기준 오름차순 정렬 + .sorted(Comparator.comparingLong(IngredientResponse::getRemainExpirationDate)) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public void updateIngredient(IngredientUpdateRequest request) { + IngredientMyRefrigerator ingredient = ingredientMyRefrigeratorRepository + .findById(request.getIngredientMyRefrigeratorPk()) + .orElseThrow(() -> new EntityNotFoundException("수정할 재료를 찾을 수 없습니다.")); + + mapper.map(request, ingredient); + } + + @Override + @Transactional + public void deleteIngredient(IngredientDeleteRequest request) { + IngredientMyRefrigerator ingredient = ingredientMyRefrigeratorRepository + .findById(request.getIngredientMyRefrigeratorPk()) + .orElseThrow(() -> new IllegalArgumentException("삭제할 재료를 찾을 수 없습니다.")); + + float currentAmount = ingredient.getIngredientAmount(); + float deleteAmount = request.getDeleteAmount(); + + if (currentAmount < deleteAmount) { + throw new IllegalArgumentException("삭제할 수량이 현재 보유 수량보다 많습니다."); + } + // 삭제할 수량이 딱 맞아 떨어지면 재료를 완전 삭제 + if (currentAmount == deleteAmount) { + ingredientMyRefrigeratorRepository.deleteById(request.getIngredientMyRefrigeratorPk()); + } else { + // 현재 수량 - 삭제할 수량의 계산 결과를 저장 + ingredient.setIngredientAmount(currentAmount - deleteAmount); + ingredientMyRefrigeratorRepository.save(ingredient); + } + } + + @Override + public ResponseRegistIngredientBookmark createIngredientBookmark(RequestRegistIngredientBookmark requestBookmark) { + User user = userRepository.findById(requestBookmark.getUserPk()) + .orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다.")); + + IngredientMyRefrigerator ingredientManagement = ingredientMyRefrigeratorRepository + .findById(requestBookmark.getIngredientMyRefrigeratorPk()) + .orElseThrow(() -> new EntityNotFoundException("재료를 찾을 수 없습니다.")); + + IngredientBookmark ingredientBookmark = new IngredientBookmark(); + + ingredientBookmark.setUser(user); + ingredientBookmark.setIngredientMyRefrigerator(ingredientManagement); + + ingredientBookmarkRepository.save(ingredientBookmark); + + mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + + return mapper.map(ingredientBookmark, ResponseRegistIngredientBookmark.class); + } + + @Override + public List getUsersIngredientBookmarkLists( + RequestIngredientBookmarkLists requestBookmarkLists) { + List ingredientBookmarkLists = ingredientBookmarkRepository + .findAllByUser_UserPk(requestBookmarkLists.getUserPk()); + + mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + + return ingredientBookmarkLists.stream().map(ingredientBookmark -> mapper + .map(ingredientBookmark, ResponseUsersIngredientBookmarkLists.class)) + .collect(Collectors.toList()); + } + + @Override + public ResponseDeleteIngredientBookmark deleteIngredientBookmark( + RequestDeleteIngredientBookmark requestDeleteBookmark) { + IngredientBookmark ingredientBookmark = ingredientBookmarkRepository + .findById(requestDeleteBookmark.getIngredientBookmarkPk()) + .orElseThrow(() -> new EntityNotFoundException("즐겨찾기를 찾을 수 없습니다.")); + + ResponseDeleteIngredientBookmark response = new ResponseDeleteIngredientBookmark(); + + try { + ingredientBookmarkRepository.deleteById(requestDeleteBookmark.getIngredientBookmarkPk()); + String message = ingredientBookmark.getIngredientMyRefrigerator() + .getIngredientManagement().getIngredientName() + " 재료의 즐겨찾기를 삭제했습니다"; + response.setMessage(message); + return response; + } catch (Exception e) { + String message = + ingredientBookmark.getIngredientMyRefrigerator() + .getIngredientManagement().getIngredientName() + " 재료의 즐겨찾기를 삭제 실패했습니다"; + response.setMessage(message); + return response; + } + } + + @Override + public List alertExpirationDate( + RequestAlertExpirationDate requestAlertExpirationDate) { + // 1. 현재일 찾기 + LocalDate currentDate = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 원하는 포맷 지정 + String dateString = currentDate.format(formatter); + + // 2. 내 냉장고 전채 조회 + List refrigeratorList = ingredientMyRefrigeratorRepository + .findByUserUserPk(requestAlertExpirationDate.getUserPk()); + + List responseAlertExpirationDates = new ArrayList<>(); + // 3. 남은 유통기한 계산 + for (IngredientMyRefrigerator ingredientMyRefrigerator : refrigeratorList) { + LocalDate expirationDate = LocalDate.parse(ingredientMyRefrigerator.getExpirationDate(), formatter); + + long daysUntilExpiration = ChronoUnit.DAYS.between(currentDate, expirationDate); + + if (daysUntilExpiration == 7 || daysUntilExpiration == 3) { + // ResponseAlertExpirationDate 객체 생성 + ResponseAlertExpirationDate response = new ResponseAlertExpirationDate( + ingredientMyRefrigerator.getIngredientMyRefrigeratorPk(), + ingredientMyRefrigerator.getIngredientManagement(), + ingredientMyRefrigerator.getIngredientAmount(), + ingredientMyRefrigerator.getExpirationDate(), + (int) daysUntilExpiration // daysUntilExpiration을 int로 변환 + ); + + // 리스트에 추가 + responseAlertExpirationDates.add(response); + } + + } + // 4. 리턴 + return responseAlertExpirationDates; + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeService.java b/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeService.java new file mode 100644 index 0000000..81c69a5 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeService.java @@ -0,0 +1,31 @@ +package moja.refrigerator.service.recipe; + +import moja.refrigerator.aggregate.recipe.Recipe; +import moja.refrigerator.dto.recipe.request.RecipeCreateRequest; +import moja.refrigerator.dto.recipe.request.RecipeLikeRequest; +import moja.refrigerator.dto.recipe.request.RecipeUpdateRequest; +import moja.refrigerator.dto.recipe.response.RecipeDetailResponse; +import moja.refrigerator.dto.recipe.response.RecipeLikeResponse; +import moja.refrigerator.dto.recipe.response.RecipeRecommendResponse; +import moja.refrigerator.dto.recipe.response.RecipeResponse; +import moja.refrigerator.repository.recipe.RecipeRepository; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface RecipeService { + void createRecipe(RecipeCreateRequest request + ,List files + ); + List getAllRecipes(); + RecipeDetailResponse getRecipe(long id); + void deleteRecipe(long recipePk); + void updateRecipe( + RecipeUpdateRequest request + ,List files + ); + List getRecommendedRecipes(Long userPk); + RecipeRecommendResponse getRandomRecipe(); + RecipeLikeResponse toggleLikeDislike(RecipeLikeRequest request); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeServiceImpl.java new file mode 100644 index 0000000..42030fb --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/recipe/RecipeServiceImpl.java @@ -0,0 +1,384 @@ +package moja.refrigerator.service.recipe; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import moja.refrigerator.aggregate.ingredient.IngredientMyRefrigerator; +import moja.refrigerator.aggregate.recipe.*; +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.dto.recipe.RecipeMatchResult; +import moja.refrigerator.dto.recipe.request.RecipeCreateRequest; +import moja.refrigerator.dto.recipe.request.RecipeLikeRequest; +import moja.refrigerator.dto.recipe.request.RecipeUpdateRequest; +import moja.refrigerator.dto.recipe.response.*; +import moja.refrigerator.repository.ingredient.IngredientMyRefrigeratorRepository; +import moja.refrigerator.repository.recipe.*; +import moja.refrigerator.repository.user.UserRepository; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class RecipeServiceImpl implements RecipeService { + + private final RecipeRepository recipeRepository; + private final UserRepository userRepository; + private final RecipeSourceRepository recipeSourceRepository; + private final RecipeCategoryRepository recipeCategoryRepository; + private final ModelMapper mapper; + private final RecipeSourceTypeRepository recipeSourceTypeRepository; + private final AmazonS3Client amazonS3Client; + private final IngredientMyRefrigeratorRepository ingredientMyRefrigeratorRepository; + private final RecipeIngredientRepository recipeIngredientRepository; + private final ReplacableIngredientRepository replacableIngredientRepository; + private final RecipeLikeDislikeRepository recipeLikeDislikeRepository; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + @Autowired + public RecipeServiceImpl( + RecipeRepository recipeRepository, + UserRepository userRepository, + RecipeSourceRepository recipeSourceRepository, + RecipeCategoryRepository recipeCategoryRepository, + ModelMapper mapper, + RecipeSourceTypeRepository recipeSourceTypeRepository, + AmazonS3Client amazonS3Client, + IngredientMyRefrigeratorRepository ingredientMyRefrigeratorRepository, + RecipeIngredientRepository recipeIngredientRepository, + ReplacableIngredientRepository replacableIngredientRepository, + RecipeLikeDislikeRepository recipeLikeDislikeRepository + ) { + this.recipeRepository = recipeRepository; + this.userRepository = userRepository; + this.recipeSourceRepository = recipeSourceRepository; + this.recipeCategoryRepository = recipeCategoryRepository; + this.mapper = mapper; + this.recipeSourceTypeRepository = recipeSourceTypeRepository; + this.amazonS3Client = amazonS3Client; + this.ingredientMyRefrigeratorRepository = ingredientMyRefrigeratorRepository; + this.recipeIngredientRepository = recipeIngredientRepository; + this.replacableIngredientRepository = replacableIngredientRepository; + this.recipeLikeDislikeRepository = recipeLikeDislikeRepository; + } + private boolean isImageFile(String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + return List.of("jpg", "jpeg", "png", "gif").contains(extension); + } + + private boolean isVideoFile(String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + return List.of("mp4", "avi", "mov", "wmv").contains(extension); + } + + @Override + @Transactional(propagation = Propagation.REQUIRED) + public void createRecipe(RecipeCreateRequest request, List files) { + Recipe recipe = new Recipe(); + recipe.setRecipeName(request.getRecipeName()); + recipe.setRecipeContent(request.getRecipeContent()); + recipe.setRecipeDifficulty(request.getRecipeDifficulty()); + recipe.setRecipeCookingTime(request.getRecipeCookingTime()); + + User user = userRepository.findById(request.getUserPk()) + .orElseThrow(IllegalArgumentException::new); + recipe.setUser(user); + + RecipeCategory recipeCategory = recipeCategoryRepository.findById(request.getRecipeCategoryPk()) + .orElseThrow(IllegalArgumentException::new); + recipe.setRecipeCategory(recipeCategory); + + if(files != null && !files.isEmpty()) { + for(MultipartFile file : files) { + try { + String recipeSourceFileName = file.getOriginalFilename(); + UUID uuid = UUID.randomUUID(); + String recipeSourceServername = uuid + recipeSourceFileName; + String recipeSourceSave = "https://" + bucket + "/recipe/" + recipeSourceServername; + + RecipeSource recipeSource = new RecipeSource(); + recipeSource.setRecipeSourceServername(recipeSourceServername); + recipeSource.setRecipeSourceSave(recipeSourceSave); + recipeSource.setRecipeSourceFileName(recipeSourceFileName); + + RecipeSourceType recipeSourceType; + if(isImageFile(recipeSourceFileName)) { + recipeSourceType = recipeSourceTypeRepository.findById(1) + .orElseThrow(IllegalArgumentException::new); + } else if (isVideoFile(recipeSourceFileName)) { + recipeSourceType = recipeSourceTypeRepository.findById(2) + .orElseThrow(IllegalArgumentException::new); + } else { + throw new IllegalArgumentException("Unsupported file type"); + } + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(file.getContentType()); + objectMetadata.setContentLength(file.getSize()); + amazonS3Client.putObject(bucket, recipeSourceServername, file.getInputStream(), objectMetadata); + + recipeSource.setRecipeSourceType(recipeSourceType); + recipeSource.setRecipe(recipe); + recipeSourceRepository.save(recipeSource); + break; + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + } + + recipeRepository.save(recipe); + } + + @Override + public List getAllRecipes() { + return recipeRepository.findAll().stream() + .map(recipe -> mapper.map(recipe, RecipeResponse.class)) + .collect(Collectors.toList()); + } + + @Override + public RecipeDetailResponse getRecipe(long id) { + Recipe recipe = recipeRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("recipe not found")); + return mapper.map(recipe, RecipeDetailResponse.class); + } + + @Override + public void deleteRecipe(long recipePk) { + Recipe recipe = recipeRepository.findByRecipePk(recipePk) + .orElseThrow(IllegalArgumentException::new); + List sources = recipe.getRecipeSource(); + if(sources != null && !sources.isEmpty()) { + for (RecipeSource recipeSource : sources) { + amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, recipeSource.getRecipeSourceServername())); + } + } + recipeRepository.delete(recipe); + } + + @Override + @Transactional + public void updateRecipe(RecipeUpdateRequest request, List files) { + Recipe recipe = recipeRepository.findByRecipePk(request.getRecipePk()) + .orElseThrow(() -> new IllegalArgumentException("Recipe not found")); + + if (request.getRecipeName() != null) recipe.setRecipeName(request.getRecipeName()); + if (request.getRecipeCookingTime() != 0) recipe.setRecipeCookingTime(request.getRecipeCookingTime()); + if (request.getRecipeDifficulty() != 0) recipe.setRecipeDifficulty(request.getRecipeDifficulty()); + if (request.getRecipeCategory() != null) { + recipe.setRecipeCategory(recipeCategoryRepository.findByRecipeCategory(request.getRecipeCategory()) + .orElseThrow(() -> new IllegalArgumentException("Category not found"))); + } + + List sources = recipe.getRecipeSource(); + List uploadedFileNames = files.stream() + .map(MultipartFile::getOriginalFilename) + .toList(); + + List recipeSourcesToDelete = sources.stream() + .filter(source -> !uploadedFileNames.contains(source.getRecipeSourceFileName())) + .toList(); + + for (RecipeSource recipeSource : recipeSourcesToDelete) { + amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, recipeSource.getRecipeSourceServername())); + recipe.getRecipeSource().remove(recipeSource); + recipeSourceRepository.delete(recipeSource); + } + + List existingFileNames = sources.stream() + .map(RecipeSource::getRecipeSourceFileName) + .toList(); + + List filesToAdd = files.stream() + .filter(file -> !existingFileNames.contains(file.getOriginalFilename())) + .toList(); + + for (MultipartFile file : filesToAdd) { + try { + String recipeSourceFileName = UUID.randomUUID() + "_" + file.getOriginalFilename(); + String recipeSourceServername = recipeSourceFileName; + String recipeSourceSave = "https://" + bucket + "/recipe/" + recipeSourceServername; + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(file.getContentType()); + objectMetadata.setContentLength(file.getSize()); + amazonS3Client.putObject(bucket, recipeSourceServername, file.getInputStream(), objectMetadata); + + RecipeSource recipeSource = new RecipeSource(); + recipeSource.setRecipeSourceServername(recipeSourceServername); + recipeSource.setRecipeSourceSave(recipeSourceSave); + recipeSource.setRecipeSourceFileName(file.getOriginalFilename()); + + RecipeSourceType recipeSourceType; + if (isImageFile(file.getOriginalFilename())) { + recipeSourceType = recipeSourceTypeRepository.findById(1) + .orElseThrow(() -> new IllegalArgumentException("Image type not found")); + } else if (isVideoFile(file.getOriginalFilename())) { + recipeSourceType = recipeSourceTypeRepository.findById(2) + .orElseThrow(() -> new IllegalArgumentException("Video type not found")); + } else { + throw new IllegalArgumentException("Unsupported file type"); + } + + recipeSource.setRecipeSourceType(recipeSourceType); + recipeSource.setRecipe(recipe); + recipeSourceRepository.save(recipeSource); + } catch (IOException e) { + throw new RuntimeException("Error processing file: " + file.getOriginalFilename(), e); + } + } + + recipeRepository.save(recipe); + } + + + @Override + public List getRecommendedRecipes(Long userPk) { + List userIngredients = + ingredientMyRefrigeratorRepository.findByUserUserPk(userPk); + List allRecipes = recipeRepository.findAll(); + + return allRecipes.stream() + .map(recipe -> checkRecipeMatch(recipe, userIngredients)) + .filter(RecipeMatchResult::isMatched) + .map(result -> { + RecipeRecommendResponse response = mapper.map(result.getRecipe(), RecipeRecommendResponse.class); + response.setMatchRate(result.getMatchRate()); + response.setRemainExpirationDays(result.getRemainExpirationDays()); + response.setUrgentIngredientName(result.getUrgentIngredientName()); + return response; + }) + .sorted(Comparator.comparingLong(RecipeRecommendResponse::getRemainExpirationDays)) + .collect(Collectors.toList()); + } + + private RecipeMatchResult checkRecipeMatch(Recipe recipe, List userIngredients) { + List recipeIngredients = recipeIngredientRepository.findByRecipe(recipe); + LocalDate currentDate = LocalDate.now(); + + if (recipeIngredients.isEmpty()) { + return new RecipeMatchResult(recipe, false, 0, 0, null); + } + + boolean hasAllNecessaryIngredients = true; + int matchedCount = 0; + long shortestRemainDays = Long.MAX_VALUE; + String urgentIngredientName = null; + + for (RecipeIngredient recipeIngredient : recipeIngredients) { + boolean hasIngredient = false; + + for (IngredientMyRefrigerator userIngredient : userIngredients) { + if (userIngredient.getIngredientManagement().getIngredientManagementPk() == + recipeIngredient.getIngredientManagement().getIngredientManagementPk()) { + + hasIngredient = true; + matchedCount++; + + // 남은 일수 계산 + LocalDate expirationDate = LocalDate.parse(userIngredient.getExpirationDate()); + long remainDays = ChronoUnit.DAYS.between(currentDate, expirationDate); + + // 더 짧은 유통기한 발견시 정보 업데이트 + if (remainDays < shortestRemainDays) { + shortestRemainDays = remainDays; + urgentIngredientName = userIngredient.getIngredientManagement().getIngredientName(); + } + break; + } + } + + if (recipeIngredient.isIngredientIsNecessary() && !hasIngredient) { + hasAllNecessaryIngredients = false; + break; + } + } + + double matchRate = ((double) matchedCount / recipeIngredients.size()) * 100; + boolean isMatched = hasAllNecessaryIngredients && matchRate >= 66; + + return new RecipeMatchResult( + recipe, + isMatched, + matchRate, + isMatched ? shortestRemainDays : 0, + isMatched ? urgentIngredientName : null + ); + } + @Override + public RecipeRecommendResponse getRandomRecipe() { + List allRecipes = recipeRepository.findAll(); + + if (allRecipes.isEmpty()) { + throw new IllegalStateException("등록된 레시피가 없습니다."); + } + + Random random = new Random(); + int randomIndex = random.nextInt(allRecipes.size()); + Recipe selectedRecipe = allRecipes.get(randomIndex); + + // Response 객체로 변환 + RecipeRecommendResponse response = mapper.map(selectedRecipe, RecipeRecommendResponse.class); + + // 재료 정보 추가 + List recipeIngredients = recipeIngredientRepository.findByRecipe(selectedRecipe); + List ingredientInfoList = recipeIngredients.stream() + .map(ri -> { + RecipeIngredientInfo info = new RecipeIngredientInfo(); + info.setIngredientName(ri.getIngredientManagement().getIngredientName()); + info.setNecessary(ri.isIngredientIsNecessary()); + return info; + }) + .collect(Collectors.toList()); + + response.setIngredients(ingredientInfoList); + + return response; + } + + @Override + @Transactional + public RecipeLikeResponse toggleLikeDislike(RecipeLikeRequest request) { + Recipe recipe = recipeRepository.findById(request.getRecipePk()) + .orElseThrow(() -> new IllegalArgumentException("레시피를 찾을 수 없습니다.")); + + User user = userRepository.findById(request.getUserPk()) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + + Optional existing = + recipeLikeDislikeRepository.findByRecipeAndUser(recipe, user); + + if (existing.isPresent()) { + if (existing.get().getLikeStatus() == request.getLikeStatus()) { + recipeLikeDislikeRepository.delete(existing.get()); + } else { + existing.get().setLikeStatus(request.getLikeStatus()); + } + } else { + RecipeLikeDislike newReaction = new RecipeLikeDislike(); + newReaction.setRecipe(recipe); + newReaction.setUser(user); + newReaction.setLikeStatus(request.getLikeStatus()); + recipeLikeDislikeRepository.save(newReaction); + } + + // 현재 좋아요/싫어요 수 계산 + long likes = recipeLikeDislikeRepository.countByRecipeAndLikeStatus(recipe, true); + long dislikes = recipeLikeDislikeRepository.countByRecipeAndLikeStatus(recipe, false); + + return new RecipeLikeResponse(likes, dislikes, request.getLikeStatus()); + } + +} \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/CustomUserDetailsService.java b/refrigerator/src/main/java/moja/refrigerator/service/user/CustomUserDetailsService.java new file mode 100644 index 0000000..604afac --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/CustomUserDetailsService.java @@ -0,0 +1,27 @@ +package moja.refrigerator.service.user; + +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.dto.user.CustomUserDetails; +import moja.refrigerator.repository.user.UserRepository; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + private final UserRepository userRepository; + + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional(readOnly = true) + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User userData = userRepository.findByUserId(username) + .orElseThrow(() -> new UsernameNotFoundException("입력하신 아이디로 가입된 사용자를 찾을 수 없습니다.: " + username)); + return new CustomUserDetails(userData); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/FollowService.java b/refrigerator/src/main/java/moja/refrigerator/service/user/FollowService.java new file mode 100644 index 0000000..e213f57 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/FollowService.java @@ -0,0 +1,5 @@ +package moja.refrigerator.service.user; + +public interface FollowService { + void toggleFollow(Long targetUserPk); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/FollowServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/user/FollowServiceImpl.java new file mode 100644 index 0000000..14e077b --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/FollowServiceImpl.java @@ -0,0 +1,50 @@ +package moja.refrigerator.service.user; + +import moja.refrigerator.aggregate.user.Follow; +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.repository.user.FollowRepository; +import moja.refrigerator.repository.user.UserRepository; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class FollowServiceImpl implements FollowService { + private final FollowRepository followRepository; + private final UserRepository userRepository; + + public FollowServiceImpl(FollowRepository followRepository, UserRepository userRepository) { + this.followRepository = followRepository; + this.userRepository = userRepository; + } + + @Override + @Transactional + public void toggleFollow(Long targetUserPk) { + // 현재 로그인한 사용자 찾기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + User follower = userRepository.findByUserId(authentication.getName()) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + + // 팔로우 대상 사용자 찾기 + User following = userRepository.findByUserPk(targetUserPk) + .orElseThrow(() -> new UsernameNotFoundException("팔로우 하려는 사용자를 찾을 수 없습니다.")); + + // 자기 자신 팔로우 방지 + if (follower.getUserPk() == following.getUserPk()) { + throw new IllegalArgumentException("자기 자신을 팔로우할 수 없습니다."); + } + + // 이미 팔로우 중이면 언팔로우, 아니면 팔로우 + if (followRepository.existsByFollowerAndFollowing(follower, following)) { + followRepository.deleteByFollowerAndFollowing(follower, following); + } else { + Follow follow = new Follow(); + follow.setFollower(follower); + follow.setFollowing(following); + followRepository.save(follow); + } + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java new file mode 100644 index 0000000..cfb1abd --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java @@ -0,0 +1,13 @@ +package moja.refrigerator.service.user; + +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; +import moja.refrigerator.dto.user.request.UserCreateRequest; +import moja.refrigerator.dto.user.request.UserUpdateRequest; + +public interface UserService { + void createUser(UserCreateRequest request); + void updateUser(UserUpdateRequest request); + void resetPassword(PasswordResetRequest request); + void updatePassword(PasswordUpdateRequest request); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java new file mode 100644 index 0000000..7329781 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java @@ -0,0 +1,133 @@ +package moja.refrigerator.service.user; + +import lombok.RequiredArgsConstructor; +import moja.refrigerator.aggregate.user.User; +import moja.refrigerator.dto.user.request.PasswordResetRequest; +import moja.refrigerator.dto.user.request.PasswordUpdateRequest; +import moja.refrigerator.dto.user.request.UserCreateRequest; +import moja.refrigerator.dto.user.request.UserUpdateRequest; +import moja.refrigerator.exception.user.DuplicateUserException; +import moja.refrigerator.repository.user.UserRepository; +import moja.refrigerator.service.email.EmailService; +import org.modelmapper.ModelMapper; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final BCryptPasswordEncoder passwordEncoder; + private final ModelMapper modelMapper; + private final EmailService emailService; + + @Override + @Transactional + public void createUser(UserCreateRequest request) { + // 중복 검사 로직 + checkDuplicateUser(request); + + // dto -> 엔티티 변환 + User user = modelMapper.map(request, User.class); + + // 비밀번호 암호화 + user.setUserPw(passwordEncoder.encode(request.getUserPw())); + user.setUserRole("ROLE_USER"); + + userRepository.save(user); + } + + @Override + @Transactional + public void updateUser(UserUpdateRequest request) { + // 현재 로그인한 사용자 정보 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String userId = authentication.getName(); + + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + + // 이메일 변경 요청이 있는 경우 + if (request.getUserEmail() != null) { + if (!request.getUserEmail().equals(user.getUserEmail()) + && userRepository.existsByUserEmail(request.getUserEmail())) { + throw new DuplicateUserException("이미 사용 중인 이메일입니다."); + } + user.setUserEmail(request.getUserEmail()); + } + + // 닉네임 변경 요청이 있는 경우 + if (request.getUserNickname() != null) { + if (!request.getUserNickname().equals(user.getUserNickname()) + && userRepository.existsByUserNickname(request.getUserNickname())) { + throw new DuplicateUserException("이미 사용 중인 닉네임입니다."); + } + user.setUserNickname(request.getUserNickname()); + } + } + + @Override + @Transactional + public void resetPassword(PasswordResetRequest request) { + // 이메일로 사용자 찾기 + User user = userRepository.findByUserEmail(request.getUserEmail()) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일로 가입된 계정이 없습니다.")); + + // 임시 비밀번호 생성 + String tempPassword = UUID.randomUUID().toString().substring(0, 12); + + try { + // 이메일 발송 + emailService.sendTempPassword(user.getUserNickname(), user.getUserEmail(), tempPassword); + // 임시 비밀번호로 업데이트 + user.setUserPw(passwordEncoder.encode(tempPassword)); + } catch (Exception e) { + System.out.println("Detailed error: " + e.getMessage()); + throw new RuntimeException("이메일 발송에 실패했습니다."); + } + + } + + @Override + @Transactional + public void updatePassword(PasswordUpdateRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String userId = authentication.getName(); + + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); + + // 기존 비밀번호 검증 + if (!passwordEncoder.matches(request.getCurrentPw(), user.getUserPw())) { + throw new IllegalArgumentException("기존 비밀번호가 일치하지 않습니다."); + } + + // 새 비밀번호 암호화 후 저장 + user.setUserPw(passwordEncoder.encode(request.getNewPw())); + } + + private void checkDuplicateUser(UserCreateRequest request) { + List errors = new ArrayList<>(); + if (userRepository.existsByUserId(request.getUserId())) { + errors.add("이미 사용 중인 아이디입니다."); + } + if (userRepository.existsByUserEmail(request.getUserEmail())) { + errors.add("이미 사용 중인 이메일입니다."); + } + if (userRepository.existsByUserNickname(request.getUserNickname())) { + errors.add("이미 사용 중인 닉네임입니다."); + } + + if (!errors.isEmpty()) { + throw new DuplicateUserException(String.join(", ", errors)); + } + } +} \ No newline at end of file