From 288908e10ace3ec606b86c566b27bb9d82f40e5b Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:24:40 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20encrypt=20decrypt=20=EC=9E=98=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/CryptoConfig.java | 20 +++++++- .../swyp8team2/crypto/application/Base62.java | 34 ++++++++++++++ .../crypto/application/CryptoService.java | 47 +++++-------------- .../crypto/application/Base62Test.java | 4 ++ 4 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/swyp8team2/crypto/application/Base62.java create mode 100644 src/test/java/com/swyp8team2/crypto/application/Base62Test.java diff --git a/src/main/java/com/swyp8team2/common/config/CryptoConfig.java b/src/main/java/com/swyp8team2/common/config/CryptoConfig.java index 0249cb1a..7e722dfd 100644 --- a/src/main/java/com/swyp8team2/common/config/CryptoConfig.java +++ b/src/main/java/com/swyp8team2/common/config/CryptoConfig.java @@ -3,21 +3,37 @@ import com.swyp8team2.common.annotation.GuestTokenCryptoService; import com.swyp8team2.crypto.application.CryptoService; import com.swyp8team2.common.annotation.ShareUrlCryptoService; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; @Configuration public class CryptoConfig { + private final String guestTokenSymmetricKey; + private final String shareUrlSymmetricKey; + private final String salt; + + public CryptoConfig( + @Value("${crypto.secret-key.guest-token}") String guestTokenSymmetricKey, + @Value("${crypto.secret-key.share-url}") String shareUrlSymmetricKey, + @Value("${crypto.salt}") String salt + ) { + this.guestTokenSymmetricKey = guestTokenSymmetricKey; + this.shareUrlSymmetricKey = shareUrlSymmetricKey; + this.salt = salt; + } + @GuestTokenCryptoService @Bean(name = GuestTokenCryptoService.QUALIFIER) public CryptoService guestTokenCryptoService() throws Exception { - return new CryptoService(); + return new CryptoService(new AesBytesEncryptor(guestTokenSymmetricKey, salt)); } @ShareUrlCryptoService @Bean(name = ShareUrlCryptoService.QUALIFIER) public CryptoService shareUrlCryptoService() throws Exception { - return new CryptoService(); + return new CryptoService(new AesBytesEncryptor(shareUrlSymmetricKey, salt)); } } diff --git a/src/main/java/com/swyp8team2/crypto/application/Base62.java b/src/main/java/com/swyp8team2/crypto/application/Base62.java new file mode 100644 index 00000000..e83ae4c0 --- /dev/null +++ b/src/main/java/com/swyp8team2/crypto/application/Base62.java @@ -0,0 +1,34 @@ +package com.swyp8team2.crypto.application; + +import java.math.BigInteger; + +public class Base62 { + + private static final String BASE62_ALPHABET = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int BASE = 62; + + public static String encode(byte[] bytes) { + BigInteger value = new BigInteger(1, bytes); + StringBuilder encoded = new StringBuilder(); + + while (value.compareTo(BigInteger.ZERO) > 0) { + BigInteger[] divRem = value.divideAndRemainder(BigInteger.valueOf(BASE)); + value = divRem[0]; + encoded.insert(0, BASE62_ALPHABET.charAt(divRem[1].intValue())); + } + + return encoded.toString(); + } + + public static byte[] decode(String encoded) { + BigInteger value = BigInteger.ZERO; + + for (char c : encoded.toCharArray()) { + value = value.multiply(BigInteger.valueOf(BASE)) + .add(BigInteger.valueOf(BASE62_ALPHABET.indexOf(c))); + } + + return value.toByteArray(); + } +} diff --git a/src/main/java/com/swyp8team2/crypto/application/CryptoService.java b/src/main/java/com/swyp8team2/crypto/application/CryptoService.java index 612755d1..c3ecd7e0 100644 --- a/src/main/java/com/swyp8team2/crypto/application/CryptoService.java +++ b/src/main/java/com/swyp8team2/crypto/application/CryptoService.java @@ -3,57 +3,36 @@ import com.swyp8team2.common.exception.BadRequestException; import com.swyp8team2.common.exception.ErrorCode; import com.swyp8team2.common.exception.InternalServerException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Base64; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; @Slf4j +@RequiredArgsConstructor public class CryptoService { - private static final String ALGORITHM = "AES"; - private final SecretKey secretKey; - - public CryptoService() throws Exception { - KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM); - keyGenerator.init(256); - this.secretKey = keyGenerator.generateKey(); - } + private final AesBytesEncryptor encryptor; public String encrypt(String data) { try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedBytes = cipher.doFinal(data.getBytes()); - return Base64.encodeBase64URLSafeString(encryptedBytes) - .replace('+', 'A') - .replace('/', 'B'); + byte[] encrypt = encryptor.encrypt(data.getBytes(StandardCharsets.UTF_8)); + return Base62.encode(encrypt); } catch (Exception e) { - log.error("encrypt error {}", e.getMessage()); - throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR); + log.debug("encrypt error {}", e.getMessage()); + throw new BadRequestException(ErrorCode.INVALID_TOKEN); } } public String decrypt(String encryptedData) { try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] decoded = Base64.decodeBase64( - encryptedData - .replace('A', '+') - .replace('B', '/') - ); - return new String(cipher.doFinal(decoded)); - } catch (IllegalBlockSizeException | BadPaddingException e) { + byte[] decryptBytes = Base62.decode(encryptedData); + byte[] decrypt = encryptor.decrypt(decryptBytes); + return new String(decrypt, StandardCharsets.UTF_8); + } catch (Exception e) { log.debug("decrypt error {}", e.getMessage()); throw new BadRequestException(ErrorCode.INVALID_TOKEN); - } catch (Exception e) { - log.error("decrypt error {}", e.getMessage()); - throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR); } } } diff --git a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java new file mode 100644 index 00000000..f795b84a --- /dev/null +++ b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class Base62Test { + +} From 276ac424e492f93f004e24bc813cbbc23883c3b0 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:24:50 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto/application/Base62Test.java | 45 ++++++++++++++++++- .../crypto/application/CryptoServiceTest.java | 5 ++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java index f795b84a..bd0c180e 100644 --- a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java +++ b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java @@ -1,4 +1,45 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.swyp8team2.crypto.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.*; + class Base62Test { - + + @Test + @DisplayName("인코딩 디코딩") + void encodingAndDecoding() throws Exception { + //given + String plainText = "Hello, World!"; + byte[] bytes = plainText.getBytes(StandardCharsets.UTF_8); + + //when + String encode = Base62.encode(bytes); + byte[] decode = Base62.decode(encode); + + String decodeText = new String(decode, StandardCharsets.UTF_8); + + //then + assertThat(decodeText).isEqualTo(plainText); + } + + @Test + @DisplayName("인코딩 디코딩 - 다른 문자열") + void encodingAndDecoding_differentText() throws Exception { + //given + String plainText = "Hello, World!"; + byte[] bytes = plainText.getBytes(StandardCharsets.UTF_8); + + //when + String encode = Base62.encode(bytes); + byte[] decode = Base62.decode("different"); + + String decodeText = new String(decode, StandardCharsets.UTF_8); + + //then + assertThat(decodeText).isNotEqualTo(plainText); + } } diff --git a/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java b/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java index 3142366c..801b4151 100644 --- a/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java +++ b/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -16,7 +17,7 @@ class CryptoServiceTest { @BeforeEach void setUp() throws Exception { - cryptoService = new CryptoService(); + cryptoService = new CryptoService(new AesBytesEncryptor("test", "123456")); } @Test @@ -38,7 +39,7 @@ void encryptAndDecrypt() { void encryptAndDecrypt_differentKey() throws Exception { // given String plainText = "Hello, World!"; - CryptoService differentCryptoService = new CryptoService(); + CryptoService differentCryptoService = new CryptoService(new AesBytesEncryptor("different", "234562")); String encryptedText = differentCryptoService.encrypt(plainText); // when then From b0bd09f0d853177e5936d4e8508aeeb8fd7fd180 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:24:59 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20=EC=84=A4=EC=A0=95=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-config b/server-config index 0afb5ee1..72816997 160000 --- a/server-config +++ b/server-config @@ -1 +1 @@ -Subproject commit 0afb5ee104ee232e74ad823f355b7a6cad80e26a +Subproject commit 72816997e97767c17a566b55a569c6faad7c5f25 From 9afc51bf7ec081fa4c2efaba7ffa0e5ba82b408f Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:24:40 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20encrypt=20decrypt=20=EC=9E=98=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # src/main/java/com/swyp8team2/common/config/CryptoConfig.java # src/main/java/com/swyp8team2/crypto/application/CryptoService.java --- server-config | 2 +- .../common/config/CryptoConfig.java | 39 +++++++++++++++++ .../swyp8team2/crypto/application/Base62.java | 34 +++++++++++++++ .../crypto/application/CryptoService.java | 43 ++++++------------- .../crypto/application/Base62Test.java | 4 ++ 5 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/swyp8team2/common/config/CryptoConfig.java create mode 100644 src/main/java/com/swyp8team2/crypto/application/Base62.java create mode 100644 src/test/java/com/swyp8team2/crypto/application/Base62Test.java diff --git a/server-config b/server-config index 79366b38..72816997 160000 --- a/server-config +++ b/server-config @@ -1 +1 @@ -Subproject commit 79366b381deaf7c65a0e6b83fcc2db23d3aedd6a +Subproject commit 72816997e97767c17a566b55a569c6faad7c5f25 diff --git a/src/main/java/com/swyp8team2/common/config/CryptoConfig.java b/src/main/java/com/swyp8team2/common/config/CryptoConfig.java new file mode 100644 index 00000000..7e722dfd --- /dev/null +++ b/src/main/java/com/swyp8team2/common/config/CryptoConfig.java @@ -0,0 +1,39 @@ +package com.swyp8team2.common.config; + +import com.swyp8team2.common.annotation.GuestTokenCryptoService; +import com.swyp8team2.crypto.application.CryptoService; +import com.swyp8team2.common.annotation.ShareUrlCryptoService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; + +@Configuration +public class CryptoConfig { + + private final String guestTokenSymmetricKey; + private final String shareUrlSymmetricKey; + private final String salt; + + public CryptoConfig( + @Value("${crypto.secret-key.guest-token}") String guestTokenSymmetricKey, + @Value("${crypto.secret-key.share-url}") String shareUrlSymmetricKey, + @Value("${crypto.salt}") String salt + ) { + this.guestTokenSymmetricKey = guestTokenSymmetricKey; + this.shareUrlSymmetricKey = shareUrlSymmetricKey; + this.salt = salt; + } + + @GuestTokenCryptoService + @Bean(name = GuestTokenCryptoService.QUALIFIER) + public CryptoService guestTokenCryptoService() throws Exception { + return new CryptoService(new AesBytesEncryptor(guestTokenSymmetricKey, salt)); + } + + @ShareUrlCryptoService + @Bean(name = ShareUrlCryptoService.QUALIFIER) + public CryptoService shareUrlCryptoService() throws Exception { + return new CryptoService(new AesBytesEncryptor(shareUrlSymmetricKey, salt)); + } +} diff --git a/src/main/java/com/swyp8team2/crypto/application/Base62.java b/src/main/java/com/swyp8team2/crypto/application/Base62.java new file mode 100644 index 00000000..e83ae4c0 --- /dev/null +++ b/src/main/java/com/swyp8team2/crypto/application/Base62.java @@ -0,0 +1,34 @@ +package com.swyp8team2.crypto.application; + +import java.math.BigInteger; + +public class Base62 { + + private static final String BASE62_ALPHABET = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int BASE = 62; + + public static String encode(byte[] bytes) { + BigInteger value = new BigInteger(1, bytes); + StringBuilder encoded = new StringBuilder(); + + while (value.compareTo(BigInteger.ZERO) > 0) { + BigInteger[] divRem = value.divideAndRemainder(BigInteger.valueOf(BASE)); + value = divRem[0]; + encoded.insert(0, BASE62_ALPHABET.charAt(divRem[1].intValue())); + } + + return encoded.toString(); + } + + public static byte[] decode(String encoded) { + BigInteger value = BigInteger.ZERO; + + for (char c : encoded.toCharArray()) { + value = value.multiply(BigInteger.valueOf(BASE)) + .add(BigInteger.valueOf(BASE62_ALPHABET.indexOf(c))); + } + + return value.toByteArray(); + } +} diff --git a/src/main/java/com/swyp8team2/crypto/application/CryptoService.java b/src/main/java/com/swyp8team2/crypto/application/CryptoService.java index 25fddd7b..c3ecd7e0 100644 --- a/src/main/java/com/swyp8team2/crypto/application/CryptoService.java +++ b/src/main/java/com/swyp8team2/crypto/application/CryptoService.java @@ -3,53 +3,36 @@ import com.swyp8team2.common.exception.BadRequestException; import com.swyp8team2.common.exception.ErrorCode; import com.swyp8team2.common.exception.InternalServerException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.util.Base64; +import java.nio.charset.StandardCharsets; @Slf4j -@Service +@RequiredArgsConstructor public class CryptoService { - private static final String ALGORITHM = "AES"; - private final SecretKey secretKey; - - public CryptoService() throws Exception { - KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM); - keyGenerator.init(256); - this.secretKey = keyGenerator.generateKey(); - } + private final AesBytesEncryptor encryptor; public String encrypt(String data) { try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedBytes = cipher.doFinal(data.getBytes()); - return Base64.getEncoder().encodeToString(encryptedBytes); + byte[] encrypt = encryptor.encrypt(data.getBytes(StandardCharsets.UTF_8)); + return Base62.encode(encrypt); } catch (Exception e) { - log.error("encrypt error {}", e.getMessage()); - throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR); + log.debug("encrypt error {}", e.getMessage()); + throw new BadRequestException(ErrorCode.INVALID_TOKEN); } } public String decrypt(String encryptedData) { try { - Cipher cipher = Cipher.getInstance(ALGORITHM); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData)); - return new String(decryptedBytes); - } catch (IllegalBlockSizeException | BadPaddingException e) { + byte[] decryptBytes = Base62.decode(encryptedData); + byte[] decrypt = encryptor.decrypt(decryptBytes); + return new String(decrypt, StandardCharsets.UTF_8); + } catch (Exception e) { log.debug("decrypt error {}", e.getMessage()); throw new BadRequestException(ErrorCode.INVALID_TOKEN); - } catch (Exception e) { - log.error("decrypt error {}", e.getMessage()); - throw new InternalServerException(ErrorCode.INTERNAL_SERVER_ERROR); } } } diff --git a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java new file mode 100644 index 00000000..f795b84a --- /dev/null +++ b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class Base62Test { + +} From 0f78923affadcdd0239684972e35448812d47ae8 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:24:50 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto/application/Base62Test.java | 45 ++++++++++++++++++- .../crypto/application/CryptoServiceTest.java | 5 ++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java index f795b84a..bd0c180e 100644 --- a/src/test/java/com/swyp8team2/crypto/application/Base62Test.java +++ b/src/test/java/com/swyp8team2/crypto/application/Base62Test.java @@ -1,4 +1,45 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.swyp8team2.crypto.application; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.*; + class Base62Test { - + + @Test + @DisplayName("인코딩 디코딩") + void encodingAndDecoding() throws Exception { + //given + String plainText = "Hello, World!"; + byte[] bytes = plainText.getBytes(StandardCharsets.UTF_8); + + //when + String encode = Base62.encode(bytes); + byte[] decode = Base62.decode(encode); + + String decodeText = new String(decode, StandardCharsets.UTF_8); + + //then + assertThat(decodeText).isEqualTo(plainText); + } + + @Test + @DisplayName("인코딩 디코딩 - 다른 문자열") + void encodingAndDecoding_differentText() throws Exception { + //given + String plainText = "Hello, World!"; + byte[] bytes = plainText.getBytes(StandardCharsets.UTF_8); + + //when + String encode = Base62.encode(bytes); + byte[] decode = Base62.decode("different"); + + String decodeText = new String(decode, StandardCharsets.UTF_8); + + //then + assertThat(decodeText).isNotEqualTo(plainText); + } } diff --git a/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java b/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java index 3142366c..801b4151 100644 --- a/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java +++ b/src/test/java/com/swyp8team2/crypto/application/CryptoServiceTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.encrypt.AesBytesEncryptor; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -16,7 +17,7 @@ class CryptoServiceTest { @BeforeEach void setUp() throws Exception { - cryptoService = new CryptoService(); + cryptoService = new CryptoService(new AesBytesEncryptor("test", "123456")); } @Test @@ -38,7 +39,7 @@ void encryptAndDecrypt() { void encryptAndDecrypt_differentKey() throws Exception { // given String plainText = "Hello, World!"; - CryptoService differentCryptoService = new CryptoService(); + CryptoService differentCryptoService = new CryptoService(new AesBytesEncryptor("different", "234562")); String encryptedText = differentCryptoService.encrypt(plainText); // when then From 035c899e112850d46b424e87d4db6c5baa3cd142 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:32:49 +0900 Subject: [PATCH 6/7] Merge pull request #65 from SWYP-team-2th/feature/shareurl Feature/shareurl --- build.gradle | 3 ++ src/docs/asciidoc/posts.adoc | 9 +++- .../auth/application/AuthService.java | 15 +++++- .../presentation/filter/GuestAuthFilter.java | 6 ++- .../annotation/GuestTokenCryptoService.java | 16 ++++++ .../annotation/ShareUrlCryptoService.java | 16 ++++++ .../common/config/SecurityConfig.java | 9 ++-- .../common/dev/DataInitializer.java | 31 ++++++++++- .../common/exception/ErrorCode.java | 1 + .../application/PostImageNameGenerator.java | 2 +- .../post/application/PostService.java | 28 +++++++++- .../java/com/swyp8team2/post/domain/Post.java | 14 +++-- .../post/presentation/PostController.java | 12 +++++ .../{ => dto}/CreatePostResponse.java | 2 +- .../user/application/UserService.java | 2 +- .../java/com/swyp8team2/user/domain/User.java | 8 +-- .../application/CommentServiceTest.java | 2 +- .../PostImageNameGeneratorTest.java | 4 +- .../post/application/PostServiceTest.java | 4 +- .../com/swyp8team2/post/domain/PostTest.java | 10 ++-- .../post/presentation/PostControllerTest.java | 53 +++++++++++++++++++ .../support/fixture/FixtureGenerator.java | 3 +- 22 files changed, 210 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/swyp8team2/common/annotation/GuestTokenCryptoService.java create mode 100644 src/main/java/com/swyp8team2/common/annotation/ShareUrlCryptoService.java rename src/main/java/com/swyp8team2/post/presentation/{ => dto}/CreatePostResponse.java (52%) diff --git a/build.gradle b/build.gradle index 8005ef46..23e36c35 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ dependencies { // gson implementation 'com.google.code.gson:gson:2.8.6' + // base64 + implementation 'commons-codec:commons-codec:1.15' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/docs/asciidoc/posts.adoc b/src/docs/asciidoc/posts.adoc index fc4dea9f..54301e7e 100644 --- a/src/docs/asciidoc/posts.adoc +++ b/src/docs/asciidoc/posts.adoc @@ -11,6 +11,13 @@ operation::post-controller-test/create-post[snippets='http-request,curl-request, operation::post-controller-test/find-post[snippets='http-request,curl-request,path-parameters,http-response,response-fields'] +[[개사굴-공유-url-조회]] +=== `GET` 게시글 공유 url 조회 + +operation::post-controller-test/find-post_share-url[snippets='http-request,curl-request,path-parameters,http-response,response-fields'] + +[[게시글-목록-조회]] + [[사진-투표-현황-조회]] === `GET` 사진 투표 현황 조회 @@ -32,8 +39,6 @@ operation::post-controller-test/find-voted-post[snippets='http-request,curl-requ operation::post-controller-test/close-post[snippets='http-request,curl-request,path-parameters,request-headers,http-response'] -[[게시글-수정]] - [[게시글-삭제]] === `DELETE` 게시글 삭제 diff --git a/src/main/java/com/swyp8team2/auth/application/AuthService.java b/src/main/java/com/swyp8team2/auth/application/AuthService.java index bbce4488..99a4e109 100644 --- a/src/main/java/com/swyp8team2/auth/application/AuthService.java +++ b/src/main/java/com/swyp8team2/auth/application/AuthService.java @@ -7,6 +7,7 @@ import com.swyp8team2.auth.domain.Provider; import com.swyp8team2.auth.domain.SocialAccount; import com.swyp8team2.auth.domain.SocialAccountRepository; +import com.swyp8team2.common.annotation.GuestTokenCryptoService; import com.swyp8team2.crypto.application.CryptoService; import com.swyp8team2.user.application.UserService; import lombok.RequiredArgsConstructor; @@ -14,7 +15,6 @@ import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor public class AuthService { private final JwtService jwtService; @@ -23,6 +23,19 @@ public class AuthService { private final UserService userService; private final CryptoService cryptoService; + public AuthService( + JwtService jwtService, + OAuthService oAuthService, + SocialAccountRepository socialAccountRepository, + UserService userService, + @GuestTokenCryptoService CryptoService cryptoService) { + this.jwtService = jwtService; + this.oAuthService = oAuthService; + this.socialAccountRepository = socialAccountRepository; + this.userService = userService; + this.cryptoService = cryptoService; + } + @Transactional public TokenPair oauthSignIn(String code, String redirectUri) { OAuthUserInfo oAuthUserInfo = oAuthService.getUserInfo(code, redirectUri); diff --git a/src/main/java/com/swyp8team2/auth/presentation/filter/GuestAuthFilter.java b/src/main/java/com/swyp8team2/auth/presentation/filter/GuestAuthFilter.java index 4b678645..91de4d43 100644 --- a/src/main/java/com/swyp8team2/auth/presentation/filter/GuestAuthFilter.java +++ b/src/main/java/com/swyp8team2/auth/presentation/filter/GuestAuthFilter.java @@ -1,6 +1,7 @@ package com.swyp8team2.auth.presentation.filter; import com.swyp8team2.auth.domain.UserInfo; +import com.swyp8team2.common.annotation.GuestTokenCryptoService; import com.swyp8team2.common.exception.ApplicationException; import com.swyp8team2.common.exception.BadRequestException; import com.swyp8team2.common.exception.ErrorCode; @@ -27,11 +28,14 @@ import static com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint.EXCEPTION_KEY; @Slf4j -@RequiredArgsConstructor public class GuestAuthFilter extends OncePerRequestFilter { private final CryptoService cryptoService; + public GuestAuthFilter(@GuestTokenCryptoService CryptoService cryptoService) { + this.cryptoService = cryptoService; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { diff --git a/src/main/java/com/swyp8team2/common/annotation/GuestTokenCryptoService.java b/src/main/java/com/swyp8team2/common/annotation/GuestTokenCryptoService.java new file mode 100644 index 00000000..90a6e2db --- /dev/null +++ b/src/main/java/com/swyp8team2/common/annotation/GuestTokenCryptoService.java @@ -0,0 +1,16 @@ +package com.swyp8team2.common.annotation; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Qualifier(GuestTokenCryptoService.QUALIFIER) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD}) +public @interface GuestTokenCryptoService { + + String QUALIFIER = "guestTokenCryptoService"; +} diff --git a/src/main/java/com/swyp8team2/common/annotation/ShareUrlCryptoService.java b/src/main/java/com/swyp8team2/common/annotation/ShareUrlCryptoService.java new file mode 100644 index 00000000..2ea4c9f4 --- /dev/null +++ b/src/main/java/com/swyp8team2/common/annotation/ShareUrlCryptoService.java @@ -0,0 +1,16 @@ +package com.swyp8team2.common.annotation; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Qualifier(ShareUrlCryptoService.QUALIFIER) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD}) +public @interface ShareUrlCryptoService { + + String QUALIFIER = "shareUrlCryptoService"; +} diff --git a/src/main/java/com/swyp8team2/common/config/SecurityConfig.java b/src/main/java/com/swyp8team2/common/config/SecurityConfig.java index e5de05a9..0994c3fe 100644 --- a/src/main/java/com/swyp8team2/common/config/SecurityConfig.java +++ b/src/main/java/com/swyp8team2/common/config/SecurityConfig.java @@ -5,6 +5,7 @@ import com.swyp8team2.auth.presentation.filter.HeaderTokenExtractor; import com.swyp8team2.auth.presentation.filter.JwtAuthFilter; import com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint; +import com.swyp8team2.common.annotation.GuestTokenCryptoService; import com.swyp8team2.crypto.application.CryptoService; import com.swyp8team2.user.domain.Role; import org.springframework.beans.factory.annotation.Qualifier; @@ -23,13 +24,10 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; -import java.util.List; - @Configuration @EnableWebSecurity public class SecurityConfig { @@ -39,7 +37,7 @@ public class SecurityConfig { public SecurityConfig( @Qualifier("handlerExceptionResolver") HandlerExceptionResolver handlerExceptionResolver, - CryptoService cryptoService + @GuestTokenCryptoService CryptoService cryptoService ) { this.handlerExceptionResolver = handlerExceptionResolver; this.cryptoService = cryptoService; @@ -112,7 +110,8 @@ public static MvcRequestMatcher[] getWhiteList(HandlerMappingIntrospector intros return new MvcRequestMatcher[]{ mvc.pattern("/auth/reissue"), mvc.pattern("/auth/guest/token"), - mvc.pattern(HttpMethod.GET, "/posts/{sharedUrl}"), + mvc.pattern(HttpMethod.GET, "/posts/shareUrl/{shareUrl}"), + mvc.pattern(HttpMethod.GET, "/posts/{postId}"), mvc.pattern(HttpMethod.GET, "/posts/{postId}/comments"), // mvc.pattern("/posts/{postId}/votes/guest/**"), mvc.pattern("/auth/oauth2/**") diff --git a/src/main/java/com/swyp8team2/common/dev/DataInitializer.java b/src/main/java/com/swyp8team2/common/dev/DataInitializer.java index 644eff7a..301755c9 100644 --- a/src/main/java/com/swyp8team2/common/dev/DataInitializer.java +++ b/src/main/java/com/swyp8team2/common/dev/DataInitializer.java @@ -4,9 +4,12 @@ import com.swyp8team2.auth.application.jwt.TokenPair; import com.swyp8team2.comment.domain.Comment; import com.swyp8team2.comment.domain.CommentRepository; +import com.swyp8team2.common.annotation.ShareUrlCryptoService; +import com.swyp8team2.crypto.application.CryptoService; import com.swyp8team2.image.domain.ImageFile; import com.swyp8team2.image.domain.ImageFileRepository; import com.swyp8team2.image.presentation.dto.ImageFileDto; +import com.swyp8team2.post.application.PostService; import com.swyp8team2.post.domain.Post; import com.swyp8team2.post.domain.PostImage; import com.swyp8team2.post.domain.PostRepository; @@ -16,6 +19,7 @@ import com.swyp8team2.user.domain.UserRepository; import com.swyp8team2.vote.application.VoteService; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -26,17 +30,38 @@ @Profile({"dev", "local"}) @Component -@RequiredArgsConstructor public class DataInitializer { private final NicknameAdjectiveRepository nicknameAdjectiveRepository; private final UserRepository userRepository; private final ImageFileRepository imageFileRepository; private final PostRepository postRepository; + private final CryptoService shaereUrlCryptoService; private final JwtService jwtService; private final VoteService voteService; private final CommentRepository commentRepository; + public DataInitializer( + NicknameAdjectiveRepository nicknameAdjectiveRepository, + UserRepository userRepository, + ImageFileRepository imageFileRepository, + PostRepository postRepository, + @ShareUrlCryptoService CryptoService shaereUrlCryptoService, + JwtService jwtService, + VoteService voteService, + CommentRepository commentRepository + ) { + this.nicknameAdjectiveRepository = nicknameAdjectiveRepository; + this.userRepository = userRepository; + this.imageFileRepository = imageFileRepository; + this.postRepository = postRepository; + this.shaereUrlCryptoService = shaereUrlCryptoService; + this.jwtService = jwtService; + this.voteService = voteService; + this.commentRepository = commentRepository; + } + + @Transactional public void init() { if (userRepository.count() > 0) { @@ -56,7 +81,9 @@ public void init() { for (int j = 0; j < 30; j += 2) { ImageFile imageFile1 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png"))); ImageFile imageFile2 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png"))); - posts.add(postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId())), "https://photopic.site/shareurl"))); + Post post = postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId())))); + post.setShareUrl(shaereUrlCryptoService.encrypt(String.valueOf(post.getId()))); + posts.add(post); } } diff --git a/src/main/java/com/swyp8team2/common/exception/ErrorCode.java b/src/main/java/com/swyp8team2/common/exception/ErrorCode.java index bbc5ea04..e717db55 100644 --- a/src/main/java/com/swyp8team2/common/exception/ErrorCode.java +++ b/src/main/java/com/swyp8team2/common/exception/ErrorCode.java @@ -35,6 +35,7 @@ public enum ErrorCode { POST_IMAGE_NAME_GENERATOR_INDEX_OUT_OF_BOUND("이미지 이름 생성기 인덱스 초과"), IMAGE_FILE_NOT_FOUND("존재하지 않는 이미지"), POST_IMAGE_NOT_FOUND("게시글 이미지 없음"), + SHARE_URL_ALREADY_EXISTS("공유 URL이 이미 존재"), //503 SERVICE_UNAVAILABLE("서비스 이용 불가"), diff --git a/src/main/java/com/swyp8team2/post/application/PostImageNameGenerator.java b/src/main/java/com/swyp8team2/post/application/PostImageNameGenerator.java index ab1edd99..b764e035 100644 --- a/src/main/java/com/swyp8team2/post/application/PostImageNameGenerator.java +++ b/src/main/java/com/swyp8team2/post/application/PostImageNameGenerator.java @@ -16,6 +16,6 @@ public String generate() { if (index >= alphabets.length) { throw new InternalServerException(ErrorCode.POST_IMAGE_NAME_GENERATOR_INDEX_OUT_OF_BOUND); } - return "뽀또" + alphabets[index++]; + return "뽀또 " + alphabets[index++]; } } diff --git a/src/main/java/com/swyp8team2/post/application/PostService.java b/src/main/java/com/swyp8team2/post/application/PostService.java index 5b643b77..1e97d25d 100644 --- a/src/main/java/com/swyp8team2/post/application/PostService.java +++ b/src/main/java/com/swyp8team2/post/application/PostService.java @@ -1,9 +1,11 @@ package com.swyp8team2.post.application; +import com.swyp8team2.common.annotation.ShareUrlCryptoService; import com.swyp8team2.common.dto.CursorBasePaginatedResponse; import com.swyp8team2.common.exception.BadRequestException; import com.swyp8team2.common.exception.ErrorCode; import com.swyp8team2.common.exception.InternalServerException; +import com.swyp8team2.crypto.application.CryptoService; import com.swyp8team2.image.domain.ImageFile; import com.swyp8team2.image.domain.ImageFileRepository; import com.swyp8team2.post.domain.Post; @@ -29,7 +31,6 @@ @Service @Transactional(readOnly = true) -@RequiredArgsConstructor public class PostService { private final PostRepository postRepository; @@ -37,12 +38,30 @@ public class PostService { private final RatioCalculator ratioCalculator; private final ImageFileRepository imageFileRepository; private final VoteRepository voteRepository; + private final CryptoService shareUrlCryptoService; + + public PostService( + PostRepository postRepository, + UserRepository userRepository, + RatioCalculator ratioCalculator, + ImageFileRepository imageFileRepository, + VoteRepository voteRepository, + @ShareUrlCryptoService CryptoService shareUrlCryptoService + ) { + this.postRepository = postRepository; + this.userRepository = userRepository; + this.ratioCalculator = ratioCalculator; + this.imageFileRepository = imageFileRepository; + this.voteRepository = voteRepository; + this.shareUrlCryptoService = shareUrlCryptoService; + } @Transactional public Long create(Long userId, CreatePostRequest request) { List postImages = createPostImages(request); - Post post = Post.create(userId, request.description(), postImages, "TODO: location"); + Post post = Post.create(userId, request.description(), postImages); Post save = postRepository.save(post); + save.setShareUrl(shareUrlCryptoService.encrypt(String.valueOf(save.getId()))); return save.getId(); } @@ -148,4 +167,9 @@ public void close(Long userId, Long postId) { .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); post.close(userId); } + + public PostResponse findByShareUrl(Long userId, String shareUrl) { + String decrypt = shareUrlCryptoService.decrypt(shareUrl); + return findById(userId, Long.valueOf(decrypt)); + } } diff --git a/src/main/java/com/swyp8team2/post/domain/Post.java b/src/main/java/com/swyp8team2/post/domain/Post.java index 96ad72dd..5d8d2bb2 100644 --- a/src/main/java/com/swyp8team2/post/domain/Post.java +++ b/src/main/java/com/swyp8team2/post/domain/Post.java @@ -20,8 +20,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; - -import static com.swyp8team2.common.util.Validator.*; +import java.util.Objects; @Getter @Entity @@ -69,8 +68,8 @@ private void validateDescription(String description) { } } - public static Post create(Long userId, String description, List images, String shareUrl) { - return new Post(null, userId, description, State.PROGRESS, images, shareUrl); + public static Post create(Long userId, String description, List images) { + return new Post(null, userId, description, State.PROGRESS, images, null); } public PostImage getBestPickedImage() { @@ -114,4 +113,11 @@ public void validateProgress() { throw new BadRequestException(ErrorCode.POST_ALREADY_CLOSED); } } + + public void setShareUrl(String shareUrl) { + if (Objects.nonNull(this.shareUrl)) { + throw new InternalServerException(ErrorCode.SHARE_URL_ALREADY_EXISTS); + } + this.shareUrl = shareUrl; + } } diff --git a/src/main/java/com/swyp8team2/post/presentation/PostController.java b/src/main/java/com/swyp8team2/post/presentation/PostController.java index 3d4273a0..c07264d7 100644 --- a/src/main/java/com/swyp8team2/post/presentation/PostController.java +++ b/src/main/java/com/swyp8team2/post/presentation/PostController.java @@ -4,6 +4,7 @@ import com.swyp8team2.common.dto.CursorBasePaginatedResponse; import com.swyp8team2.post.application.PostService; import com.swyp8team2.post.presentation.dto.CreatePostRequest; +import com.swyp8team2.post.presentation.dto.CreatePostResponse; import com.swyp8team2.post.presentation.dto.PostImageVoteStatusResponse; import com.swyp8team2.post.presentation.dto.PostResponse; import com.swyp8team2.post.presentation.dto.SimplePostResponse; @@ -51,6 +52,17 @@ public ResponseEntity findPost( return ResponseEntity.ok(postService.findById(userId, postId)); } + @GetMapping("/shareUrl/{shareUrl}") + public ResponseEntity findPostByShareUrl( + @PathVariable("shareUrl") String shareUrl, + @AuthenticationPrincipal UserInfo userInfo + ) { + Long userId = Optional.ofNullable(userInfo) + .map(UserInfo::userId) + .orElse(null); + return ResponseEntity.ok(postService.findByShareUrl(userId, shareUrl)); + } + @GetMapping("/{postId}/status") public ResponseEntity> findVoteStatus( @PathVariable("postId") Long postId diff --git a/src/main/java/com/swyp8team2/post/presentation/CreatePostResponse.java b/src/main/java/com/swyp8team2/post/presentation/dto/CreatePostResponse.java similarity index 52% rename from src/main/java/com/swyp8team2/post/presentation/CreatePostResponse.java rename to src/main/java/com/swyp8team2/post/presentation/dto/CreatePostResponse.java index 44467835..f6629c64 100644 --- a/src/main/java/com/swyp8team2/post/presentation/CreatePostResponse.java +++ b/src/main/java/com/swyp8team2/post/presentation/dto/CreatePostResponse.java @@ -1,4 +1,4 @@ -package com.swyp8team2.post.presentation; +package com.swyp8team2.post.presentation.dto; public record CreatePostResponse(Long postId) { } diff --git a/src/main/java/com/swyp8team2/user/application/UserService.java b/src/main/java/com/swyp8team2/user/application/UserService.java index b089de58..b97757fb 100644 --- a/src/main/java/com/swyp8team2/user/application/UserService.java +++ b/src/main/java/com/swyp8team2/user/application/UserService.java @@ -29,7 +29,7 @@ public Long createUser(String nickname, String profileImageUrl) { private String getProfileImage(String profileImageUrl) { return Optional.ofNullable(profileImageUrl) - .orElse("defailt_profile_image"); + .orElse("https://t1.kakaocdn.net/account_images/default_profile.jpeg"); } private String getNickname(String nickname) { diff --git a/src/main/java/com/swyp8team2/user/domain/User.java b/src/main/java/com/swyp8team2/user/domain/User.java index 85ad7041..5686f490 100644 --- a/src/main/java/com/swyp8team2/user/domain/User.java +++ b/src/main/java/com/swyp8team2/user/domain/User.java @@ -29,21 +29,18 @@ public class User extends BaseEntity { private String profileUrl; - private String seq; - @Enumerated(jakarta.persistence.EnumType.STRING) public Role role; - public User(Long id, String nickname, String profileUrl, String seq, Role role) { + public User(Long id, String nickname, String profileUrl, Role role) { this.id = id; this.nickname = nickname; this.profileUrl = profileUrl; - this.seq = seq; this.role = role; } public static User create(String nickname, String profileUrl) { - return new User(null, nickname, profileUrl, UUID.randomUUID().toString(), Role.USER); + return new User(null, nickname, profileUrl, Role.USER); } public static User createGuest() { @@ -51,7 +48,6 @@ public static User createGuest() { null, "guest_" + System.currentTimeMillis(), "https://image.photopic.site/images-dev/resized_202502240006030.png", - UUID.randomUUID().toString(), Role.GUEST ); } diff --git a/src/test/java/com/swyp8team2/comment/application/CommentServiceTest.java b/src/test/java/com/swyp8team2/comment/application/CommentServiceTest.java index bd5b9844..65ca8fbe 100644 --- a/src/test/java/com/swyp8team2/comment/application/CommentServiceTest.java +++ b/src/test/java/com/swyp8team2/comment/application/CommentServiceTest.java @@ -74,7 +74,7 @@ void findComments() { Comment comment1 = new Comment(1L, postId, 100L, "첫 번째 댓글"); Comment comment2 = new Comment(2L, postId, 100L, "두 번째 댓글"); SliceImpl commentSlice = new SliceImpl<>(List.of(comment1, comment2), PageRequest.of(0, size), false); - User user = new User(100L, "닉네임","http://example.com/profile.png", "seq", Role.USER); + User user = new User(100L, "닉네임","http://example.com/profile.png", Role.USER); // Mock 설정 given(commentRepository.findByPostId(eq(postId), eq(cursor), any(PageRequest.class))).willReturn(commentSlice); diff --git a/src/test/java/com/swyp8team2/post/application/PostImageNameGeneratorTest.java b/src/test/java/com/swyp8team2/post/application/PostImageNameGeneratorTest.java index 626bb21f..8f763b94 100644 --- a/src/test/java/com/swyp8team2/post/application/PostImageNameGeneratorTest.java +++ b/src/test/java/com/swyp8team2/post/application/PostImageNameGeneratorTest.java @@ -26,7 +26,7 @@ void generate() throws Exception { String generate2 = postImageNameGenerator.generate(); //then - assertThat(generate1).isEqualTo("뽀또A"); - assertThat(generate2).isEqualTo("뽀또B"); + assertThat(generate1).isEqualTo("뽀또 A"); + assertThat(generate2).isEqualTo("뽀또 B"); } } diff --git a/src/test/java/com/swyp8team2/post/application/PostServiceTest.java b/src/test/java/com/swyp8team2/post/application/PostServiceTest.java index 4f5118d9..dde9b629 100644 --- a/src/test/java/com/swyp8team2/post/application/PostServiceTest.java +++ b/src/test/java/com/swyp8team2/post/application/PostServiceTest.java @@ -73,10 +73,10 @@ void create() throws Exception { () -> assertThat(post.getUserId()).isEqualTo(userId), () -> assertThat(images).hasSize(2), () -> assertThat(images.get(0).getImageFileId()).isEqualTo(1L), - () -> assertThat(images.get(0).getName()).isEqualTo("뽀또A"), + () -> assertThat(images.get(0).getName()).isEqualTo("뽀또 A"), () -> assertThat(images.get(0).getVoteCount()).isEqualTo(0), () -> assertThat(images.get(1).getImageFileId()).isEqualTo(2L), - () -> assertThat(images.get(1).getName()).isEqualTo("뽀또B"), + () -> assertThat(images.get(1).getName()).isEqualTo("뽀또 B"), () -> assertThat(images.get(1).getVoteCount()).isEqualTo(0) ); } diff --git a/src/test/java/com/swyp8team2/post/domain/PostTest.java b/src/test/java/com/swyp8team2/post/domain/PostTest.java index ab59071e..c0908cf2 100644 --- a/src/test/java/com/swyp8team2/post/domain/PostTest.java +++ b/src/test/java/com/swyp8team2/post/domain/PostTest.java @@ -2,8 +2,6 @@ import com.swyp8team2.common.exception.BadRequestException; import com.swyp8team2.common.exception.ErrorCode; -import com.swyp8team2.common.exception.InternalServerException; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,17 +23,15 @@ void create() throws Exception { PostImage.create("뽀또A", 1L), PostImage.create("뽀또B", 2L) ); - String shareUrl = "shareUrl"; //when - Post post = Post.create(userId, description, postImages, shareUrl); + Post post = Post.create(userId, description, postImages); //then List images = post.getImages(); assertAll( () -> assertThat(post.getUserId()).isEqualTo(userId), () -> assertThat(post.getDescription()).isEqualTo(description), - () -> assertThat(post.getShareUrl()).isEqualTo(shareUrl), () -> assertThat(post.getState()).isEqualTo(State.PROGRESS), () -> assertThat(images).hasSize(2), () -> assertThat(images.get(0).getName()).isEqualTo("뽀또A"), @@ -56,7 +52,7 @@ void create_invalidPostImageCount() throws Exception { ); //when then - assertThatThrownBy(() -> Post.create(1L, "description", postImages, "shareUrl")) + assertThatThrownBy(() -> Post.create(1L, "description", postImages)) .isInstanceOf(BadRequestException.class) .hasMessage(ErrorCode.INVALID_POST_IMAGE_COUNT.getMessage()); } @@ -72,7 +68,7 @@ void create_descriptionCountExceeded() throws Exception { ); //when then - assertThatThrownBy(() -> Post.create(1L, description, postImages, "shareUrl")) + assertThatThrownBy(() -> Post.create(1L, description, postImages)) .isInstanceOf(BadRequestException.class) .hasMessage(ErrorCode.DESCRIPTION_LENGTH_EXCEEDED.getMessage()); } diff --git a/src/test/java/com/swyp8team2/post/presentation/PostControllerTest.java b/src/test/java/com/swyp8team2/post/presentation/PostControllerTest.java index 0d3701f1..9d70892f 100644 --- a/src/test/java/com/swyp8team2/post/presentation/PostControllerTest.java +++ b/src/test/java/com/swyp8team2/post/presentation/PostControllerTest.java @@ -3,6 +3,7 @@ import com.swyp8team2.common.dto.CursorBasePaginatedResponse; import com.swyp8team2.post.presentation.dto.AuthorDto; import com.swyp8team2.post.presentation.dto.CreatePostRequest; +import com.swyp8team2.post.presentation.dto.CreatePostResponse; import com.swyp8team2.post.presentation.dto.PostImageVoteStatusResponse; import com.swyp8team2.post.presentation.dto.PostResponse; import com.swyp8team2.post.presentation.dto.SimplePostResponse; @@ -134,6 +135,58 @@ void findPost() throws Exception { )); } + @Test + @WithAnonymousUser + @DisplayName("게시글 공유 url 상세 조회") + void findPost_shareUrl() throws Exception { + //given + PostResponse response = new PostResponse( + 1L, + new AuthorDto( + 1L, + "author", + "https://image.photopic.site/profile-image" + ), + "description", + List.of( + new PostImageResponse(1L, "뽀또A", "https://image.photopic.site/image/1", "https://image.photopic.site/image/resize/1", true), + new PostImageResponse(2L, "뽀또B", "https://image.photopic.site/image/2", "https://image.photopic.site/image/resize/2", false) + ), + "https://photopic.site/shareurl", + true, + LocalDateTime.of(2025, 2, 13, 12, 0) + ); + given(postService.findByShareUrl(any(), any())) + .willReturn(response); + + //when then + mockMvc.perform(RestDocumentationRequestBuilders.get("/posts/shareUrl/{shareUrl}", "JNOfBVfcG2z89afSiRrOyQ")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))) + .andDo(restDocs.document( + pathParameters( + parameterWithName("shareUrl").description("공유 url") + ), + responseFields( + fieldWithPath("id").type(JsonFieldType.NUMBER).description("게시글 Id"), + fieldWithPath("author").type(JsonFieldType.OBJECT).description("게시글 작성자 정보"), + fieldWithPath("author.id").type(JsonFieldType.NUMBER).description("게시글 작성자 유저 Id"), + fieldWithPath("author.nickname").type(JsonFieldType.STRING).description("게시글 작성자 닉네임"), + fieldWithPath("author.profileUrl").type(JsonFieldType.STRING).description("게시글 작성자 프로필 이미지"), + fieldWithPath("description").type(JsonFieldType.STRING).description("설명"), + fieldWithPath("images[]").type(JsonFieldType.ARRAY).description("투표 선택지 목록"), + fieldWithPath("images[].id").type(JsonFieldType.NUMBER).description("투표 선택지 Id"), + fieldWithPath("images[].imageName").type(JsonFieldType.STRING).description("사진 이름"), + fieldWithPath("images[].imageUrl").type(JsonFieldType.STRING).description("사진 이미지"), + fieldWithPath("images[].thumbnailUrl").type(JsonFieldType.STRING).description("확대 사진 이미지"), + fieldWithPath("images[].voted").type(JsonFieldType.BOOLEAN).description("투표 여부"), + fieldWithPath("shareUrl").type(JsonFieldType.STRING).description("게시글 공유 URL"), + fieldWithPath("createdAt").type(JsonFieldType.STRING).description("게시글 생성 시간"), + fieldWithPath("isAuthor").type(JsonFieldType.BOOLEAN).description("게시글 작성자 여부") + ) + )); + } + @Test @WithMockUserInfo @DisplayName("게시글 투표 상태 조회") diff --git a/src/test/java/com/swyp8team2/support/fixture/FixtureGenerator.java b/src/test/java/com/swyp8team2/support/fixture/FixtureGenerator.java index feafc88d..a11bf052 100644 --- a/src/test/java/com/swyp8team2/support/fixture/FixtureGenerator.java +++ b/src/test/java/com/swyp8team2/support/fixture/FixtureGenerator.java @@ -17,8 +17,7 @@ public static Post createPost(Long userId, ImageFile imageFile1, ImageFile image List.of( PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId()) - ), - "shareUrl" + key + ) ); } From db7e97ab12022a386d3dad911eea0a1b986fd207 Mon Sep 17 00:00:00 2001 From: wlsh44 Date: Fri, 28 Feb 2025 11:43:24 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EB=8D=94=EB=AF=B8=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=88=AC=ED=91=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/swyp8team2/common/dev/DataInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/swyp8team2/common/dev/DataInitializer.java b/src/main/java/com/swyp8team2/common/dev/DataInitializer.java index 301755c9..1ad636bf 100644 --- a/src/main/java/com/swyp8team2/common/dev/DataInitializer.java +++ b/src/main/java/com/swyp8team2/common/dev/DataInitializer.java @@ -81,7 +81,7 @@ public void init() { for (int j = 0; j < 30; j += 2) { ImageFile imageFile1 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png"))); ImageFile imageFile2 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png"))); - Post post = postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또A", imageFile1.getId()), PostImage.create("뽀또B", imageFile2.getId())))); + Post post = postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("뽀또 A", imageFile1.getId()), PostImage.create("뽀또 B", imageFile2.getId())))); post.setShareUrl(shaereUrlCryptoService.encrypt(String.valueOf(post.getId()))); posts.add(post); }