diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
index 8268975b..b5a23a02 100644
--- a/.idea/dbnavigator.xml
+++ b/.idea/dbnavigator.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/CategorizedAnswerController.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/CategorizedAnswerController.java
new file mode 100644
index 00000000..9f13d083
--- /dev/null
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/CategorizedAnswerController.java
@@ -0,0 +1,38 @@
+package com.web.baebaeBE.domain.categorized.answer.controller;
+
+import com.web.baebaeBE.domain.categorized.answer.controller.api.CategorizedAnswerApi;
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerRequest;
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerResponse;
+import com.web.baebaeBE.domain.categorized.answer.service.CategorizedAnswerService;
+import com.web.baebaeBE.global.authorization.annotation.AuthorizationAnswer;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/categorizedAnswer")
+public class CategorizedAnswerController implements CategorizedAnswerApi {
+
+ private final CategorizedAnswerService categorizedAnswerService;
+
+
+ @GetMapping("{answerId}")
+ @AuthorizationAnswer
+ public ResponseEntity> getCategoriesByAnswerId(
+ @PathVariable Long answerId
+ ) {
+ return ResponseEntity.ok(categorizedAnswerService.getCategoriesByAnswerId(answerId));
+ }
+
+ @PutMapping("/{answerId}")
+ @AuthorizationAnswer
+ public ResponseEntity updateCategoriesByAnswerId(@PathVariable Long answerId, @RequestBody CategorizedAnswerRequest.CategoryList categoryIds) {
+ categorizedAnswerService.updateCategoriesByAnswerId(answerId, categoryIds);
+ return ResponseEntity.noContent().build();
+ }
+
+
+}
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/api/CategorizedAnswerApi.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/api/CategorizedAnswerApi.java
new file mode 100644
index 00000000..628d12ab
--- /dev/null
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/controller/api/CategorizedAnswerApi.java
@@ -0,0 +1,56 @@
+package com.web.baebaeBE.domain.categorized.answer.controller.api;
+
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerRequest;
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.util.List;
+
+@Tag(name = "CategorizedAnswer", description = "카테고리 내부의 피드에 관련된 API")
+public interface CategorizedAnswerApi {
+
+ @Operation(summary = "피드가 속한 카테고리 조회",
+ description = "Answer ID를 받아 해당 Answer에 연결된 모든 카테고리를 조회합니다.",
+ security = @SecurityRequirement(name = "bearerAuth")
+ )
+ @Parameter(
+ in = ParameterIn.HEADER,
+ name = "Authorization", required = true,
+ schema = @Schema(type = "string"),
+ description = "Bearer [Access 토큰]")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "조회 성공")
+ })
+ @GetMapping("{answerId}")
+ ResponseEntity> getCategoriesByAnswerId(
+ @Parameter(description = "Answer의 ID", required = true) @PathVariable Long answerId
+ );
+
+
+ @Operation(summary = "피드가 속한 카테고리 수정",
+ description = "Answer ID와 Category ID 리스트를 받아 피드가 속할 카테고리 정보를 수정합니다.",
+ security = @SecurityRequirement(name = "bearerAuth")
+ )
+ @Parameter(
+ in = ParameterIn.HEADER,
+ name = "Authorization", required = true,
+ schema = @Schema(type = "string"),
+ description = "Bearer [Access 토큰]")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "수정 성공")
+ })
+ @PutMapping("/{answerId}")
+ public ResponseEntity updateCategoriesByAnswerId(@PathVariable Long answerId, @RequestBody CategorizedAnswerRequest.CategoryList categoryIds) ;
+}
\ No newline at end of file
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerRequest.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerRequest.java
new file mode 100644
index 00000000..7edbaf63
--- /dev/null
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerRequest.java
@@ -0,0 +1,20 @@
+package com.web.baebaeBE.domain.categorized.answer.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+
+public class CategorizedAnswerRequest {
+
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CategoryList{
+ private List categoryIds;
+
+ }
+}
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerResponse.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerResponse.java
new file mode 100644
index 00000000..2a0fbc99
--- /dev/null
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/dto/CategorizedAnswerResponse.java
@@ -0,0 +1,31 @@
+package com.web.baebaeBE.domain.categorized.answer.dto;
+
+import com.web.baebaeBE.domain.category.dto.CategoryResponse;
+import com.web.baebaeBE.domain.category.entity.Category;
+import lombok.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CategorizedAnswerResponse {
+
+ @Getter
+ @Setter
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CategoryInformationResponse {
+ private Long categoryId;
+ private String categoryName;
+ private String categoryImage;
+
+ public static CategorizedAnswerResponse.CategoryInformationResponse of(Category category) {
+ return CategorizedAnswerResponse.CategoryInformationResponse.builder()
+ .categoryId(category.getId())
+ .categoryName(category.getCategoryName())
+ .categoryImage(category.getCategoryImage())
+ .build();
+ }
+ }
+
+}
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java
index 33a365e8..43735700 100644
--- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/entity/CategorizedAnswer.java
@@ -4,6 +4,8 @@
import com.web.baebaeBE.domain.category.entity.Category;
import jakarta.persistence.*;
import lombok.*;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
import java.time.LocalDateTime;
@@ -18,14 +20,17 @@ public class CategorizedAnswer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "categorized_answer_id")
private Long id;
@ManyToOne
@JoinColumn(name = "category_id", nullable = false)
+ @OnDelete(action = OnDeleteAction.CASCADE)
private Category category;
@ManyToOne
@JoinColumn(name = "answer_id", nullable = false)
+ @OnDelete(action = OnDeleteAction.CASCADE)
private Answer answer;
}
\ No newline at end of file
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/repository/CategorizedAnswerRepository.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/repository/CategorizedAnswerRepository.java
index 9937f5ee..22617964 100644
--- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/repository/CategorizedAnswerRepository.java
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/repository/CategorizedAnswerRepository.java
@@ -7,8 +7,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
+import java.util.Optional;
public interface CategorizedAnswerRepository extends JpaRepository {
Page findByAnswer_Member_IdAndCategory_Id(Long memberId, Long categoryId, Pageable pageable);
Page findByAnswer_Member_Id(Long memberId, Pageable pageable);
+ List findAllByAnswerId(Long answerId);
}
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/service/CategorizedAnswerService.java b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/service/CategorizedAnswerService.java
index 83287077..75ab5171 100644
--- a/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/service/CategorizedAnswerService.java
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/domain/categorized/answer/service/CategorizedAnswerService.java
@@ -1,9 +1,18 @@
package com.web.baebaeBE.domain.categorized.answer.service;
import com.web.baebaeBE.domain.answer.dto.AnswerDetailResponse;
+import com.web.baebaeBE.domain.answer.entity.Answer;
+import com.web.baebaeBE.domain.answer.exception.AnswerError;
import com.web.baebaeBE.domain.answer.repository.AnswerMapper;
+import com.web.baebaeBE.domain.answer.repository.AnswerRepository;
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerRequest;
+import com.web.baebaeBE.domain.categorized.answer.dto.CategorizedAnswerResponse;
import com.web.baebaeBE.domain.categorized.answer.entity.CategorizedAnswer;
import com.web.baebaeBE.domain.categorized.answer.repository.CategorizedAnswerRepository;
+import com.web.baebaeBE.domain.category.entity.Category;
+import com.web.baebaeBE.domain.category.exception.CategoryException;
+import com.web.baebaeBE.domain.category.repository.CategoryRepository;
+import com.web.baebaeBE.global.error.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
@@ -11,6 +20,11 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
+
@Service
@Slf4j
@RequiredArgsConstructor
@@ -18,6 +32,25 @@
public class CategorizedAnswerService {
private final CategorizedAnswerRepository categorizedAnswerRepository;
private final AnswerMapper answerMapper;
+ private final CategoryRepository categoryRepository;
+ private final AnswerRepository answerRepository;
+
+ public List getCategoriesByAnswerId(Long answerId) {
+ List categorizedAnswers = categorizedAnswerRepository.findAllByAnswerId(answerId);
+
+ for(CategorizedAnswer categorizedAnswer : categorizedAnswers) {
+ log.info("categorizedAnswer : {}", categorizedAnswer.getCategory().getId());
+ }
+ List categoryInformationResponses = new ArrayList<>();
+
+ for(CategorizedAnswer categorizedAnswer : categorizedAnswers) {
+ categoryInformationResponses.add
+ (CategorizedAnswerResponse.CategoryInformationResponse.of(categorizedAnswer.getCategory()));
+ }
+
+ return categoryInformationResponses;
+ }
+
public Page getAnswersByMemberAndCategory(Long memberId, Long categoryId, Pageable pageable) {
Page categorizedAnswers;
@@ -27,6 +60,49 @@ public Page getAnswersByMemberAndCategory(Long memberId, L
categorizedAnswers = categorizedAnswerRepository.findByAnswer_Member_IdAndCategory_Id(memberId, categoryId, pageable);
return categorizedAnswers.map(categorizedAnswer -> answerMapper.toDomain(categorizedAnswer.getAnswer()));
+ }
+
+
+ @Transactional
+ public void updateCategoriesByAnswerId(Long answerId, CategorizedAnswerRequest.CategoryList categoryList) {
+ List existingCategorizedAnswers = categorizedAnswerRepository.findAllByAnswerId(answerId);
+ List existingCategoryIds = existingCategorizedAnswers.stream()
+ .map(categorizedAnswer -> categorizedAnswer.getCategory().getId())
+ .collect(Collectors.toList());
+
+ List newCategoryIds = categoryList.getCategoryIds();
+
+ // 추가해야 할 카테고리 ID 계산
+ List toAddCategoryIds = newCategoryIds.stream()
+ .filter(categoryId -> !existingCategoryIds.contains(categoryId))
+ .collect(Collectors.toList());
+
+ // 삭제해야 할 카테고리 ID 계산
+ List toRemoveCategoryIds = existingCategoryIds.stream()
+ .filter(categoryId -> !newCategoryIds.contains(categoryId))
+ .collect(Collectors.toList());
+
+ Answer answer = answerRepository.findByAnswerId(answerId)
+ .orElseThrow(() -> new BusinessException(AnswerError.NO_EXIST_ANSWER));
+
+ // 새로운 카테고리 ID에 대해 반복하여 CategorizedAnswer를 생성하고 저장
+ for (Long categoryId : toAddCategoryIds) {
+ Category category = categoryRepository.findById(categoryId)
+ .orElseThrow(() -> new BusinessException(CategoryException.CATEGORY_NOT_FOUND));
+ CategorizedAnswer newCategorizedAnswer = CategorizedAnswer.builder()
+ .category(category)
+ .answer(answer)
+ .build();
+ categorizedAnswerRepository.save(newCategorizedAnswer);
+ }
+ // 삭제해야 할 카테고리 ID에 대해 반복하여 해당 CategorizedAnswer를 삭제
+ for (Long categoryId : toRemoveCategoryIds) {
+ CategorizedAnswer categorizedAnswerToRemove = existingCategorizedAnswers.stream()
+ .filter(categorizedAnswer -> categorizedAnswer.getCategory().getId().equals(categoryId))
+ .findFirst()
+ .orElseThrow(() -> new NoSuchElementException("No CategorizedAnswer found for the given category id"));
+ categorizedAnswerRepository.delete(categorizedAnswerToRemove);
+ }
}
}
\ No newline at end of file
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/global/config/SecurityConfig.java b/baebae-BE/src/main/java/com/web/baebaeBE/global/config/SecurityConfig.java
index ddc44bd5..ba198413 100644
--- a/baebae-BE/src/main/java/com/web/baebaeBE/global/config/SecurityConfig.java
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/global/config/SecurityConfig.java
@@ -5,6 +5,9 @@
import com.web.baebaeBE.domain.login.service.OAuth2UserCustomService;
import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
@@ -22,8 +25,10 @@
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import static com.web.baebaeBE.global.security.SecurityConstants.NO_AUTH_LIST;
import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;
@@ -34,8 +39,9 @@
@EnableMethodSecurity
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
- private final OAuth2UserCustomService oAuth2UserCustomService;
+ @Value("${allowed.origins}")
+ private String[] allowedOrigins;
// Spring Security 제외 목록 (인증,인가 검사 제외)
@@ -84,7 +90,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173", "https://api.flipit.co.kr", "https://www.flipit.co.kr", "https://flipit.co.kr")); // 허용할 오리진 설정
+ configuration.setAllowedOrigins(List.of(allowedOrigins)); // 허용할 오리진 설정
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH")); // 허용할 HTTP 메소드 설정
configuration.setAllowedHeaders(Collections.singletonList("*")); // 허용할 HTTP 헤더 설정
configuration.setAllowCredentials(true); // 쿠키를 포함한 요청 허용 설정
diff --git a/baebae-BE/src/main/java/com/web/baebaeBE/global/config/WebConfig.java b/baebae-BE/src/main/java/com/web/baebaeBE/global/config/WebConfig.java
index 54737547..f4a29b42 100644
--- a/baebae-BE/src/main/java/com/web/baebaeBE/global/config/WebConfig.java
+++ b/baebae-BE/src/main/java/com/web/baebaeBE/global/config/WebConfig.java
@@ -1,15 +1,19 @@
package com.web.baebaeBE.global.config;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
+ @Value("${allowed.origins}")
+ private String[] allowedOrigins;
+
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
- .allowedOrigins("http://localhost:5173", "https://api.flipit.co.kr", "https://www.flipit.co.kr", "https://flipit.co.kr")
+ .allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*") // 모든 헤더 허용
.allowCredentials(true)