Skip to content

Commit d03b63f

Browse files
committed
feat: 대학 이미지 업로드 경로 충돌 방지
- 대학 영문명 디렉토리명에 원본 영문명 해시를 추가 - 파견 대학 영문명 중복 검증과 유니크 제약을 추가 - 정규화 충돌과 영문명 중복 검증 테스트를 추가
1 parent 136df16 commit d03b63f

7 files changed

Lines changed: 109 additions & 4 deletions

File tree

src/main/java/com/example/solidconnection/admin/university/service/AdminHostUniversityService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public AdminHostUniversityDetailResponse getHostUniversity(Long id) {
6767
)
6868
public AdminHostUniversityDetailResponse createHostUniversity(AdminHostUniversityCreateRequest request) {
6969
validateKoreanNameNotExists(request.koreanName());
70+
validateEnglishNameNotExists(request.englishName());
7071

7172
Country country = findCountryByCode(request.countryCode());
7273
Region region = findRegionByCode(request.regionCode());
@@ -97,6 +98,13 @@ private void validateKoreanNameNotExists(String koreanName) {
9798
});
9899
}
99100

101+
private void validateEnglishNameNotExists(String englishName) {
102+
hostUniversityRepository.findByEnglishName(englishName)
103+
.ifPresent(existingUniversity -> {
104+
throw new CustomException(HOST_UNIVERSITY_ALREADY_EXISTS);
105+
});
106+
}
107+
100108
@Transactional
101109
@DefaultCacheOut(
102110
key = {"univApplyInfoTextSearch", "university:recommend:general"},
@@ -108,6 +116,7 @@ public AdminHostUniversityDetailResponse updateHostUniversity(Long id, AdminHost
108116
.orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND));
109117

110118
validateKoreanNameNotDuplicated(request.koreanName(), id);
119+
validateEnglishNameNotDuplicated(request.englishName(), id);
111120

112121
Country country = findCountryByCode(request.countryCode());
113122
Region region = findRegionByCode(request.regionCode());
@@ -140,6 +149,15 @@ private void validateKoreanNameNotDuplicated(String koreanName, Long excludeId)
140149
});
141150
}
142151

152+
private void validateEnglishNameNotDuplicated(String englishName, Long excludeId) {
153+
hostUniversityRepository.findByEnglishName(englishName)
154+
.ifPresent(existingUniversity -> {
155+
if (!existingUniversity.getId().equals(excludeId)) {
156+
throw new CustomException(HOST_UNIVERSITY_ALREADY_EXISTS);
157+
}
158+
});
159+
}
160+
143161
private Country findCountryByCode(String countryCode) {
144162
return countryRepository.findByCode(countryCode)
145163
.orElseThrow(() -> new CustomException(COUNTRY_NOT_FOUND));

src/main/java/com/example/solidconnection/s3/domain/UploadDirectoryName.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
import com.example.solidconnection.common.exception.CustomException;
44
import com.example.solidconnection.common.exception.ErrorCode;
5+
import java.nio.charset.StandardCharsets;
6+
import java.security.MessageDigest;
7+
import java.security.NoSuchAlgorithmException;
8+
import java.util.HexFormat;
59

610
public final class UploadDirectoryName {
711

12+
private static final int HASH_PREFIX_LENGTH = 12;
13+
814
private UploadDirectoryName() {
915
}
1016

@@ -24,6 +30,16 @@ public static String fromUniversityEnglishName(String englishName) {
2430
throw new CustomException(ErrorCode.INVALID_INPUT);
2531
}
2632

27-
return directoryName;
33+
return directoryName + "_" + hash(englishName.trim());
34+
}
35+
36+
private static String hash(String value) {
37+
try {
38+
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
39+
byte[] digest = messageDigest.digest(value.getBytes(StandardCharsets.UTF_8));
40+
return HexFormat.of().formatHex(digest).substring(0, HASH_PREFIX_LENGTH);
41+
} catch (NoSuchAlgorithmException e) {
42+
throw new CustomException(ErrorCode.NOT_DEFINED_ERROR);
43+
}
2844
}
2945
}

