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 extends GrantedAuthority> 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 extends GrantedAuthority> authorities = authentication.getAuthorities();
+ Iterator extends GrantedAuthority> 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