From 962984228821ec45b5a31b3d1e56035f98622f0b Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:03:05 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20FriendController=EC=97=90=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=EC=8B=A0=EC=B2=AD=EC=88=98=EB=9D=BD=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kpring/user/controller/FriendController.kt | 13 +++++++++++++ .../kotlin/kpring/user/service/FriendService.kt | 5 +++++ .../kotlin/kpring/user/service/FriendServiceImpl.kt | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/user/src/main/kotlin/kpring/user/controller/FriendController.kt b/user/src/main/kotlin/kpring/user/controller/FriendController.kt index 094672a9..a045a19a 100644 --- a/user/src/main/kotlin/kpring/user/controller/FriendController.kt +++ b/user/src/main/kotlin/kpring/user/controller/FriendController.kt @@ -54,6 +54,19 @@ class FriendController( return ResponseEntity.ok(ApiResponse(data = response)) } + @PatchMapping("/user/{userId}/friend/{friendId}") + fun acceptFriendRequest( + @RequestHeader("Authorization") token: String, + @PathVariable userId: Long, + @PathVariable friendId: Long, + ): ResponseEntity> { + val validationResult = authClient.getTokenInfo(token) + val validatedUserId = authValidator.checkIfAccessTokenAndGetUserId(validationResult) + authValidator.checkIfUserIsSelf(userId.toString(), validatedUserId) + val response = friendService.acceptFriendRequest(userId, friendId) + return ResponseEntity.ok(ApiResponse(data = response)) + } + @DeleteMapping("/user/{userId}/friend/{friendId}") fun deleteFriend( @RequestHeader("Authorization") token: String, diff --git a/user/src/main/kotlin/kpring/user/service/FriendService.kt b/user/src/main/kotlin/kpring/user/service/FriendService.kt index aa903558..6270e968 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendService.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendService.kt @@ -15,6 +15,11 @@ interface FriendService { friendId: Long, ): AddFriendResponse + fun acceptFriendRequest( + userId: Long, + friendId: Long, + ): AddFriendResponse + fun deleteFriend( userId: Long, friendId: Long, diff --git a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt index d1d7e16a..6df1b2ac 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt @@ -49,6 +49,13 @@ class FriendServiceImpl( return AddFriendResponse(friend.id!!) } + override fun acceptFriendRequest( + userId: Long, + friendId: Long, + ): AddFriendResponse { + TODO("Not yet implemented") + } + override fun deleteFriend( userId: Long, friendId: Long, From 9ed65cb8ff178aa25bbdd967d9bdf277c5712631 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:17:06 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20Friend=EC=97=90=20updateRequestSt?= =?UTF-8?q?atus=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- user/src/main/kotlin/kpring/user/entity/Friend.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/user/src/main/kotlin/kpring/user/entity/Friend.kt b/user/src/main/kotlin/kpring/user/entity/Friend.kt index da9aeab5..62d376cc 100644 --- a/user/src/main/kotlin/kpring/user/entity/Friend.kt +++ b/user/src/main/kotlin/kpring/user/entity/Friend.kt @@ -17,4 +17,8 @@ class Friend( @Enumerated(EnumType.STRING) @Column(nullable = false) var requestStatus: FriendRequestStatus, -) +) { + fun updateRequestStatus(requestStatus: FriendRequestStatus) { + this.requestStatus = requestStatus + } +} From e5dc63360e0b4a0d241859cb3706813487d3b7d0 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:18:10 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20FriendRepository=20=EC=97=90=20fi?= =?UTF-8?q?ndByUserIdAndFriendIdAndRequestStatus=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/kpring/user/repository/FriendRepository.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt index ee5bc406..037dddad 100644 --- a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt +++ b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt @@ -3,6 +3,7 @@ package kpring.user.repository import kpring.user.entity.Friend import kpring.user.entity.FriendRequestStatus import org.springframework.data.jpa.repository.JpaRepository +import java.util.* interface FriendRepository : JpaRepository { fun existsByUserIdAndFriendId( @@ -14,4 +15,10 @@ interface FriendRepository : JpaRepository { userId: Long, requestStatus: FriendRequestStatus, ): List + + fun findByUserIdAndFriendIdAndRequestStatus( + userId: Long, + friendId: Long, + requestStatus: FriendRequestStatus, + ): Optional } From 173c667296c43a38a11336c787cda03b17d1c764 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:30:53 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20UserErrorCode=20=EC=97=90=20FRIEN?= =?UTF-8?q?DSHIP=5FALREADY=5FEXISTS=5FOR=5FNOT=5FFOUND=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 --- user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt b/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt index e0dea1b5..8b8f15ae 100644 --- a/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt +++ b/user/src/main/kotlin/kpring/user/exception/UserErrorCode.kt @@ -19,6 +19,11 @@ enum class UserErrorCode( ALREADY_FRIEND(HttpStatus.BAD_REQUEST, "4030", "이미 친구입니다."), NOT_SELF_FOLLOW(HttpStatus.BAD_REQUEST, "4031", "자기자신에게 친구요청을 보낼 수 없습니다"), + FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND( + HttpStatus.BAD_REQUEST, + "4032", + "해당하는 친구신청이 없거나 이미 친구입니다.", + ), ; override fun message(): String = this.message From a19b81b1885c5c5cdc333f807bf38ed270faf500 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:31:43 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20FriendService=20=EC=97=90=20accep?= =?UTF-8?q?tFriendRequest=20=EB=A9=94=EC=84=9C=EB=93=9C=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 --- .../kpring/user/service/FriendServiceImpl.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt index 6df1b2ac..cac0f080 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt @@ -53,7 +53,13 @@ class FriendServiceImpl( userId: Long, friendId: Long, ): AddFriendResponse { - TODO("Not yet implemented") + val receivedFriend = getFriendshipWithStatus(userId, friendId, FriendRequestStatus.RECEIVED) + val requestedFriend = getFriendshipWithStatus(friendId, userId, FriendRequestStatus.REQUESTED) + + receivedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED) + requestedFriend.updateRequestStatus(FriendRequestStatus.ACCEPTED) + + return AddFriendResponse(friendId) } override fun deleteFriend( @@ -80,4 +86,14 @@ class FriendServiceImpl( throw ServiceException(UserErrorCode.ALREADY_FRIEND) } } + + private fun getFriendshipWithStatus( + userId: Long, + friendId: Long, + requestStatus: FriendRequestStatus, + ): Friend { + return friendRepository + .findByUserIdAndFriendIdAndRequestStatus(userId, friendId, requestStatus) + .orElseThrow { throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) } + } } From 36c8fffa9b93f5d661e7aec664c5b16510891900 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:46:07 +0900 Subject: [PATCH 06/16] =?UTF-8?q?test:=20=EC=B9=9C=EA=B5=AC=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=88=98=EB=9D=BD=20controller=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/FriendControllerTest.kt | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt b/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt index 0367a6bf..53867dc6 100644 --- a/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt +++ b/user/src/test/kotlin/kpring/user/controller/FriendControllerTest.kt @@ -344,4 +344,153 @@ internal class FriendControllerTest( } } } + describe("친구신청 수락 API") { + it("친구신청 수락 성공") { + // given + val data = + AddFriendResponse(friendId = CommonTest.TEST_FRIEND_ID) + + val response = ApiResponse(data = data) + every { authClient.getTokenInfo(any()) }.returns( + ApiResponse(data = TokenInfo(TokenType.ACCESS, CommonTest.TEST_USER_ID.toString())), + ) + every { authValidator.checkIfAccessTokenAndGetUserId(any()) } returns CommonTest.TEST_USER_ID.toString() + every { authValidator.checkIfUserIsSelf(any(), any()) } returns Unit + every { + friendService.acceptFriendRequest( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + } returns data + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isOk + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest200", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "data.friendId" type Strings mean "친구신청을 받은 사용자의 아이디" + } + } + } + } + it("친구신청 수락 실패 : 권한이 없는 토큰") { + // given + val response = + FailMessageResponse.builder().message(UserErrorCode.NOT_ALLOWED.message()).build() + every { authClient.getTokenInfo(any()) } throws ServiceException(UserErrorCode.NOT_ALLOWED) + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isForbidden + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest403", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "message" type Strings mean "에러 메시지" + } + } + } + } + it("친구신청 수락 실패 : 서버 내부 오류") { + // given + val response = + FailMessageResponse.serverError + every { authClient.getTokenInfo(any()) } throws RuntimeException("서버 내부 오류") + + // when + val result = + webTestClient.patch() + .uri( + "/api/v1/user/{userId}/friend/{friendId}", + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + ) + .header("Authorization", CommonTest.TEST_TOKEN) + .exchange() + + // then + val docsRoot = + result + .expectStatus().isEqualTo(500) + .expectBody().json(objectMapper.writeValueAsString(response)) + + // docs + docsRoot + .restDoc( + identifier = "acceptFriendRequest500", + description = "친구신청 수락 API", + ) { + request { + path { + "userId" mean "사용자 아이디" + "friendId" mean "친구신청을 받은 사용자의 아이디" + } + header { + "Authorization" mean "Bearer token" + } + } + response { + body { + "message" type Strings mean "에러 메시지" + } + } + } + } + } }) From 7095dc6b66355794290bc61397728f18f47aba61 Mon Sep 17 00:00:00 2001 From: minahYu Date: Tue, 18 Jun 2024 16:46:28 +0900 Subject: [PATCH 07/16] =?UTF-8?q?test:=20=EC=B9=9C=EA=B5=AC=EC=8B=A0?= =?UTF-8?q?=EC=B2=AD=20=EC=88=98=EB=9D=BD=20service=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/FriendServiceImplTest.kt | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt index e5a24eaa..bd5ae755 100644 --- a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt +++ b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt @@ -13,6 +13,7 @@ import kpring.user.global.CommonTest import kpring.user.repository.FriendRepository import kpring.user.repository.UserRepository import org.springframework.security.crypto.password.PasswordEncoder +import java.util.* internal class FriendServiceImplTest : FunSpec({ val userRepository: UserRepository = mockk() @@ -107,7 +108,10 @@ internal class FriendServiceImplTest : FunSpec({ val friendList = listOf(mockk(relaxed = true)) every { - friendRepository.findAllByUserIdAndRequestStatus(CommonTest.TEST_USER_ID, FriendRequestStatus.RECEIVED) + friendRepository.findAllByUserIdAndRequestStatus( + CommonTest.TEST_USER_ID, + FriendRequestStatus.RECEIVED, + ) } returns friendList val response = friendService.getFriendRequests(CommonTest.TEST_USER_ID) @@ -116,4 +120,75 @@ internal class FriendServiceImplTest : FunSpec({ request.username shouldBe friend.username } } + + test("친구신청수락_성공") { + val receivedFriend = mockk(relaxed = true) + val requestedFriend = mockk(relaxed = true) + + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } returns Optional.of(receivedFriend) + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_FRIEND_ID, + CommonTest.TEST_USER_ID, + FriendRequestStatus.REQUESTED, + ) + } returns Optional.of(requestedFriend) + + every { receivedFriend.updateRequestStatus(any()) } just Runs + every { requestedFriend.updateRequestStatus(any()) } just Runs + + val response = + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + response.friendId shouldBe CommonTest.TEST_FRIEND_ID + + verify(exactly = 2) { + friendRepository.findByUserIdAndFriendIdAndRequestStatus(any(), any(), any()) + } + } + + test("친구신청수락_실패_해당하는 친구신청이 없는 케이스") { + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) + + val exception = + shouldThrow { + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + } + exception.errorCode.message() shouldBe "해당하는 친구신청이 없거나 이미 친구입니다." + } + + test("친구신청수락_실패_이미 친구인 케이스") { + + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_USER_ID, + CommonTest.TEST_FRIEND_ID, + FriendRequestStatus.RECEIVED, + ) + } returns Optional.empty() + every { + friendRepository.findByUserIdAndFriendIdAndRequestStatus( + CommonTest.TEST_FRIEND_ID, + CommonTest.TEST_USER_ID, + FriendRequestStatus.REQUESTED, + ) + } returns Optional.empty() + + val exception = + shouldThrow { + friendService.acceptFriendRequest(CommonTest.TEST_USER_ID, CommonTest.TEST_FRIEND_ID) + } + exception.errorCode.message() shouldBe "해당하는 친구신청이 없거나 이미 친구입니다." + } }) From 2341ca4c026c370324c32f90eb21d1e86cb02683 Mon Sep 17 00:00:00 2001 From: allone9425 Date: Wed, 19 Jun 2024 14:16:05 +0900 Subject: [PATCH 08/16] =?UTF-8?q?[feat]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20API=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/src/components/Auth/LoginBox.tsx | 73 +++++++++++++++++--------- front/src/store/useLoginStore.ts | 17 ++++++ 2 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 front/src/store/useLoginStore.ts diff --git a/front/src/components/Auth/LoginBox.tsx b/front/src/components/Auth/LoginBox.tsx index 29c7b62b..f437549b 100644 --- a/front/src/components/Auth/LoginBox.tsx +++ b/front/src/components/Auth/LoginBox.tsx @@ -5,6 +5,35 @@ import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import { useNavigate } from "react-router"; import { LoginValidation } from "../../hooks/LoginValidation"; +import { useLoginStore } from "../../store/useLoginStore"; + +async function login(email: string, password: string) { + try { + const response = await fetch( + "http://kpring.duckdns.org/user/api/v1/login", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email, password }), + } + ); + + const data = await response.json(); + if (response.ok) { + console.log("로그인 성공:", data); + return data.data; + } else { + console.error("로그인 실패:", data); + return null; + } + } catch (error) { + console.error("API 호출 중 오류 발생:", error); + return null; + } +} + function LoginBox() { const { values, @@ -15,6 +44,7 @@ function LoginBox() { validatePassword, validators, } = LoginValidation(); + const { setTokens } = useLoginStore(); const onChangeHandler = ( field: string, event: React.ChangeEvent @@ -25,30 +55,20 @@ function LoginBox() { setErrors((prevErrors) => ({ ...prevErrors, [`${field}Error`]: error })); }; - const clickSubmitHandler = (e: React.FormEvent) => { + const clickSubmitHandler = async (e: React.FormEvent) => { e.preventDefault(); - - const emailError = validateEmail(values.email); - const passwordError = validatePassword(values.password); - - setErrors({ - emailError, - passwordError, - }); - - // setState가 비동기적으로 업데이트되어서 업데이트 완료 후 검사하도록 처리 - setTimeout(() => { - // 유효성 검사를 해서 모든 에러가 없을때만 실행이 되고 alert를 통해 사용자에게 성공 메세지를 보여줌 - if (!emailError && !passwordError) { - alert("로그인 성공!"); - setValues({ - email: "", - password: "", - }); - } - }, 0); + //console.log("폼 제출 시도:", values); + const result = await login(values.email, values.password); + if (result) { + //console.log("토큰 설정:", result); + setTokens(result.accessToken, result.refreshToken); + //navigate("/"); + } else { + console.error("로그인 실패."); + alert("로그인 실패. 이메일 혹은 비밀번호를 확인해 주세요."); + } }; - const navigation = useNavigate(); + const navigate = useNavigate(); return (
@@ -63,7 +83,8 @@ function LoginBox() { " border="1px solid #e4d4e7" padding="20px" - onSubmit={clickSubmitHandler}> + onSubmit={clickSubmitHandler} + >

디코타운에 어서오세요!

@@ -99,7 +120,8 @@ function LoginBox() { type="submit" variant="contained" startIcon={} - sx={{ width: "90%" }}> + sx={{ width: "90%" }} + > 로그인 @@ -108,7 +130,8 @@ function LoginBox() { color="secondary" startIcon={} sx={{ mt: "20px", width: "90%", mb: "20px" }} - onClick={() => navigation("/join")}> + onClick={() => navigate("/join")} + > 회원가입
diff --git a/front/src/store/useLoginStore.ts b/front/src/store/useLoginStore.ts new file mode 100644 index 00000000..d33b78fc --- /dev/null +++ b/front/src/store/useLoginStore.ts @@ -0,0 +1,17 @@ +import create from "zustand"; + +interface LoginState { + accessToken: string; + refreshToken: string; + setTokens: (accessToken: string, refreshToken: string) => void; +} + +export const useLoginStore = create((set) => ({ + accessToken: "", + refreshToken: "", + setTokens: (accessToken, refreshToken) => { + set({ accessToken, refreshToken }); + localStorage.setItem("dicoTown_AccessToken", accessToken); + localStorage.setItem("dicoTown_RefreshToken", refreshToken); + }, +})); From 29329233a75cd884c00d9bacba331b8cd8706194 Mon Sep 17 00:00:00 2001 From: allone9425 Date: Wed, 19 Jun 2024 14:16:42 +0900 Subject: [PATCH 09/16] =?UTF-8?q?[update]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=ED=8E=98=EC=9D=B4=EC=A7=80=20API=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/src/components/Auth/JoinBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/components/Auth/JoinBox.tsx b/front/src/components/Auth/JoinBox.tsx index 3f8f138e..89be8f6a 100644 --- a/front/src/components/Auth/JoinBox.tsx +++ b/front/src/components/Auth/JoinBox.tsx @@ -43,7 +43,7 @@ function JoinBox() { const submitJoin = async () => { try { const response = await axios.post( - "http://localhost:30002/api/v1/user", + "http://kpring.duckdns.org/user/api/v1/user", { email: values.email, password: values.password, From a0666fe808aeaf9ee017d5934869734d4bf69466 Mon Sep 17 00:00:00 2001 From: minahYu Date: Wed, 19 Jun 2024 17:50:38 +0900 Subject: [PATCH 10/16] =?UTF-8?q?chore:=20FriendService=20=EC=97=90=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kpring/user/service/FriendService.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/user/src/main/kotlin/kpring/user/service/FriendService.kt b/user/src/main/kotlin/kpring/user/service/FriendService.kt index 6270e968..6fd10a30 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendService.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendService.kt @@ -6,15 +6,41 @@ import kpring.user.dto.response.GetFriendRequestsResponse import kpring.user.dto.response.GetFriendsResponse interface FriendService { + /** + * 로그인한 사용자에게 친구신청을 한 사람들의 목록을 조회하는 메서드 + * + * @param userId : 로그인한 사용자의 ID. + * @return 로그인한 사용자 ID, 해당 사용자에게 친구신청을 보낸 사람들의 리스트를 GetFriendRequestsResponse 리턴 + */ fun getFriendRequests(userId: Long): GetFriendRequestsResponse fun getFriends(userId: Long): GetFriendsResponse + /** + * 로그인한 사용자가 friendId를 가진 사용자에게 친구 신청을 하는 메서드 + * + * @param userId : 친구 요청을 하고자 하는 사용자의 ID. + * @param friendId : 친구 요청을 받는 사용자의 ID. + * @return 로그인한 사용자가 친구 신청을 보낸 사용자의 ID를 담고 있는 AddFriendResponse 리턴 + * @throws NOT_SELF_FOLLOW + * : 로그인한 사용자가 스스로에게 친구신청을 시도할 때 발생하는 예외 + * @throws ALREADY_FRIEND + * : 친구신청을 받은 사용자와 이미 친구일 때 발생하는 예외 + */ fun addFriend( userId: Long, friendId: Long, ): AddFriendResponse + /** + * 사용자가 친구 신청을 수락해, 두 사용자 간의 상태를 ACCEPTED 로 변경하는 메서드 + * + * @param userId : 친구 요청을 수락하는 사용자의 ID. + * @param friendId : 친구 요청을 보낸 사용자의 ID. + * @return 새로 친구가 된 사용자의 ID를 담고 있는 AddFriendResponse 리턴 + * @throws FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND + * : friendId가 보낸 친구 신청이 없거나, 해당 사용자와 이미 친구일 때 발생하는 예외 + */ fun acceptFriendRequest( userId: Long, friendId: Long, From f06178430800e46da2e3f46da1b47903e55fae08 Mon Sep 17 00:00:00 2001 From: minahYu Date: Wed, 19 Jun 2024 21:08:07 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refac:=20findByUserIdAndFriendIdAndReques?= =?UTF-8?q?tStatus=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A6=AC=ED=84=B4?= =?UTF-8?q?=ED=83=80=EC=9E=85=20Optional=EC=97=90=EC=84=9C=20Frien?= =?UTF-8?q?d=3F=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kpring/user/repository/FriendRepository.kt | 3 +-- .../main/kotlin/kpring/user/service/FriendServiceImpl.kt | 2 +- .../kotlin/kpring/user/service/FriendServiceImplTest.kt | 9 ++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt index 037dddad..eadac275 100644 --- a/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt +++ b/user/src/main/kotlin/kpring/user/repository/FriendRepository.kt @@ -3,7 +3,6 @@ package kpring.user.repository import kpring.user.entity.Friend import kpring.user.entity.FriendRequestStatus import org.springframework.data.jpa.repository.JpaRepository -import java.util.* interface FriendRepository : JpaRepository { fun existsByUserIdAndFriendId( @@ -20,5 +19,5 @@ interface FriendRepository : JpaRepository { userId: Long, friendId: Long, requestStatus: FriendRequestStatus, - ): Optional + ): Friend? } diff --git a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt index cac0f080..6c02d364 100644 --- a/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt +++ b/user/src/main/kotlin/kpring/user/service/FriendServiceImpl.kt @@ -94,6 +94,6 @@ class FriendServiceImpl( ): Friend { return friendRepository .findByUserIdAndFriendIdAndRequestStatus(userId, friendId, requestStatus) - .orElseThrow { throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) } + ?: throw ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) } } diff --git a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt index bd5ae755..4b174825 100644 --- a/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt +++ b/user/src/test/kotlin/kpring/user/service/FriendServiceImplTest.kt @@ -13,7 +13,6 @@ import kpring.user.global.CommonTest import kpring.user.repository.FriendRepository import kpring.user.repository.UserRepository import org.springframework.security.crypto.password.PasswordEncoder -import java.util.* internal class FriendServiceImplTest : FunSpec({ val userRepository: UserRepository = mockk() @@ -131,14 +130,14 @@ internal class FriendServiceImplTest : FunSpec({ CommonTest.TEST_FRIEND_ID, FriendRequestStatus.RECEIVED, ) - } returns Optional.of(receivedFriend) + } returns receivedFriend every { friendRepository.findByUserIdAndFriendIdAndRequestStatus( CommonTest.TEST_FRIEND_ID, CommonTest.TEST_USER_ID, FriendRequestStatus.REQUESTED, ) - } returns Optional.of(requestedFriend) + } returns requestedFriend every { receivedFriend.updateRequestStatus(any()) } just Runs every { requestedFriend.updateRequestStatus(any()) } just Runs @@ -176,14 +175,14 @@ internal class FriendServiceImplTest : FunSpec({ CommonTest.TEST_FRIEND_ID, FriendRequestStatus.RECEIVED, ) - } returns Optional.empty() + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) every { friendRepository.findByUserIdAndFriendIdAndRequestStatus( CommonTest.TEST_FRIEND_ID, CommonTest.TEST_USER_ID, FriendRequestStatus.REQUESTED, ) - } returns Optional.empty() + } throws ServiceException(UserErrorCode.FRIENDSHIP_ALREADY_EXISTS_OR_NOT_FOUND) val exception = shouldThrow { From 8b1d753f3c2977d7da6fd79413e094f8f4c71b42 Mon Sep 17 00:00:00 2001 From: yudonggeun Date: Sun, 23 Jun 2024 11:28:05 +0900 Subject: [PATCH 12/16] chore : move infra to other repo --- infra/.helmignore | 23 ------- infra/Chart.yaml | 12 ---- infra/charts/auth/.helmignore | 23 ------- infra/charts/auth/Chart.yaml | 8 --- infra/charts/auth/templates/_helpers.tpl | 62 ------------------ infra/charts/auth/templates/configmap.yaml | 12 ---- infra/charts/auth/templates/deployment.yaml | 44 ------------- .../auth/templates/redis-deployment.yaml | 24 ------- .../charts/auth/templates/redis-service.yaml | 14 ---- infra/charts/auth/templates/service.yaml | 23 ------- .../auth/templates/tests/test-connection.yaml | 13 ---- infra/charts/auth/values.yaml | 15 ----- infra/charts/gateway/.helmignore | 23 ------- infra/charts/gateway/Chart.yaml | 24 ------- infra/charts/gateway/templates/ingress.yaml | 36 ---------- infra/charts/gateway/values.yaml | 22 ------- infra/charts/ingress-nginx-4.10.1.tgz | Bin 55564 -> 0 bytes infra/charts/server/.helmignore | 23 ------- infra/charts/server/Chart.yaml | 24 ------- infra/charts/server/templates/deployment.yaml | 44 ------------- .../server/templates/mongo-deployment.yaml | 31 --------- .../server/templates/mongo-service.yaml | 16 ----- infra/charts/server/templates/service.yaml | 23 ------- infra/charts/server/values.yaml | 17 ----- infra/charts/user/.helmignore | 23 ------- infra/charts/user/Chart.yaml | 24 ------- infra/charts/user/templates/deployment.yaml | 38 ----------- .../user/templates/mysql-deployment.yaml | 33 ---------- .../charts/user/templates/mysql-service.yaml | 16 ----- infra/charts/user/templates/service.yaml | 23 ------- infra/charts/user/values.yaml | 19 ------ infra/compose.yml | 13 ---- infra/readme.md | 53 --------------- infra/templates/_helpers.tpl | 62 ------------------ infra/templates/profile-config.yaml | 9 --- infra/values.yaml | 7 -- 36 files changed, 876 deletions(-) delete mode 100644 infra/.helmignore delete mode 100644 infra/Chart.yaml delete mode 100644 infra/charts/auth/.helmignore delete mode 100644 infra/charts/auth/Chart.yaml delete mode 100644 infra/charts/auth/templates/_helpers.tpl delete mode 100644 infra/charts/auth/templates/configmap.yaml delete mode 100644 infra/charts/auth/templates/deployment.yaml delete mode 100644 infra/charts/auth/templates/redis-deployment.yaml delete mode 100644 infra/charts/auth/templates/redis-service.yaml delete mode 100644 infra/charts/auth/templates/service.yaml delete mode 100644 infra/charts/auth/templates/tests/test-connection.yaml delete mode 100644 infra/charts/auth/values.yaml delete mode 100644 infra/charts/gateway/.helmignore delete mode 100644 infra/charts/gateway/Chart.yaml delete mode 100644 infra/charts/gateway/templates/ingress.yaml delete mode 100644 infra/charts/gateway/values.yaml delete mode 100644 infra/charts/ingress-nginx-4.10.1.tgz delete mode 100644 infra/charts/server/.helmignore delete mode 100644 infra/charts/server/Chart.yaml delete mode 100644 infra/charts/server/templates/deployment.yaml delete mode 100644 infra/charts/server/templates/mongo-deployment.yaml delete mode 100644 infra/charts/server/templates/mongo-service.yaml delete mode 100644 infra/charts/server/templates/service.yaml delete mode 100644 infra/charts/server/values.yaml delete mode 100644 infra/charts/user/.helmignore delete mode 100644 infra/charts/user/Chart.yaml delete mode 100644 infra/charts/user/templates/deployment.yaml delete mode 100644 infra/charts/user/templates/mysql-deployment.yaml delete mode 100644 infra/charts/user/templates/mysql-service.yaml delete mode 100644 infra/charts/user/templates/service.yaml delete mode 100644 infra/charts/user/values.yaml delete mode 100644 infra/compose.yml delete mode 100644 infra/readme.md delete mode 100644 infra/templates/_helpers.tpl delete mode 100644 infra/templates/profile-config.yaml delete mode 100644 infra/values.yaml diff --git a/infra/.helmignore b/infra/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/Chart.yaml b/infra/Chart.yaml deleted file mode 100644 index db697856..00000000 --- a/infra/Chart.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v2 -name: kpring -description: A Helm chart for Kubernetes - -type: application -version: 0.1.0 -appVersion: "1.16.0" - -dependencies: - - name: ingress-nginx - version: 4.x.x - repository: https://kubernetes.github.io/ingress-nginx \ No newline at end of file diff --git a/infra/charts/auth/.helmignore b/infra/charts/auth/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/auth/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/auth/Chart.yaml b/infra/charts/auth/Chart.yaml deleted file mode 100644 index d74d2973..00000000 --- a/infra/charts/auth/Chart.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v2 -name: auth -description: kpring auth application - -type: application -version: 0.1.0 - -appVersion: "1.16.0" diff --git a/infra/charts/auth/templates/_helpers.tpl b/infra/charts/auth/templates/_helpers.tpl deleted file mode 100644 index 55c38e84..00000000 --- a/infra/charts/auth/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "swagger.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "swagger.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "swagger.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "swagger.labels" -}} -helm.sh/chart: {{ include "swagger.chart" . }} -{{ include "swagger.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "swagger.selectorLabels" -}} -app.kubernetes.io/name: {{ include "swagger.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "swagger.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "swagger.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/infra/charts/auth/templates/configmap.yaml b/infra/charts/auth/templates/configmap.yaml deleted file mode 100644 index bd9b7546..00000000 --- a/infra/charts/auth/templates/configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: auth-config - namespace: {{ .Release.namespace }} - labels: - app: auth -data: - jwt.access.duration: "{{ .Values.service.jwt.duration.access }}" - jwt.refresh.duration: "{{ .Values.service.jwt.duration.refresh }}" -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/deployment.yaml b/infra/charts/auth/templates/deployment.yaml deleted file mode 100644 index 45035cce..00000000 --- a/infra/charts/auth/templates/deployment.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} - labels: - app: {{ .Values.service.name }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ .Values.service.name }} - template: - metadata: - labels: - app: {{ .Values.service.name }} - spec: - containers: - - name: {{ .Values.service.name }} - image: {{ .Values.service.image }} - ports: - - containerPort: {{ .Values.service.port }} - env: - - name: REDIS_HOST - value: {{ .Values.redis.name }} - - name: REDIS_PORT - value: "{{ .Values.redis.port }}" - - name: JWT_ACCESS_DURATION - valueFrom: - configMapKeyRef: - name: auth-config - key: jwt.access.duration - - name: JWT_REFRESH_DURATION - valueFrom: - configMapKeyRef: - name: auth-config - key: jwt.refresh.duration - - name: APPLICATION_PROFILE - valueFrom: - configMapKeyRef: - name: profile-config - key: profile -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/redis-deployment.yaml b/infra/charts/auth/templates/redis-deployment.yaml deleted file mode 100644 index 6f9c056b..00000000 --- a/infra/charts/auth/templates/redis-deployment.yaml +++ /dev/null @@ -1,24 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis - namespace: {{ .Release.namespace }} - labels: - app: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis - template: - metadata: - labels: - app: redis - spec: - containers: - - name: redis - image: redis:alpine - ports: - - containerPort: 6379 -{{- end }} \ No newline at end of file diff --git a/infra/charts/auth/templates/redis-service.yaml b/infra/charts/auth/templates/redis-service.yaml deleted file mode 100644 index d7c85dee..00000000 --- a/infra/charts/auth/templates/redis-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.redis.name }} - namespace: {{ .Release.namespace }} -spec: - type: ClusterIP - ports: - - port: {{ .Values.redis.port }} - protocol: TCP - selector: - app: {{ .Values.redis.name }} -{{- end }} diff --git a/infra/charts/auth/templates/service.yaml b/infra/charts/auth/templates/service.yaml deleted file mode 100644 index 153f851f..00000000 --- a/infra/charts/auth/templates/service.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.global.enable.auth }} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Values.service.name }} - namespace: {{ .Release.namespace }} -spec: - {{- if eq .Values.global.profile "local" }} - type: NodePort - {{- else }} - type: ClusterIP - {{- end }} - - ports: - - port: 80 - targetPort: {{ .Values.service.port }} - protocol: TCP - {{- if eq .Values.global.profile "local" }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - selector: - app: {{ .Values.service.name }} -{{- end }} diff --git a/infra/charts/auth/templates/tests/test-connection.yaml b/infra/charts/auth/templates/tests/test-connection.yaml deleted file mode 100644 index 5023d861..00000000 --- a/infra/charts/auth/templates/tests/test-connection.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ .Values.service.name }}-test-connection" - annotations: - "helm.sh/hook": test -spec: - containers: - - name: wget - image: busybox - command: ['wget'] - args: ['{{ .Values.service.name }}:{{ .Values.service.port }}'] - restartPolicy: Never diff --git a/infra/charts/auth/values.yaml b/infra/charts/auth/values.yaml deleted file mode 100644 index 86222375..00000000 --- a/infra/charts/auth/values.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# auth service -service: - name: auth-application - image: youdong98/kpring-auth-application:latest - port: 8080 - nodePort: 30001 - jwt: - duration: - access: "3600000" - refresh: "86400000" - -redis: - name: redis - image: redis:alpine - port: 6379 diff --git a/infra/charts/gateway/.helmignore b/infra/charts/gateway/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/infra/charts/gateway/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/infra/charts/gateway/Chart.yaml b/infra/charts/gateway/Chart.yaml deleted file mode 100644 index a32f8293..00000000 --- a/infra/charts/gateway/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: gateway -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/infra/charts/gateway/templates/ingress.yaml b/infra/charts/gateway/templates/ingress.yaml deleted file mode 100644 index 2cc6720a..00000000 --- a/infra/charts/gateway/templates/ingress.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ .Values.ingress.name }} - labels: - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - ingressClassName: {{ .Values.ingress.className }} - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType }} - backend: - service: - name: {{ .service }} - port: - number: 8080 - {{- end }} - {{- end }} diff --git a/infra/charts/gateway/values.yaml b/infra/charts/gateway/values.yaml deleted file mode 100644 index 4b6fa172..00000000 --- a/infra/charts/gateway/values.yaml +++ /dev/null @@ -1,22 +0,0 @@ -ingress: - name: kpring-ingress - enabled: false - className: "nginx" - annotations: - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/rewrite-target: /$2 - hosts: - - host: - paths: - - path: /auth(/|$)(.*) - pathType: ImplementationSpecific - service: auth-application - - path: /user(/|$)(.*) - pathType: ImplementationSpecific - service: user-application - - path: /server(/|$)(.*) - pathType: ImplementationSpecific - service: server-application - tls: [] - -resources: {} diff --git a/infra/charts/ingress-nginx-4.10.1.tgz b/infra/charts/ingress-nginx-4.10.1.tgz deleted file mode 100644 index 278d0a5405f0baa814f05000ae8891357f2e4652..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55564 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 wre29#+&9{7jIOr@t&KB+LUGc52~(yJNJ . -``` - -`helm dependency update` 명령은 helm이 의존하는 다른 helm을 설치하는 사전 작업입니다. nginx-ingress-controller 설치를 위해서 해당 과정이 `21-06-04` 기준으로 추가되었습니다. - -다음은 windows powershell을 통해서 배포하는 예시입니다. -```shell -PS C:\Users\ydong\OneDrive\document\side_project\ksping> cd infra -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm install dev . -Error: INSTALLATION FAILED: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: ingress-nginx -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm dependency build -Hang tight while we grab the latest from your chart repositories... -...Successfully got an update from the "elastic" chart repository -...Successfully got an update from the "nginx-ingress" chart repository -...Successfully got an update from the "sentry" chart repository -...Successfully got an update from the "bitnami" chart repository -...Successfully got an update from the "stable" chart repository -Update Complete. ⎈Happy Helming!⎈ -Saving 1 charts -Downloading ingress-nginx from repo https://kubernetes.github.io/ingress-nginx -Deleting outdated charts -PS C:\Users\ydong\OneDrive\document\side_project\ksping\infra> helm install dev . -NAME: dev -LAST DEPLOYED: Tue Jun 4 21:10:41 2024 -NAMESPACE: default -STATUS: deployed -REVISION: 1 -``` - -## Finish -이제 모든 서비스가 배포되었습니다. 로컬에서 서비스를 확인해주세요! \ No newline at end of file diff --git a/infra/templates/_helpers.tpl b/infra/templates/_helpers.tpl deleted file mode 100644 index 2d3a5659..00000000 --- a/infra/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "kpring.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "kpring.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "kpring.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "kpring.labels" -}} -helm.sh/chart: {{ include "kpring.chart" . }} -{{ include "kpring.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "kpring.selectorLabels" -}} -app.kubernetes.io/name: {{ include "kpring.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "kpring.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "kpring.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/infra/templates/profile-config.yaml b/infra/templates/profile-config.yaml deleted file mode 100644 index 7471c1dc..00000000 --- a/infra/templates/profile-config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: profile-config - namespace: {{ .Release.namespace }} - labels: - app: profile -data: - profile: {{ .Values.global.profile }} diff --git a/infra/values.yaml b/infra/values.yaml deleted file mode 100644 index ac183f7f..00000000 --- a/infra/values.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# service enable setting -global: - profile: local - enable: - auth: true - user: true - server: true From fd4cb4fdffa61ad910047073c64301ac53f07125 Mon Sep 17 00:00:00 2001 From: yudonggeun Date: Sun, 23 Jun 2024 11:31:07 +0900 Subject: [PATCH 13/16] =?UTF-8?q?chore=20:=20auth=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20registry=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EB=B0=8F=20jib,=20openapi3=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth/build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 93c5f609..b96a1ae8 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -92,7 +92,7 @@ jib { } } to { - image = "youdong98/kpring-auth-application" + image = "kpring/auth-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -100,5 +100,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") From 49236c0fcf2232bece1051170f1011227134fbcf Mon Sep 17 00:00:00 2001 From: yudonggeun Date: Sun, 23 Jun 2024 11:31:47 +0900 Subject: [PATCH 14/16] =?UTF-8?q?chore=20:=20server=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20registry=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EB=B0=8F=20jib,=20openapi3=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index c2fd02cc..2638a059 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -89,7 +89,7 @@ jib { } } to { - image = "youdong98/kpring-server-application" + image = "kpring/server-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -97,5 +97,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") From 63b257267a161f818f98bb376f47b80f5bfec9fd Mon Sep 17 00:00:00 2001 From: yudonggeun Date: Sun, 23 Jun 2024 11:32:22 +0900 Subject: [PATCH 15/16] =?UTF-8?q?chore=20:=20user=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20registry=20=EC=A3=BC=EC=86=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=EB=B0=8F=20jib,=20openapi3=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- user/build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/user/build.gradle.kts b/user/build.gradle.kts index 3c49ed7d..d4e13cfa 100644 --- a/user/build.gradle.kts +++ b/user/build.gradle.kts @@ -73,7 +73,7 @@ jib { } } to { - image = "youdong98/kpring-user-application" + image = "kpring/user-application" setAllowInsecureRegistries(true) tags = setOf("latest", version.toString()) } @@ -81,5 +81,3 @@ jib { jvmFlags = listOf("-Xms512m", "-Xmx512m") } } - -tasks.getByName("jib").dependsOn("openapi3") From d9f695f1073934b1df7f2be57721763ff0efdf12 Mon Sep 17 00:00:00 2001 From: yudonggeun Date: Sun, 23 Jun 2024 11:34:39 +0900 Subject: [PATCH 16/16] =?UTF-8?q?chore=20:=20ci=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jib, openapi3 task 분리됨에 따른 수정입니다. --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad670b25..235bc133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,10 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - # Gradle jib를 통한 이미지 배포 - - name: update image using jib + - name: make api documents + run: ./gradlew --info openapi3 + + - name: push image using jib run: ./gradlew --info jib tagging: