Skip to content

Commit

Permalink
[feat] 식당 제보 api 생성 (#92)
Browse files Browse the repository at this point in the history
* [dependency] add AWS dependency

* [feat] configure AwsConfig.java

* [feat] create S3Service.java

* [fix] add address in Store.java

* [feat] create dtos for post store api

* [fix] change name of dtos used for heart post and delete

* [fix] change controller due to changed dto name

* [fix] add bucket-endpoint in when uploading image

* [fix] add validation in Store post request

* [feat] add all args constructor in Point.java

* [feat] add static factory method in entities

* [feat] add method for checking existence of store by latitude and longitude in StoreRepository.java

* [feat] add method for checking store existence in StoreFinder.java

* [feat] add method for finding university in UniversityFinder.java

* [feat] create MenuUpdater

* [feat] create ReportUpdater

* [feat] create StoreImageUpdater

* [feat] create StoreUpdater

* [feat] create UniversityStoreUpdater

* [fix] add validation in MenuPostRequest.java

* [feat] create University error code

* [feat] add error codes in StoreErrorCode.java

* [feat] create command for Store post api

* [feat] add exceptions in GlobalExceptionHandler.java

* [feat] create store post logic

* [feat] create post store api

* [refac] add final keyword in method parameter

* [refac] fix menu create method's signature

* [refac] add toEntityMethod in StorePostCommand.java

* [refac] add final keyword in StorePostResponse.java

* [refac] delete unused static factory method in Store.java
  • Loading branch information
Parkjyun authored Jul 16, 2024
1 parent bc9c7af commit 76697a1
Show file tree
Hide file tree
Showing 32 changed files with 586 additions and 40 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ dependencies {

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// AWS
implementation("software.amazon.awssdk:s3:2.21.0")
implementation("software.amazon.awssdk:bom:2.21.0")
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import lombok.extern.slf4j.Slf4j;
import org.hankki.hankkiserver.api.dto.HankkiResponse;
import org.hankki.hankkiserver.common.code.BusinessErrorCode;
import org.hankki.hankkiserver.common.code.StoreErrorCode;
import org.hankki.hankkiserver.common.exception.BadRequestException;
import org.hankki.hankkiserver.common.exception.ConflictException;
import org.hankki.hankkiserver.common.exception.NotFoundException;
import org.hankki.hankkiserver.common.exception.UnauthorizedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

@RestControllerAdvice
@Slf4j
Expand Down Expand Up @@ -45,9 +48,21 @@ public HankkiResponse<Void> handleMissingServletRequestParameterException(Missin
return HankkiResponse.fail(BusinessErrorCode.BAD_REQUEST);
}

@ExceptionHandler(MaxUploadSizeExceededException.class)
public HankkiResponse<Void> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error("handleMaxUploadSizeExceededException() in GlobalExceptionHandler throw MaxUploadSizeExceededException : {}", e.getMessage());
return HankkiResponse.fail(StoreErrorCode.STORE_FILE_SIZE_EXCEEDED);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public HankkiResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException() in GlobalExceptionHandler throw MethodArgumentNotValidException : {}", e.getMessage());
return HankkiResponse.fail(BusinessErrorCode.BAD_REQUEST);
}

@ExceptionHandler(Exception.class)
public HankkiResponse<Void> handleException(Exception e) {
log.error("handleException() in GlobalExceptionHandler throw [{}] : {}, {}", e.getClass(), e.getMessage(), e.getStackTrace());
log.error("handleException() in GlobalExceptionHandler throw Exception [{}] : {}", e.getClass() , e.getMessage());
return HankkiResponse.fail(BusinessErrorCode.INTERNAL_SERVER_ERROR);

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.hankki.hankkiserver.api.favorite.service;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.common.code.BusinessErrorCode;
import org.hankki.hankkiserver.common.exception.InternalServerException;
import org.hankki.hankkiserver.domain.favorite.model.Favorite;
import org.hankki.hankkiserver.domain.favorite.repository.FavoriteRepository;
import org.springframework.stereotype.Component;
Expand All @@ -14,7 +11,7 @@ public class FavoriteUpdater {

private final FavoriteRepository favoriteRepository;

protected Long save(Favorite favorite) {
protected Long save(final Favorite favorite) {
return favoriteRepository.save(favorite).getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class MenuFinder {

private final MenuRepository menuRepository;

public List<Menu> findAllByStore(Store store) {
public List<Menu> findAllByStore(final Store store) {
return menuRepository.findAllByStore(store);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hankki.hankkiserver.api.menu.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.domain.menu.model.Menu;
import org.hankki.hankkiserver.domain.menu.repository.MenuRepository;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class MenuUpdater {

private final MenuRepository menuRepository;

public void saveAll(final List<Menu> menus) {
menuRepository.saveAll(menus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.hankki.hankkiserver.api.report.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.domain.report.model.Report;
import org.hankki.hankkiserver.domain.report.repository.ReportRepository;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ReportUpdater {

private final ReportRepository repository;

public void save(final Report report) {
repository.save(report);
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
package org.hankki.hankkiserver.api.store.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.dto.HankkiResponse;
import org.hankki.hankkiserver.api.store.controller.request.StoreDuplicateValidationRequest;
import org.hankki.hankkiserver.api.store.controller.request.StorePostRequest;
import org.hankki.hankkiserver.api.store.service.StoreCommandService;
import org.hankki.hankkiserver.api.store.service.StoreQueryService;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.response.*;
import org.hankki.hankkiserver.common.code.CommonSuccessCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.hankki.hankkiserver.auth.UserId;
import org.hankki.hankkiserver.api.store.service.HeartCommandService;
import org.hankki.hankkiserver.api.store.service.StoreQueryService;
import org.hankki.hankkiserver.api.store.service.command.StoreDeleteCommand;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.command.StoreValidationCommand;
import org.hankki.hankkiserver.api.store.service.response.*;
import org.hankki.hankkiserver.auth.UserId;
import org.hankki.hankkiserver.common.code.CommonSuccessCode;
import org.hankki.hankkiserver.api.store.service.command.HeartDeleteCommand;
import org.hankki.hankkiserver.api.store.service.command.HeartPostCommand;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class StoreController {

private final StoreCommandService storeCommandService;
private final StoreQueryService storeQueryService;
private final HeartCommandService heartCommandService;

Expand Down Expand Up @@ -49,17 +65,23 @@ public HankkiResponse<PriceCategoriesResponse> getPrices() {

@PostMapping("/stores/{id}/hearts")
public HankkiResponse<HeartCreateResponse> createHeartStore(@UserId final Long userId, @PathVariable final Long id) {
return HankkiResponse.success(CommonSuccessCode.CREATED, heartCommandService.createHeart(StorePostCommand.of(userId, id)));
return HankkiResponse.success(CommonSuccessCode.CREATED, heartCommandService.createHeart(HeartPostCommand.of(userId, id)));
}

@DeleteMapping("/stores/{id}/hearts")
public HankkiResponse<HeartDeleteResponse> deleteHeartStore(@UserId final Long userId, @PathVariable final Long id) {
return HankkiResponse.success(CommonSuccessCode.OK, heartCommandService.deleteHeart(StoreDeleteCommand.of(userId, id)));
return HankkiResponse.success(CommonSuccessCode.OK, heartCommandService.deleteHeart(HeartDeleteCommand.of(userId, id)));
}

@PostMapping("/stores/validate")
public HankkiResponse<Void> validateDuplicatedStore(@RequestBody final StoreDuplicateValidationRequest request) {
storeQueryService.validateDuplicatedStore(StoreValidationCommand.of(request));
return HankkiResponse.success(CommonSuccessCode.OK);
}
@PostMapping("/stores")
public HankkiResponse<StorePostResponse> createStore(@RequestPart(required = false) final MultipartFile image,
@Valid @RequestPart final StorePostRequest request,
@UserId final Long userId) {
return HankkiResponse.success(CommonSuccessCode.CREATED, storeCommandService.createStore(StorePostCommand.of(image, request, userId)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.hankki.hankkiserver.api.store.controller.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record MenuPostRequest(
@NotBlank @Size(max = 30)
String name,
@Max(8000)
int price
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hankki.hankkiserver.api.store.controller.request;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import org.hankki.hankkiserver.domain.store.model.StoreCategory;

import java.util.List;

public record StorePostRequest(
String name,
StoreCategory category,
String address,
double latitude,
double longitude,
long universityId,
@Size(min = 1) @Valid
List<MenuPostRequest> menus
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.service.UserFinder;
import org.hankki.hankkiserver.api.store.service.command.StoreDeleteCommand;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.command.HeartDeleteCommand;
import org.hankki.hankkiserver.api.store.service.command.HeartPostCommand;
import org.hankki.hankkiserver.api.store.service.response.HeartCreateResponse;
import org.hankki.hankkiserver.api.store.service.response.HeartDeleteResponse;
import org.hankki.hankkiserver.common.code.HeartErrorCode;
Expand All @@ -25,18 +25,18 @@ public class HeartCommandService {
private final UserFinder userFinder;
private final StoreFinder storeFinder;

public HeartCreateResponse createHeart(final StorePostCommand storePostCommand) {
User user = userFinder.getUserReference(storePostCommand.userId());
Store store = storeFinder.getStoreReference(storePostCommand.storeId());
public HeartCreateResponse createHeart(final HeartPostCommand heartPostCommand) {
User user = userFinder.getUserReference(heartPostCommand.userId());
Store store = storeFinder.getStoreReference(heartPostCommand.storeId());
validateStoreHeartCreation(user, store);
saveStoreHeart(user, store);
increaseStoreHeartCount(store);
return HeartCreateResponse.of(store);
}

public HeartDeleteResponse deleteHeart(final StoreDeleteCommand storeDeleteCommand) {
User user = userFinder.getUserReference(storeDeleteCommand.userId());
Store store = storeFinder.getStoreReference(storeDeleteCommand.storeId());
public HeartDeleteResponse deleteHeart(final HeartDeleteCommand heartDeleteCommand) {
User user = userFinder.getUserReference(heartDeleteCommand.userId());
Store store = storeFinder.getStoreReference(heartDeleteCommand.storeId());
validateStoreHeartRemoval(user, store);
heartDeleter.deleteHeart(user,store);
decreaseStoreHeartCount(store);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.hankki.hankkiserver.api.store.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.api.auth.service.UserFinder;
import org.hankki.hankkiserver.api.menu.service.MenuUpdater;
import org.hankki.hankkiserver.api.report.service.ReportUpdater;
import org.hankki.hankkiserver.api.store.service.command.StorePostCommand;
import org.hankki.hankkiserver.api.store.service.response.StorePostResponse;
import org.hankki.hankkiserver.api.university.service.UniversityFinder;
import org.hankki.hankkiserver.api.universitystore.service.UniversityStoreUpdater;
import org.hankki.hankkiserver.common.code.StoreErrorCode;
import org.hankki.hankkiserver.common.exception.BadRequestException;
import org.hankki.hankkiserver.domain.menu.model.Menu;
import org.hankki.hankkiserver.domain.report.model.Report;
import org.hankki.hankkiserver.domain.store.model.Store;
import org.hankki.hankkiserver.domain.store.model.StoreImage;
import org.hankki.hankkiserver.domain.university.model.University;
import org.hankki.hankkiserver.domain.universitystore.model.UniversityStore;
import org.hankki.hankkiserver.external.s3.S3Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.List;

@Service
@RequiredArgsConstructor
public class StoreCommandService {

@Value("${store.default-image}")
private String DEFAULT_IMAGE_URL;

private static final String STORE_IMAGE_DIRECTORY = "store/";

private final StoreUpdater storeUpdater;
private final MenuUpdater menuUpdater;
private final StoreImageUpdater storeImageUpdater;
private final S3Service s3Service;
private final UniversityStoreUpdater universityStoreUpdater;
private final UniversityFinder universityFinder;
private final ReportUpdater reportUpdater;
private final UserFinder userFinder;
private final StoreFinder storeFinder;

@Transactional(rollbackFor = Exception.class)
public StorePostResponse createStore(final StorePostCommand command) {
if (storeExists(command.latitude(), command.longitude())) {
throw new BadRequestException(StoreErrorCode.BAD_STORE_INFO);
}

Store store = storeUpdater.save(command.toEntity());
saveImages(command, store);
menuUpdater.saveAll(getMenus(command, store));

University university = universityFinder.findById(command.universityId());
universityStoreUpdater.save(UniversityStore.create(store, university));
reportUpdater.save(Report.create(userFinder.getUser(command.userId()), store, university));

return StorePostResponse.of(store);
}

private boolean storeExists(final double latitude, final double longitude) {
return storeFinder.existsByLatitudeAndLongitude(latitude, longitude);
}

private void saveImages(final StorePostCommand command, final Store store) {
if (isNullOrEmptyImage(command)) {
storeImageUpdater.saveImage(StoreImage.createImage(store, DEFAULT_IMAGE_URL));
}
else {
try {
String imageUrl = s3Service.uploadImage(STORE_IMAGE_DIRECTORY, command.image());
storeImageUpdater.saveImage(StoreImage.createImage(store, imageUrl));
} catch (IOException e) {
storeImageUpdater.saveImage(StoreImage.createImage(store, DEFAULT_IMAGE_URL));
}
}
}

private boolean isNullOrEmptyImage(final StorePostCommand command) {
return command.image() == null || command.image().isEmpty();
}

private List<Menu> getMenus(final StorePostCommand command, final Store store) {
return command.menus().stream()//메뉴를 엔티티로 저장한다.
.map(menuPostRequest -> Menu.create(store, menuPostRequest.name(), menuPostRequest.price()))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ protected Store findByIdWithHeartAndIsDeletedFalse(final Long id) {
protected Optional<Store> findByLatitudeAndLongitude(final double latitude, final double longitude) {
return storeRepository.findByPoint_LatitudeAndPoint_Longitude(latitude, longitude);
}
protected boolean existsByLatitudeAndLongitude(final double latitude, final double longitude) {
return storeRepository.existsByPoint_LatitudeAndPoint_Longitude(latitude, longitude);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.hankki.hankkiserver.api.store.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.domain.store.model.StoreImage;
import org.hankki.hankkiserver.domain.store.repository.StoreImageRepository;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class StoreImageUpdater {

private final StoreImageRepository storeImageRepository;

public void saveImage(final StoreImage image) {
storeImageRepository.save(image);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.hankki.hankkiserver.api.store.service;

import lombok.RequiredArgsConstructor;
import org.hankki.hankkiserver.domain.store.model.Store;
import org.hankki.hankkiserver.domain.store.repository.StoreRepository;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class StoreUpdater {

private final StoreRepository storeRepository;

public Store save(final Store store) {
return storeRepository.save(store);
}
}
Loading

0 comments on commit 76697a1

Please sign in to comment.