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 a86cfc2..70664b3 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 @@ -56,7 +56,8 @@ public enum CustomExceptionType { */ INSTRUMENT_NOT_FOUND_BY_ID(2600, "일치하는 매물 정보를 찾을 수 없습니다."), INSTRUMENT_DELETE_PERMISSION_DENIED(2601, "악기 매물을 삭제할 권한이 없습니다. 매물은 판매자 본인만 삭제할 수 있습니다."), - INSTRUMENT_UPDATE_PERMISSION_DENIED(2602, "악기 매물 정보를 수정할 권한이 없습니다. 매물 정보는 판매자 본인만 수정할 수 있습니다"); + INSTRUMENT_UPDATE_PERMISSION_DENIED(2602, "악기 매물 정보를 수정할 권한이 없습니다. 매물 정보는 판매자 본인만 수정할 수 있습니다."), + UNEXPECTED_LINK_IN_INSTRUMENT_DESCRIPTION(2603, "악기 설명에는 링크(URL)를 첨부할 수 없습니다. 링크 정보를 제거한 후 다시 시도하세요."); private final Integer code; private final String message; diff --git a/src/main/java/com/ajou/hertz/domain/instrument/controller/InstrumentController.java b/src/main/java/com/ajou/hertz/domain/instrument/controller/InstrumentController.java index 5a1c17b..cce2800 100644 --- a/src/main/java/com/ajou/hertz/domain/instrument/controller/InstrumentController.java +++ b/src/main/java/com/ajou/hertz/domain/instrument/controller/InstrumentController.java @@ -269,6 +269,10 @@ public Page findAudioEquipmentsV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PostMapping( value = "/electric-guitars", headers = API_VERSION_HEADER_NAME + "=" + 1, @@ -295,6 +299,10 @@ public ResponseEntity createNewElectricGuitarV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PostMapping( value = "/bass-guitars", headers = API_VERSION_HEADER_NAME + "=" + 1, @@ -321,6 +329,10 @@ public ResponseEntity createNewBassGuitarV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PostMapping( value = "/acoustic-and-classic-guitars", headers = API_VERSION_HEADER_NAME + "=" + 1, @@ -353,6 +365,10 @@ public ResponseEntity createNewAcousticAndClas headers = API_VERSION_HEADER_NAME + "=" + 1, consumes = MediaType.MULTIPART_FORM_DATA_VALUE ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) public ResponseEntity createNewEffectorV1( @AuthenticationPrincipal UserPrincipal userPrincipal, @ParameterObject @ModelAttribute @Valid CreateNewEffectorRequest createNewEffectorRequest @@ -374,6 +390,10 @@ public ResponseEntity createNewEffectorV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PostMapping( value = "/amplifiers", headers = API_VERSION_HEADER_NAME + "=" + 1, @@ -400,6 +420,10 @@ public ResponseEntity createNewAmplifierV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PostMapping( value = "/audio-equipments", headers = API_VERSION_HEADER_NAME + "=" + 1, @@ -427,6 +451,10 @@ public ResponseEntity createNewAudioEquipmentV1( """, security = @SecurityRequirement(name = "access-token") ) + @ApiResponses({ + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400", description = "[2603] 악기 설명에 링크(URL)이 첨부되어 있는 경우.", content = @Content) + }) @PatchMapping("/electric-guitars/{electricGuitarId}") public ElectricGuitarResponse updateElectricGuitarV1( @AuthenticationPrincipal UserPrincipal userPrincipal, diff --git a/src/main/java/com/ajou/hertz/domain/instrument/dto/InstrumentDto.java b/src/main/java/com/ajou/hertz/domain/instrument/dto/InstrumentDto.java index 69966cf..19180ad 100644 --- a/src/main/java/com/ajou/hertz/domain/instrument/dto/InstrumentDto.java +++ b/src/main/java/com/ajou/hertz/domain/instrument/dto/InstrumentDto.java @@ -4,12 +4,12 @@ import java.util.Map; import com.ajou.hertz.common.dto.AddressDto; -import com.ajou.hertz.domain.instrument.constant.InstrumentCategory; -import com.ajou.hertz.domain.instrument.constant.InstrumentProgressStatus; import com.ajou.hertz.domain.instrument.acoustic_and_classic_guitar.entity.AcousticAndClassicGuitar; import com.ajou.hertz.domain.instrument.amplifier.entity.Amplifier; import com.ajou.hertz.domain.instrument.audio_equipment.entity.AudioEquipment; import com.ajou.hertz.domain.instrument.bass_guitar.entity.BassGuitar; +import com.ajou.hertz.domain.instrument.constant.InstrumentCategory; +import com.ajou.hertz.domain.instrument.constant.InstrumentProgressStatus; import com.ajou.hertz.domain.instrument.effector.entity.Effector; import com.ajou.hertz.domain.instrument.electric_guitar.entity.ElectricGuitar; import com.ajou.hertz.domain.instrument.entity.Instrument; @@ -58,7 +58,7 @@ protected InstrumentDto(Instrument instrument) { instrument.getQualityStatus(), instrument.getPrice(), instrument.getHasAnomaly(), - instrument.getDescription(), + instrument.getDescription().getDescription(), instrument.getImages().toDtos(), instrument.getHashtags().toStrings() ); diff --git a/src/main/java/com/ajou/hertz/domain/instrument/entity/Instrument.java b/src/main/java/com/ajou/hertz/domain/instrument/entity/Instrument.java index 6e931ea..60a9dd8 100644 --- a/src/main/java/com/ajou/hertz/domain/instrument/entity/Instrument.java +++ b/src/main/java/com/ajou/hertz/domain/instrument/entity/Instrument.java @@ -61,8 +61,8 @@ public abstract class Instrument extends TimeTrackedBaseEntity { @Column(nullable = false) private Boolean hasAnomaly; - @Column(length = 1000, nullable = false) - private String description; + @Embedded + private InstrumentDescription description; @Embedded private InstrumentImages images = new InstrumentImages(); @@ -89,7 +89,7 @@ protected Instrument( this.qualityStatus = qualityStatus; this.price = price; this.hasAnomaly = hasAnomaly; - this.description = description; + this.description = new InstrumentDescription(description); } public void update(InstrumentUpdateRequest updateRequest) { @@ -109,7 +109,7 @@ public void update(InstrumentUpdateRequest updateRequest) { this.hasAnomaly = updateRequest.getHasAnomaly(); } if (updateRequest.getDescription() != null) { - this.description = updateRequest.getDescription(); + this.description = new InstrumentDescription(updateRequest.getDescription()); } } } diff --git a/src/main/java/com/ajou/hertz/domain/instrument/entity/InstrumentDescription.java b/src/main/java/com/ajou/hertz/domain/instrument/entity/InstrumentDescription.java new file mode 100644 index 0000000..b72bf1f --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/instrument/entity/InstrumentDescription.java @@ -0,0 +1,50 @@ +package com.ajou.hertz.domain.instrument.entity; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.ajou.hertz.domain.instrument.exception.UnexpectedLinkInInstrumentDescriptionException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Embeddable +public class InstrumentDescription { + + @Column(length = 1000, nullable = false) + private String description; + + public InstrumentDescription(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 UnexpectedLinkInInstrumentDescriptionException(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof InstrumentDescription 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/instrument/exception/UnexpectedLinkInInstrumentDescriptionException.java b/src/main/java/com/ajou/hertz/domain/instrument/exception/UnexpectedLinkInInstrumentDescriptionException.java new file mode 100644 index 0000000..c3ccf03 --- /dev/null +++ b/src/main/java/com/ajou/hertz/domain/instrument/exception/UnexpectedLinkInInstrumentDescriptionException.java @@ -0,0 +1,11 @@ +package com.ajou.hertz.domain.instrument.exception; + +import com.ajou.hertz.common.exception.BadRequestException; +import com.ajou.hertz.common.exception.constant.CustomExceptionType; + +public class UnexpectedLinkInInstrumentDescriptionException extends BadRequestException { + + public UnexpectedLinkInInstrumentDescriptionException() { + super(CustomExceptionType.UNEXPECTED_LINK_IN_INSTRUMENT_DESCRIPTION); + } +} diff --git a/src/test/java/com/ajou/hertz/unit/domain/instrument/entity/InstrumentDescriptionTest.java b/src/test/java/com/ajou/hertz/unit/domain/instrument/entity/InstrumentDescriptionTest.java new file mode 100644 index 0000000..4bc4149 --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/instrument/entity/InstrumentDescriptionTest.java @@ -0,0 +1,48 @@ +package com.ajou.hertz.unit.domain.instrument.entity; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.ajou.hertz.domain.instrument.entity.InstrumentDescription; +import com.ajou.hertz.domain.instrument.exception.UnexpectedLinkInInstrumentDescriptionException; + +class InstrumentDescriptionTest { + + @ParameterizedTest + @ValueSource(strings = { + "The violin is a small-bodied string instrument with a long neck, known for its beautiful melodies.", + "A drum set consists of various percussion instruments and cymbals, providing powerful rhythms.", + "The flute is a lightweight woodwind made of metal, known for its smooth and clear sound.", + "An electronic keyboard incorporates various sounds and rhythms, allowing for versatile performances.", + "The harmonica is a small wind instrument that produces sound by blowing air through it, offering a unique musical experience." + }) + void 악기_설명에_링크가_존재하지_않는다면_정상적으로_객체가_생성된다(String description) { + // given + + // when + InstrumentDescription instrumentDescription = new InstrumentDescription(description); + + // then + assertThat(instrumentDescription).isNotNull(); + } + + @ParameterizedTest + @ValueSource(strings = { + "Keyboard instrument striking strings with hammers. Learn more at Musician's Friend: https://www.musiciansfriend.com/keyboards-midi/pianos", + "Stringed instrument played with a bow, high-pitched. More info at Violinist: http://www.violinist.com/", + "Stringed instrument for various genres, strummed or plucked. Details at Guitar Center: www.guitarcenter.com/Acoustic-Guitars.gc", + "Woodwind instrument, sound by blowing air across the mouthpiece. Additional info at Flute World: https://fluteworld.com/", + "Percussion instruments struck by sticks or hands. Explore more at Drumming.com: http://drumming.com/" + }) + void 악기_설명에_링크가_포함되었다면_예외가_발생한다(String description) { + // given + + // when + Throwable ex = catchThrowable(() -> new InstrumentDescription(description)); + + // then + assertThat(ex).isInstanceOf(UnexpectedLinkInInstrumentDescriptionException.class); + } +} \ No newline at end of file