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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.egobook.app.data.api.AccountApiService
import com.egobook.app.data.local.UserInfoStorage
import com.egobook.app.data.model.account.LinkRequest
import com.egobook.app.data.util.safeApiCall
import com.egobook.app.data.util.safeApiCallWithSuspendTransform
import com.egobook.app.data.util.safeAuthApiCall
import com.egobook.app.domain.repository.account.AccountRepository
import com.egobook.app.domain.repository.account.LinkedAccountInfo
import javax.inject.Inject
Expand Down Expand Up @@ -50,29 +50,26 @@ class AccountRepositoryImpl @Inject constructor(
}

// idToken 전송 (API 호출) 및 토큰 저장
return safeApiCallWithSuspendTransform(
return safeAuthApiCall(
apiCall = {
apiService.linkToGoogle(LinkRequest(idToken = idToken))
},
transform = { tokenData ->
// Access, Refresh Token 저장 (Recover Token은 null로 설정하여 저장하지 않음)
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken,
recoverToken = null
)

// 로그인 타입을 GOOGLE로 변경
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)

// 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")

Unit
}
)
).map { tokenData ->
// Access, Refresh Token 저장 (Recover Token은 null로 설정하여 저장하지 않음)
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken,
recoverToken = null
)

// 로그인 타입을 GOOGLE로 변경
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)

// 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")
}
}

override suspend fun getLinkedAccountInfo(): Result<LinkedAccountInfo> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.egobook.app.data.model.auth.TokenRequestByGuest
import com.egobook.app.data.model.auth.TokensRequest
import com.egobook.app.data.model.auth.TokensRequestAgainByGuest
import com.egobook.app.data.util.safeApiCallWithSuspendTransform
import com.egobook.app.data.util.safeAuthApiCall
import com.egobook.app.domain.repository.auth.AuthRepository
import kotlinx.coroutines.flow.first
import timber.log.Timber
Expand All @@ -20,26 +21,24 @@ class AuthRepositoryImpl @Inject constructor(
) : AuthRepository {

override suspend fun googleSignUp(idToken: String): Result<Unit> {
return safeApiCallWithSuspendTransform(
return safeAuthApiCall(
apiCall = {
apiService.googleSignUp(
TokenRequestByGoogle(idToken = idToken)
)
},
transform = { tokenData ->
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken
)
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)

//유저 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")
Unit
}
)
).map { tokenData ->
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken
)
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)

//유저 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")
}
}

override suspend fun guestLogin(): Result<Unit> {
Expand Down Expand Up @@ -112,34 +111,32 @@ class AuthRepositoryImpl @Inject constructor(
}
}

//구글 로그인 시 사용
// 구글 로그인 시 사용
override suspend fun refreshTokens(idToken: String): Result<Unit> {
// 액세스 토큰 가져오기 (없으면 null)
val accessToken = userInfoStorage.getAccessToken().first()

return safeApiCallWithSuspendTransform(
return safeAuthApiCall(
apiCall = {
apiService.reGetTokens(
TokensRequest(
idToken = idToken,
accessToken = accessToken
)
)
},
transform = { tokenData ->
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken
)
//로그인 타입 저장
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)
//유저 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")
Unit
}
)
).map { tokenData ->
userInfoStorage.saveAllTokens(
accessToken = tokenData.accessToken,
refreshToken = tokenData.refreshToken
)
//로그인 타입 저장
val loginType = UserInfoStorage.LoginType.GOOGLE
userInfoStorage.saveLoginType(loginType)
//유저 이메일 저장
userInfoStorage.saveUserEmail(tokenData.email)
Timber.d("구글 로그인 성공, loginType=$loginType, email=${tokenData.email}")
}
}

override suspend fun refreshGuestTokens(): Result<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import com.egobook.app.data.repository.diary.paging.DiariesPagingSource
import com.egobook.app.data.util.safeApiCall
import com.egobook.app.domain.model.diary.entity.Diary
import com.egobook.app.domain.model.diary.entity.DiaryFilter
import com.egobook.app.domain.model.diary.entity.DiaryRewards
import com.egobook.app.domain.model.diary.entity.DiarySummary
import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryCreateRequest
import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryEntity
import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryRewardsEntity
import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toDiaryUpdateRequest
import com.egobook.app.domain.model.diary.mapper.DiaryMapper.toRequestParams
import com.egobook.app.domain.repository.diary.DiaryRepository
Expand Down Expand Up @@ -61,14 +63,14 @@ class DiaryRepositoryImpl @Inject constructor(
)
}

override suspend fun addDiary(diary: Diary): Result<Unit> {
override suspend fun addDiary(diary: Diary): Result<DiaryRewards> {
return safeApiCall(
apiCall = {
apiService.addDiary(
diary.toDiaryCreateRequest()
)
},
transform = { Unit }
transform = { it.toDiaryRewardsEntity() }
)
}

Expand Down
74 changes: 67 additions & 7 deletions app/src/main/java/com/egobook/app/data/util/ApiResponseExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@ package com.egobook.app.data.util

import com.egobook.app.data.model.ApiResponse
import com.egobook.app.data.model.ApiResponseEmpty
import com.egobook.app.domain.model.auth.AuthError
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException


/**
* 로그인/회원가입 전용 에러 매핑 함수
*/
fun ApiResponse<*>.toAuthError(): AuthError {
Timber.d("toAuthError called with status: $status, message: $message")
return when (this.status) {
400 -> AuthError.BadRequest()
401 -> AuthError.InvalidCredentials()
403 -> AuthError.WaitDelete()
404 -> AuthError.UserNotFound()
409 -> AuthError.UserAlreadyExists()
else -> AuthError.Unknown(this.message)
}
}

/**
* 로그인/회원가입 전용 Result로 변환 확장 함수
*/
fun <T> ApiResponse<T>.toAuthResult(): Result<T> {
return if (this.status == 200) {
Result.success(this.data)
} else {
Result.failure(this.toAuthError())
}
}


/**
Expand All @@ -10,7 +41,7 @@ import com.egobook.app.data.model.ApiResponseEmpty
inline fun <T, R> ApiResponse<T>.toResult(
transform: (T) -> R
): Result<R> {
return if (this.code == "SUCCESS") {
return if (this.status == 200) {
Result.success(transform(this.data))
} else {
Result.failure(Exception(this.message))
Expand All @@ -21,13 +52,40 @@ inline fun <T, R> ApiResponse<T>.toResult(
* ApiResponse를 Result로 변환 (변환 없이)
*/
fun <T> ApiResponse<T>.toResult(): Result<T> {
return if (this.code == "SUCCESS") {
return if (this.status == 200) {
Result.success(this.data)
} else {
Result.failure(Exception(this.message))
}
}

/**
* (로그인/회원가입 전용) API 호출을 안전하게 실행하는 헬퍼 함수
*/
suspend fun <T> safeAuthApiCall(
apiCall: suspend () -> ApiResponse<T>
): Result<T> {
return try {
apiCall().toAuthResult()
} catch (e: HttpException) {
val statusCode = e.code()
Timber.d("HttpException caught with code: $statusCode")
val authError = when (statusCode) {
400 -> AuthError.BadRequest()
401 -> AuthError.InvalidCredentials()
403 -> AuthError.WaitDelete()
404 -> AuthError.UserNotFound()
409 -> AuthError.UserAlreadyExists()
else -> AuthError.Unknown(e.message)
}
Result.failure(authError)
} catch (e: IOException) {
Result.failure(AuthError.NetworkError())
} catch (e: Exception) {
Result.failure(AuthError.Unknown(e.message))
}
}

/**
* API 호출을 안전하게 실행하는 헬퍼 함수
*/
Expand Down Expand Up @@ -66,13 +124,15 @@ suspend inline fun <T, R> safeApiCallWithSuspendTransform(
): Result<R> {
return try {
val response = apiCall()
if (response.code == "SUCCESS") {
if (response.status == 200) {
Result.success(transform(response.data))
} else {
Result.failure(Exception(response.message))
Result.failure(response.toAuthError())
}
} catch (e: IOException) {
Result.failure(AuthError.NetworkError())
} catch (e: Exception) {
Result.failure(e)
Result.failure(AuthError.Unknown(e.message))
}
}

Expand All @@ -81,7 +141,7 @@ suspend inline fun <T, R> safeApiCallWithSuspendTransform(
*/

fun ApiResponseEmpty.toResult(): Result<Unit> {
return if (this.code == "SUCCESS") {
return if (this.status == 200) {
Result.success(Unit)
} else {
Result.failure(Exception(this.message))
Expand All @@ -99,4 +159,4 @@ suspend fun safeApiCallEmpty(
} catch (e: Exception) {
Result.failure(e)
}
}
}
34 changes: 34 additions & 0 deletions app/src/main/java/com/egobook/app/domain/model/auth/AuthError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.egobook.app.domain.model.auth

sealed class AuthError(
override val message: String? = null
) : Throwable(message) {

//400
class BadRequest :
AuthError("잘못된 요청입니다.")

//401
class InvalidCredentials :
AuthError("유효하지 않은 계정입니다.")

//403
class WaitDelete :
AuthError("탈퇴 처리 중인 계정입니다. 관리자에게 문의하세요.")

//409
class UserAlreadyExists :
AuthError("이미 가입된 구글 계정입니다.")

//404
class UserNotFound :
AuthError("존재하지 않는 사용자입니다.")

//미정
class NetworkError :
AuthError("네트워크 오류가 발생했습니다. 잠시 후 다시 시도해주세요.")

//else
class Unknown(message: String?) :
AuthError(message)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,3 @@ data class Diary(
}


enum class DiaryType(val value: String, val displayType: String) {
EMOTION("EMOTION", "감정"),
CONCERN("CONCERN", "고민"),
PRAISE("PRAISE", "칭찬"),
GRATITUDE("GRATITUDE", "감사");
companion object {
/**
* API value로 일기 타입 찾기 (예: "EMOTION", "CONCERN")
*/
fun from(value: String): DiaryType {
return entries.find { it.value == value }
?: throw IllegalArgumentException("Unknown diary type: $value")
}

/**
* 한글 displayType으로 DiaryType 찾기 (예: "감정", "고민")
*/
fun fromDisplayType(displayType: String): DiaryType {
return entries.find { it.displayType == displayType }
?: throw IllegalArgumentException("Unknown display type: $displayType")
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.egobook.app.domain.model.diary.entity

data class DiaryRewards(
val type: List<DiaryType>,
val rewards: List<DiaryReward>
)

data class DiaryReward(
val rewardType: RewardType,
val amount: Int,
val message: String
)
Loading