From ed406ef1dd842fcfea4463f9400b487366cecc99 Mon Sep 17 00:00:00 2001 From: Sejin Park <95167215+sejineer@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:46:45 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20API=20=EA=B5=AC=ED=98=84=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT]: Building 상세 정보 조회 API 구현 * [FEAT]: 이미지 업로드 API 구현 --- build.gradle | 2 + .../hexagonal/domain/auth/AuthController.java | 1 - .../hexagonal/domain/auth/JwtValidator.java | 12 ++++++ .../domain/building/BlobManager.java | 37 ++++++++++++++++++ .../hexagonal/domain/building/Building.java | 11 ++++-- .../domain/building/BuildingController.java | 39 +++++++++++++++++++ .../building/BuildingDetailResponse.java | 37 ++++++++++++++++++ .../domain/building/BuildingImage.java | 24 ++++++++++++ .../domain/building/BuildingRepository.java | 7 ++++ .../hexagonal/domain/building/Image.java | 22 +++++++++++ .../building/application/BuildingService.java | 30 ++++++++++---- .../org/khtml/hexagonal/domain/user/User.java | 1 + .../hexagonal/domain/user/UserController.java | 15 +++++++ .../hexagonal/domain/user/UserService.java | 11 ++++++ src/main/resources/application.yml | 8 ++++ 15 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/khtml/hexagonal/domain/building/BlobManager.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/building/BuildingController.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/building/BuildingDetailResponse.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/building/BuildingImage.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/building/Image.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/user/UserController.java create mode 100644 src/main/java/org/khtml/hexagonal/domain/user/UserService.java diff --git a/build.gradle b/build.gradle index f19e322..8e63f0a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.6' implementation 'io.jsonwebtoken:jjwt-impl:0.12.6' implementation 'io.jsonwebtoken:jjwt-jackson:0.12.6' + + implementation 'com.azure:azure-storage-blob:12.27.0' } tasks.named('test') { diff --git a/src/main/java/org/khtml/hexagonal/domain/auth/AuthController.java b/src/main/java/org/khtml/hexagonal/domain/auth/AuthController.java index 992adf5..475b43e 100644 --- a/src/main/java/org/khtml/hexagonal/domain/auth/AuthController.java +++ b/src/main/java/org/khtml/hexagonal/domain/auth/AuthController.java @@ -19,7 +19,6 @@ public class AuthController { public ApiResponse login( @Valid @RequestBody LoginRequest loginRequest ) { - System.out.println("loginRequest = " + loginRequest); return ApiResponse.success(authService.login(loginRequest)); } } diff --git a/src/main/java/org/khtml/hexagonal/domain/auth/JwtValidator.java b/src/main/java/org/khtml/hexagonal/domain/auth/JwtValidator.java index f3d9fca..8e88ecb 100644 --- a/src/main/java/org/khtml/hexagonal/domain/auth/JwtValidator.java +++ b/src/main/java/org/khtml/hexagonal/domain/auth/JwtValidator.java @@ -4,6 +4,7 @@ import io.jsonwebtoken.security.Keys; import jakarta.servlet.ServletRequest; import lombok.RequiredArgsConstructor; +import org.khtml.hexagonal.domain.user.User; import org.khtml.hexagonal.domain.user.UserRepository; import org.khtml.hexagonal.global.config.JwtProperty; import org.slf4j.Logger; @@ -42,5 +43,16 @@ public boolean validateAccessTokenFromRequest(ServletRequest servletRequest, Str return false; } + public User getUserFromToken(String token) { + String extracted = extractTokenFromHeader(token); + Claims claims = Jwts.parser().verifyWith(Keys.hmacShaKeyFor(jwtProperty.getSecretKey().getBytes())).build() + .parseSignedClaims(extracted).getPayload(); + return userRepository.findById(Long.parseLong(claims.getSubject())).orElseThrow(); + } + + private String extractTokenFromHeader(String header) { + return header.substring("Bearer ".length()); + } + } diff --git a/src/main/java/org/khtml/hexagonal/domain/building/BlobManager.java b/src/main/java/org/khtml/hexagonal/domain/building/BlobManager.java new file mode 100644 index 0000000..652f99f --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/building/BlobManager.java @@ -0,0 +1,37 @@ +package org.khtml.hexagonal.domain.building; + +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.InputStream; + +@Component +public class BlobManager { + + @Value("${azure.storage.account-name}") + private String accountName; + + @Value("${azure.storage.account-key}") + private String accountKey; + + public String storeFile(String filename, InputStream content, long length) { + BlobClient client = containerClient().getBlobClient(filename.trim()); + client.upload(content, length); + return "File uploaded with success!"; + } + + private BlobContainerClient containerClient() { + String connectionString = "DefaultEndpointsProtocol=https;" + + "AccountName=" + accountName + + ";AccountKey=" + accountKey + + ";EndpointSuffix=core.windows.net"; + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .connectionString(connectionString).buildClient(); + return blobServiceClient.getBlobContainerClient("images"); + } + +} diff --git a/src/main/java/org/khtml/hexagonal/domain/building/Building.java b/src/main/java/org/khtml/hexagonal/domain/building/Building.java index 48b1a51..c7f3c71 100644 --- a/src/main/java/org/khtml/hexagonal/domain/building/Building.java +++ b/src/main/java/org/khtml/hexagonal/domain/building/Building.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.khtml.hexagonal.domain.user.User; import java.util.List; @@ -101,9 +102,11 @@ public class Building { @Column(name = "repair_list") private String repairList; - @Column(name = "material_list") - private String materialList; + @Column(name = "description") + private String description; - @Column(name = "condition_list") - private String conditionList; + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + } diff --git a/src/main/java/org/khtml/hexagonal/domain/building/BuildingController.java b/src/main/java/org/khtml/hexagonal/domain/building/BuildingController.java new file mode 100644 index 0000000..bdcdc36 --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/building/BuildingController.java @@ -0,0 +1,39 @@ +package org.khtml.hexagonal.domain.building; + +import lombok.RequiredArgsConstructor; +import org.khtml.hexagonal.domain.auth.JwtValidator; +import org.khtml.hexagonal.domain.building.application.BuildingService; +import org.khtml.hexagonal.domain.user.User; +import org.khtml.hexagonal.global.support.response.ApiResponse; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RequestMapping("/api/v1/buildings") +@RequiredArgsConstructor +@RestController +public class BuildingController { + + private final BuildingService buildingService; + private final JwtValidator jwtValidator; + + @GetMapping("/{building-id}") + public ApiResponse getBuildingDetail( + @PathVariable(name = "building-id") String buildingId + ) { + return ApiResponse.success(BuildingDetailResponse.toResponse(buildingService.getBuilding(buildingId))); + } + + @PostMapping("/{building-id}/register") + public ApiResponse registerBuilding( + @RequestHeader("Authorization") String token, + @RequestParam("images") List multipartFiles + ) throws IOException { + User requestUser = jwtValidator.getUserFromToken(token); + buildingService.registerBuilding(requestUser, multipartFiles); + return ApiResponse.success(); + } + +} diff --git a/src/main/java/org/khtml/hexagonal/domain/building/BuildingDetailResponse.java b/src/main/java/org/khtml/hexagonal/domain/building/BuildingDetailResponse.java new file mode 100644 index 0000000..1bd418b --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/building/BuildingDetailResponse.java @@ -0,0 +1,37 @@ +package org.khtml.hexagonal.domain.building; + +public record BuildingDetailResponse( + String buildingId, + String address, + String description, + String phoneNumber, + Integer crackScore, + Integer leakScore, + Integer corrosionScore, + Integer agingScore, + Integer totalScore, + String repairList, + String roofMaterial, + String buildingStructureType, + String wallMaterial, + String windowDoorMaterial +) { + public static BuildingDetailResponse toResponse(Building building) { + return new BuildingDetailResponse( + building.getGisBuildingId(), + building.getLegalDistrictName() + " " + building.getLandLotNumber(), + building.getDescription(), + building.getUser() == null ? null : building.getUser().getPhoneNumber(), + building.getCrackScore(), + building.getLeakScore(), + building.getCorrosionScore(), + building.getAgingScore(), + building.getTotalScore(), + building.getRepairList(), + building.getRoofMaterial(), + building.getBuildingStructureType(), + building.getWallMaterial(), + building.getWindowDoorMaterial() + ); + } +} diff --git a/src/main/java/org/khtml/hexagonal/domain/building/BuildingImage.java b/src/main/java/org/khtml/hexagonal/domain/building/BuildingImage.java new file mode 100644 index 0000000..2b5a085 --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/building/BuildingImage.java @@ -0,0 +1,24 @@ +package org.khtml.hexagonal.domain.building; + + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.khtml.hexagonal.domain.common.BaseEntity; + +@Table(name = "building_image") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class BuildingImage extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "building_id") + private Building building; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "image_id") + private Image image; + +} diff --git a/src/main/java/org/khtml/hexagonal/domain/building/BuildingRepository.java b/src/main/java/org/khtml/hexagonal/domain/building/BuildingRepository.java index 3391424..404c280 100644 --- a/src/main/java/org/khtml/hexagonal/domain/building/BuildingRepository.java +++ b/src/main/java/org/khtml/hexagonal/domain/building/BuildingRepository.java @@ -1,6 +1,13 @@ package org.khtml.hexagonal.domain.building; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface BuildingRepository extends JpaRepository { + + @EntityGraph(attributePaths = {"user"}) + Optional findBuildingByGisBuildingId(String gisBuildingId); + } diff --git a/src/main/java/org/khtml/hexagonal/domain/building/Image.java b/src/main/java/org/khtml/hexagonal/domain/building/Image.java new file mode 100644 index 0000000..9b314ce --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/building/Image.java @@ -0,0 +1,22 @@ +package org.khtml.hexagonal.domain.building; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.khtml.hexagonal.domain.common.BaseEntity; +import org.khtml.hexagonal.domain.user.User; + +@Table(name = "image") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Image extends BaseEntity { + + private String url; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + +} diff --git a/src/main/java/org/khtml/hexagonal/domain/building/application/BuildingService.java b/src/main/java/org/khtml/hexagonal/domain/building/application/BuildingService.java index f7169bc..a251c6f 100644 --- a/src/main/java/org/khtml/hexagonal/domain/building/application/BuildingService.java +++ b/src/main/java/org/khtml/hexagonal/domain/building/application/BuildingService.java @@ -1,33 +1,40 @@ package org.khtml.hexagonal.domain.building.application; +import lombok.RequiredArgsConstructor; import org.khtml.hexagonal.domain.ai.dto.BuildingUpdate; +import org.khtml.hexagonal.domain.building.BlobManager; import org.khtml.hexagonal.domain.building.Building; +import org.khtml.hexagonal.domain.building.BuildingDetailResponse; import org.khtml.hexagonal.domain.building.BuildingRepository; +import org.khtml.hexagonal.domain.user.User; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.util.List; + +@RequiredArgsConstructor @Service public class BuildingService { private final BuildingRepository buildingRepository; - - public BuildingService(BuildingRepository buildingRepository) { - this.buildingRepository = buildingRepository; - } + private final BlobManager blobManager; public Building createBuilding(Building building) { return buildingRepository.save(building); } - public Building getBuilding(Long id) { - return buildingRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Building not found")); + public Building getBuilding(String buildingId) { + return buildingRepository.findBuildingByGisBuildingId(buildingId) + .orElseThrow(() -> new IllegalArgumentException("Building not found")); } public void deleteBuilding(Long id) { buildingRepository.deleteById(id); } - public Building updateBuilding(Long id, BuildingUpdate buildingUpdate) { - Building existingBuilding = getBuilding(id); + public Building updateBuilding(String buildingId, BuildingUpdate buildingUpdate) { + Building existingBuilding = getBuilding(buildingId); existingBuilding.setStructureReason(buildingUpdate.getStructureReason()); existingBuilding.setRoofMaterial(buildingUpdate.getRoofMaterial()); existingBuilding.setRoofCondition(buildingUpdate.getRoofCondition()); @@ -46,4 +53,11 @@ public Building updateBuilding(Long id, BuildingUpdate buildingUpdate) { return buildingRepository.save(existingBuilding); } + + public void registerBuilding(User requestUser, List multipartFiles) throws IOException { + MultipartFile file = multipartFiles.getFirst(); + String url = blobManager.storeFile(file.getOriginalFilename(), file.getInputStream(), file.getSize()); + + } + } diff --git a/src/main/java/org/khtml/hexagonal/domain/user/User.java b/src/main/java/org/khtml/hexagonal/domain/user/User.java index d64a815..2a86675 100644 --- a/src/main/java/org/khtml/hexagonal/domain/user/User.java +++ b/src/main/java/org/khtml/hexagonal/domain/user/User.java @@ -41,4 +41,5 @@ public User(String providerId, UserType userType, String phoneNumber, String use this.numberOfPersons = numberOfPersons; this.locationConsent = locationConsent; } + } diff --git a/src/main/java/org/khtml/hexagonal/domain/user/UserController.java b/src/main/java/org/khtml/hexagonal/domain/user/UserController.java new file mode 100644 index 0000000..1bb524d --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/user/UserController.java @@ -0,0 +1,15 @@ +package org.khtml.hexagonal.domain.user; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/v1/users") +@RequiredArgsConstructor +@RestController +public class UserController { + + private final UserService userService; + + +} diff --git a/src/main/java/org/khtml/hexagonal/domain/user/UserService.java b/src/main/java/org/khtml/hexagonal/domain/user/UserService.java new file mode 100644 index 0000000..dee5b71 --- /dev/null +++ b/src/main/java/org/khtml/hexagonal/domain/user/UserService.java @@ -0,0 +1,11 @@ +package org.khtml.hexagonal.domain.user; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class UserService { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 734bb13..7a357ed 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,6 +22,14 @@ openai: key: ${OPEN_API_KEY} url: ${OPEN_API_URL} +azure: + storage: + account-name: ${BLOB_ACCOUNT_NAME} + account-key: ${BLOB_ACCOUNT_KEY} + endpoint: ${BLOB_ENDPOINT} + +--- + spring.config.activate.on-profile: local spring: