From fb16ee56e3b534127f2f557e141fd253b197e7cc Mon Sep 17 00:00:00 2001 From: tinon1004 <tinon1004@ajou.ac.kr> Date: Fri, 26 Apr 2024 19:19:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20#107=20=ED=95=A9=EC=A3=BC=EC=8B=A4=20?= =?UTF-8?q?=EB=A7=A4=EB=AC=BC=20=EC=83=9D=EC=84=B1=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ajou/hertz/common/dto/CoordinateDto.java | 20 ++ .../ajou/hertz/common/dto/FullAddressDto.java | 29 +++ .../common/dto/request/CoordinateRequest.java | 26 +++ .../dto/request/FullAddressRequest.java | 25 +++ .../dto/response/CoordinateResponse.java | 24 +++ .../dto/response/FullAddressResponse.java | 40 ++++ .../ajou/hertz/common/entity/Coordinate.java | 11 +- .../ajou/hertz/common/entity/FullAddress.java | 74 ++++--- .../constant/CustomExceptionType.java | 78 ++++---- .../controller/PracticeRoomController.java | 67 +++++++ .../practice_room/dto/PracticeRoomDto.java | 61 ++++++ .../request/CreateNewPracticeRoomRequest.java | 102 ++++++++++ .../response/PracticeRoomImageResponse.java | 35 ++++ .../dto/response/PracticeRoomResponse.java | 91 +++++++++ .../practice_room/entity/PracticeRoom.java | 181 ++++++++++++------ .../entity/PracticeRoomDescription.java | 49 +++++ .../entity/PracticeRoomHashtags.java | 20 +- .../entity/PracticeRoomImages.java | 21 +- ...inkInPracticeRoomDescriptionException.java | 10 + .../service/PracticeRoomCommandService.java | 54 ++++++ .../PracticeRoomHashtagCommandService.java | 32 ++++ .../PracticeRoomImageCommandService.java | 43 +++++ 22 files changed, 940 insertions(+), 153 deletions(-) create mode 100644 src/main/java/com/ajou/hertz/common/dto/CoordinateDto.java create mode 100644 src/main/java/com/ajou/hertz/common/dto/FullAddressDto.java create mode 100644 src/main/java/com/ajou/hertz/common/dto/request/CoordinateRequest.java create mode 100644 src/main/java/com/ajou/hertz/common/dto/request/FullAddressRequest.java create mode 100644 src/main/java/com/ajou/hertz/common/dto/response/CoordinateResponse.java create mode 100644 src/main/java/com/ajou/hertz/common/dto/response/FullAddressResponse.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/controller/PracticeRoomController.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomImageResponse.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomResponse.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomDescription.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/exception/UnexpectedLinkInPracticeRoomDescriptionException.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomHashtagCommandService.java create mode 100644 src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomImageCommandService.java diff --git a/src/main/java/com/ajou/hertz/common/dto/CoordinateDto.java b/src/main/java/com/ajou/hertz/common/dto/CoordinateDto.java new file mode 100644 index 0000000..6e8f7f6 --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/CoordinateDto.java @@ -0,0 +1,20 @@ +package com.ajou.hertz.common.dto; + +import com.ajou.hertz.common.entity.Coordinate; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class CoordinateDto { + + private String lat; + private String lng; + + public static CoordinateDto from(Coordinate coordinate) { + return new CoordinateDto(coordinate.getLat(), coordinate.getLng()); + } +} diff --git a/src/main/java/com/ajou/hertz/common/dto/FullAddressDto.java b/src/main/java/com/ajou/hertz/common/dto/FullAddressDto.java new file mode 100644 index 0000000..ca0807f --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/FullAddressDto.java @@ -0,0 +1,29 @@ +package com.ajou.hertz.common.dto; + +import com.ajou.hertz.common.entity.FullAddress; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class FullAddressDto { + + private String sido; + private String sgg; + private String lotNumberAddress; + private String roadAddress; + private String detailAddress; + + public static FullAddressDto from(FullAddress fullAddress) { + return new FullAddressDto( + fullAddress.getSido(), + fullAddress.getSgg(), + fullAddress.getLotNumberAddress(), + fullAddress.getRoadAddress(), + fullAddress.getDetailAddress() + ); + } +} diff --git a/src/main/java/com/ajou/hertz/common/dto/request/CoordinateRequest.java b/src/main/java/com/ajou/hertz/common/dto/request/CoordinateRequest.java new file mode 100644 index 0000000..6b9d38e --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/request/CoordinateRequest.java @@ -0,0 +1,26 @@ +package com.ajou.hertz.common.dto.request; + +import com.ajou.hertz.common.entity.Coordinate; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Setter +@Getter +public class CoordinateRequest { + + @Schema(description = "위도", example = "37.123456") + @NotBlank + private String lat; + + @Schema(description = "경도", example = "127.123456") + @NotBlank + private String lng; + + + public Coordinate toEntity() { + return new Coordinate(lat, lng); + } +} diff --git a/src/main/java/com/ajou/hertz/common/dto/request/FullAddressRequest.java b/src/main/java/com/ajou/hertz/common/dto/request/FullAddressRequest.java new file mode 100644 index 0000000..9c7e276 --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/request/FullAddressRequest.java @@ -0,0 +1,25 @@ +package com.ajou.hertz.common.dto.request; + +import com.ajou.hertz.common.entity.FullAddress; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Setter +@Getter +public class FullAddressRequest { + + @Schema(description = "전체 주소", example = "서울특별시 강남구 역삼동 123-45") + @NotBlank + private String fullAddress; + + @Schema(description = "상세주소", example = "아주 빌딩 2층") + @NotBlank + private String detailAddress; + + public FullAddress toEntity() { + return FullAddress.of(fullAddress, detailAddress); + } +} diff --git a/src/main/java/com/ajou/hertz/common/dto/response/CoordinateResponse.java b/src/main/java/com/ajou/hertz/common/dto/response/CoordinateResponse.java new file mode 100644 index 0000000..78b0a5d --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/response/CoordinateResponse.java @@ -0,0 +1,24 @@ +package com.ajou.hertz.common.dto.response; + +import com.ajou.hertz.common.dto.CoordinateDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class CoordinateResponse { + + @Schema(description = "위도", example = "37.123456") + private String lat; + + @Schema(description = "경도", example = "127.123456") + private String lng; + + public static CoordinateResponse from(CoordinateDto coordinateDto) { + return new CoordinateResponse(coordinateDto.getLat(), coordinateDto.getLng()); + } +} diff --git a/src/main/java/com/ajou/hertz/common/dto/response/FullAddressResponse.java b/src/main/java/com/ajou/hertz/common/dto/response/FullAddressResponse.java new file mode 100644 index 0000000..6f1dc0e --- /dev/null +++ b/src/main/java/com/ajou/hertz/common/dto/response/FullAddressResponse.java @@ -0,0 +1,40 @@ +package com.ajou.hertz.common.dto.response; + +import com.ajou.hertz.common.dto.FullAddressDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class FullAddressResponse { + + + @Schema(description = "시/도", example = "서울특별시") + private String sido; + + @Schema(description = "시/군/구", example = "강남구") + private String sgg; + + @Schema(description = "지번주소", example = "수원시 팔달구 권선로11") + private String lotNumberAddress; + + @Schema(description = "도로명 주소", example = "서울특별시 강남구 역삼동 123-45번지") + private String roadAddress; + + @Schema(description = "상세주소", example = "아주 빌딩 2층") + private String detailAddress; + + public static FullAddressResponse from(FullAddressDto fullAddressDto) { + return new FullAddressResponse( + fullAddressDto.getSido(), + fullAddressDto.getSgg(), + fullAddressDto.getLotNumberAddress(), + fullAddressDto.getRoadAddress(), + fullAddressDto.getDetailAddress() + ); + } +} diff --git a/src/main/java/com/ajou/hertz/common/entity/Coordinate.java b/src/main/java/com/ajou/hertz/common/entity/Coordinate.java index bcc5153..e0c683c 100644 --- a/src/main/java/com/ajou/hertz/common/entity/Coordinate.java +++ b/src/main/java/com/ajou/hertz/common/entity/Coordinate.java @@ -8,15 +8,14 @@ import lombok.NoArgsConstructor; @AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Embeddable public class Coordinate { - @Column(nullable = false) - private String lat; - - @Column(nullable = false) - private String lng; + @Column(nullable = false) + private String lat; + @Column(nullable = false) + private String lng; } diff --git a/src/main/java/com/ajou/hertz/common/entity/FullAddress.java b/src/main/java/com/ajou/hertz/common/entity/FullAddress.java index 9421c3d..91a50a9 100644 --- a/src/main/java/com/ajou/hertz/common/entity/FullAddress.java +++ b/src/main/java/com/ajou/hertz/common/entity/FullAddress.java @@ -1,9 +1,6 @@ package com.ajou.hertz.common.entity; -import java.util.Arrays; - import com.ajou.hertz.common.exception.InvalidAddressFormatException; - import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AccessLevel; @@ -11,58 +8,59 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.Arrays; + @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Embeddable public class FullAddress { - @Column(nullable = false) - private String sido; - - @Column(nullable = false) - private String sgg; + @Column(nullable = false) + private String sido; - private String lotNumberAddress; + @Column(nullable = false) + private String sgg; - private String roadAddress; + private String lotNumberAddress; - @Column(nullable = false) - private String detailAddress; + private String roadAddress; - public static FullAddress of(String fullAddress, String detailAddress) { + @Column(nullable = false) + private String detailAddress; - String[] parsedAddress = fullAddress.split(" "); + public static FullAddress of(String fullAddress, String detailAddress) { - if (parsedAddress.length < 2) { - throw new InvalidAddressFormatException(fullAddress); + String[] parsedAddress = fullAddress.split(" "); - } + if (parsedAddress.length < 2) { + throw new InvalidAddressFormatException(fullAddress); - String sido = parsedAddress[0]; - StringBuilder sggBuilder = new StringBuilder(); - String lotNumberAddress = null; - String roadAddress = null; + } - int num; - for (num = 1; num < parsedAddress.length; num++) { - if (parsedAddress[num].matches(".*[동면읍소로길]$")) { - break; - } - sggBuilder.append(parsedAddress[num]).append(" "); - } + String sido = parsedAddress[0]; + StringBuilder sggBuilder = new StringBuilder(); + String lotNumberAddress = null; + String roadAddress = null; - String sgg = sggBuilder.toString().trim(); + int num; + for (num = 1; num < parsedAddress.length; num++) { + if (parsedAddress[num].matches(".*[동면읍소로길]$")) { + break; + } + sggBuilder.append(parsedAddress[num]).append(" "); + } - if (parsedAddress.length > num) { - if (fullAddress.matches(".+[로길].+")) { - roadAddress = String.join(" ", Arrays.copyOfRange(parsedAddress, num, parsedAddress.length)); - } else { - lotNumberAddress = String.join(" ", Arrays.copyOfRange(parsedAddress, num, parsedAddress.length)); - } - } + String sgg = sggBuilder.toString().trim(); - return new FullAddress(sido, sgg, lotNumberAddress, roadAddress, detailAddress); - } + if (parsedAddress.length > num) { + if (fullAddress.matches(".+[로길].+")) { + roadAddress = String.join(" ", Arrays.copyOfRange(parsedAddress, num, parsedAddress.length)); + } else { + lotNumberAddress = String.join(" ", Arrays.copyOfRange(parsedAddress, num, parsedAddress.length)); + } + } + return new FullAddress(sido, sgg, lotNumberAddress, roadAddress, detailAddress); + } } \ No newline at end of file diff --git a/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java b/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java index 70664b3..7fe906b 100644 --- a/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java +++ b/src/main/java/com/ajou/hertz/common/exception/constant/CustomExceptionType.java @@ -13,52 +13,58 @@ * <li>2200 ~ 2399: 유저 관련 예외</li> * <li>2400 ~ 2599: 주소 관련 예외</li> * <li>2600 ~ 2799: 악기 관련 예외</li> + * <li>2800 ~ 2999: 악기 관련 예외</li> * </ul> */ @AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter public enum CustomExceptionType { - MULTIPART_FILE_NOT_READABLE(1000, "파일을 읽을 수 없습니다. 올바른 파일인지 다시 확인 후 요청해주세요."), - IO_PROCESSING(1001, "입출력 처리 중 오류가 발생했습니다."), - SEND_MESSAGE(1002, "문자 발송 과정에서 알 수 없는 에러가 발생했습니다."), + MULTIPART_FILE_NOT_READABLE(1000, "파일을 읽을 수 없습니다. 올바른 파일인지 다시 확인 후 요청해주세요."), + IO_PROCESSING(1001, "입출력 처리 중 오류가 발생했습니다."), + SEND_MESSAGE(1002, "문자 발송 과정에서 알 수 없는 에러가 발생했습니다."), - /** - * 로그인, 인증 관련 예외 - */ - ACCESS_DENIED(2000, "접근이 거부되었습니다."), - UNAUTHORIZED(2001, "유효하지 않은 인증 정보로 인해 인증 과정에서 문제가 발생하였습니다."), - TOKEN_VALIDATE(2002, "유효하지 않은 token입니다. Token 값이 잘못되었거나 만료되어 유효하지 않은 경우로 token 갱신이 필요합니다."), - PASSWORD_MISMATCH(2003, "비밀번호가 일치하지 않습니다."), - USER_AUTH_CODE_NOT_FOUND(2004, "본인 인증 코드 발행 이력을 찾을 수 없습니다. 인증 코드가 만료되었거나 잘못된 코드가 전송되었을 수 있습니다. 다시 시도해주세요."), - INVALID_AUTH_CODE(2005, "유효하지 않은 인증 코드 또는 전화번호입니다. 인증 코드가 만료되었거나 잘못된 정보가 전송되었을 수 있습니다. 다시 시도해주세요."), + /** + * 로그인, 인증 관련 예외 + */ + ACCESS_DENIED(2000, "접근이 거부되었습니다."), + UNAUTHORIZED(2001, "유효하지 않은 인증 정보로 인해 인증 과정에서 문제가 발생하였습니다."), + TOKEN_VALIDATE(2002, "유효하지 않은 token입니다. Token 값이 잘못되었거나 만료되어 유효하지 않은 경우로 token 갱신이 필요합니다."), + PASSWORD_MISMATCH(2003, "비밀번호가 일치하지 않습니다."), + USER_AUTH_CODE_NOT_FOUND(2004, "본인 인증 코드 발행 이력을 찾을 수 없습니다. 인증 코드가 만료되었거나 잘못된 코드가 전송되었을 수 있습니다. 다시 시도해주세요."), + INVALID_AUTH_CODE(2005, "유효하지 않은 인증 코드 또는 전화번호입니다. 인증 코드가 만료되었거나 잘못된 정보가 전송되었을 수 있습니다. 다시 시도해주세요."), - /** - * 유저 관련 예외 - */ - USER_EMAIL_DUPLICATION(2200, "이미 사용 중인 이메일입니다."), - USER_NOT_FOUND_BY_ID(2201, "일치하는 회원을 찾을 수 없습니다."), - USER_NOT_FOUND_BY_EMAIL(2202, "일치하는 회원을 찾을 수 없습니다."), - USER_PHONE_DUPLICATION(2203, "이미 사용 중인 전화번호입니다."), - USER_KAKAO_UID_DUPLICATION(2204, "이미 가입한 계정입니다."), - USER_NOT_FOUND_BY_KAKAO_UID(2205, "일치하는 회원을 찾을 수 없습니다."), - USER_NOT_FOUND_BY_PHONE(2206, "일치하는 회원을 찾을 수 없습니다."), + /** + * 유저 관련 예외 + */ + USER_EMAIL_DUPLICATION(2200, "이미 사용 중인 이메일입니다."), + USER_NOT_FOUND_BY_ID(2201, "일치하는 회원을 찾을 수 없습니다."), + USER_NOT_FOUND_BY_EMAIL(2202, "일치하는 회원을 찾을 수 없습니다."), + USER_PHONE_DUPLICATION(2203, "이미 사용 중인 전화번호입니다."), + USER_KAKAO_UID_DUPLICATION(2204, "이미 가입한 계정입니다."), + USER_NOT_FOUND_BY_KAKAO_UID(2205, "일치하는 회원을 찾을 수 없습니다."), + USER_NOT_FOUND_BY_PHONE(2206, "일치하는 회원을 찾을 수 없습니다."), - KAKAO_CLIENT(10000, "카카오 서버와의 통신 중 오류가 발생했습니다."), + KAKAO_CLIENT(10000, "카카오 서버와의 통신 중 오류가 발생했습니다."), - /** - * 주소 관련 예외 - */ - INVALID_ADDRESS_FORMAT(2400, "주소 형식이 올바르지 않습니다."), + /** + * 주소 관련 예외 + */ + INVALID_ADDRESS_FORMAT(2400, "주소 형식이 올바르지 않습니다."), - /** - * 악기 관련 예외 - */ - INSTRUMENT_NOT_FOUND_BY_ID(2600, "일치하는 매물 정보를 찾을 수 없습니다."), - INSTRUMENT_DELETE_PERMISSION_DENIED(2601, "악기 매물을 삭제할 권한이 없습니다. 매물은 판매자 본인만 삭제할 수 있습니다."), - INSTRUMENT_UPDATE_PERMISSION_DENIED(2602, "악기 매물 정보를 수정할 권한이 없습니다. 매물 정보는 판매자 본인만 수정할 수 있습니다."), - UNEXPECTED_LINK_IN_INSTRUMENT_DESCRIPTION(2603, "악기 설명에는 링크(URL)를 첨부할 수 없습니다. 링크 정보를 제거한 후 다시 시도하세요."); + /** + * 악기 관련 예외 + */ + INSTRUMENT_NOT_FOUND_BY_ID(2600, "일치하는 매물 정보를 찾을 수 없습니다."), + INSTRUMENT_DELETE_PERMISSION_DENIED(2601, "악기 매물을 삭제할 권한이 없습니다. 매물은 판매자 본인만 삭제할 수 있습니다."), + INSTRUMENT_UPDATE_PERMISSION_DENIED(2602, "악기 매물 정보를 수정할 권한이 없습니다. 매물 정보는 판매자 본인만 수정할 수 있습니다."), + UNEXPECTED_LINK_IN_INSTRUMENT_DESCRIPTION(2603, "악기 설명에는 링크(URL)를 첨부할 수 없습니다. 링크 정보를 제거한 후 다시 시도하세요."), - private final Integer code; - private final String message; + /** + * 합주실 관련 예외 + */ + UNEXPECTED_LINK_IN_PRACTICEROOM_DESCRIPTION(2800, "합주실 관련 설명에는 링크(URL)를 첨부할 수 없습니다. 링크 정보를 제거한 후 다시 시도하세요."); + + private final Integer code; + private final String message; } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/controller/PracticeRoomController.java b/src/main/java/com/ajou/hertz/domain/practice_room/controller/PracticeRoomController.java new file mode 100644 index 0000000..fe28a91 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/controller/PracticeRoomController.java @@ -0,0 +1,67 @@ +package com.ajou.hertz.domain.practice_room.controller; + +import com.ajou.hertz.common.auth.UserPrincipal; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import com.ajou.hertz.domain.practice_room.dto.request.CreateNewPracticeRoomRequest; +import com.ajou.hertz.domain.practice_room.dto.response.PracticeRoomResponse; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomCommandService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +import static com.ajou.hertz.common.constant.GlobalConstants.API_VERSION_HEADER_NAME; + +@Tag(name = "합주실 관련 API") +@RequiredArgsConstructor +@RequestMapping("/api/practice-rooms") +@RestController +public class PracticeRoomController { + + private final PracticeRoomCommandService practiceRoomCommandService; + + @Operation( + summary = "합주실 매물 등록", + description = """ + <p>합주실 매물을 등록합니다. + <p>요청 시 <strong>multipart/form-data</strong> content-type으로 요쳥해야 합니다. + """, + security = @SecurityRequirement(name = "access-token") + ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2800] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) + @PostMapping( + headers = API_VERSION_HEADER_NAME + "=" + 1, + consumes = MediaType.MULTIPART_FORM_DATA_VALUE + ) + public ResponseEntity<PracticeRoomResponse> createNewPracticeRoomV1( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @ParameterObject @ModelAttribute @Valid CreateNewPracticeRoomRequest createNewPracticeRoomRequest + ) { + PracticeRoomDto practiceRoomCreated = practiceRoomCommandService.createNewPracticeRoom( + userPrincipal.getUserId(), + createNewPracticeRoomRequest + ); + return ResponseEntity + .created(URI.create("/api/practice-rooms/" + practiceRoomCreated.getId())) + .body(PracticeRoomResponse.from(practiceRoomCreated)); + } + + +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java new file mode 100644 index 0000000..1ae44f0 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java @@ -0,0 +1,61 @@ +package com.ajou.hertz.domain.practice_room.dto; + +import com.ajou.hertz.common.dto.CoordinateDto; +import com.ajou.hertz.common.dto.FullAddressDto; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.user.dto.UserDto; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class PracticeRoomDto { + + private Long id; + private UserDto seller; + private String title; + private FullAddressDto tradeAddress; + private Boolean hasSoundEquipment; + private Boolean hasInstrument; + private Integer pricePerDay; + private Integer pricePerHour; + private Integer pricePerMonth; + private Short capacity; + private String size; + private Boolean hasParkingLot; + private String description; + private CoordinateDto coordinate; + private List<PracticeRoomImageDto> images; + private List<String> hashtags; + + public PracticeRoomDto(PracticeRoom practiceRoom) { + this( + practiceRoom.getId(), + UserDto.from(practiceRoom.getSeller()), + practiceRoom.getTitle(), + FullAddressDto.from(practiceRoom.getTradeAddress()), + practiceRoom.getHasSoundEquipment(), + practiceRoom.getHasInstrument(), + practiceRoom.getPricePerDay(), + practiceRoom.getPricePerHour(), + practiceRoom.getPricePerMonth(), + practiceRoom.getCapacity(), + practiceRoom.getSize(), + practiceRoom.getHasParkingLot(), + practiceRoom.getDescription().getDescription(), + CoordinateDto.from(practiceRoom.getCoordinate()), + practiceRoom.getImages().toDtos(), + practiceRoom.getHashtags().toStrings() + ); + } + + public static PracticeRoomDto from(PracticeRoom practiceRoom) { + return new PracticeRoomDto(practiceRoom); + } + +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java new file mode 100644 index 0000000..8288167 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java @@ -0,0 +1,102 @@ +package com.ajou.hertz.domain.practice_room.dto.request; + +import com.ajou.hertz.common.dto.request.CoordinateRequest; +import com.ajou.hertz.common.dto.request.FullAddressRequest; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.user.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.validator.constraints.Length; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Setter // for multipart/form-data with @ModelAttribute +@Getter +public class CreateNewPracticeRoomRequest { + + @Schema(description = "매물 제목", example = "Ajou Practice Room") + @NotBlank + private String title; + + @Schema(description = "연습실 장소") + @NotNull + private FullAddressRequest tradeAddress; + + @Schema(description = "좌표") + @NotNull + private CoordinateRequest coordinate; + + @Schema(description = "사운드 장비 여부", example = "true") + @NotNull + private Boolean hasSoundEquipment; + + @Schema(description = "악기 여부", example = "true") + @NotNull + private Boolean hasInstrument; + + @Schema(description = "하루 대여 가격", example = "100000") + @NotNull + private Integer pricePerDay; + + @Schema(description = "시간 당 가격", example = "10000") + @NotNull + private Integer pricePerHour; + + @Schema(description = "월 별 대여 가격", example = "100000") + @NotNull + private Integer pricePerMonth; + + @Schema(description = "수용 인원", example = "100") + @NotNull + private Short capacity; + + @Schema(description = "합주실 크기", example = "1000") + @NotNull + private String size; + + @Schema(description = "주차장 여부", example = "true") + @NotNull + private Boolean hasParkingLot; + + @Schema(description = "특이사항 및 상세 설명. 내용이 없을 경우에는 빈 문자열로 요청하면 됩니다.", example = "Ajou PracticeRoom은 수원시에 위치해 있으며...") + @NotNull + private String description; + + @Schema(description = "합주실 이미지 (최소 4개, 최대 7개)") + @NotNull + @Size(min = 4, max = 7) + private List<MultipartFile> images; + + @Schema(description = "해시태그(각 해시태그마다 최대 10글자)", example = "[\"밴드\", \"락\"]") + private List<@NotBlank @Length(max = 10) String> hashtags; + + + public PracticeRoom toEntity(User seller) { + return PracticeRoom.create( + seller, + getTitle(), + getDescription(), + getCapacity(), + getSize(), + getHasSoundEquipment(), + getHasInstrument(), + getPricePerHour(), + getPricePerDay(), + getPricePerMonth(), + getHasParkingLot(), + getTradeAddress().toEntity(), + getCoordinate().toEntity() + ); + } + +} + diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomImageResponse.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomImageResponse.java new file mode 100644 index 0000000..6c00e18 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomImageResponse.java @@ -0,0 +1,35 @@ +package com.ajou.hertz.domain.practice_room.dto.response; + +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomImageDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.annotation.Nullable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class PracticeRoomImageResponse { + + @Schema(description = "Id of instrument image", example = "3") + private Long id; + + @Schema(description = "이미지 이름", example = "fender-guitar.jpg") + private String name; + + @Schema(description = "이미지 url", example = "https://instrument-image-url") + private String url; + + public static PracticeRoomImageResponse from(@Nullable PracticeRoomImageDto practiceRoomImageDto) { + if (practiceRoomImageDto == null) { + return null; + } + return new PracticeRoomImageResponse( + practiceRoomImageDto.getId(), + practiceRoomImageDto.getName(), + practiceRoomImageDto.getUrl() + ); + } +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomResponse.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomResponse.java new file mode 100644 index 0000000..a7c0014 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/response/PracticeRoomResponse.java @@ -0,0 +1,91 @@ +package com.ajou.hertz.domain.practice_room.dto.response; + +import com.ajou.hertz.common.dto.response.CoordinateResponse; +import com.ajou.hertz.common.dto.response.FullAddressResponse; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class PracticeRoomResponse { + + @Schema(description = "Id of practiceRoom", example = "2") + private Long id; + + @Schema(description = "Id of seller", example = "1") + private Long sellerId; + + @Schema(description = "매물 제목", example = "Ajou Practice Room") + private String title; + + @Schema(description = "합주실 장소") + private FullAddressResponse tradeAddress; + + @Schema(description = "사운드 장비 여부", example = "true") + private Boolean hasSoundEquipment; + + @Schema(description = "악기 여부", example = "true") + private Boolean hasInstrument; + + @Schema(description = "하루 대여 가격", example = "100000") + private Integer pricePerDay; + + @Schema(description = "시간 당 가격", example = "10000") + private Integer pricePerHour; + + @Schema(description = "월 별 대여 가격", example = "100000") + private Integer pricePerMonth; + + @Schema(description = "수용 인원", example = "100") + private Short capacity; + + @Schema(description = "합주실 크기", example = "1000") + private String size; + + @Schema(description = "주차장 여부", example = "true") + private Boolean hasParkingLot; + + @Schema(description = "특이사항 및 상세 설명. 내용이 없을 경우에는 빈 문자열로 요청하면 됩니다.", example = "Ajou PracticeRoom은 수원시에 위치해 있으며...") + private String description; + + @Schema(description = "합주실 위치 좌표") + private CoordinateResponse coordinate; + + @Schema(description = "합주실 이미지") + private List<PracticeRoomImageResponse> images; + + @Schema(description = "해시태그", example = "[\"밴드\", \"락\"]") + private List<String> hashtags; + + protected PracticeRoomResponse(PracticeRoomDto practiceRoomDto) { + this.id = practiceRoomDto.getId(); + this.sellerId = practiceRoomDto.getSeller().getId(); + this.title = practiceRoomDto.getTitle(); + this.tradeAddress = FullAddressResponse.from(practiceRoomDto.getTradeAddress()); + this.hasSoundEquipment = practiceRoomDto.getHasSoundEquipment(); + this.hasInstrument = practiceRoomDto.getHasInstrument(); + this.pricePerDay = practiceRoomDto.getPricePerDay(); + this.pricePerHour = practiceRoomDto.getPricePerHour(); + this.pricePerMonth = practiceRoomDto.getPricePerMonth(); + this.capacity = practiceRoomDto.getCapacity(); + this.size = practiceRoomDto.getSize(); + this.hasParkingLot = practiceRoomDto.getHasParkingLot(); + this.description = practiceRoomDto.getDescription(); + this.coordinate = CoordinateResponse.from(practiceRoomDto.getCoordinate()); + this.images = practiceRoomDto.getImages().stream() + .map(PracticeRoomImageResponse::from) + .toList(); + this.hashtags = practiceRoomDto.getHashtags(); + } + + public static PracticeRoomResponse from(PracticeRoomDto practiceRoomDto) { + return new PracticeRoomResponse(practiceRoomDto); + } +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java index e3f0aea..ee3a2df 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java @@ -4,76 +4,131 @@ import com.ajou.hertz.common.entity.FullAddress; import com.ajou.hertz.common.entity.TimeTrackedBaseEntity; import com.ajou.hertz.domain.user.entity.User; - -import jakarta.persistence.Column; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -@AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Inheritance(strategy = InheritanceType.JOINED) @Getter @Entity public class PracticeRoom extends TimeTrackedBaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "practice_room_id", nullable = false) - private Long id; - - @JoinColumn(name = "seller_id", nullable = false) - @ManyToOne(fetch = FetchType.LAZY) - private User seller; - - @Column(nullable = false) - private String title; - - @Column(nullable = false) - @Embedded - private FullAddress fullAddress; - - @Column(nullable = false) - private Boolean hasSoundEquipment; - - @Column(nullable = false) - private Boolean hasInstrument; - - @Column(nullable = false) - private Integer pricePerDay; - - @Column(nullable = false) - private Integer pricePerHour; - - @Column(nullable = false) - private Integer pricePerMonth; - - @Column(nullable = false) - private Short capacity; - - @Column(nullable = false) - private String size; - - @Column(nullable = false) - private Boolean hasParkingLot; - - @Column(length = 1000, nullable = false) - private String description; - - @Embedded - private Coordinate coordinate; - - @Embedded - private PracticeRoomImages images = new PracticeRoomImages(); - - @Embedded - private PracticeRoomHashtags hashtags = new PracticeRoomHashtags(); + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "practice_room_id", nullable = false) + private Long id; + + @JoinColumn(name = "seller_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private User seller; + + @Column(nullable = false) + private String title; + + @Embedded + private FullAddress tradeAddress; + + @Column(nullable = false) + private Boolean hasSoundEquipment; + + @Column(nullable = false) + private Boolean hasInstrument; + + @Column(nullable = false) + private Integer pricePerDay; + + @Column(nullable = false) + private Integer pricePerHour; + + @Column(nullable = false) + private Integer pricePerMonth; + + @Column(nullable = false) + private Short capacity; + + @Column(nullable = false) + private String size; + + @Column(nullable = false) + private Boolean hasParkingLot; + + @Embedded + private PracticeRoomDescription description; + + @Embedded + private Coordinate coordinate; + + @Embedded + private PracticeRoomImages images = new PracticeRoomImages(); + + @Embedded + private PracticeRoomHashtags hashtags = new PracticeRoomHashtags(); + + protected PracticeRoom( + Long id, + User seller, + String title, + FullAddress tradeAddress, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerDay, + Integer pricePerHour, + Integer pricePerMonth, + Short capacity, + String size, + Boolean hasParkingLot, + String description, + Coordinate coordinate + + ) { + this.id = id; + this.seller = seller; + this.title = title; + this.tradeAddress = tradeAddress; + this.hasSoundEquipment = hasSoundEquipment; + this.hasInstrument = hasInstrument; + this.pricePerDay = pricePerDay; + this.pricePerHour = pricePerHour; + this.pricePerMonth = pricePerMonth; + this.capacity = capacity; + this.size = size; + this.hasParkingLot = hasParkingLot; + this.description = new PracticeRoomDescription(description); + this.coordinate = coordinate; + } + + public static PracticeRoom create( + User seller, + String title, + String description, + Short capacity, + String size, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerHour, + Integer pricePerDay, + Integer pricePerMonth, + Boolean hasParkingLot, + FullAddress tradeAddress, + Coordinate coordinate + ) { + return new PracticeRoom( + null, + seller, + title, + tradeAddress, + hasSoundEquipment, + hasInstrument, + pricePerDay, + pricePerHour, + pricePerMonth, + capacity, + size, + hasParkingLot, + description, + coordinate + ); + } } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomDescription.java b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomDescription.java new file mode 100644 index 0000000..09e730e --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomDescription.java @@ -0,0 +1,49 @@ +package com.ajou.hertz.domain.practice_room.entity; + +import com.ajou.hertz.domain.practice_room.exception.UnexpectedLinkInPracticeRoomDescriptionException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class PracticeRoomDescription { + + @Column(length = 1000, nullable = false) + private String description; + + public PracticeRoomDescription(String description) { + validateDescription(description); + this.description = description; + } + + private void validateDescription(String description) { + Matcher descriptionUrlMatcher = Pattern.compile("http://|https://|www\\.").matcher(description); + if (descriptionUrlMatcher.find()) { + throw new UnexpectedLinkInPracticeRoomDescriptionException(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PracticeRoomDescription that)) { + return false; + } + return Objects.equals(this.getDescription(), that.getDescription()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getDescription()); + } +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomHashtags.java b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomHashtags.java index f620389..498d047 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomHashtags.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomHashtags.java @@ -1,20 +1,30 @@ package com.ajou.hertz.domain.practice_room.entity; -import java.util.LinkedList; -import java.util.List; - import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import java.util.LinkedList; +import java.util.List; + @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Embeddable public class PracticeRoomHashtags { - @OneToMany(mappedBy = "practiceRoom") - private List<PracticeRoomHashtag> content = new LinkedList<>(); + @OneToMany(mappedBy = "practiceRoom") + private List<PracticeRoomHashtag> content = new LinkedList<>(); + + public void addAll(List<PracticeRoomHashtag> hashtags) { + content.addAll(hashtags); + } + + public List<String> toStrings() { + return content.stream() + .map(PracticeRoomHashtag::getContent) + .toList(); + } } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomImages.java b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomImages.java index 96e34b4..abedc78 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomImages.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoomImages.java @@ -1,20 +1,31 @@ package com.ajou.hertz.domain.practice_room.entity; -import java.util.LinkedList; -import java.util.List; - +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomImageDto; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import java.util.LinkedList; +import java.util.List; + @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Embeddable public class PracticeRoomImages { - @OneToMany(mappedBy = "practiceRoom") - private List<PracticeRoomImage> content = new LinkedList<>(); + @OneToMany(mappedBy = "practiceRoom") + private List<PracticeRoomImage> content = new LinkedList<>(); + + public void addAll(List<PracticeRoomImage> images) { + content.addAll(images); + } + + public List<PracticeRoomImageDto> toDtos() { + return content.stream() + .map(PracticeRoomImageDto::from) + .toList(); + } } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/exception/UnexpectedLinkInPracticeRoomDescriptionException.java b/src/main/java/com/ajou/hertz/domain/practice_room/exception/UnexpectedLinkInPracticeRoomDescriptionException.java new file mode 100644 index 0000000..b9f35f7 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/exception/UnexpectedLinkInPracticeRoomDescriptionException.java @@ -0,0 +1,10 @@ +package com.ajou.hertz.domain.practice_room.exception; + +import com.ajou.hertz.common.exception.BadRequestException; +import com.ajou.hertz.common.exception.constant.CustomExceptionType; + +public class UnexpectedLinkInPracticeRoomDescriptionException extends BadRequestException { + public UnexpectedLinkInPracticeRoomDescriptionException() { + super(CustomExceptionType.UNEXPECTED_LINK_IN_PRACTICEROOM_DESCRIPTION); + } +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java new file mode 100644 index 0000000..c288fb2 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java @@ -0,0 +1,54 @@ +package com.ajou.hertz.domain.practice_room.service; + +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import com.ajou.hertz.domain.practice_room.dto.request.CreateNewPracticeRoomRequest; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomHashtag; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomImage; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomRepository; +import com.ajou.hertz.domain.user.entity.User; +import com.ajou.hertz.domain.user.service.UserQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class PracticeRoomCommandService { + + private final PracticeRoomRepository practiceRoomRepository; + private final UserQueryService userQueryService; + private final PracticeRoomImageCommandService practiceRoomImageCommandService; + private final PracticeRoomHashtagCommandService practiceRoomHashtagCommandService; + + /** + * 신규 합주실 매물을 생성 및 저장한다. + * + * @param sellerId 악기 판매자의 id + * @param createNewPracticeRoomRequest 판매하고자 하는 합주실의 정보 + * @return 생성된 합주실 entity + */ + public PracticeRoomDto createNewPracticeRoom(Long sellerId, CreateNewPracticeRoomRequest createNewPracticeRoomRequest) { + User seller = userQueryService.getById(sellerId); + PracticeRoom practiceRoom = practiceRoomRepository.save(createNewPracticeRoomRequest.toEntity(seller)); + + List<PracticeRoomImage> savedPracticeRoomImages = practiceRoomImageCommandService.saveImages( + practiceRoom, + createNewPracticeRoomRequest.getImages() + ); + practiceRoom.getImages().addAll(savedPracticeRoomImages); + + List<PracticeRoomHashtag> savedPracticeRoomHashtags = practiceRoomHashtagCommandService.saveHashtags( + practiceRoom, + createNewPracticeRoomRequest.getHashtags() + ); + practiceRoom.getHashtags().addAll(savedPracticeRoomHashtags); + + PracticeRoom updatedPracticeRoom = practiceRoomRepository.save(practiceRoom); + + return new PracticeRoomDto(updatedPracticeRoom); + } +} \ No newline at end of file diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomHashtagCommandService.java b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomHashtagCommandService.java new file mode 100644 index 0000000..6a9b014 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomHashtagCommandService.java @@ -0,0 +1,32 @@ +package com.ajou.hertz.domain.practice_room.service; + +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomHashtag; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomHashtagRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class PracticeRoomHashtagCommandService { + + private final PracticeRoomHashtagRepository practiceRoomHashtagRepository; + + /** + * 전달된 hashtag content list로 <code>PracticeRoomHashtag</code> list를 만들어 저장 및 등록한다. + * + * @param practiceRoom hashtag가 작성된 합주실 + * @param hashtags hashtag list + * @return 생성된 hashtag list + */ + public List<PracticeRoomHashtag> saveHashtags(PracticeRoom practiceRoom, List<String> hashtags) { + List<PracticeRoomHashtag> practiceRoomHashtags = hashtags.stream() + .map(hashtagContent -> PracticeRoomHashtag.create(practiceRoom, hashtagContent)) + .toList(); + return practiceRoomHashtagRepository.saveAll(practiceRoomHashtags); + } +} diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomImageCommandService.java b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomImageCommandService.java new file mode 100644 index 0000000..e30ec20 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomImageCommandService.java @@ -0,0 +1,43 @@ +package com.ajou.hertz.domain.practice_room.service; + +import com.ajou.hertz.common.file.service.FileService; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomImage; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomImageRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class PracticeRoomImageCommandService { + + private static final String PRACTICEROOM_IMAGE_UPLOAD_PATH = "practiceRoom-image/"; + + private final FileService fileService; + private final PracticeRoomImageRepository practiceRoomImageRepository; + + /** + * 전달받은 multipart files로 practiceRoom image entity를 생성 후 저장한다. + * + * @param practiceRoom 이미지의 대상이 되는 practiceRoom entity + * @param images 저장하고자 하는 image + * @return 저장된 practiceRoom images + */ + public List<PracticeRoomImage> saveImages(PracticeRoom practiceRoom, Iterable<MultipartFile> images) { + List<PracticeRoomImage> practiceRoomImages = fileService + .uploadFiles(images, PRACTICEROOM_IMAGE_UPLOAD_PATH) + .stream() + .map(fileDto -> PracticeRoomImage.create( + practiceRoom, + fileDto.getOriginalName(), + fileDto.getStoredName(), + fileDto.getUrl() + )).toList(); + return practiceRoomImageRepository.saveAll(practiceRoomImages); + } +}