Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2d01738
[SPOT-254][MODIFY] Add LikeOrScrap To getPost
dvlp-sy Apr 6, 2025
db46ce2
[SPOT-254][TEST] Update Test for getPost
dvlp-sy Apr 6, 2025
83b4c75
[SPOT-254][MERGE] 스터디 게시글 조회 시 좋아요/스크랩 확인 로직 추가
dvlp-sy Apr 6, 2025
1cdb9b3
[SPOT-255][MODIFY] 기존 이미지 재사용 위한 existingImage 필드 RequestDTO에 추가
msk226 Apr 7, 2025
3ea329e
[SPOT-255][MODIFY] 이미지 업데이트 로직 수정
msk226 Apr 7, 2025
4691b20
[SPOT-255][MODIFY] 관련 API 명세 추가
msk226 Apr 7, 2025
c90226f
[SPOT-257][MODIFY] 카카오 소셜 로그인 시, `isSpotMember` 체크리스트 입력 유무로 결정하도록 로직 변경
msk226 Apr 7, 2025
ea0c6b6
[SPOT-257][MODIFY] 네이버 소셜 로그인 시, `isSpotMember` 체크리스트 입력 유무로 결정하도록 로직 변경
msk226 Apr 7, 2025
be15a1f
[MERGE] Merge pull request #395 from SPOTeam/SPOT-255/modify
msk226 Apr 7, 2025
2bfe636
[SPOT-256][FEAT] 테마 별 조회 위한 Request DTO 재정의
msk226 Apr 7, 2025
4d9e7c7
[SPOT-256][FEAT] 컨트롤러에 DTO 변경 사항 반영
msk226 Apr 7, 2025
2f3277e
[SPOT-256][FEAT] 서비스 코드에 DTO 변경 사항 반영 및 관련 검색 조건 제작 메서드 구현
msk226 Apr 7, 2025
315afde
[SPOT-256][FEAT] 쿼리에 테마 별 조회 위한 필터링 조건 추가
msk226 Apr 7, 2025
773304d
[SPOT-256][FEAT] 해당 변경 사항으로 인한 테스트 코드 수정
msk226 Apr 7, 2025
46027a6
[MERGE] Merge pull request #397 from SPOTeam/SPOT-256/feat
msk226 Apr 7, 2025
b82c0a7
[MERGE] Merge pull request #396 from SPOTeam/SPOT-257/modify
msk226 Apr 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/main/java/com/example/spot/domain/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class Post extends BaseEntity {
@JoinColumn(name = "member_id")
private Member member;

public void edit(PostUpdateRequest request, List<String> images) {
public void edit(PostUpdateRequest request, List<String> images, String existingImage) {
if (StringUtils.hasText(request.getTitle())) {
this.title = request.getTitle();
}
Expand All @@ -78,13 +78,25 @@ public void edit(PostUpdateRequest request, List<String> images) {

this.isAnonymous = request.isAnonymous();

this.image = (images != null && !images.isEmpty() && StringUtils.hasText(images.get(0)))
? images.get(0)
: null;
updateImage(images, existingImage);

if (request.getType() != null) {
this.board = request.getType();
}

if (request.getType() != null) {
this.board = request.getType();
}
}

private void updateImage(List<String> images, String existingImage) {
if (StringUtils.hasText(existingImage)) {
this.image = existingImage;
} else if (images != null && !images.isEmpty() && StringUtils.hasText(images.get(0))) {
this.image = images.get(0);
} else {
this.image = null;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.example.spot.domain.enums.Status;
import com.example.spot.domain.enums.StudySortBy;
import com.example.spot.domain.enums.StudyState;
import com.example.spot.domain.enums.Theme;
import com.example.spot.domain.enums.ThemeType;
import com.example.spot.domain.mapping.MemberStudy;
import com.example.spot.domain.mapping.QMemberStudy;
import com.example.spot.domain.mapping.QRegionStudy;
Expand Down Expand Up @@ -424,6 +426,10 @@ private static void getConditions(Map<String, Object> search, QStudy study,
if (search.get("fee") != null) {
builder.and(study.fee.loe((Integer) search.get("fee")));
}
if (search.get("themeTypes") != null) {
List<ThemeType> themeTypes = (List<ThemeType>) search.get("themeTypes");
builder.and(study.studyThemes.any().theme.studyTheme.in(themeTypes));
}
}

}
18 changes: 18 additions & 0 deletions src/main/java/com/example/spot/service/auth/AuthServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import com.example.spot.domain.Member;
import com.example.spot.domain.auth.RsaKey;
import com.example.spot.repository.MemberStudyRepository;
import com.example.spot.repository.MemberThemeRepository;
import com.example.spot.repository.PreferredRegionRepository;
import com.example.spot.repository.StudyReasonRepository;
import com.example.spot.web.dto.rsa.Rsa;
import com.example.spot.domain.auth.RefreshToken;
import com.example.spot.domain.auth.VerificationCode;
Expand Down Expand Up @@ -67,6 +70,10 @@ public class AuthServiceImpl implements AuthService{
private final RefreshTokenRepository refreshTokenRepository;
private final VerificationCodeRepository verificationCodeRepository;

private final MemberThemeRepository memberThemeRepository;
private final PreferredRegionRepository preferredRegionRepository;
private final StudyReasonRepository studyReasonRepository;

private final MailService mailService;
private final NaverOAuthService naverOAuthService;

Expand Down Expand Up @@ -263,6 +270,10 @@ private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO res
Member member = memberRepository.findByEmailAndLoginType(email, LoginType.NAVER)
.orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND));

if (!isMemberExistsByCheckList(member)) {
isSpotMember = Boolean.FALSE;
}

// 로그인을 위한 토큰 발급
TokenDTO token = jwtTokenProvider.createToken(member.getId());
saveRefreshToken(member, token);
Expand All @@ -277,6 +288,13 @@ private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO res
return SocialLoginSignInDTO.toDTO(isSpotMember, signInDTO);
}

public boolean isMemberExistsByCheckList(Member member) {
Long memberId = member.getId();
return memberThemeRepository.existsByMemberId(memberId) &&
preferredRegionRepository.existsByMemberId(memberId) &&
studyReasonRepository.existsByMemberId(memberId);
}

/**
* 현재 SPOT에 가입되어 있지 않은 회원에 한해 회원 정보를 생성하여 DB에 저장합니다.
* @param memberDTO : naverCallback을 바탕으로 생성된 프로필 객체
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ public MemberResponseDTO.SocialLoginSignInDTO signUpByKAKAO(String accessToken)

updateMemberProfileImage(member, kaKaoUser);

isSpotMember = true;
if (isMemberExistsByCheckList(member)) {
isSpotMember = true;
}

// JWT 토큰 생성
TokenDTO token = jwtTokenProvider.createToken(member.getId());

Expand Down Expand Up @@ -322,7 +325,9 @@ public SocialLoginSignInDTO signUpByKAKAOForTest(String code) throws JsonProcess

saveRefreshToken(member, token);

isSpotMember = true;
if (isMemberExistsByCheckList(member)) {
isSpotMember = true;
}

// 로그인 DTO 반환
MemberSignInDTO memberSignInDto = MemberSignInDTO.builder()
Expand Down Expand Up @@ -674,6 +679,14 @@ private Long parseUsernameToMemberId(String username) {
}
}

public boolean isMemberExistsByCheckList(Member member) {
Long memberId = member.getId();
return memberThemeRepository.existsByMemberId(memberId) &&
preferredRegionRepository.existsByMemberId(memberId) &&
studyReasonRepository.existsByMemberId(memberId);
}


@Override
@Transactional
public void save(Member member) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public PostCreateResponse updatePost(Long memberId, Long postId, PostUpdateReque
}

// 게시글 수정
post.edit(postUpdateRequest, getImageUrls(postUpdateRequest.getImage()));
post.edit(postUpdateRequest, getImageUrls(postUpdateRequest.getImage()), postUpdateRequest.getExistingImage());

// 수정된 게시글 정보 반환
return PostCreateResponse.toDTO(post);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import com.example.spot.domain.enums.StudySortBy;
import com.example.spot.domain.enums.ThemeType;
import com.example.spot.web.dto.search.SearchRequestDTO.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyWithThemeDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.HotKeywordDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.MyPageDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.StudyPreviewDTO;
Expand Down Expand Up @@ -41,16 +42,16 @@ StudyPreviewDTO findInterestStudiesByConditionsSpecific(Pageable pageable, Long
SearchRequestStudyDTO request, ThemeType theme, StudySortBy sortBy);

// 내 관심 지역 스터디 페이징 조회
StudyPreviewDTO findInterestRegionStudiesByConditionsAll(Pageable pageable, Long memberId,
SearchRequestStudyDTO request, StudySortBy sortBy);
StudyPreviewDTO findInterestRegionStudiesByConditionsAll(
Pageable pageable, Long memberId, SearchRequestStudyWithThemeDTO request, StudySortBy sortBy);

// 내 특정 관심 지역 스터디 페이징 조회
StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(Pageable pageable, Long memberId,
SearchRequestStudyDTO request, String regionCode, StudySortBy sortBy);
StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(
Pageable pageable, Long memberId, SearchRequestStudyWithThemeDTO request, String regionCode, StudySortBy sortBy);

// 모집 중 스터디 조회
StudyPreviewDTO findRecruitingStudiesByConditions(Pageable pageable,
SearchRequestStudyDTO request, StudySortBy sortBy);
StudyPreviewDTO findRecruitingStudiesByConditions(
Pageable pageable, SearchRequestStudyWithThemeDTO request, StudySortBy sortBy);

// 찜한 스터디 조회
StudyPreviewDTO findLikedStudies(Long memberId, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
import com.example.spot.repository.StudyThemeRepository;
import com.example.spot.repository.ThemeRepository;
import com.example.spot.security.utils.SecurityUtils;
import com.example.spot.web.dto.search.SearchRequestDTO.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyWithThemeDTO;
import com.example.spot.web.dto.search.SearchResponseDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.HotKeywordDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.MyPageDTO;
Expand Down Expand Up @@ -472,8 +473,8 @@ public StudyPreviewDTO findInterestStudiesByConditionsSpecific(Pageable pageable
*
*/
@Override
public StudyPreviewDTO findInterestRegionStudiesByConditionsAll(Pageable pageable,
Long memberId, SearchRequestStudyDTO request, StudySortBy sortBy) {
public StudyPreviewDTO findInterestRegionStudiesByConditionsAll(
Pageable pageable, Long memberId, SearchRequestStudyWithThemeDTO request, StudySortBy sortBy) {

// 회원이 참가하고 있는 스터디 ID 가져오기
List<Long> memberOngoingStudyIds = getOngoingStudyIds(memberId);
Expand All @@ -498,7 +499,7 @@ public StudyPreviewDTO findInterestRegionStudiesByConditionsAll(Pageable pageabl
throw new StudyHandler(ErrorStatus._STUDY_REGION_NOT_EXIST);

// 검색 조건 맵 생성
Map<String, Object> conditions = getSearchConditions(request);
Map<String, Object> conditions = getSearchConditionsWithTheme(request);


// 검색 조건에 맞는 스터디 갯수 조회
Expand Down Expand Up @@ -535,8 +536,8 @@ public StudyPreviewDTO findInterestRegionStudiesByConditionsAll(Pageable pageabl
*
*/
@Override
public StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(Pageable pageable,
Long memberId, SearchRequestStudyDTO request, String regionCode, StudySortBy sortBy) {
public StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(
Pageable pageable, Long memberId, SearchRequestStudyWithThemeDTO request, String regionCode, StudySortBy sortBy) {

// 회원이 참가하고 있는 스터디 ID 가져오기
List<Long> memberOngoingStudyIds = getOngoingStudyIds(memberId);
Expand Down Expand Up @@ -568,7 +569,7 @@ public StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(Pageable pa
throw new StudyHandler(ErrorStatus._STUDY_REGION_NOT_EXIST);

// 검색 조건 맵 생성
Map<String, Object> conditions = getSearchConditions(request);
Map<String, Object> conditions = getSearchConditionsWithTheme(request);

// 검색 조건에 맞는 스터디 갯수 조회
long totalElements = studyRepository.countStudyByConditionsAndRegionStudiesAndNotInIds(
Expand Down Expand Up @@ -598,11 +599,11 @@ public StudyPreviewDTO findInterestRegionStudiesByConditionsSpecific(Pageable pa
*
*/
@Override
public StudyPreviewDTO findRecruitingStudiesByConditions(Pageable pageable,
SearchRequestStudyDTO request, StudySortBy sortBy) {
public StudyPreviewDTO findRecruitingStudiesByConditions(
Pageable pageable, SearchRequestStudyWithThemeDTO request, StudySortBy sortBy) {

// 검색 조건 맵 생성
Map<String, Object> conditions = getSearchConditions(request);
Map<String, Object> conditions = getSearchConditionsWithTheme(request);

// 검색 조건(모집 중)에 맞는 스터디 조회
List<Study> studies = studyRepository.findRecruitingStudyByConditions(conditions,
Expand Down Expand Up @@ -884,6 +885,38 @@ private static Map<String, Object> getSearchConditions(SearchRequestStudyDTO req
return search;
}

/**
* 검색 조건을 입력 받아 검색 조건 맵을 생성하는 메서드입니다.
*
* @param request 검색 조건을 입력 받습니다.
*
* @return 검색 조건 맵을 반환합니다.
*
*/
private static Map<String, Object> getSearchConditionsWithTheme(SearchRequestStudyWithThemeDTO request) {
Map<String, Object> search = new HashMap<>();

if (request.getGender() != null)
search.put("gender", request.getGender());
if (request.getMinAge() != null)
search.put("minAge", request.getMinAge());
if (request.getMaxAge() != null)
search.put("maxAge", request.getMaxAge());
if (request.getIsOnline() != null)
search.put("isOnline", request.getIsOnline());
if (request.getHasFee() != null)
search.put("hasFee", request.getHasFee());
if (request.getFee() != null)
search.put("fee", request.getFee());

if (request.getThemeTypes() != null && !request.getThemeTypes().isEmpty()) {
search.put("themeTypes", request.getThemeTypes());
}

return search;
}


/**
* 스터디 목록을 DTO로 변환하는 메서드입니다.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface StudyPostQueryService {
StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, ThemeQuery themeQuery);

// 스터디 게시글 불러오기
StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId);
StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap);

// 스터디 게시글 댓글 목록 불러오기
StudyPostCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,15 @@ public StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long stu

/**
* 스터디 게시판의 특정 게시글을 조회하는 메서드입니다.
* @param studyId 게시글을 조회할 타겟 스터디의 아이디를 입력 받습니다.
* @param postId 조회할 타겟 게시글의 아이디를 입력 받습니다.
*
* @param studyId 게시글을 조회할 타겟 스터디의 아이디를 입력 받습니다.
* @param postId 조회할 타겟 게시글의 아이디를 입력 받습니다.
* @param likeOrScrap
* @return 스터디 게시글의 정보를 반환합니다.
*/
@Override
@Transactional(readOnly = false)
public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId) {
public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap) {

//=== Exception ===//
Long memberId = SecurityUtils.getCurrentUserId();
Expand All @@ -121,7 +123,12 @@ public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId) {
.orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND));

//=== Feature ===//
studyPost.plusHitNum();

// 조회수 증가는 일반 조회 시에만 실행
if (!likeOrScrap) {
studyPost.plusHitNum();
}

studyPost = studyPostRepository.save(studyPost);
memberRepository.save(member);
studyRepository.save(study);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ public ApiResponse<PostAnnouncementResponse> getPostAnnouncement() {
@Tag(name = "게시판", description = "게시판 관련 API")
@Operation(
summary = "[게시판] 게시글 수정 API",
description = "게시글 Id를 받아 게시글을 수정합니다.",
description = "게시글 Id를 받아 게시글을 수정합니다. existingImage는 기존 이미지 URL입니다. 수정할 이미지가 없을 경우 null로 보내주세요. 요청 시, 요청 타입은 Multipart/form-data로 보내야 합니다."
+ "\n" + "existingImage와 image 둘 중 하나만 보내주세요. 둘 다 보내면 기존 이미지로 덮어씌워집니다.",
security = @SecurityRequirement(name = "accessToken")
)
@PatchMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import com.example.spot.security.utils.SecurityUtils;
import com.example.spot.service.study.StudyCommandService;
import com.example.spot.service.study.StudyQueryService;
import com.example.spot.validation.annotation.ExistMember;
import com.example.spot.web.dto.search.SearchRequestDTO.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyDTO;
import com.example.spot.web.dto.search.SearchRequestStudyWithThemeDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.HotKeywordDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.MyPageDTO;
import com.example.spot.web.dto.search.SearchResponseDTO.StudyPreviewDTO;
Expand Down Expand Up @@ -206,7 +206,7 @@ public ApiResponse<StudyPreviewDTO> interestStudiesByConditionsSpecific(
@Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true)
@Parameter(name = "sortBy", description = "정렬 기준을 입력 받습니다.", required = true)
public ApiResponse<StudyPreviewDTO> interestRegionStudiesByConditionsAll(
@ModelAttribute @Valid SearchRequestStudyDTO searchRequestStudyDTO,
@ModelAttribute @Valid SearchRequestStudyWithThemeDTO searchRequestStudyDTO,
@RequestParam @Min(0) Integer page,
@RequestParam @Min(1) Integer size,
@RequestParam StudySortBy sortBy
Expand Down Expand Up @@ -240,7 +240,7 @@ public ApiResponse<StudyPreviewDTO> interestRegionStudiesByConditionsAll(
@Parameter(name = "sortBy", description = "정렬 기준을 입력 받습니다.", required = true)
public ApiResponse<StudyPreviewDTO> interestRegionStudiesByConditionsSpecific(
@RequestParam String regionCode,
@ModelAttribute @Valid SearchRequestStudyDTO searchRequestStudyDTO,
@ModelAttribute @Valid SearchRequestStudyWithThemeDTO searchRequestStudyDTO,
@RequestParam @Min(0) Integer page,
@RequestParam @Min(1) Integer size,
@RequestParam StudySortBy sortBy
Expand Down Expand Up @@ -274,7 +274,7 @@ public ApiResponse<StudyPreviewDTO> interestRegionStudiesByConditionsSpecific(
@Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true)
@Parameter(name = "sortBy", description = "정렬 기준을 입력 받습니다.", required = true)
public ApiResponse<StudyPreviewDTO> recruitingStudiesByConditions(
@ModelAttribute @Valid SearchRequestStudyDTO searchRequestStudyDTO,
@ModelAttribute @Valid SearchRequestStudyWithThemeDTO searchRequestStudyDTO,
@RequestParam @Min(0) Integer page,
@RequestParam @Min(1) Integer size,
@RequestParam StudySortBy sortBy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ public ApiResponse<StudyPostResDTO.PostListDTO> getAllPosts(
@GetMapping("/studies/{studyId}/posts/{postId}")
public ApiResponse<StudyPostResDTO.PostDetailDTO> getPost(
@PathVariable @ExistStudy Long studyId,
@PathVariable @ExistStudyPost Long postId) {
StudyPostResDTO.PostDetailDTO postDetailDTO = studyPostQueryService.getPost(studyId, postId);
@PathVariable @ExistStudyPost Long postId,
@RequestParam Boolean likeOrScrap) {
StudyPostResDTO.PostDetailDTO postDetailDTO = studyPostQueryService.getPost(studyId, postId, likeOrScrap);
return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, postDetailDTO);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class PostUpdateRequest {

private MultipartFile image;

private String existingImage;

public Board getType() {
return Board.findByValue(type);
}
Expand Down
Loading
Loading