diff --git a/src/main/java/com/codiary/backend/BackendApplication.java b/src/main/java/com/codiary/backend/BackendApplication.java index 865c3d0f..fd170659 100644 --- a/src/main/java/com/codiary/backend/BackendApplication.java +++ b/src/main/java/com/codiary/backend/BackendApplication.java @@ -4,10 +4,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.bind.annotation.CrossOrigin; @SpringBootApplication @EnableJpaAuditing @EnableScheduling +@CrossOrigin(origins = {"http://localhost:3000", "https://api.codiary.site", "https://www.codiary.site"}) public class BackendApplication { public static void main(String[] args) { diff --git a/src/main/java/com/codiary/backend/domain/category/controller/CategoryController.java b/src/main/java/com/codiary/backend/domain/category/controller/CategoryController.java index 204fae22..4315ae09 100644 --- a/src/main/java/com/codiary/backend/domain/category/controller/CategoryController.java +++ b/src/main/java/com/codiary/backend/domain/category/controller/CategoryController.java @@ -2,6 +2,7 @@ import com.codiary.backend.domain.category.converter.CategoryConverter; import com.codiary.backend.domain.category.dto.CategoryResponseDTO; +import com.codiary.backend.domain.category.entity.Category; import com.codiary.backend.domain.category.service.CategoryService; import com.codiary.backend.domain.member.entity.MemberCategory; import com.codiary.backend.domain.member.security.CustomMemberDetails; @@ -43,4 +44,15 @@ public ApiResponse deleteCategory(@PathVariable("member_category_id") Lo categoryService.deleteCategory(memberCategoryId, customMemberDetails.getId()); return ApiResponse.onSuccess(SuccessStatus.CATEGORY_OK, "카테고리 삭제가 완료되었습니다."); } + + @Operation(summary = "카테고리 검색", description = "카테고리를 검색합니다.") + @GetMapping("/search") + public ApiResponse> searchCategory( + @RequestParam(value = "category", defaultValue = "", required = false) String category, + @AuthenticationPrincipal CustomMemberDetails memberDetails + ) { + Long memberId = memberDetails.getId(); + List categories = categoryService.searchCategory(memberId, category); + return ApiResponse.onSuccess(SuccessStatus.POST_OK, CategoryConverter.toSimpleCategoryListDTO(categories)); + } } diff --git a/src/main/java/com/codiary/backend/domain/category/converter/CategoryConverter.java b/src/main/java/com/codiary/backend/domain/category/converter/CategoryConverter.java index 8408a933..9576b9f4 100644 --- a/src/main/java/com/codiary/backend/domain/category/converter/CategoryConverter.java +++ b/src/main/java/com/codiary/backend/domain/category/converter/CategoryConverter.java @@ -1,6 +1,7 @@ package com.codiary.backend.domain.category.converter; import com.codiary.backend.domain.category.dto.CategoryResponseDTO; +import com.codiary.backend.domain.category.entity.Category; import com.codiary.backend.domain.member.entity.MemberCategory; import java.util.List; @@ -19,4 +20,17 @@ public static List toMemberCategoryListDT .map(CategoryConverter::toMemberCategoryDTO) .toList(); } + + public static CategoryResponseDTO.SimpleCategoryDTO toSimpleCategoryDTO(Category category) { + return CategoryResponseDTO.SimpleCategoryDTO.builder() + .categoryId(category.getCategoryId()) + .categoryName(category.getName()) + .build(); + } + + public static List toSimpleCategoryListDTO(List memberCategoryList) { + return memberCategoryList.stream() + .map(CategoryConverter::toSimpleCategoryDTO) + .toList(); + } } diff --git a/src/main/java/com/codiary/backend/domain/category/dto/CategoryResponseDTO.java b/src/main/java/com/codiary/backend/domain/category/dto/CategoryResponseDTO.java index 434055bc..faddd708 100644 --- a/src/main/java/com/codiary/backend/domain/category/dto/CategoryResponseDTO.java +++ b/src/main/java/com/codiary/backend/domain/category/dto/CategoryResponseDTO.java @@ -15,4 +15,12 @@ public record MemberCategoryDTO( String categoryName, Long categoryId ) {} + + @Builder + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SimpleCategoryDTO( + Long categoryId, + String categoryName + ) {} } diff --git a/src/main/java/com/codiary/backend/domain/category/repository/CategoryRepository.java b/src/main/java/com/codiary/backend/domain/category/repository/CategoryRepository.java index 8a88ee46..3afbb7ef 100644 --- a/src/main/java/com/codiary/backend/domain/category/repository/CategoryRepository.java +++ b/src/main/java/com/codiary/backend/domain/category/repository/CategoryRepository.java @@ -3,8 +3,10 @@ import com.codiary.backend.domain.category.entity.Category; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface CategoryRepository extends JpaRepository { Optional findByName(String name); + List findByNameContaining(String categoryName); } diff --git a/src/main/java/com/codiary/backend/domain/category/service/CategoryService.java b/src/main/java/com/codiary/backend/domain/category/service/CategoryService.java index 8bb6a34a..980cfc44 100644 --- a/src/main/java/com/codiary/backend/domain/category/service/CategoryService.java +++ b/src/main/java/com/codiary/backend/domain/category/service/CategoryService.java @@ -1,5 +1,6 @@ package com.codiary.backend.domain.category.service; +import com.codiary.backend.domain.category.dto.CategoryResponseDTO; import com.codiary.backend.domain.category.entity.Category; import com.codiary.backend.domain.category.repository.CategoryRepository; import com.codiary.backend.domain.member.entity.Member; @@ -82,4 +83,12 @@ public Category addCategory(Post post, String categoryName) { .name(categoryName) .build())); } + + public List searchCategory(Long memberId, String category) { + //validation: 멤버인지 확인 + memberRepository.findById(memberId) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND)); + + return categoryRepository.findByNameContaining(category); + } } diff --git a/src/main/java/com/codiary/backend/domain/post/controller/PostController.java b/src/main/java/com/codiary/backend/domain/post/controller/PostController.java index 72aabe2d..db8bab27 100644 --- a/src/main/java/com/codiary/backend/domain/post/controller/PostController.java +++ b/src/main/java/com/codiary/backend/domain/post/controller/PostController.java @@ -1,6 +1,7 @@ package com.codiary.backend.domain.post.controller; import com.codiary.backend.domain.alert.service.AlertService; +import com.codiary.backend.domain.category.dto.CategoryResponseDTO; import com.codiary.backend.domain.member.entity.Member; import com.codiary.backend.domain.member.security.CustomMemberDetails; import com.codiary.backend.domain.member.service.MemberCommandService; @@ -230,7 +231,6 @@ public ApiResponse> searchPost( return ApiResponse.onSuccess(SuccessStatus.POST_OK, PostConverter.toPostListResponseDto(postPage)); } - // 게시글의 카테고리 설정 및 변경 @PatchMapping("/category/{postId}") @Operation(summary = "게시글의 카테고리 설정 및 변경 API", description = "게시글의 카테고리를 설정 및 변경합니다.") diff --git a/src/main/java/com/codiary/backend/domain/post/repository/PostRepositoryImpl.java b/src/main/java/com/codiary/backend/domain/post/repository/PostRepositoryImpl.java index fc5654ab..dd5db6e1 100644 --- a/src/main/java/com/codiary/backend/domain/post/repository/PostRepositoryImpl.java +++ b/src/main/java/com/codiary/backend/domain/post/repository/PostRepositoryImpl.java @@ -49,6 +49,8 @@ public Page searchPost(Long memberId, String keyword, Pageable pageable) { .from(post) .leftJoin(post.member, member) .leftJoin(member.image, memberImage) + .leftJoin(post.team, team).fetchJoin() + .orderBy(getOrderBy(pageable.getSort())) .where(keywordEq(keyword)) // Full-Text Search 조건 .where(canAccess(memberId)) .offset(pageable.getOffset()) @@ -59,6 +61,7 @@ public Page searchPost(Long memberId, String keyword, Pageable pageable) { .select(post.count()) .from(post) .where(keywordEq(keyword)) // Full-Text Search 조건 + .where(canAccess(memberId)) .fetchOne(); return new PageImpl<>(postList, pageable, total); @@ -118,11 +121,12 @@ public Page getLatestPostsOfFollowings(Long memberId, Pageable pageable) { @Override public Page getPostList(Pageable pageable) { - OrderSpecifier[] orderSpecifiers = createPostListOrderSpecifier(pageable.getSort()); + OrderSpecifier[] orderSpecifiers = getOrderBy(pageable.getSort()); List posts = queryFactory .selectDistinct(post) .from(post) + .join(post.team, team).fetchJoin() .where(post.postAccess.eq(PostAccess.ENTIRE)) .orderBy(orderSpecifiers) .offset(pageable.getOffset()) @@ -138,7 +142,7 @@ public Page getPostList(Pageable pageable) { return new PageImpl<>(posts, pageable, total); } - private OrderSpecifier[] createPostListOrderSpecifier(Sort sort) { + private OrderSpecifier[] getOrderBy(Sort sort) { List orderSpecifiers = new ArrayList<>(); for (Sort.Order order : sort) { @@ -174,7 +178,7 @@ public Page getPostsByCategoryId(Long memberId, Long categoryId, Pageable .selectDistinct(post) .from(post) .where(post.categoriesList.any().categoryId.eq(categoryId).and(canAccess(memberId))) - .orderBy(createPostListOrderSpecifier(pageable.getSort())) + .orderBy(getOrderBy(pageable.getSort())) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); diff --git a/src/main/java/com/codiary/backend/domain/team/controller/TeamController.java b/src/main/java/com/codiary/backend/domain/team/controller/TeamController.java index 04703ccf..f5a8e344 100644 --- a/src/main/java/com/codiary/backend/domain/team/controller/TeamController.java +++ b/src/main/java/com/codiary/backend/domain/team/controller/TeamController.java @@ -14,6 +14,7 @@ import com.codiary.backend.global.apiPayload.ApiResponse; import com.codiary.backend.global.apiPayload.code.status.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; @@ -157,4 +158,14 @@ public ApiResponse deleteTeamBannerImage( String response = teamService.deleteTeamBannerImage(teamId, memberId); return ApiResponse.onSuccess(SuccessStatus.TEAM_OK, response); } + + + @GetMapping("/list") + @Operation(summary = "팀 리스트 조회 API", description = "팀 설정을 위한 팀 전체 리스트를 조회합니다.") + public ApiResponse findTeams(){ + List teams = teamService.getTeams(); + return ApiResponse.onSuccess(SuccessStatus.TEAM_OK, TeamConverter.toTeamPreviewListDTO(teams)); + } + + } diff --git a/src/main/java/com/codiary/backend/domain/team/converter/TeamConverter.java b/src/main/java/com/codiary/backend/domain/team/converter/TeamConverter.java index 42697161..fedca4e2 100644 --- a/src/main/java/com/codiary/backend/domain/team/converter/TeamConverter.java +++ b/src/main/java/com/codiary/backend/domain/team/converter/TeamConverter.java @@ -9,6 +9,7 @@ import com.codiary.backend.domain.team.entity.TeamProfileImage; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class TeamConverter { public static TeamResponseDTO.TeamDTO toTeamResponseDto(Team team) { @@ -96,4 +97,21 @@ public static TeamResponseDTO.TeamImageDTO toTeamImageResponseDTO(TeamBannerImag .url(bannerImage.getImageUrl()) .build(); } + + public static TeamResponseDTO.TeamPreviewDTO toTeamPreviewDTO(Team team) { + return TeamResponseDTO.TeamPreviewDTO.builder() + .teamId(team.getTeamId()) + .teamName(team.getName()) + .build(); + } + + public static TeamResponseDTO.TeamPreviewListDTO toTeamPreviewListDTO(List teamList) { + List teamPreviewDTOList = IntStream.range(0, teamList.size()) + .mapToObj(i->toTeamPreviewDTO(teamList.get(i))) + .collect(Collectors.toList()); + return TeamResponseDTO.TeamPreviewListDTO.builder() + .teams(teamPreviewDTOList) + .build(); + } + } diff --git a/src/main/java/com/codiary/backend/domain/team/dto/response/TeamResponseDTO.java b/src/main/java/com/codiary/backend/domain/team/dto/response/TeamResponseDTO.java index f1aa9d56..96a890d5 100644 --- a/src/main/java/com/codiary/backend/domain/team/dto/response/TeamResponseDTO.java +++ b/src/main/java/com/codiary/backend/domain/team/dto/response/TeamResponseDTO.java @@ -7,7 +7,10 @@ import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; public class TeamResponseDTO { @@ -88,4 +91,20 @@ public record TeamMemberDTO( public record TeamImageDTO( String url) { } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @Builder + public record TeamPreviewDTO ( + Long teamId, + String teamName + ){} + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + @Builder + public record TeamPreviewListDTO ( + List teams + ){} } diff --git a/src/main/java/com/codiary/backend/domain/team/service/TeamService.java b/src/main/java/com/codiary/backend/domain/team/service/TeamService.java index ade45456..d68cfef1 100644 --- a/src/main/java/com/codiary/backend/domain/team/service/TeamService.java +++ b/src/main/java/com/codiary/backend/domain/team/service/TeamService.java @@ -18,6 +18,7 @@ import com.codiary.backend.global.common.uuid.UuidRepository; import com.codiary.backend.global.s3.AmazonS3Manager; import java.util.ArrayList; +import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -250,4 +251,9 @@ public String deleteTeamBannerImage(Long teamId, Long memberId) { // response: 성공을 반환 return "성공적으로 삭제되었습니다!"; } + + public List getTeams() { + return teamRepository.findAllByOrderByTeamIdDesc(); + } + } diff --git a/src/main/java/com/codiary/backend/global/config/SecurityConfig.java b/src/main/java/com/codiary/backend/global/config/SecurityConfig.java index 9deb1023..00847f72 100644 --- a/src/main/java/com/codiary/backend/global/config/SecurityConfig.java +++ b/src/main/java/com/codiary/backend/global/config/SecurityConfig.java @@ -7,8 +7,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -42,16 +44,18 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // // return http.build(); return http - .httpBasic(httpBasic -> httpBasic.disable()) - .csrf(csrf -> csrf.disable()) + .httpBasic(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) + .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests( authorize -> authorize + .requestMatchers("/error").permitAll() // Member 관련 접근 .requestMatchers("/api/v2/auth/**").permitAll() - .requestMatchers("/api/v2/oauth/**").permitAll() + .requestMatchers("/api/v2/member/**").permitAll() // Post 관련 접근 + .requestMatchers("/api/v2/post/**").permitAll() .requestMatchers("/api/v2/post/search").permitAll() .requestMatchers("/api/v2/post","/api/v2/post/{postId}", "/api/v2/post/visibility/{postId}", "/api/v2/post/coauthor/{postId}", "/api/v2/post/category/{postId}").permitAll() .requestMatchers("/api/v2/post/title/paging", "/api/v2/post/team/{teamId}/{postId}", "/api/v2/post/team/{teamId}/member/{memberId}/paging", "/api/v2/post/project/{projectId}/team/{teamId}/paging").permitAll() @@ -61,22 +65,28 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/v2/post/latest", "api/v2/post/following").permitAll() // 전체 최신글 조회 & 팔로잉 멤버들의 최신글 조회 // Comment 관련 접근 + .requestMatchers("api/v2/comment/**").permitAll() .requestMatchers("/api/v2/post/{post_id}/comment").permitAll() // 게시물에 댓글 생성, 조회 .requestMatchers("/api/v2/comment/{comment_id}/reply").permitAll() // 댓글에 대댓글 생성, 조회 .requestMatchers("/api/v2/comment/{comment_id}").permitAll() // 댓글 & 대댓글 수정 삭제 // Team 관련 접근 + .requestMatchers("/api/v2/team/**").permitAll() .requestMatchers("/api/v2/team/team_member").permitAll() .requestMatchers("/api/v2/team/{team_id}/profile_image").permitAll() .requestMatchers("/api/v2/team/{team_id}/banner_image").permitAll() - // Bookmark 관련 접근 - // Calendar 관련 접근 + + // follow 관련 접근 + .requestMatchers("/api/v2/follow/**").permitAll() + // Project 관련 접근 + .requestMatchers("/api/v2/project/**").permitAll() // Category 관련 접근 + .requestMatchers("/api/v2/category/**").permitAll() // 알람 관련 접근 - .requestMatchers("/api/v2/connect", "/api/v2/disconnect").permitAll() + .requestMatchers("/api/v2/connect", "/api/v2/disconnect", "/api/v2/alert/**").permitAll() // 기타 관련 접근 - .requestMatchers("/", "/api-docs/**", "/api-docs/swagger-config/*", "/swagger-ui/*", "/swagger-ui/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/**", "/api-docs/**", "/api-docs/swagger-config/*", "/swagger-ui/*", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), EmailPasswordAuthenticationFilter.class).build(); @@ -93,8 +103,10 @@ public PasswordEncoder passwordEncoder() { CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.setAllowedOriginPatterns(List.of("*", "http://localhost:3000", "https://www.codiary.site")); - config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codiary.site")); + config.setAllowedOriginPatterns(List.of("http://localhost:3000", "https://www.codiary.site", "https://codiary.site", "https://api.codiary.site", + "http://localhost:3000/", "https://www.codiary.site/", "https://codiary.site/", "https://api.codiary.site/")); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://www.codiary.site", "https://codiary.site", "https://api.codiary.site", + "http://localhost:3000/", "https://www.codiary.site/", "https://codiary.site/", "https://api.codiary.site/")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setExposedHeaders(List.of("*"));