Skip to content

Commit

Permalink
Merge pull request #9 from ahtop00/feature/#2
Browse files Browse the repository at this point in the history
✨ [Feat] Add an API to add stores to a specific area
  • Loading branch information
ahtop00 authored Nov 21, 2024
2 parents 52f4f59 + 33b7faf commit 790c566
Show file tree
Hide file tree
Showing 16 changed files with 315 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public enum ErrorStatus implements BaseErrorCode {
// 예시,,,
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."),

//음식 카테고리 없음
FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD4001", "해당 음식 카테고리가 없습니다.");
//해당 지역 혹은 카테고리 없음
FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD4001", "해당 음식 카테고리가 없습니다."),
REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION4001", "해당 지역이 없습니다.");


private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@ public ResponseEntity<Object> handleFoodCategoryNotFoundException(FoodCategoryNo
);
}

//RegionNotFoundException에 대한 에러 처리
@ExceptionHandler(RegionNotFoundException.class)
public ResponseEntity<Object> handleRegionNotFoundException(RegionNotFoundException e, HttpServletRequest request) {
WebRequest webRequest = new ServletWebRequest(request);

ApiResponse<Object> body = ApiResponse.onFailure(
ErrorStatus.REGION_NOT_FOUND.getCode(),
ErrorStatus.REGION_NOT_FOUND.getMessage(),
null
);

return super.handleExceptionInternal(
e,
body,
new HttpHeaders(),
ErrorStatus.REGION_NOT_FOUND.getHttpStatus(),
webRequest
);
}

private ResponseEntity<Object> handleExceptionInternal(Exception e, ErrorReasonDTO reason,
HttpHeaders headers, HttpServletRequest request) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package javalab.umc7th_mission.apiPayload.exception;

import javalab.umc7th_mission.apiPayload.code.status.ErrorStatus;
import org.springframework.http.HttpStatus;