src/main/java/com/example/solidconnection/university/domain/HostUniversity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class HostUniversity extends BaseEntity {
2828
@Column(name = "korean_name", nullable = false, unique = true, length = 100)
2929
private String koreanName;
3030

31-
@Column(name = "english_name", nullable = false, length = 100)
31+
@Column(name = "english_name", nullable = false, unique = true, length = 100)
3232
private String englishName;
3333

3434
@Column(name = "format_name", nullable = false, length = 100)

src/main/java/com/example/solidconnection/university/repository/HostUniversityRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ default HostUniversity getHostUniversityById(Long id) {
1616
}
1717

1818
Optional<HostUniversity> findByKoreanName(String koreanName);
19+
20+
Optional<HostUniversity> findByEnglishName(String englishName);
1921
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE host_university
2+
ADD CONSTRAINT uk_host_university_english_name UNIQUE (english_name);

src/test/java/com/example/solidconnection/admin/service/AdminHostUniversityServiceTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,31 @@ class 생성 {
256256
.isInstanceOf(CustomException.class)
257257
.hasMessage(ErrorCode.HOST_UNIVERSITY_ALREADY_EXISTS.getMessage());
258258
}
259+
260+
@Test
261+
void 이미_존재하는_영문명으로_생성하면_예외_응답을_반환한다() {
262+
// given
263+
HostUniversity existing = universityFixture.괌_대학();
264+
Country country = countryFixture.미국();
265+
Region region = regionFixture.영미권();
266+
267+
AdminHostUniversityCreateRequest request = new AdminHostUniversityCreateRequest(
268+
"신규 대학",
269+
existing.getEnglishName(),
270+
"표시명",
271+
null, null, null,
272+
"https://logo.com/image.png",
273+
"https://background.com/image.png",
274+
null,
275+
country.getCode(),
276+
region.getCode()
277+
);
278+
279+
// when & then
280+
assertThatCode(() -> adminHostUniversityService.createHostUniversity(request))
281+
.isInstanceOf(CustomException.class)
282+
.hasMessage(ErrorCode.HOST_UNIVERSITY_ALREADY_EXISTS.getMessage());
283+
}
259284
}
260285

261286
@Nested
@@ -341,6 +366,30 @@ class 수정 {
341366
.hasMessage(ErrorCode.HOST_UNIVERSITY_ALREADY_EXISTS.getMessage());
342367
}
343368

369+
@Test
370+
void 다른_대학의_영문명으로_수정하면_예외_응답을_반환한다() {
371+
// given
372+
HostUniversity university1 = universityFixture.괌_대학();
373+
HostUniversity university2 = universityFixture.메이지_대학();
374+
375+
AdminHostUniversityUpdateRequest request = new AdminHostUniversityUpdateRequest(
376+
university1.getKoreanName(),
377+
university2.getEnglishName(),
378+
"수정된 표시명",
379+
null, null, null,
380+
"https://logo.com/image.png",
381+
"https://background.com/image.png",
382+
null,
383+
university1.getCountry().getCode(),
384+
university1.getRegion().getCode()
385+
);
386+
387+
// when & then
388+
assertThatCode(() -> adminHostUniversityService.updateHostUniversity(university1.getId(), request))
389+
.isInstanceOf(CustomException.class)
390+
.hasMessage(ErrorCode.HOST_UNIVERSITY_ALREADY_EXISTS.getMessage());
391+
}
392+
344393
@Test
345394
void 같은_대학의_한글명으로_수정하면_성공한다() {
346395
// given

src/test/java/com/example/solidconnection/s3/domain/UploadDirectoryNameTest.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ class 대학_영문명_변환_테스트 {
2323
String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName);
2424

2525
// then
26-
assertThat(directoryName).isEqualTo("university_of_tokyo");
26+
assertThat(directoryName)
27+
.startsWith("university_of_tokyo_")
28+
.matches("university_of_tokyo_[0-9a-f]{12}");
2729
}
2830

2931
@Test
@@ -35,7 +37,23 @@ class 대학_영문명_변환_테스트 {
3537
String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName);
3638

3739
// then
38-
assertThat(directoryName).isEqualTo("texas_a_and_m_university_austin");
40+
assertThat(directoryName)
41+
.startsWith("texas_a_and_m_university_austin_")
42+
.matches("texas_a_and_m_university_austin_[0-9a-f]{12}");
43+
}
44+
45+
@Test
46+
void 같은_slug로_변환되는_서로_다른_영문명은_다른_디렉토리명을_반환한다() {
47+
// given
48+
String englishName = "Texas A&M University";
49+
String normalizedCollisionName = "Texas A and M University";
50+
51+
// when
52+
String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName);
53+
String collisionDirectoryName = UploadDirectoryName.fromUniversityEnglishName(normalizedCollisionName);
54+
55+
// then
56+
assertThat(directoryName).isNotEqualTo(collisionDirectoryName);
3957
}
4058

4159
@Test

0 commit comments

Comments
 (0)