Skip to content

Commit

Permalink
Merge pull request #36 from YAPP-Github/feature/TNT-103
Browse files Browse the repository at this point in the history
[TNT-103] 로그인 구현
  • Loading branch information
hoyahozz authored Jan 26, 2025
2 parents a6c1bb5 + 240ef15 commit 1809f19
Show file tree
Hide file tree
Showing 46 changed files with 512 additions and 132 deletions.
1 change: 1 addition & 0 deletions .github/workflows/android-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
run: |
echo RELEASE_BASE_API_URL=\"${{ secrets.RELEASE_BASE_API_URL }}\" >> ./local.properties
echo DEBUG_BASE_API_URL=\"${{ secrets.DEBUG_BASE_API_URL }}\" >> ./local.properties
echo KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }} >> ./local.properties
- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand Down
8 changes: 8 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

private val kakaoNativeAppKey: String =
gradleLocalProperties(rootDir, providers).getProperty("KAKAO_NATIVE_APP_KEY")

plugins {
id("tnt.android.application")
id("tnt.android.compose")
Expand All @@ -15,6 +20,8 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField("String", "KAKAO_NATIVE_APP_KEY", "\"${kakaoNativeAppKey}\"")
}

buildTypes {
Expand Down Expand Up @@ -51,4 +58,5 @@ dependencies {
implementation(projects.data.session)

implementation(libs.androidx.activity.compose)
implementation(libs.kakao.user)
}
4 changes: 3 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TnT"
tools:targetApi="31" />
tools:targetApi="31" >

</application>

</manifest>
9 changes: 8 additions & 1 deletion app/src/main/java/co/kr/tnt/TnTApplication.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package co.kr.tnt

import android.app.Application
import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class TnTApplication : Application()
class TnTApplication : Application() {
override fun onCreate() {
super.onCreate()

KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY)
}
}
1 change: 1 addition & 0 deletions core/login/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
19 changes: 19 additions & 0 deletions core/login/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import co.kr.tnt.setNamespace
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
id("tnt.android.library")
}

android {
setNamespace("core.login")

defaultConfig {
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] =
gradleLocalProperties(rootDir, providers).getProperty("KAKAO_NATIVE_APP_KEY")
}
}

dependencies {
implementation(libs.kakao.user)
}
20 changes: 20 additions & 0 deletions core/login/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="oauth"
android:scheme="kakao${KAKAO_NATIVE_APP_KEY}" />
</intent-filter>
</activity>
</application>
</manifest>
5 changes: 5 additions & 0 deletions core/login/src/main/java/co/kr/tnt/login/LoginAccessToken.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package co.kr.tnt.login

interface LoginAccessToken {
val value: String
}
9 changes: 9 additions & 0 deletions core/login/src/main/java/co/kr/tnt/login/LoginException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.kr.tnt.login

sealed class LoginException(override val message: String?) : Exception(message) {
/** 유저가 뒤로가기 동작 등으로 로그인 자체를 취소한 경우 */
data class CancelException(override val message: String?) : LoginException(message)

/** 유저가 실제 로그인을 시도하였으나 SDK의 문제로 로그인에 실패한 경우 */
data class AuthException(override val message: String?) : LoginException(message)
}
9 changes: 9 additions & 0 deletions core/login/src/main/java/co/kr/tnt/login/LoginSdk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package co.kr.tnt.login

import android.content.Context

interface LoginSdk {
suspend fun login(context: Context): Result<LoginAccessToken>
suspend fun logout(): Result<Unit>
suspend fun unlink(): Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.kr.tnt.login.kakao

import co.kr.tnt.login.LoginAccessToken

@JvmInline
value class KakaoAccessToken(override val value: String) : LoginAccessToken
78 changes: 78 additions & 0 deletions core/login/src/main/java/co/kr/tnt/login/kakao/KakaoLoginSdk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package co.kr.tnt.login.kakao

import android.content.Context
import co.kr.tnt.login.LoginAccessToken
import co.kr.tnt.login.LoginException.AuthException
import co.kr.tnt.login.LoginException.CancelException
import co.kr.tnt.login.LoginSdk
import com.kakao.sdk.auth.model.OAuthToken
import com.kakao.sdk.common.model.ClientError
import com.kakao.sdk.common.model.ClientErrorCause
import com.kakao.sdk.user.UserApiClient
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

@Singleton
class KakaoLoginSdk @Inject constructor() : LoginSdk {
override suspend fun login(context: Context): Result<LoginAccessToken> = runCatching {
suspendCancellableCoroutine { continuation ->
val callback: (OAuthToken?, Throwable?) -> Unit = callback@{ token, throwable ->
if (!continuation.isActive) {
return@callback
}

when {
throwable != null -> {
if (throwable is ClientError && throwable.reason == ClientErrorCause.Cancelled) {
continuation.resumeWithException(CancelException(throwable.message))
return@callback
}

continuation.resumeWithException(AuthException(throwable.message))
}

token != null -> continuation.resume(KakaoAccessToken(token.accessToken))
}
}

val userApiClient = UserApiClient.instance

if (userApiClient.isKakaoTalkLoginAvailable(context)) {
// 카카오톡 로그인
userApiClient.loginWithKakaoTalk(context, callback = callback)
} else {
// 카카오톡 웹 로그인
userApiClient.loginWithKakaoAccount(context, callback = callback)
}
}
}

override suspend fun logout(): Result<Unit> = runCatching {
suspendCancellableCoroutine { continuation ->
val userApiClient = UserApiClient.instance

userApiClient.logout { throwable ->
when {
throwable != null -> continuation.resumeWithException(AuthException(throwable.message))
else -> continuation.resume(Unit)
}
}
}
}

override suspend fun unlink(): Result<Unit> = runCatching {
suspendCancellableCoroutine { continuation ->
val userApiClient = UserApiClient.instance

userApiClient.unlink { throwable ->
when {
throwable != null -> continuation.resumeWithException(AuthException(throwable.message))
else -> continuation.resume(Unit)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ sealed interface Route {

@Serializable
data object TraineeConnect : Route

@Serializable
data class RoleSelection(
val authId: String,
val authType: String,
val email: String,
) : Route
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package co.kr.data.network.di

import co.kr.data.network.authenticator.Authenticator
import co.kr.data.network.interceptor.SessionInterceptor
import co.kr.data.network.service.TnTService
import co.kr.data.network.service.ApiService
import co.kr.tnt.data.network.BuildConfig
import dagger.Module
import dagger.Provides
Expand All @@ -25,15 +25,15 @@ internal object NetworkModule {

@Provides
@Singleton
fun provideTnTService(
fun provideApiService(
okHttpClient: OkHttpClient,
converterFactory: Converter.Factory,
): TnTService {
): ApiService {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_API_URL)
.addConverterFactory(converterFactory)
.client(okHttpClient).build()
.create(TnTService::class.java)
.create(ApiService::class.java)
}

@Provides
Expand Down Expand Up @@ -61,6 +61,7 @@ internal object NetworkModule {
fun provideJson(): Json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
encodeDefaults = true
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.AuthType
import kotlinx.serialization.Serializable

// TODO fcm token
@Serializable
data class LoginRequest(
val socialType: AuthType,
val fcmToken: String = "EMPTY",
val socialAccessToken: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.AuthType
import co.kr.tnt.domain.model.LoginResult
import kotlinx.serialization.Serializable

/**
* @property sessionId 세션 ID, 비회원인 경우 null
*/
@Serializable
data class LoginResponse(
val sessionId: String?,
val socialId: String,
val socialEmail: String,
val socialType: AuthType,
val isSignUp: Boolean,
)

fun LoginResponse.toDomain(): LoginResult =
LoginResult(
authId = socialId,
email = socialEmail,
authType = socialType,
isSignUp = isSignUp,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package co.kr.data.network.service

import co.kr.data.network.model.LoginRequest
import co.kr.data.network.model.LoginResponse
import retrofit2.http.Body
import retrofit2.http.POST

interface ApiService {
@POST("/login")
suspend fun postLogin(
@Body request: LoginRequest,
): LoginResponse
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.kr.data.network.source

import co.kr.data.network.model.LoginRequest
import co.kr.data.network.model.LoginResponse
import co.kr.data.network.service.ApiService
import co.kr.data.network.util.networkHandler
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class LoginRemoteDataSource @Inject constructor(
private val apiService: ApiService,
) {
suspend fun postLogin(loginRequest: LoginRequest): LoginResponse = networkHandler {
apiService.postLogin(loginRequest)
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 1809f19

Please sign in to comment.