diff --git a/backend/src/main/java/moadong/club/controller/ClubProfileController.java b/backend/src/main/java/moadong/club/controller/ClubProfileController.java index d0c97c9cf..f57e54c7a 100644 --- a/backend/src/main/java/moadong/club/controller/ClubProfileController.java +++ b/backend/src/main/java/moadong/club/controller/ClubProfileController.java @@ -31,6 +31,14 @@ public ResponseEntity getClubDetail(@PathVariable String clubId) { return Response.ok(clubDetailedPageResponse); } + @GetMapping("/@{clubName}") + @Operation(summary = "클럽 상세 정보 조회", description = "클럽 이름을 이용해 상세 정보를 조회합니다.") + public ResponseEntity getClubDetailByClubName(@PathVariable String clubName) { + ClubDetailedResponse clubDetailedResponse = clubProfileService.getClubDetailByClubName(clubName); + return Response.ok(clubDetailedResponse); + } + + @PutMapping("/info") @Operation(summary = "클럽 약력 수정", description = "클럽 약력을 수정합니다.
" + "tags는 최대 2개, 5글자 이내로 입력해야 합니다.
" diff --git a/backend/src/main/java/moadong/club/entity/Club.java b/backend/src/main/java/moadong/club/entity/Club.java index bac93a549..9f1296591 100644 --- a/backend/src/main/java/moadong/club/entity/Club.java +++ b/backend/src/main/java/moadong/club/entity/Club.java @@ -13,6 +13,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import org.springframework.data.domain.Persistable; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; @@ -27,6 +28,8 @@ public class Club implements Persistable { @Id private String id; + @Indexed(name = "uk_club_name_not_blank", unique = true, + partialFilter = "{ \"name\": { \"$type\": \"string\", \"$gt\": \"\" } }") private String name; private String category; diff --git a/backend/src/main/java/moadong/club/repository/ClubRepository.java b/backend/src/main/java/moadong/club/repository/ClubRepository.java index aa4e47003..f371fa5da 100644 --- a/backend/src/main/java/moadong/club/repository/ClubRepository.java +++ b/backend/src/main/java/moadong/club/repository/ClubRepository.java @@ -20,4 +20,9 @@ public interface ClubRepository extends MongoRepository { Long countByIdIn(List id); List findAllByClubRecruitmentInformation_ClubRecruitmentStatus(ClubRecruitmentStatus status); + + Optional findClubByName(String name); + + boolean existsByNameAndIdNot(String name, String id); + } diff --git a/backend/src/main/java/moadong/club/service/ClubProfileService.java b/backend/src/main/java/moadong/club/service/ClubProfileService.java index 561babfbd..00a2f5778 100644 --- a/backend/src/main/java/moadong/club/service/ClubProfileService.java +++ b/backend/src/main/java/moadong/club/service/ClubProfileService.java @@ -20,6 +20,7 @@ import moadong.user.payload.CustomUserDetails; import org.bson.types.ObjectId; import org.javers.core.Javers; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,8 +43,9 @@ public class ClubProfileService { public void updateClubInfo(ClubInfoRequest request, CustomUserDetails user) { Club club = clubRepository.findClubByUserId(user.getId()) .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); + validateClubNameUnique(club.getId(), request.name()); club.update(request); - Club saved = clubRepository.save(club); + Club saved = saveClub(club); javers.commit(user.getUsername(), saved); } @@ -94,13 +96,24 @@ public ClubDetailedResponse getClubDetail(String clubId) { return new ClubDetailedResponse(clubDetailedResult); } + public ClubDetailedResponse getClubDetailByClubName(String clubName) { + Club club = clubRepository.findClubByName(clubName) + .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); + + ClubDetailedResult clubDetailedResult = ClubDetailedResult.of( + club + ); + return new ClubDetailedResponse(clubDetailedResult); + } + @Transactional public void updateClubInfoByClubId(String clubId, ClubInfoRequest request, CustomUserDetails user) { ObjectId objectId = ObjectIdConverter.convertString(clubId); Club club = clubRepository.findClubById(objectId) .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); + validateClubNameUnique(club.getId(), request.name()); club.update(request); - Club saved = clubRepository.save(club); + Club saved = saveClub(club); javers.commit(user.getUsername(), saved); } @@ -128,4 +141,23 @@ public void updateClubRecruitmentInfoByClubId(String clubId, ClubRecruitmentInfo ); } } + + private void validateClubNameUnique(String clubId, String name) { + if (name == null || name.isBlank()) { + return; + } + + if (clubRepository.existsByNameAndIdNot(name, clubId)) { + throw new RestApiException(ErrorCode.CLUB_NAME_ALREADY_EXISTS); + } + } + + // 레이스 컨디션을 위한 시큐어 코딩 + private Club saveClub(Club club) { + try { + return clubRepository.save(club); + } catch (DuplicateKeyException e) { + throw new RestApiException(ErrorCode.CLUB_NAME_ALREADY_EXISTS); + } + } } diff --git a/backend/src/main/java/moadong/global/exception/ErrorCode.java b/backend/src/main/java/moadong/global/exception/ErrorCode.java index 2d31d706d..42cf5ec68 100644 --- a/backend/src/main/java/moadong/global/exception/ErrorCode.java +++ b/backend/src/main/java/moadong/global/exception/ErrorCode.java @@ -18,6 +18,7 @@ public enum ErrorCode { TOO_MANY_TAGS(HttpStatus.BAD_REQUEST, "600-8", "태그는 최대 3개까지 입력할 수 있습니다."), TOO_LONG_TAG(HttpStatus.BAD_REQUEST, "600-9", "태그는 최대 5글자까지 입력할 수 있습니다."), TOO_LONG_INTRODUCTION(HttpStatus.BAD_REQUEST, "600-10", "소개는 최대 24글자까지 입력할 수 있습니다."), + CLUB_NAME_ALREADY_EXISTS(HttpStatus.CONFLICT, "600-11", "이미 사용 중인 동아리 이름입니다."), // 601xx: 파일/미디어 관련 오류 IMAGE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "601-1", "이미지 업로드에 실패하였습니다."),