diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/local/UserDataSource.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/local/UserDataSource.kt new file mode 100644 index 0000000..d8b74ab --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/local/UserDataSource.kt @@ -0,0 +1,39 @@ +package com.puzzling.puzzlingaos.data.datasource.local + +import androidx.datastore.core.Serializer +import com.puzzling.puzzlingaos.data.entity.User +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import org.apache.commons.lang3.SerializationException +import java.io.InputStream +import java.io.OutputStream +import javax.inject.Inject + +class UserDataSource @Inject constructor() : Serializer { + override val defaultValue: User + get() = User() + + override suspend fun readFrom(input: InputStream): User { + return try { + Json.decodeFromString( + deserializer = User.serializer(), + string = input.readBytes().decodeToString(), + ) + } catch (e: SerializationException) { + e.printStackTrace() + defaultValue + } + } + + override suspend fun writeTo(t: User, output: OutputStream) { + withContext(Dispatchers.IO) { + output.write( + Json.encodeToString( + serializer = User.serializer(), + value = t, + ).encodeToByteArray(), + ) + } + } +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/AuthDataSource.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/AuthDataSource.kt new file mode 100644 index 0000000..261f66e --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/AuthDataSource.kt @@ -0,0 +1,10 @@ +package com.puzzling.puzzlingaos.data.datasource.remote + +import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto +import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto + +interface AuthDataSource { + suspend fun login(socialPlatform: String): ResponseLoginDto + + suspend fun getToken(): ResponseTokenDto +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/impl/AuthDataSourceImpl.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/impl/AuthDataSourceImpl.kt new file mode 100644 index 0000000..0218140 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/datasource/remote/impl/AuthDataSourceImpl.kt @@ -0,0 +1,21 @@ +package com.puzzling.puzzlingaos.data.datasource.remote.impl + +import com.puzzling.puzzlingaos.data.datasource.remote.AuthDataSource +import com.puzzling.puzzlingaos.data.model.request.RequestLoginDto +import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto +import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto +import com.puzzling.puzzlingaos.data.service.AuthService +import com.puzzling.puzzlingaos.data.service.ReIssueTokenService +import javax.inject.Inject + +class AuthDataSourceImpl @Inject constructor( + private val authService: AuthService, + private val reIssueTokenService: ReIssueTokenService, +) : AuthDataSource { + + override suspend fun login(socialPlatform: String): ResponseLoginDto = + authService.login(RequestLoginDto(socialPlatform)) + + override suspend fun getToken(): ResponseTokenDto = + reIssueTokenService.getToken() +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/entity/CurrentProject.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/CurrentProject.kt new file mode 100644 index 0000000..5152fed --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/CurrentProject.kt @@ -0,0 +1,9 @@ +package com.puzzling.puzzlingaos.data.entity + +import kotlinx.serialization.Serializable + +@Serializable +data class CurrentProject( + val projectId: Int, + val projectName: String, +) diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/entity/Token.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/Token.kt index 8e9e57f..cbcdc53 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/data/entity/Token.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/Token.kt @@ -5,4 +5,5 @@ import kotlinx.serialization.Serializable @Serializable data class Token( val accessToken: String? = null, + val refreshToken: String? = null, ) diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/entity/User.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/User.kt new file mode 100644 index 0000000..e198595 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/entity/User.kt @@ -0,0 +1,9 @@ +package com.puzzling.puzzlingaos.data.entity + +import kotlinx.serialization.Serializable + +@Serializable +data class User( + val memberId: Int? = null, + val name: String? = null, +) diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/model/request/RequestLoginDto.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/model/request/RequestLoginDto.kt new file mode 100644 index 0000000..db0b29e --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/model/request/RequestLoginDto.kt @@ -0,0 +1,10 @@ +package com.puzzling.puzzlingaos.data.model.request + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestLoginDto( + @SerializedName("socialPlatform") + val socialPlatform: String, +) diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseLoginDto.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseLoginDto.kt new file mode 100644 index 0000000..f24fe24 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseLoginDto.kt @@ -0,0 +1,35 @@ +package com.puzzling.puzzlingaos.data.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseLoginDto( + @SerialName("status") + val status: Int, + @SerialName("success") + val success: Boolean, + @SerialName("message") + val message: String, + @SerialName("data") + val data: LoginData, + +) { + @Serializable + data class LoginData( + @SerialName("name") + val name: String, + @SerialName("memberId") + val memberId: Int, + @SerialName("projectId") + val projectId: Int?, + @SerialName("projectName") + val projectName: String?, + @SerialName("accessToken") + val accessToken: String, + @SerialName("refreshToken") + val refreshToken: String, + @SerialName("isNewUser") + val isNewUser: Boolean, + ) +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseTokenDto.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseTokenDto.kt new file mode 100644 index 0000000..4f1364f --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/model/response/ResponseTokenDto.kt @@ -0,0 +1,26 @@ +package com.puzzling.puzzlingaos.data.model.response + +import com.puzzling.puzzlingaos.data.entity.Token +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseTokenDto( + @SerialName("status") + val status: Int, + @SerialName("success") + val success: Boolean, + @SerialName("message") + val message: String, + @SerialName("date") + val data: NewToken?, +) { + @Serializable + data class NewToken( + @SerialName("accessToken") + val accessToken: String, + + ) + + fun getToken() = Token(data?.accessToken) +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/AuthRepositoryImpl.kt new file mode 100644 index 0000000..b34a9d8 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/AuthRepositoryImpl.kt @@ -0,0 +1,37 @@ +package com.puzzling.puzzlingaos.data.repository + +import android.util.Log +import androidx.datastore.core.DataStore +import com.puzzling.puzzlingaos.data.datasource.remote.AuthDataSource +import com.puzzling.puzzlingaos.data.entity.Token +import com.puzzling.puzzlingaos.data.entity.User +import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto +import com.puzzling.puzzlingaos.domain.repository.AuthRepository +import kotlinx.coroutines.flow.first +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val tokenDataStore: DataStore, + private val userDataSource: DataStore, + private val authDataSource: AuthDataSource, +) : AuthRepository { + override suspend fun login(socialPlatform: String): Result = runCatching { + authDataSource.login(socialPlatform) + }.onSuccess { result -> + tokenDataStore.updateData { Token(result.data.accessToken, result.data.refreshToken) } + userDataSource.updateData { User(result.data.memberId, result.data.name) } + Log.d("AuthRepoImpl", "$result") + Log.d("AuthRepoImpl", "AuthRepoImpl get 토큰 ${userDataSource.data.first()}") + }.onFailure { + Log.d("AuthRepoImpl", "$it 에러") + } + + override suspend fun getToken(): Result = runCatching { + authDataSource.getToken().getToken() + }.onSuccess { token -> + tokenDataStore.updateData { Token(token.accessToken, token.refreshToken) } + Log.d("AuthRepoImpl", "${tokenDataStore.data.first()}") + }.onFailure { + Log.d("AuthRepoImpl", "$it 에러") + } +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/repository/TokenRepositoryImpl.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/TokenRepositoryImpl.kt index e2f9412..d9dc379 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/data/repository/TokenRepositoryImpl.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/TokenRepositoryImpl.kt @@ -8,8 +8,8 @@ import javax.inject.Inject class TokenRepositoryImpl @Inject constructor(private val dataStore: DataStore) : TokenRepository { - override suspend fun setToken(token: String) { - dataStore.updateData { Token(token) } + override suspend fun setToken(accessToken: String) { + dataStore.updateData { Token(accessToken) } } override suspend fun getToken(): Token = dataStore.data.first() diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..da5813f --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/repository/UserRepositoryImpl.kt @@ -0,0 +1,16 @@ +package com.puzzling.puzzlingaos.data.repository + +import androidx.datastore.core.DataStore +import com.puzzling.puzzlingaos.data.entity.User +import com.puzzling.puzzlingaos.domain.repository.UserRepository +import kotlinx.coroutines.flow.first +import javax.inject.Inject + +class UserRepositoryImpl @Inject constructor(private val dataStore: DataStore) : + UserRepository { + override suspend fun setUserInfo(userInfo: User) { + dataStore.updateData { User(userInfo.memberId, userInfo.name) } + } + + override suspend fun getUserInfo(): User = dataStore.data.first() +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/service/AuthService.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/service/AuthService.kt new file mode 100644 index 0000000..fa6af28 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/service/AuthService.kt @@ -0,0 +1,13 @@ +package com.puzzling.puzzlingaos.data.service + +import com.puzzling.puzzlingaos.data.model.request.RequestLoginDto +import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto +import retrofit2.http.Body +import retrofit2.http.POST + +interface AuthService { + @POST("api/v1/auth") + suspend fun login( + @Body socialPlatform: RequestLoginDto, + ): ResponseLoginDto +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/service/CryptoService.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/service/CryptoService.kt index d052fb7..5aec7ef 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/data/service/CryptoService.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/service/CryptoService.kt @@ -4,6 +4,8 @@ import android.os.Build import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import androidx.annotation.RequiresApi +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream import java.security.KeyStore @@ -20,18 +22,19 @@ class CryptoService @Inject constructor() { load(null) } - private val encryptCipher = Cipher.getInstance(TRANSFORMATION).apply { - init(Cipher.ENCRYPT_MODE, getKey()) - } + private val encryptCipher + get() = Cipher.getInstance(TRANSFORMATION).apply { + init(Cipher.ENCRYPT_MODE, getKey()) + } - private fun getDecryptCipher(iv: ByteArray): Cipher { + private fun getDecryptCipherForIv(iv: ByteArray): Cipher { return Cipher.getInstance(TRANSFORMATION).apply { init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv)) } } private fun getKey(): SecretKey { - val existingKey = keyStore.getEntry("secret", null) as? KeyStore.SecretKeyEntry + val existingKey = keyStore.getEntry(ALIAS, null) as? KeyStore.SecretKeyEntry return existingKey?.secretKey ?: createKey() } @@ -39,43 +42,70 @@ class CryptoService @Inject constructor() { return KeyGenerator.getInstance(ALGORITHM).apply { init( KeyGenParameterSpec.Builder( - "secret", + ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT, - ).setBlockModes(BLOCK_MODE).setEncryptionPaddings( - PADDING, - ).setUserAuthenticationRequired(false) // 지문인식 같은거 false - .setRandomizedEncryptionRequired(true).build(), + ) + .setKeySize(KEY_SIZE * 8) // key size in bits + .setBlockModes(BLOCK_MODE) + .setEncryptionPaddings(PADDING) + .setUserAuthenticationRequired(false) + .setRandomizedEncryptionRequired(true) + .build(), ) }.generateKey() } - fun encrypt(bytes: ByteArray, outputStream: OutputStream): ByteArray { - val encryptedBytes = encryptCipher.doFinal(bytes) + fun encrypt(bytes: ByteArray, outputStream: OutputStream) { + val cipher = encryptCipher + val iv = cipher.iv outputStream.use { - it.write(encryptCipher.iv.size) - it.write(encryptCipher.iv) - it.write(encryptedBytes.size) - it.write(encryptedBytes) + it.write(iv) + // write the payload in chunks to make sure to support larger data amounts (this would otherwise fail silently and result in corrupted data being read back) + // ////////////////////////////////// + val inputStream = ByteArrayInputStream(bytes) + val buffer = ByteArray(CHUNK_SIZE) + while (inputStream.available() > CHUNK_SIZE) { + inputStream.read(buffer) + val ciphertextChunk = cipher.update(buffer) + it.write(ciphertextChunk) + } + // the last chunk must be written using doFinal() because this takes the padding into account + val remainingBytes = inputStream.readBytes() + val lastChunk = cipher.doFinal(remainingBytes) + it.write(lastChunk) + // //////////////////////////////// } - - return encryptedBytes } fun decrypt(inputStream: InputStream): ByteArray { return inputStream.use { - val ivSize = it.read() - val iv = ByteArray(ivSize) + val iv = ByteArray(KEY_SIZE) it.read(iv) + val cipher = getDecryptCipherForIv(iv) + val outputStream = ByteArrayOutputStream() - val encryptedBytesSize = it.read() - val encryptedBytes = ByteArray(encryptedBytesSize) - it.read(encryptedBytes) + // read the payload in chunks to make sure to support larger data amounts (this would otherwise fail silently and result in corrupted data being read back) + // ////////////////////////////////// + val buffer = ByteArray(CHUNK_SIZE) + while (inputStream.available() > CHUNK_SIZE) { + inputStream.read(buffer) + val ciphertextChunk = cipher.update(buffer) + outputStream.write(ciphertextChunk) + } + // the last chunk must be read using doFinal() because this takes the padding into account + val remainingBytes = inputStream.readBytes() + val lastChunk = cipher.doFinal(remainingBytes) + outputStream.write(lastChunk) + // //////////////////////////////// - getDecryptCipher(iv).doFinal(encryptedBytes) + outputStream.toByteArray() } } companion object { + private const val CHUNK_SIZE = 1024 * 4 // bytes + private const val KEY_SIZE = 16 // bytes + private const val ALIAS = "my_alias" private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 diff --git a/app/src/main/java/com/puzzling/puzzlingaos/data/service/ReIssueTokenService.kt b/app/src/main/java/com/puzzling/puzzlingaos/data/service/ReIssueTokenService.kt new file mode 100644 index 0000000..b5b12e4 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/data/service/ReIssueTokenService.kt @@ -0,0 +1,9 @@ +package com.puzzling.puzzlingaos.data.service + +import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto +import retrofit2.http.GET + +interface ReIssueTokenService { + @GET("api/v1/auth/token") + suspend fun getToken(): ResponseTokenDto +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/ApiModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/ApiModule.kt index 25a6bc7..d34f37c 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/ApiModule.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/ApiModule.kt @@ -1,8 +1,10 @@ package com.puzzling.puzzlingaos.di +import com.puzzling.puzzlingaos.data.service.AuthService import com.puzzling.puzzlingaos.data.service.MyPageService import com.puzzling.puzzlingaos.data.service.PersonalReviewService import com.puzzling.puzzlingaos.data.service.ProjectService +import com.puzzling.puzzlingaos.data.service.ReIssueTokenService import com.puzzling.puzzlingaos.data.service.TeamReviewService import com.puzzling.puzzlingaos.data.service.WriteReviewService import dagger.Module @@ -39,4 +41,14 @@ object ApiModule { @Singleton fun provideWriteReviewService(@PuzzlingRetrofit retrofit: Retrofit): WriteReviewService = retrofit.create(WriteReviewService::class.java) + + @Provides + @Singleton + fun provideAuthService(@PuzzlingRetrofit retrofit: Retrofit): AuthService = + retrofit.create(AuthService::class.java) + + @Provides + @Singleton + fun provideReIssueTokenService(@ReIssueRetrofit retrofit: Retrofit): ReIssueTokenService = + retrofit.create(ReIssueTokenService::class.java) } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/DataSourceModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/DataSourceModule.kt index 2de1142..2bf7ca2 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/DataSourceModule.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/DataSourceModule.kt @@ -1,10 +1,12 @@ package com.puzzling.puzzlingaos.di +import com.puzzling.puzzlingaos.data.datasource.remote.AuthDataSource import com.puzzling.puzzlingaos.data.datasource.remote.MyDashBoardDataSource import com.puzzling.puzzlingaos.data.datasource.remote.MyPageDataSource import com.puzzling.puzzlingaos.data.datasource.remote.ProjectDataSource import com.puzzling.puzzlingaos.data.datasource.remote.TeamDashBoardDataSource import com.puzzling.puzzlingaos.data.datasource.remote.WriteReviewDataSource +import com.puzzling.puzzlingaos.data.datasource.remote.impl.AuthDataSourceImpl import com.puzzling.puzzlingaos.data.datasource.remote.impl.MyDashBoardDataSourceImpl import com.puzzling.puzzlingaos.data.datasource.remote.impl.MyPageDataSourceImpl import com.puzzling.puzzlingaos.data.datasource.remote.impl.ProjectDataSourceImpl @@ -38,4 +40,8 @@ abstract class DataSourceModule { @Singleton @Binds abstract fun providesWriteReviewDataSource(DataSourceImpl: WriteReviewDataSourceImpl): WriteReviewDataSource + + @Singleton + @Binds + abstract fun providesAuthDataSource(DataSourceImpl: AuthDataSourceImpl): AuthDataSource } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/DataStoreModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/DataStoreModule.kt index 22a0c10..41b7fed 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/DataStoreModule.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/DataStoreModule.kt @@ -5,7 +5,9 @@ import androidx.datastore.core.DataStore import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import com.puzzling.puzzlingaos.data.datasource.local.TokenDataSource +import com.puzzling.puzzlingaos.data.datasource.local.UserDataSource import com.puzzling.puzzlingaos.data.entity.Token +import com.puzzling.puzzlingaos.data.entity.User import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -16,19 +18,33 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object DataStoreModule { - private const val USER_PREFERENCES_NAME = "user_preferences" - private const val DATA_STORE_FILE_NAME = "user_prefs.pb" + private const val USER_DATA_STORE_FILE_NAME = "user_prefs.pb" + private const val TOKEN_DATA_STORE_FILE_NAME = "token_prefs.pb" @Provides @Singleton - fun providePreferencesDataStore( + fun provideTokenDataStore( @ApplicationContext appContext: Context, tokenDataSource: TokenDataSource, ): DataStore { return DataStoreFactory.create( serializer = tokenDataSource, produceFile = { - appContext.dataStoreFile(DATA_STORE_FILE_NAME) + appContext.dataStoreFile(TOKEN_DATA_STORE_FILE_NAME) + }, + ) + } + + @Provides + @Singleton + fun provideUserDataStore( + @ApplicationContext appContext: Context, + userDataSource: UserDataSource, + ): DataStore { + return DataStoreFactory.create( + serializer = userDataSource, + produceFile = { + appContext.dataStoreFile(USER_DATA_STORE_FILE_NAME) }, ) } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/Qualifier.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/Qualifier.kt index 6d3e0db..d6ece9d 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/Qualifier.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/Qualifier.kt @@ -5,3 +5,7 @@ import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.BINARY) annotation class PuzzlingRetrofit + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class ReIssueRetrofit \ No newline at end of file diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/ReIssueRetrofitModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/ReIssueRetrofitModule.kt new file mode 100644 index 0000000..8a8e532 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/ReIssueRetrofitModule.kt @@ -0,0 +1,69 @@ +package com.puzzling.puzzlingaos.di + +import android.util.Log +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.puzzling.puzzlingaos.BuildConfig +import com.puzzling.puzzlingaos.domain.repository.TokenRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object ReIssueRetrofitModule { + private const val PUZZLING_BASE_URL = BuildConfig.PUZZLING_BASE_URL + + @Provides + @Singleton + @ReIssueRetrofit + fun provideReIssueOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, + @ReIssueRetrofit tokenInterceptor: Interceptor, + ): OkHttpClient = + OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .addInterceptor(tokenInterceptor) + .build() + + @Singleton + @Provides + @ReIssueRetrofit + fun provideReIssueTokenRetrofit(@ReIssueRetrofit okHttpClient: OkHttpClient): Retrofit = + Retrofit.Builder() + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .baseUrl(PUZZLING_BASE_URL) + .client(okHttpClient) + .build() + + @Singleton + @Provides + @ReIssueRetrofit + fun reIssueTokenInterceptor(tokenRepository: TokenRepository): Interceptor { + val requestInterceptor = Interceptor { chain -> + val original = chain.request() + val builder = original.newBuilder() + val token = runBlocking { + tokenRepository.getToken() + } + Log.d("ReIssueRetrofitModule", "토큰 $token") + builder.addHeader( + "Authorization", + "Bearer ${token.accessToken}", + ).addHeader( + "Refresh", + "Bearer ${token.refreshToken}", + ) + chain.proceed(builder.build()) + } + return requestInterceptor + } +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/RepositoryModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/RepositoryModule.kt index 8f0f859..36f8f3b 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/RepositoryModule.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/RepositoryModule.kt @@ -34,4 +34,12 @@ abstract class RepositoryModule { @Singleton @Binds abstract fun providesTokenRepository(repoImpl: TokenRepositoryImpl): TokenRepository + + @Singleton + @Binds + abstract fun providesAuthRepository(repoImpl: AuthRepositoryImpl): AuthRepository + + @Singleton + @Binds + abstract fun providesUserRepository(repoImpl: UserRepositoryImpl): UserRepository } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/di/RetrofitModule.kt b/app/src/main/java/com/puzzling/puzzlingaos/di/RetrofitModule.kt index fefc640..5c64e31 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/di/RetrofitModule.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/di/RetrofitModule.kt @@ -3,13 +3,14 @@ package com.puzzling.puzzlingaos.di import android.util.Log import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.puzzling.puzzlingaos.BuildConfig -import com.puzzling.puzzlingaos.BuildConfig.JWT_ACCESS_TOKEN +import com.puzzling.puzzlingaos.domain.repository.TokenRepository import com.puzzling.puzzlingaos.util.isJsonArray import com.puzzling.puzzlingaos.util.isJsonObject import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType @@ -27,10 +28,14 @@ object RetrofitModule { @Provides @Singleton - fun provideOkHttpClient(loggingInterceptor: HttpLoggingInterceptor): OkHttpClient = + @PuzzlingRetrofit + fun provideOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, + @PuzzlingRetrofit tokenInterceptor: Interceptor, + ): OkHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) - .addInterceptor(tokenInterceptor()) + .addInterceptor(tokenInterceptor) .build() @Provides @@ -56,19 +61,27 @@ object RetrofitModule { @Singleton @Provides @PuzzlingRetrofit - fun providePuzzlingRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder() - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) - .baseUrl(PUZZLING_BASE_URL) - .client(okHttpClient) - .build() + fun providePuzzlingRetrofit(@PuzzlingRetrofit okHttpClient: OkHttpClient): Retrofit = + Retrofit.Builder() + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .baseUrl(PUZZLING_BASE_URL) + .client(okHttpClient) + .build() - private fun tokenInterceptor(): Interceptor { + @Singleton + @Provides + @PuzzlingRetrofit + fun tokenInterceptor(tokenRepository: TokenRepository): Interceptor { val requestInterceptor = Interceptor { chain -> val original = chain.request() val builder = original.newBuilder() + val accessToken = runBlocking { + tokenRepository.getToken().accessToken + } + Log.d("RetrofitModule", "토큰 $accessToken") builder.addHeader( "Authorization", - JWT_ACCESS_TOKEN, + "Bearer $accessToken", ) chain.proceed(builder.build()) } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/AuthRepository.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/AuthRepository.kt new file mode 100644 index 0000000..652124a --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/AuthRepository.kt @@ -0,0 +1,11 @@ +package com.puzzling.puzzlingaos.domain.repository + +import com.puzzling.puzzlingaos.data.entity.Token +import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto + +interface AuthRepository { + + suspend fun login(socialPlatform: String): Result + + suspend fun getToken(): Result +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/TokenRepository.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/TokenRepository.kt index 93f43ad..ee251fd 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/TokenRepository.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/TokenRepository.kt @@ -3,7 +3,7 @@ package com.puzzling.puzzlingaos.domain.repository import com.puzzling.puzzlingaos.data.entity.Token interface TokenRepository { - suspend fun setToken(token: String) + suspend fun setToken(accessToken: String) suspend fun getToken(): Token } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/UserRepository.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/UserRepository.kt new file mode 100644 index 0000000..aaf24b4 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/repository/UserRepository.kt @@ -0,0 +1,9 @@ +package com.puzzling.puzzlingaos.domain.repository + +import com.puzzling.puzzlingaos.data.entity.User + +interface UserRepository { + + suspend fun setUserInfo(userInfo: User) + suspend fun getUserInfo(): User +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetTokenUseCase.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetLocalTokenUseCase.kt similarity index 56% rename from app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetTokenUseCase.kt rename to app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetLocalTokenUseCase.kt index 3687519..2dc6b46 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetTokenUseCase.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetLocalTokenUseCase.kt @@ -5,9 +5,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class GetTokenUseCase @Inject constructor( - private val tokenRepository: TokenRepository, +class GetLocalTokenUseCase @Inject constructor( + private val repository: TokenRepository, ) { - suspend operator fun invoke() = tokenRepository.getToken() + suspend operator fun invoke() = repository.getToken() } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetRemoteTokenUseCase.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetRemoteTokenUseCase.kt new file mode 100644 index 0000000..3e2b1e0 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetRemoteTokenUseCase.kt @@ -0,0 +1,9 @@ +package com.puzzling.puzzlingaos.domain.usecase.onboarding + +import com.puzzling.puzzlingaos.domain.repository.AuthRepository +import javax.inject.Inject + +class GetRemoteTokenUseCase @Inject constructor(private val repository: AuthRepository) { + + suspend operator fun invoke() = repository.getToken() +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetUserUseCase.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetUserUseCase.kt new file mode 100644 index 0000000..f462767 --- /dev/null +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/GetUserUseCase.kt @@ -0,0 +1,11 @@ +package com.puzzling.puzzlingaos.domain.usecase.onboarding + +import com.puzzling.puzzlingaos.domain.repository.UserRepository +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GetUserUseCase @Inject constructor(private val repository: UserRepository) { + + suspend operator fun invoke() = repository.getUserInfo() +} diff --git a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostTokenUseCase.kt b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostLocalTokenUseCase.kt similarity index 60% rename from app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostTokenUseCase.kt rename to app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostLocalTokenUseCase.kt index dcf39d5..a839458 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostTokenUseCase.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/domain/usecase/onboarding/PostLocalTokenUseCase.kt @@ -5,6 +5,6 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class PostTokenUseCase @Inject constructor(private val tokenRepository: TokenRepository) { - suspend operator fun invoke(token: String) = tokenRepository.setToken(token) +class PostLocalTokenUseCase @Inject constructor(private val repository: TokenRepository) { + suspend operator fun invoke(token: String) = repository.setToken(token) } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginActivity.kt b/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginActivity.kt index bbfd50d..113e6cf 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginActivity.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginActivity.kt @@ -12,6 +12,7 @@ import com.puzzling.puzzlingaos.base.BaseActivity import com.puzzling.puzzlingaos.data.service.KakaoAuthService import com.puzzling.puzzlingaos.databinding.ActivityLoginBinding import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -23,13 +24,27 @@ class LoginActivity : BaseActivity(R.layout.activity_login private val viewModel: LoginViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + isAlreadyLogin() startKakaoLogin() isKakaoLoginSuccess() } + private fun isAlreadyLogin() { + viewModel.checkIsAlreadyLogin() + viewModel.isAlreadyLogin.observe(this) { isAlreadyLogin -> + if (isAlreadyLogin) { + val intent = + Intent(this@LoginActivity, ChooseJoinRegisterActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + finish() + viewModel.getToken() + } + } + } + private fun startKakaoLogin() { binding.ibLoginKakao.setOnClickListener { kakakoAuthService.startKakaoLogin(viewModel.kakaoLoginCallback) @@ -46,6 +61,8 @@ class LoginActivity : BaseActivity(R.layout.activity_login intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) startActivity(intent) finish() + delay(100) + viewModel.login("KAKAO") } } } diff --git a/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginViewModel.kt b/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginViewModel.kt index 6b60acb..c13737d 100644 --- a/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginViewModel.kt +++ b/app/src/main/java/com/puzzling/puzzlingaos/presentation/onboarding/LoginViewModel.kt @@ -1,12 +1,17 @@ package com.puzzling.puzzlingaos.presentation.onboarding import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.kakao.sdk.auth.model.OAuthToken import com.puzzling.puzzlingaos.data.datasource.local.LocalDataSource -import com.puzzling.puzzlingaos.domain.usecase.onboarding.GetTokenUseCase -import com.puzzling.puzzlingaos.domain.usecase.onboarding.PostTokenUseCase +import com.puzzling.puzzlingaos.domain.repository.AuthRepository +import com.puzzling.puzzlingaos.domain.usecase.onboarding.GetLocalTokenUseCase +import com.puzzling.puzzlingaos.domain.usecase.onboarding.GetRemoteTokenUseCase +import com.puzzling.puzzlingaos.domain.usecase.onboarding.GetUserUseCase +import com.puzzling.puzzlingaos.domain.usecase.onboarding.PostLocalTokenUseCase import com.puzzling.puzzlingaos.util.KakaoLoginCallback import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -16,22 +21,43 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val postTokenUseCase: PostTokenUseCase, - private val getTokenUseCase: GetTokenUseCase, + private val postLocalTokenUseCase: PostLocalTokenUseCase, + private val getLocalTokenUseCase: GetLocalTokenUseCase, + private val getUserUseCase: GetUserUseCase, + private val getRemoteTokenUseCase: GetRemoteTokenUseCase, + private val authRepository: AuthRepository, ) : ViewModel() { private val _isKakaoLogin = MutableStateFlow(false) val isKakaoLogin = _isKakaoLogin.asStateFlow() + private val _isAlreadyLogin = MutableLiveData() + val isAlreadyLogin: LiveData get() = _isAlreadyLogin + val kakaoLoginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error -> KakaoLoginCallback { _isKakaoLogin.value = true - Log.d("LoginViewModel", "토큰!! $token") + Log.d("LoginViewModel", "카카오 로그인 set 토큰 $token") LocalDataSource.setAccessToken("$token") viewModelScope.launch { - postTokenUseCase.invoke(it) - Log.d("LoginViewModel", "토큰!! usecase ${getTokenUseCase.invoke()}") + postLocalTokenUseCase.invoke(it) + Log.d("LoginViewModel", "카카오 로그인 get 토큰 ${getLocalTokenUseCase.invoke()}") } }.handleResult(token, error) } + + fun login(socialPlatform: String) = viewModelScope.launch { + Log.d("LoginActivity", "로그인 함수 호출") + authRepository.login("KAKAO") + } + + fun checkIsAlreadyLogin() = viewModelScope.launch { + var userInfo = getUserUseCase.invoke() + _isAlreadyLogin.value = !userInfo.name.isNullOrBlank() + Log.d("userInfo", "$userInfo") + } + + fun getToken() = viewModelScope.launch { + getRemoteTokenUseCase.invoke() + } }