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", "이미지 업로드에 실패하였습니다."),