Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ jobs:
- name: Run unit tests
run: ./gradlew test
env:
          BASE_URL: ${{ secrets.BASE_URL }}
          KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
BASE_URL: ${{ secrets.BASE_URL }}
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}

- name: Upload test reports
if: always()
Expand Down Expand Up @@ -96,4 +96,4 @@ jobs:
run: bundle exec fastlane distribute
env:
BASE_URL: ${{ secrets.BASE_URL }}
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
336 changes: 24 additions & 312 deletions .idea/caches/deviceStreaming.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// data/dto/request/auth/LogoutRequestDto.kt
package com.hsLink.hslink.data.dto.request.auth

import kotlinx.serialization.Serializable

@Serializable
data class LogoutRequestDto(
val refreshToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// data/dto/request/auth/WithdrawRequestDto.kt
package com.hsLink.hslink.data.dto.request.auth

import kotlinx.serialization.Serializable

@Serializable
data class WithdrawRequestDto(
val refreshToken: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// data/dto/response/auth/WithdrawResponseDto.kt
package com.hsLink.hslink.data.dto.response.auth

import kotlinx.serialization.Serializable

@Serializable
data class WithdrawResponseDto(
val userId: Long,
val deletedAt: String
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.hsLink.hslink.data.repositoryimpl

import com.hsLink.hslink.data.dto.request.auth.LogoutRequestDto
import com.hsLink.hslink.data.dto.request.auth.SocialLoginRequestDto
import com.hsLink.hslink.data.dto.request.auth.WithdrawRequestDto
import com.hsLink.hslink.data.dto.response.auth.SocialLoginResponseDto
import com.hsLink.hslink.data.dto.response.auth.WithdrawResponseDto
import com.hsLink.hslink.data.local.TokenDataStore
import com.hsLink.hslink.data.service.login.AuthService
import com.hsLink.hslink.domain.repository.AuthRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
Expand Down Expand Up @@ -32,4 +36,47 @@ class AuthRepositoryImpl @Inject constructor(
Result.failure(e)
}
}

// ← 새로 추가: 로그아웃
override suspend fun logout(): Result<Unit> {
return try {
val refreshToken = tokenDataStore.refreshToken.first()
if (refreshToken.isNullOrEmpty()) {
return Result.failure(Exception("토큰이 존재하지 않습니다"))
}

val request = LogoutRequestDto(refreshToken)
val response = authService.logout(request) // <- 이제 Response<Unit>

if (response.isSuccessful) {
tokenDataStore.clearTokens()
Result.success(Unit)
} else {
Result.failure(Exception("로그아웃에 실패했습니다: ${response.code()}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}

override suspend fun withdraw(): Result<WithdrawResponseDto> {
return try {
val refreshToken = tokenDataStore.refreshToken.first() // ← 수정
if (refreshToken.isNullOrEmpty()) {
return Result.failure(Exception("토큰이 존재하지 않습니다"))
}

val request = WithdrawRequestDto(refreshToken)
val response = authService.withdraw(request)

if (response.isSuccessful && response.body()?.isSuccess == true) {
tokenDataStore.clearTokens()
Result.success(response.body()!!.result)
} else {
Result.failure(Exception(response.body()?.message ?: "계정 탈퇴에 실패했습니다"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
Comment on lines +62 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NPE risk with !! operator.

Line 74 uses the !! operator on response.body(), which could throw a NullPointerException even after checking isSuccessful and isSuccess. Although unlikely, HTTP responses can have null bodies in edge cases.

Apply this diff to handle the null case safely:

-            if (response.isSuccessful && response.body()?.isSuccess == true) {
+            val body = response.body()
+            if (response.isSuccessful && body?.isSuccess == true && body.result != null) {
                 tokenDataStore.clearTokens()
-                Result.success(response.body()!!.result)
+                Result.success(body.result)
             } else {
-                Result.failure(Exception(response.body()?.message ?: "계정 탈퇴에 실패했습니다"))
+                Result.failure(Exception(body?.message ?: "계정 탈퇴에 실패했습니다"))
             }
🤖 Prompt for AI Agents
In app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt
around lines 62 to 81, the code uses response.body()!! at line 74 which can
throw an NPE even after basic success checks; change the logic to safely handle
a null body by first capturing val body = response.body() and then if
(response.isSuccessful && body?.isSuccess == true) return
Result.success(body.result) else return Result.failure(Exception(body?.message
?: "계정 탈퇴에 실패했습니다")); remove the use of !! and ensure every access to body is
null-checked, returning a failure with a clear message when body is null.

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package com.hsLink.hslink.data.service.login

import com.hsLink.hslink.core.network.BaseResponse
import com.hsLink.hslink.data.dto.request.auth.LogoutRequestDto
import com.hsLink.hslink.data.dto.request.auth.SocialLoginRequestDto
import com.hsLink.hslink.data.dto.request.auth.WithdrawRequestDto
import com.hsLink.hslink.data.dto.response.auth.SocialLoginResponseDto
import com.hsLink.hslink.data.dto.response.auth.WithdrawResponseDto
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.POST

interface AuthService {
@POST("auth/login")
suspend fun socialLogin(@Body request: SocialLoginRequestDto): Response<SocialLoginResponseDto>

@POST("auth/logout")
suspend fun logout(@Body request: LogoutRequestDto): Response<Unit>

@DELETE("auth/withdraw")
suspend fun withdraw(@Body request: WithdrawRequestDto): Response<BaseResponse<WithdrawResponseDto>>
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.hsLink.hslink.domain.repository

import com.hsLink.hslink.data.dto.response.auth.SocialLoginResponseDto
import com.hsLink.hslink.data.dto.response.auth.WithdrawResponseDto

interface AuthRepository {
suspend fun loginWithSocialToken(
provider: String,
accessToken: String
): Result<SocialLoginResponseDto>

suspend fun logout(): Result<Unit>
suspend fun withdraw(): Result<WithdrawResponseDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// presentation/mypage/component/common/ConfirmDialog.kt (이름 변경)
package com.hsLink.hslink.presentation.mypage.component.career

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.hsLink.hslink.core.designsystem.component.HsLinkActionButton
import com.hsLink.hslink.core.designsystem.component.HsLinkActionButtonSize
import com.hsLink.hslink.core.designsystem.component.HsLinkButtonSize
import com.hsLink.hslink.core.designsystem.component.HsLinkSelectButton
import com.hsLink.hslink.core.designsystem.theme.HsLinkTheme


@Preview(showBackground = true)
@Composable
private fun ConfirmDialogLogoutPreview() {
HsLinkTheme {
ConfirmDialog(
title = "로그아웃을\n하시겠습니까?",
message = null,
cancelText = "취소",
confirmText = "확인",
onDismiss = {},
onConfirm = {}
)
}
}

@Composable
fun ConfirmDialog(
modifier: Modifier = Modifier,
title: String,
message: String? = null,
cancelText: String = "취소",
confirmText: String = "확인",
onDismiss: () -> Unit,
onConfirm: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismiss,
containerColor = Color.White, // ← 완전 하얀색 배경
title = {
Text(
text = title,
style = HsLinkTheme.typography.title_20Strong,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
text = message?.let {
{
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp), // ← 전체 여백
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = it,
style = HsLinkTheme.typography.body_14Normal,
color = HsLinkTheme.colors.Grey600,
textAlign = TextAlign.Center,
lineHeight = 20.sp, // ← 줄 간격 고정값
modifier = Modifier.fillMaxWidth()
)
}
}
},
confirmButton = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// ← 취소하기 (회색 배경)
HsLinkActionButton(
label = cancelText, // "취소하기"
onClick = onDismiss,
size = HsLinkActionButtonSize.Small, // ← 회색 배경
modifier = Modifier.weight(1f)
)

// ← 확인 버튼 (파란색 배경)
HsLinkActionButton(
label = confirmText, // "로그아웃" 또는 "삭제하기"
onClick = onConfirm,
size = HsLinkActionButtonSize.Large, // ← 파란색 배경
modifier = Modifier.weight(1f)
)
}
},
dismissButton = null,
modifier = modifier
)
}
Loading