public class RegionNotFoundException extends RuntimeException {
private final HttpStatus status = HttpStatus.NOT_FOUND;

public RegionNotFoundException(ErrorStatus errorStatus) {
super(errorStatus.getMessage());
}

public HttpStatus getStatus() {
return status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package javalab.umc7th_mission.converter;

import javalab.umc7th_mission.domain.FoodCategory;
import javalab.umc7th_mission.domain.mapping.StoreCategory;

import java.util.List;
import java.util.stream.Collectors;

public class StoreCategoryConverter {
public static List<StoreCategory> toStoreCategoryList(List<FoodCategory> foodCategoryList){

return foodCategoryList.stream()
.map(foodCategory ->
StoreCategory.builder()
.foodCategory(foodCategory)
.build()
).collect(Collectors.toList());
}
}
40 changes: 40 additions & 0 deletions src/main/java/javalab/umc7th_mission/converter/StoreConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package javalab.umc7th_mission.converter;

import javalab.umc7th_mission.domain.Member;
import javalab.umc7th_mission.domain.Region;
import javalab.umc7th_mission.domain.Store;
import javalab.umc7th_mission.domain.mapping.StoreAddress;
import javalab.umc7th_mission.web.dto.MemberRequestDTO;
import javalab.umc7th_mission.web.dto.StoreRequestDTO;
import javalab.umc7th_mission.web.dto.StoreResponseDTO;

public class StoreConverter {

public static StoreResponseDTO.AddResultDTO toAddResultDTO(Store store) {
return new StoreResponseDTO.AddResultDTO(
store.getId(),
store.getCreatedAt()
);
}

public static Store toStore(StoreRequestDTO.AddDto request) {
return Store.builder()
.name(request.getName())
.phoneNumber(request.getPhoneNumber())
.description(request.getDescription())
.openingHours(request.getOpeningHours())
.closingHours(request.getClosingHours())
.build();
}

public static StoreAddress toStoreAddress(Store store, Region region, StoreRequestDTO.AddDto request) {
return StoreAddress.builder()
.region(region)
.store(store)
.detailAddress(request.getDetailedAddress())
.zipCode(request.getZipcode())
.build();
}
}


6 changes: 4 additions & 2 deletions src/main/java/javalab/umc7th_mission/domain/Store.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ public class Store extends BaseEntity {
@Column(nullable = false, length = 255)
private String closingHours;

@OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<StoreAddress> addressList = new ArrayList<>();
//24.11.20 StoreAddress와의 관계 재설정!!
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "address_id")
private StoreAddress address;

@OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<StoreCategory> categoryList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public class StoreAddress extends BaseEntity {
@JoinColumn(name = "region_id", referencedColumnName = "id")
private Region region;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "store_id", referencedColumnName = "id")
@OneToOne(mappedBy = "address",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Store store;

@Column(nullable = false, length = 100)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public Long countCompletedMissionByMember(Long memberId, String regionName) {
.from(memberMission)
.join(memberMission.mission, mission)
.join(mission.store, store)
.join(storeAddress, storeAddress)
.join(store.address, storeAddress)
.where(
memberMission.member.id.eq(memberId),
memberMission.status.eq(MissionStatus.COMPLETE),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package javalab.umc7th_mission.repository.StoreAddressRepository;

import javalab.umc7th_mission.domain.mapping.StoreAddress;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StoreAddressRepository extends JpaRepository<StoreAddress, Long>, StoreAddressRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package javalab.umc7th_mission.repository.StoreAddressRepository;

public interface StoreAddressRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package javalab.umc7th_mission.repository.StoreAddressRepository;

public class StoreAddressRepositoryImpl implements StoreAddressRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package javalab.umc7th_mission.service.StoreService;

import javalab.umc7th_mission.domain.Store;
import javalab.umc7th_mission.web.dto.StoreRequestDTO;

public interface StoreCommandService {
// 가게 저장
Store addStore(StoreRequestDTO.AddDto request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package javalab.umc7th_mission.service.StoreService;

import jakarta.transaction.Transactional;
import javalab.umc7th_mission.apiPayload.code.status.ErrorStatus;
import javalab.umc7th_mission.apiPayload.exception.FoodCategoryNotFoundException;
import javalab.umc7th_mission.apiPayload.exception.RegionNotFoundException;
import javalab.umc7th_mission.converter.StoreCategoryConverter;
import javalab.umc7th_mission.converter.StoreConverter;
import javalab.umc7th_mission.domain.FoodCategory;
import javalab.umc7th_mission.domain.Region;
import javalab.umc7th_mission.domain.Store;
import javalab.umc7th_mission.domain.mapping.StoreAddress;
import javalab.umc7th_mission.domain.mapping.StoreCategory;
import javalab.umc7th_mission.repository.FoodCategoryRepository.FoodCategoryRepository;
import javalab.umc7th_mission.repository.RegionRepository.RegionRepository;
import javalab.umc7th_mission.repository.StoreAddressRepository.StoreAddressRepository;
import javalab.umc7th_mission.repository.StoreRepository.StoreRepository;
import javalab.umc7th_mission.web.dto.StoreRequestDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class StoreCommandServiceImpl implements StoreCommandService {
private final StoreRepository storeRepository;
private final StoreAddressRepository storeAddressRepository;
private final RegionRepository regionRepository;
private final FoodCategoryRepository foodCategoryRepository;

@Override
@Transactional
public Store addStore(StoreRequestDTO.AddDto request) {
Region region = regionRepository.findByName(request.getRegion());
if (region == null) {
throw new RegionNotFoundException(ErrorStatus.REGION_NOT_FOUND);
}


Store newStore = StoreConverter.toStore(request);
StoreAddress storeAddress = StoreConverter.toStoreAddress(newStore, region, request);

//StoreCategory 생성필요
//foodCategory list 조회
List<FoodCategory> foodCategoryList = request.getStoreCategory().stream()
.map(category -> {
return foodCategoryRepository.findById(category).orElseThrow(() -> new FoodCategoryNotFoundException(ErrorStatus.FOOD_CATEGORY_NOT_FOUND));
}).collect(Collectors.toList());

List<StoreCategory> storeCategoryList = StoreCategoryConverter.toStoreCategoryList(foodCategoryList);
storeCategoryList.forEach(memberPrefer -> {memberPrefer.setStore(newStore);});

newStore.setCategoryList(storeCategoryList);
storeAddress.setStore(newStore);
newStore.setAddress(storeAddress);

storeAddressRepository.save(storeAddress);
return storeRepository.save(newStore);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package javalab.umc7th_mission.web.controller;

import jakarta.validation.Valid;
import javalab.umc7th_mission.apiPayload.ApiResponse;
import javalab.umc7th_mission.converter.StoreConverter;
import javalab.umc7th_mission.domain.Store;
import javalab.umc7th_mission.service.StoreService.StoreCommandService;
import javalab.umc7th_mission.web.dto.StoreRequestDTO;
import javalab.umc7th_mission.web.dto.StoreResponseDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/stores")
public class StoreRestController {
private final StoreCommandService storeCommandService;

@PostMapping("/")
public ApiResponse<StoreResponseDTO.AddResultDTO> join(@RequestBody @Valid StoreRequestDTO.AddDto request){

Store store = storeCommandService.addStore(request);
return ApiResponse.onSuccess(StoreConverter.toAddResultDTO(store));
}
}
71 changes: 71 additions & 0 deletions src/main/java/javalab/umc7th_mission/web/dto/StoreRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package javalab.umc7th_mission.web.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Getter;

import java.util.List;

public class StoreRequestDTO {

@Getter
public static class AddDto {
@NotBlank(message = "가게 이름은 필수 항목입니다.")
@Size(max = 30, message = "최대 30자까지 입력 가능합니다.")
private final String name;

@NotBlank(message = "전화번호는 필수 항목입니다.")
//전화번호 형식 검증 정규식
@Pattern(regexp = "^(02|0\\d{2})-\\d{3,4}-\\d{4}$", message = "전화번호 형식이 올바르지 않습니다.")
private final String phoneNumber;

@NotBlank(message = "가게 설명은 필수 항목입니다.")
@Size(max = 500, message = "최대 500자까지 입력 가능합니다.")
private final String description;

@NotBlank(message = "오픈 시간은 필수 항목입니다.")
@Size(max = 255, message = "최대 255자까지 입력 가능하니다.")
@Pattern(
regexp = "^([01]?[0-9]|2[0-3]):[0-5][0-9]$",
message = "오픈 시간은 'HH:mm' 형식이어야 합니다. (예: 09:00, 23:59)"
)
private final String openingHours;

@NotBlank(message = "마감 시간은 필수 항목입니다.")
@Size(max = 255, message = "최대 255자까지 입력 가능하니다.")
@Pattern(
regexp = "^([01]?[0-9]|2[0-3]):[0-5][0-9]$",
message = "마감 시간은 'HH:mm' 형식이어야 합니다. (예: 09:00, 23:59)"
)
private final String closingHours;

private final String region;
private final String detailedAddress;
private final String zipcode;
private final List<Long> storeCategory;

public AddDto(
String name,
String phoneNumber,
String description,
String openingHours,
String closingHours,

String region,
String detailedAddress,
String zipcode,
List<Long> storeCategory
) {
this.name = name;
this.phoneNumber = phoneNumber;
this.description = description;
this.openingHours = openingHours;
this.closingHours = closingHours;
this.region = region;
this.detailedAddress = detailedAddress;
this.zipcode = zipcode;
this.storeCategory = storeCategory;
}
}
}
24 changes: 24 additions & 0 deletions src/main/java/javalab/umc7th_mission/web/dto/StoreResponseDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package javalab.umc7th_mission.web.dto;

import java.time.LocalDateTime;

public class StoreResponseDTO {
public static class AddResultDTO {
private final Long storeId;
private final LocalDateTime createdAt;

public AddResultDTO(Long storeId, LocalDateTime createdAt) {
this.storeId = storeId;
this.createdAt = createdAt;
}

// 명시적 Getter (Jackson 직렬화를 위해 필요)
public Long getMemberId() {
return storeId;
}

public LocalDateTime getCreatedAt() {
return createdAt;
}
}
}

0 comments on commit 790c566

Please sign in to comment.