Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RequiredArgsConstructor
@RequestMapping("/admin/host-universities")
Expand All @@ -44,20 +46,33 @@ public ResponseEntity<AdminHostUniversityDetailResponse> getHostUniversity(
return ResponseEntity.ok(response);
}

@PostMapping
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<AdminHostUniversityDetailResponse> createHostUniversity(
@Valid @RequestBody AdminHostUniversityCreateRequest request
@Valid @RequestPart("request") AdminHostUniversityCreateRequest request,
@RequestPart("logoFile") MultipartFile logoFile,
@RequestPart("backgroundFile") MultipartFile backgroundFile
) {
AdminHostUniversityDetailResponse response = adminHostUniversityService.createHostUniversity(request);
AdminHostUniversityDetailResponse response = adminHostUniversityService.createHostUniversity(
request,
logoFile,
backgroundFile
);
return ResponseEntity.ok(response);
}

@PutMapping("/{host-university-id}")
@PutMapping(value = "/{host-university-id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<AdminHostUniversityDetailResponse> updateHostUniversity(
@PathVariable("host-university-id") Long hostUniversityId,
@Valid @RequestBody AdminHostUniversityUpdateRequest request
@Valid @RequestPart("request") AdminHostUniversityUpdateRequest request,
@RequestPart(value = "logoFile", required = false) MultipartFile logoFile,
@RequestPart(value = "backgroundFile", required = false) MultipartFile backgroundFile
) {
AdminHostUniversityDetailResponse response = adminHostUniversityService.updateHostUniversity(hostUniversityId, request);
AdminHostUniversityDetailResponse response = adminHostUniversityService.updateHostUniversity(
hostUniversityId,
request,
logoFile,
backgroundFile
);
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ public record AdminHostUniversityCreateRequest(
@Size(max = 500, message = "숙소 URL은 500자 이하여야 합니다")
String accommodationUrl,

@NotBlank(message = "로고 이미지 URL은 필수입니다")
@Size(max = 500, message = "로고 이미지 URL은 500자 이하여야 합니다")
String logoImageUrl,

@NotBlank(message = "배경 이미지 URL은 필수입니다")
@Size(max = 500, message = "배경 이미지 URL은 500자 이하여야 합니다")
String backgroundImageUrl,

@Size(max = 1000, message = "상세 정보는 1000자 이하여야 합니다")
String detailsForLocal,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ public record AdminHostUniversityUpdateRequest(
@Size(max = 500, message = "숙소 URL은 500자 이하여야 합니다")
String accommodationUrl,

@NotBlank(message = "로고 이미지 URL은 필수입니다")
@Size(max = 500, message = "로고 이미지 URL은 500자 이하여야 합니다")
String logoImageUrl,

@NotBlank(message = "배경 이미지 URL은 필수입니다")
@Size(max = 500, message = "배경 이미지 URL은 500자 이하여야 합니다")
String backgroundImageUrl,

@Size(max = 1000, message = "상세 정보는 1000자 이하여야 합니다")
String detailsForLocal,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,33 @@
import com.example.solidconnection.location.country.repository.CountryRepository;
import com.example.solidconnection.location.region.domain.Region;
import com.example.solidconnection.location.region.repository.RegionRepository;
import com.example.solidconnection.s3.domain.UploadDirectoryName;
import com.example.solidconnection.s3.domain.UploadPath;
import com.example.solidconnection.s3.dto.UploadedFileUrlResponse;
import com.example.solidconnection.s3.service.S3Service;
import com.example.solidconnection.university.domain.HostUniversity;
import com.example.solidconnection.university.repository.HostUniversityRepository;
import com.example.solidconnection.university.repository.UnivApplyInfoRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
@Slf4j
public class AdminHostUniversityService {

private final HostUniversityRepository hostUniversityRepository;
private final CountryRepository countryRepository;
private final RegionRepository regionRepository;
private final UnivApplyInfoRepository univApplyInfoRepository;
private final CustomCacheManager cacheManager;
private final S3Service s3Service;

@Transactional(readOnly = true)
public Page<AdminHostUniversityResponse> getHostUniversities(
Expand Down Expand Up @@ -65,29 +73,51 @@ public AdminHostUniversityDetailResponse getHostUniversity(Long id) {
cacheManager = "customCacheManager",
prefix = true
)
public AdminHostUniversityDetailResponse createHostUniversity(AdminHostUniversityCreateRequest request) {
public AdminHostUniversityDetailResponse createHostUniversity(
AdminHostUniversityCreateRequest request,
MultipartFile logoFile,
MultipartFile backgroundFile
) {
validateKoreanNameNotExists(request.koreanName());

Country country = findCountryByCode(request.countryCode());
Region region = findRegionByCode(request.regionCode());
String directoryName = UploadDirectoryName.fromUniversityNames(request.englishName(), request.koreanName());
UploadedFileUrlResponse logoImage = null;
UploadedFileUrlResponse backgroundImage = null;

HostUniversity hostUniversity = new HostUniversity(
null,
request.koreanName(),
request.englishName(),
request.formatName(),
request.homepageUrl(),
request.englishCourseUrl(),
request.accommodationUrl(),
request.logoImageUrl(),
request.backgroundImageUrl(),
request.detailsForLocal(),
country,
region
);
try {
logoImage = uploadUniversityImage(
logoFile,
UploadPath.ADMIN_UNIVERSITY_LOGO,
directoryName
);
backgroundImage = uploadUniversityImage(
backgroundFile,
UploadPath.ADMIN_UNIVERSITY_BACKGROUND,
directoryName
);

HostUniversity savedHostUniversity = hostUniversityRepository.save(hostUniversity);
return AdminHostUniversityDetailResponse.from(savedHostUniversity);
HostUniversity hostUniversity = new HostUniversity(
null,
request.koreanName(),
request.englishName(),
request.formatName(),
request.homepageUrl(),
request.englishCourseUrl(),
request.accommodationUrl(),
logoImage.fileUrl(),
backgroundImage.fileUrl(),
request.detailsForLocal(),
country,
region
);
HostUniversity savedHostUniversity = hostUniversityRepository.saveAndFlush(hostUniversity);
return AdminHostUniversityDetailResponse.from(savedHostUniversity);
} catch (RuntimeException e) {
deleteUploadedImages(logoImage, backgroundImage);
throw e;
}
}

private void validateKoreanNameNotExists(String koreanName) {
Expand All @@ -103,32 +133,97 @@ private void validateKoreanNameNotExists(String koreanName) {
cacheManager = "customCacheManager",
prefix = true
)
public AdminHostUniversityDetailResponse updateHostUniversity(Long id, AdminHostUniversityUpdateRequest request) {
public AdminHostUniversityDetailResponse updateHostUniversity(
Long id,
AdminHostUniversityUpdateRequest request,
MultipartFile logoFile,
MultipartFile backgroundFile
) {
HostUniversity hostUniversity = hostUniversityRepository.findById(id)
.orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND));

validateKoreanNameNotDuplicated(request.koreanName(), id);

Country country = findCountryByCode(request.countryCode());
Region region = findRegionByCode(request.regionCode());
String directoryName = UploadDirectoryName.fromUniversityNames(request.englishName(), request.koreanName());
UploadedFileUrlResponse logoImage = null;
UploadedFileUrlResponse backgroundImage = null;

hostUniversity.update(
request.koreanName(),
request.englishName(),
request.formatName(),
request.homepageUrl(),
request.englishCourseUrl(),
request.accommodationUrl(),
request.logoImageUrl(),
request.backgroundImageUrl(),
request.detailsForLocal(),
country,
region
);
try {
logoImage = uploadUniversityImageIfExists(
logoFile,
UploadPath.ADMIN_UNIVERSITY_LOGO,
directoryName
);
backgroundImage = uploadUniversityImageIfExists(
backgroundFile,
UploadPath.ADMIN_UNIVERSITY_BACKGROUND,
directoryName
);

evictUnivApplyInfoDetailCaches(id);
hostUniversity.update(
request.koreanName(),
request.englishName(),
request.formatName(),
request.homepageUrl(),
request.englishCourseUrl(),
request.accommodationUrl(),
getImageUrlOrDefault(logoImage, hostUniversity.getLogoImageUrl()),
getImageUrlOrDefault(backgroundImage, hostUniversity.getBackgroundImageUrl()),
request.detailsForLocal(),
country,
region
);
hostUniversityRepository.flush();
evictUnivApplyInfoDetailCaches(id);
return AdminHostUniversityDetailResponse.from(hostUniversity);
} catch (RuntimeException e) {
deleteUploadedImages(logoImage, backgroundImage);
throw e;
}
}

return AdminHostUniversityDetailResponse.from(hostUniversity);
private UploadedFileUrlResponse uploadUniversityImage(
MultipartFile imageFile,
UploadPath uploadPath,
String directoryName
) {
return s3Service.uploadFile(imageFile, uploadPath, directoryName);
}

private UploadedFileUrlResponse uploadUniversityImageIfExists(
MultipartFile imageFile,
UploadPath uploadPath,
String directoryName
) {
if (imageFile == null || imageFile.isEmpty()) {
return null;
}
return uploadUniversityImage(imageFile, uploadPath, directoryName);
}

private String getImageUrlOrDefault(UploadedFileUrlResponse uploadedImage, String defaultImageUrl) {
if (uploadedImage == null) {
return defaultImageUrl;
}
return uploadedImage.fileUrl();
}

private void deleteUploadedImages(UploadedFileUrlResponse... uploadedImages) {
for (UploadedFileUrlResponse uploadedImage : uploadedImages) {
if (uploadedImage != null) {
try {
s3Service.deleteUploadedFile(uploadedImage);
} catch (RuntimeException deleteException) {
log.warn(
"Failed to delete uploaded university image. fileUrl={}",
uploadedImage.fileUrl(),
deleteException
);
}
}
}
}

private void validateKoreanNameNotDuplicated(String koreanName, Long excludeId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package com.example.solidconnection.s3.controller;

import com.example.solidconnection.common.resolver.AuthorizedUser;
import com.example.solidconnection.s3.domain.UploadDirectoryName;
import com.example.solidconnection.s3.domain.UploadPath;
import com.example.solidconnection.s3.dto.UploadedFileUrlResponse;
import com.example.solidconnection.s3.dto.UrlPrefixResponse;
import com.example.solidconnection.s3.service.S3Service;
import com.example.solidconnection.security.annotation.RequireRoleAccess;
import com.example.solidconnection.siteuser.domain.Role;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -80,38 +77,6 @@ public ResponseEntity<List<UploadedFileUrlResponse>> uploadChatFile(
return ResponseEntity.ok(chatImageUrls);
}

@RequireRoleAccess(roles = Role.ADMIN)
@PostMapping("/admin/university/logo")
public ResponseEntity<UploadedFileUrlResponse> uploadAdminUniversityLogo(
@AuthorizedUser long adminId,
@RequestParam("file") MultipartFile imageFile,
@RequestParam("englishName") String englishName
) {
String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName);
UploadedFileUrlResponse logoImageUrl = s3Service.uploadFile(
imageFile,
UploadPath.ADMIN_UNIVERSITY_LOGO,
directoryName
);
return ResponseEntity.ok(logoImageUrl);
}

@RequireRoleAccess(roles = Role.ADMIN)
@PostMapping("/admin/university/background")
public ResponseEntity<UploadedFileUrlResponse> uploadAdminUniversityBackground(
@AuthorizedUser long adminId,
@RequestParam("file") MultipartFile imageFile,
@RequestParam("englishName") String englishName
) {
String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName);
UploadedFileUrlResponse backgroundImageUrl = s3Service.uploadFile(
imageFile,
UploadPath.ADMIN_UNIVERSITY_BACKGROUND,
directoryName
);
return ResponseEntity.ok(backgroundImageUrl);
}

@GetMapping("/s3-url-prefix")
public ResponseEntity<UrlPrefixResponse> getS3UrlPrefix() {
return ResponseEntity.ok(new UrlPrefixResponse(s3Default, s3Uploaded, cloudFrontDefault, cloudFrontUploaded));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ public final class UploadDirectoryName {
private UploadDirectoryName() {
}

public static String fromUniversityEnglishName(String englishName) {
public static String fromUniversityNames(String englishName, String koreanName) {
if (englishName == null || englishName.isBlank()) {
throw new CustomException(ErrorCode.INVALID_INPUT);
}
if (koreanName == null || koreanName.isBlank()) {
throw new CustomException(ErrorCode.INVALID_INPUT);
}

String directoryName = englishName.trim()
.toLowerCase()
Expand All @@ -30,7 +33,7 @@ public static String fromUniversityEnglishName(String englishName) {
throw new CustomException(ErrorCode.INVALID_INPUT);
}

return directoryName + "_" + hash(englishName.trim());
return directoryName + "_" + hash(koreanName.trim());
}

private static String hash(String value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package com.example.solidconnection.s3.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;

public record UploadedFileUrlResponse(
String fileUrl) {
String fileUrl,
@JsonIgnore String deletionKey) {

public UploadedFileUrlResponse(String fileUrl) {
this(fileUrl, fileUrl);
}
}
Loading
Loading