Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/#52 signup 기능을 구현한다 #74

Merged
merged 10 commits into from
Apr 3, 2024
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ android {
dependencies {
implementation(libs.androidx.core.splashscreen)
implementation(project(":feature:login"))
implementation(project(":feature:signup"))
implementation(project(":feature:home"))
implementation(project(":feature:postlist"))
implementation(project(":feature:mypage"))
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/withpeace/withpeace/WithpeaceApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme
import com.withpeace.withpeace.navigation.WithpeaceNavHost
import kotlinx.coroutines.launch


@Composable
fun WithpeaceApp(
startDestination: String,
Expand Down
30 changes: 28 additions & 2 deletions app/src/main/java/com/withpeace/withpeace/navigation/NavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.app.profileeditor.navigation.profileEditorNavGraph
import com.withpeace.withpeace.feature.gallery.navigation.galleryNavGraph
import com.withpeace.withpeace.feature.gallery.navigation.navigateToGallery
import com.withpeace.withpeace.feature.home.navigation.homeNavGraph
import com.withpeace.withpeace.feature.home.navigation.navigateHome
import com.withpeace.withpeace.feature.login.navigation.LOGIN_ROUTE
import com.withpeace.withpeace.feature.login.navigation.loginNavGraph
import com.withpeace.withpeace.feature.login.navigation.navigateLogin
Expand All @@ -19,6 +20,8 @@ import com.withpeace.withpeace.feature.mypage.navigation.myPageNavGraph
import com.withpeace.withpeace.feature.postlist.navigation.postListGraph
import com.withpeace.withpeace.feature.registerpost.navigation.IMAGE_LIST_ARGUMENT
import com.withpeace.withpeace.feature.registerpost.navigation.registerPostNavGraph
import com.withpeace.withpeace.feature.signup.navigation.navigateSignUp
import com.withpeace.withpeace.feature.signup.navigation.signUpNavGraph

@Composable
fun WithpeaceNavHost(
Expand All @@ -32,7 +35,30 @@ fun WithpeaceNavHost(
navController = navController,
startDestination = startDestination,
) {
loginNavGraph(onShowSnackBar = onShowSnackBar)
loginNavGraph(
onShowSnackBar = onShowSnackBar,
onSignUpNeeded = {
navController.navigateSignUp()
},
onLoginSuccess = {
navController.navigateHome()
},
)
signUpNavGraph(
onShowSnackBar = onShowSnackBar,
onNavigateToGallery = {
navController.navigateToGallery(imageLimit = 1)
},
onSignUpSuccess = {
navController.navigateHome(
navOptions = navOptions {
popUpTo(navController.graph.id) {
inclusive = true
}
},
)
},
)
registerPostNavGraph(
onShowSnackBar = onShowSnackBar,
onCompleteRegisterPost = {},
Expand Down Expand Up @@ -97,4 +123,4 @@ fun WithpeaceNavHost(
)
postListGraph(onShowSnackBar)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.withpeace.withpeace.core.data.mapper

import com.withpeace.withpeace.core.domain.model.role.Role

internal fun String.roleToDomain(): Role {
return when (this) {
"GUEST" -> Role.GUEST
"USER" -> Role.USER
else -> Role.UNKNOWN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.withpeace.withpeace.core.data.repository
import com.skydoves.sandwich.message
import com.skydoves.sandwich.suspendMapSuccess
import com.skydoves.sandwich.suspendOnFailure
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.data.mapper.roleToDomain
import com.withpeace.withpeace.core.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.role.Role
import com.withpeace.withpeace.core.domain.repository.TokenRepository
import com.withpeace.withpeace.core.network.di.request.SignUpRequest
import com.withpeace.withpeace.core.network.di.response.LoginResponse
import com.withpeace.withpeace.core.network.di.service.AuthService
import com.withpeace.withpeace.core.network.di.service.UserService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
Expand All @@ -17,40 +19,33 @@ import javax.inject.Inject

class DefaultTokenRepository @Inject constructor(
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
private val userPreferenceDataSource: UserPreferenceDataSource,
private val authService: AuthService,
) : TokenRepository {
override suspend fun isLogin(): Boolean {
val token = tokenPreferenceDataSource.accessToken.firstOrNull()
return token != null
val userRole = userPreferenceDataSource.userRole.firstOrNull()
return token != null && userRole?.roleToDomain() == Role.USER //TODO("토큰이 만료되었는지 확인 필요")
}

override suspend fun signUp(
email: String,
nickname: String,
onError: (String) -> Unit,
): Flow<Unit> = flow {
authService.signUp(
SignUpRequest(email = email, nickname = nickname, deviceToken = null),
).suspendMapSuccess {
val data = this.data
tokenPreferenceDataSource.updateAccessToken(data.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.refreshToken)
emit(Unit)
}.suspendOnFailure { onError(message()) }
}.flowOn(Dispatchers.IO)

override fun getTokenByGoogle(
idToken: String,
onError: (String) -> Unit,
): Flow<Unit> = flow {
): Flow<Role> = flow {
authService.googleLogin(AUTHORIZATION_FORMAT.format(idToken)).suspendMapSuccess {
val data = this.data
tokenPreferenceDataSource.updateAccessToken(data.tokenResponse.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.tokenResponse.refreshToken)
emit(Unit)
saveLocalLoginInfo(data)
emit(data.role.roleToDomain())
}.suspendOnFailure { onError(message()) }
}.flowOn(Dispatchers.IO)

private suspend fun saveLocalLoginInfo(data: LoginResponse) {
tokenPreferenceDataSource.updateAccessToken(data.tokenResponse.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.tokenResponse.refreshToken)
userPreferenceDataSource.updateUserId(data.userId)
userPreferenceDataSource.updateUserRole(data.role)
}

companion object {
private const val AUTHORIZATION_FORMAT = "Bearer %s"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import com.skydoves.sandwich.suspendOnError
import com.skydoves.sandwich.suspendOnException
import com.withpeace.withpeace.core.data.mapper.toDomain
import com.withpeace.withpeace.core.data.util.convertToFile
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import com.withpeace.withpeace.core.domain.model.SignUpInfo
import com.withpeace.withpeace.core.domain.model.WithPeaceError
import com.withpeace.withpeace.core.domain.model.profile.ChangedProfile
import com.withpeace.withpeace.core.domain.model.profile.Nickname
import com.withpeace.withpeace.core.domain.model.profile.ProfileInfo
import com.withpeace.withpeace.core.domain.model.role.Role
import com.withpeace.withpeace.core.domain.repository.UserRepository
import com.withpeace.withpeace.core.network.di.common.getErrorBody
import com.withpeace.withpeace.core.network.di.request.NicknameRequest
Expand All @@ -31,6 +34,7 @@ class DefaultUserRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val userService: UserService,
private val tokenPreferenceDataSource: TokenPreferenceDataSource,
private val userPreferenceDataSource: UserPreferenceDataSource,
) : UserRepository {
override fun getProfile(
onError: suspend (WithPeaceError) -> Unit,
Expand All @@ -49,12 +53,38 @@ class DefaultUserRepository @Inject constructor(
}
}

override fun registerProfile(
nickname: String,
profileImage: String,
onError: (WithPeaceError) -> Unit,
): Flow<Unit> {
TODO("Not yet implemented")
override suspend fun signUp(
signUpInfo: SignUpInfo,
onError: suspend (WithPeaceError) -> Unit,
): Flow<Unit> = flow {
val nicknameRequestBody =
signUpInfo.nickname.toRequestBody("text/plain".toMediaTypeOrNull())
val request =
if (signUpInfo.profileImage.isNullOrEmpty()) {
userService.signUp(
nicknameRequestBody,
)
} else {
val profileImagePart = getImagePart(signUpInfo.profileImage!!)
userService.signUp(nicknameRequestBody, profileImagePart)
}

request.suspendMapSuccess {
val data = this.data
userPreferenceDataSource.updateUserRole(Role.USER.name)
tokenPreferenceDataSource.updateAccessToken(data.accessToken)
tokenPreferenceDataSource.updateRefreshToken(data.refreshToken)
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
onError(WithPeaceError.UnAuthorized())
} else {
val errorBody = errorBody?.getErrorBody()
onError(WithPeaceError.GeneralError(errorBody?.code, errorBody?.message))
}
}.suspendOnException {
onError(WithPeaceError.GeneralError(message = messageOrNull))
}
}

override fun updateProfile(
Expand Down Expand Up @@ -134,9 +164,12 @@ class DefaultUserRepository @Inject constructor(
}
}



override fun logout(onError: suspend (WithPeaceError) -> Unit): Flow<Unit> = flow {
userService.logout().suspendMapSuccess {
tokenPreferenceDataSource.removeAll()
userPreferenceDataSource.removeAll()
emit(Unit)
}.suspendOnError {
if (statusCode.code == 401) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.withpeace.withpeace.core.datastore.dataStore
package com.withpeace.withpeace.core.datastore.dataStore.token

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.withpeace.withpeace.core.datastore.dataStore
package com.withpeace.withpeace.core.datastore.dataStore.token

import kotlinx.coroutines.flow.Flow

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.withpeace.withpeace.core.datastore.dataStore.user

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Named

class DefaultUserPreferenceDataSource @Inject constructor(
@Named("user") private val dataStore: DataStore<Preferences>,
) : UserPreferenceDataSource {
override val userId: Flow<Long?> = dataStore.data.map { preferences ->
preferences[USER_ID]
}
override val userRole: Flow<String?> = dataStore.data.map { preferences ->
preferences[USER_ROLE]
}

override suspend fun updateUserId(userId: Long) {
dataStore.edit { preferences ->
preferences[USER_ID] = userId
}
}

override suspend fun updateUserRole(userRole: String) {
dataStore.edit { preferences ->
preferences[USER_ROLE] = userRole
}
}

override suspend fun removeAll() {
dataStore.edit { preferences ->
preferences.clear()
}
}

companion object {
private val USER_ID = longPreferencesKey("USER_ID")
private val USER_ROLE = stringPreferencesKey("USER_ROLE")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.withpeace.withpeace.core.datastore.dataStore.user

import kotlinx.coroutines.flow.Flow

interface UserPreferenceDataSource {

val userId: Flow<Long?>

val userRole: Flow<String?>

suspend fun updateUserId(userId: Long)

suspend fun updateUserRole(userRole: String)
suspend fun removeAll()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ import javax.inject.Singleton
object DataStoreModule {

private const val AUTH_DATASTORE_NAME = "AUTH_PREFERENCES"
private const val USER_DATASTORE_NAME = "USER_PREFERENCES"

private val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = AUTH_DATASTORE_NAME)
private val Context.userDataStore: DataStore<Preferences> by preferencesDataStore(name = USER_DATASTORE_NAME)

@Provides
@Singleton
@Named("auth")
fun providesTokenDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.authDataStore

@Provides
@Singleton
@Named("user")
fun providesUserDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.userDataStore
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.withpeace.withpeace.core.datastore.di

import com.withpeace.withpeace.core.datastore.dataStore.DefaultTokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.token.DefaultTokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.token.TokenPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.DefaultUserPreferenceDataSource
import com.withpeace.withpeace.core.datastore.dataStore.user.UserPreferenceDataSource
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -17,4 +19,10 @@ interface PreferenceDataSourceModule {
fun bindsTokenPreferenceDataSource(
defaultTokenPreferenceDataSource: DefaultTokenPreferenceDataSource,
): TokenPreferenceDataSource

@Binds
@Singleton
fun bindsUserPreferenceDataSource(
defaultUserPreferenceDataSource: DefaultUserPreferenceDataSource,
): UserPreferenceDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.withpeace.withpeace.core.domain.model

data class SignUpInfo(
val nickname: String,
val profileImage: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.withpeace.withpeace.core.domain.model.role

enum class Role {
GUEST, USER, UNKNOWN
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package com.withpeace.withpeace.core.domain.repository

import com.withpeace.withpeace.core.domain.model.role.Role
import kotlinx.coroutines.flow.Flow

interface TokenRepository {

suspend fun isLogin(): Boolean

suspend fun signUp(
email: String,
nickname: String,
onError: (String) -> Unit,
): Flow<Unit>

fun getTokenByGoogle(
idToken: String,
onError: (String) -> Unit,
): Flow<Unit>
): Flow<Role>
}
Loading
Loading