Skip to content

Commit

Permalink
[FEAT]: 이미지 업로드 API 구현 (#6)
Browse files Browse the repository at this point in the history
* [FEAT]: Building 상세 정보 조회 API 구현

* [FEAT]: 이미지 업로드 API 구현
  • Loading branch information
sejineer authored Aug 13, 2024
1 parent 477751e commit ed406ef
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 13 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class AuthController {
public ApiResponse<TokenResult> login(
@Valid @RequestBody LoginRequest loginRequest
) {
System.out.println("loginRequest = " + loginRequest);
return ApiResponse.success(authService.login(loginRequest));
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/khtml/hexagonal/domain/auth/JwtValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

}

37 changes: 37 additions & 0 deletions src/main/java/org/khtml/hexagonal/domain/building/BlobManager.java
Original file line number Diff line number Diff line change
@@ -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");
}

}
11 changes: 7 additions & 4 deletions src/main/java/org/khtml/hexagonal/domain/building/Building.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.khtml.hexagonal.domain.user.User;

import java.util.List;

Expand Down Expand Up @@ -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;

}
Original file line number Diff line number Diff line change
@@ -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<BuildingDetailResponse> 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<MultipartFile> multipartFiles
) throws IOException {
User requestUser = jwtValidator.getUserFromToken(token);
buildingService.registerBuilding(requestUser, multipartFiles);
return ApiResponse.success();
}

}
Original file line number Diff line number Diff line change
@@ -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()
);
}
}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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<Building, Long> {

@EntityGraph(attributePaths = {"user"})
Optional<Building> findBuildingByGisBuildingId(String gisBuildingId);

}
22 changes: 22 additions & 0 deletions src/main/java/org/khtml/hexagonal/domain/building/Image.java
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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());
Expand All @@ -46,4 +53,11 @@ public Building updateBuilding(Long id, BuildingUpdate buildingUpdate) {

return buildingRepository.save(existingBuilding);
}

public void registerBuilding(User requestUser, List<MultipartFile> multipartFiles) throws IOException {
MultipartFile file = multipartFiles.getFirst();
String url = blobManager.storeFile(file.getOriginalFilename(), file.getInputStream(), file.getSize());

}

}
1 change: 1 addition & 0 deletions src/main/java/org/khtml/hexagonal/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ public User(String providerId, UserType userType, String phoneNumber, String use
this.numberOfPersons = numberOfPersons;
this.locationConsent = locationConsent;
}

}
15 changes: 15 additions & 0 deletions src/main/java/org/khtml/hexagonal/domain/user/UserController.java
Original file line number Diff line number Diff line change
@@ -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;


}
11 changes: 11 additions & 0 deletions src/main/java/org/khtml/hexagonal/domain/user/UserService.java
Original file line number Diff line number Diff line change
@@ -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 {
}
8 changes: 8 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit ed406ef

Please sign in to comment.