From ece116a411325c447fddc51e21e4fef37965e259 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Thu, 28 Nov 2024 07:00:11 -0500 Subject: [PATCH] Start saving data to proto datastore from successful rest api requests --- api/src/requests/user/create/handler.rs | 4 +- .../requests/user/create/models/response.rs | 2 +- .../confirm/ConfirmationScreenViewModel.kt | 4 +- .../auth/presentation/login/LoginScreen.kt | 7 +-- .../login/LoginScreenViewModel.kt | 45 ++++++++----------- .../register/RegistrationScreen.kt | 13 +++++- .../register/RegistrationScreenViewModel.kt | 11 ++++- .../data/pref/PreferencesDataStore.kt | 18 ++++---- .../model/auth/AuthLoginPostResponse.kt | 11 ++++- .../model/user/UserCreatePostResponse.kt | 2 +- ...oreProvider.kt => AppDataStoreProvider.kt} | 10 ++--- .../debtpirate/di/UseCaseModule.kt | 19 ++++++++ .../domain/usecase/UpdateStoreDataUseCase.kt | 35 +++++++++++++++ ..._data_store.proto => app_data_store.proto} | 9 ++-- 14 files changed, 131 insertions(+), 59 deletions(-) rename app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/{PrefsDataStoreProvider.kt => AppDataStoreProvider.kt} (55%) create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt rename app/app/src/main/proto/{prefs_data_store.proto => app_data_store.proto} (57%) diff --git a/api/src/requests/user/create/handler.rs b/api/src/requests/user/create/handler.rs index 5188897..3866fd0 100644 --- a/api/src/requests/user/create/handler.rs +++ b/api/src/requests/user/create/handler.rs @@ -111,13 +111,13 @@ async fn register_new_user_request( UserRegistrationResponse { user_id, - expires_at, + expires_at: None, session_token: None, } } else { UserRegistrationResponse { user_id, - expires_at, + expires_at: Some(expires_at), session_token: Some(verification_token), } }; diff --git a/api/src/requests/user/create/models/response.rs b/api/src/requests/user/create/models/response.rs index 4fd6c4c..4849e22 100644 --- a/api/src/requests/user/create/models/response.rs +++ b/api/src/requests/user/create/models/response.rs @@ -11,7 +11,7 @@ pub struct UserRegistrationResponse { pub user_id: i32, #[serde(serialize_with = "humantime_serde::serialize")] - pub expires_at: SystemTime, + pub expires_at: Option, pub session_token: Option, } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/confirm/ConfirmationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/confirm/ConfirmationScreenViewModel.kt index 727cd97..8409c39 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/confirm/ConfirmationScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/confirm/ConfirmationScreenViewModel.kt @@ -5,7 +5,7 @@ import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import ing.bikeshedengineer.debtpirate.PrefsDataStore +import ing.bikeshedengineer.debtpirate.AppDataStore import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitNewUserVerificationRequestUseCase import ing.bikeshedengineer.debtpirate.navigation.Navigator import kotlinx.coroutines.flow.MutableStateFlow @@ -16,7 +16,7 @@ import javax.inject.Inject @HiltViewModel class ConfirmationScreenViewModel @Inject constructor( private val navigator: Navigator, - private val prefsStore: DataStore, + private val prefsStore: DataStore, private val verifyNewUser: SubmitNewUserVerificationRequestUseCase ) : ViewModel() { private var _isLoading = MutableStateFlow(true) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt index 08e398d..c28e20b 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreen.kt @@ -1,7 +1,6 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login import android.annotation.SuppressLint -import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.foundation.background @@ -76,8 +75,10 @@ fun LoginScreen( val toastMessages = viewModel.toastMessages.collectAsState("") LaunchedEffect(toastMessages.value) { val message = toastMessages.value - Log.d("LoginScreen", message) - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + + if (message.isNotEmpty()) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } } Scaffold( diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt index 41d4ea9..3354393 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/login/LoginScreenViewModel.kt @@ -1,16 +1,13 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login -import android.util.Log -import android.widget.Toast -import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import ing.bikeshedengineer.debtpirate.PrefsDataStore import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.LoginCredentialsValidationResult import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateLoginCredentialsUseCase -import ing.bikeshedengineer.debtpirate.domain.model.Token +import ing.bikeshedengineer.debtpirate.domain.repository.InvalidCredentialsException +import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase import ing.bikeshedengineer.debtpirate.navigation.Destination import ing.bikeshedengineer.debtpirate.navigation.Navigator import kotlinx.coroutines.flow.MutableSharedFlow @@ -37,13 +34,12 @@ enum class InvalidReason { PasswordTooShort } - @HiltViewModel class LoginScreenViewModel @Inject constructor( private val navigator: Navigator, - private val prefsStore: DataStore, private val submitLoginCredentials: SubmitLoginCredentialsUseCase, private val validateLoginCredentials: ValidateLoginCredentialsUseCase, + private val updateStoreData: UpdateStoreDataUseCase ) : ViewModel() { private val _emailAddress = MutableStateFlow("") val emailAddress = _emailAddress.asStateFlow() @@ -93,28 +89,23 @@ class LoginScreenViewModel @Inject constructor( private suspend fun onSubmitLoginRequest(emailAddress: String, password: String) { try { - val result = submitLoginCredentials(emailAddress, password) + val (userId, auth, session) = submitLoginCredentials(emailAddress, password) + updateStoreData( + userId = userId, + authToken = auth.token, + authTokenExpiresAt = auth.expiresAt, + sessionToken = session.token, + sessionTokenExpiresAt = session.expiresAt + ) } catch (err: Exception) { - _toastMessages.emit("Invalid Email Address or Password") - } - } + when (err) { + is InvalidCredentialsException -> { + _toastMessages.emit("Invalid Email Address or Password") + } - fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) { - viewModelScope.launch { - prefsStore.updateData { currentPrefs -> - val updatedSessionToken = currentPrefs.sessionToken.toBuilder() - .setToken(sessionToken.token) - .setExpiresAt(sessionToken.expiresAt.toEpochSecond()) - - val updatedAuthToken = currentPrefs.authToken.toBuilder() - .setToken(authToken.token) - .setExpiresAt(authToken.expiresAt.toEpochSecond()) - - currentPrefs.toBuilder() - .setUserId(userId) - .setSessionToken(updatedSessionToken) - .setAuthToken(updatedAuthToken) - .build() + else -> { + _toastMessages.emit("Cannot Login, Please Try Again Later") + } } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt index 25d3663..fc98d8b 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -231,9 +232,19 @@ private fun RegistrationComponent( keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done + imeAction = ImeAction.Send ), onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateConfirmPassword(it)) }, + keyboardActions = KeyboardActions( + onSend = { + viewModel.registerNewAccount( + emailAddress.value, + name.value, + password.value, + confirmPassword.value + ) + } + ), modifier = Modifier .fillMaxWidth() .padding(PaddingValues(top = 4.dp)) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt index e098b2f..7faf874 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/screen/auth/presentation/register/RegistrationScreenViewModel.kt @@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.NewAccountRegistrationValidationResult import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.SubmitAccountRegistrationRequestUseCase import ing.bikeshedengineer.debtpirate.app.screen.auth.usecase.ValidateNewAccountRegistrationUseCase +import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase import ing.bikeshedengineer.debtpirate.navigation.Destination import ing.bikeshedengineer.debtpirate.navigation.Navigator import kotlinx.coroutines.flow.MutableSharedFlow @@ -21,6 +22,7 @@ class RegistrationScreenViewModel @Inject constructor( private val navigator: Navigator, private val validateNewAccount: ValidateNewAccountRegistrationUseCase, private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase, + private val updateStoreData: UpdateStoreDataUseCase, ) : ViewModel() { fun navigateUp() { viewModelScope.launch { @@ -113,10 +115,17 @@ class RegistrationScreenViewModel @Inject constructor( if (fieldsAreValid) { viewModelScope.launch { try { - // TODO: Store the registration result data in the store val result = submitAccountRegistrationRequest(emailAddress, name, confirmPassword) + val (userId, expiresAt, sessionToken) = result + + updateStoreData( + userId = result.userId, + sessionToken = sessionToken, + sessionTokenExpiresAt = expiresAt + ) + _onRegistrationComplete.emit(Pair(emailAddress, confirmPassword)) } catch (err: Throwable) { // TODO... diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/pref/PreferencesDataStore.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/pref/PreferencesDataStore.kt index 2d8ca68..c435bdf 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/pref/PreferencesDataStore.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/pref/PreferencesDataStore.kt @@ -6,26 +6,26 @@ import androidx.datastore.core.DataStore import androidx.datastore.core.Serializer import androidx.datastore.dataStore import com.google.protobuf.InvalidProtocolBufferException -import ing.bikeshedengineer.debtpirate.PrefsDataStore +import ing.bikeshedengineer.debtpirate.AppDataStore import java.io.InputStream import java.io.OutputStream -object PrefsDataStoreSerializer : Serializer { - override val defaultValue: PrefsDataStore - get() = PrefsDataStore.getDefaultInstance() +object PrefsDataStoreSerializer : Serializer { + override val defaultValue: AppDataStore + get() = AppDataStore.getDefaultInstance() - override suspend fun readFrom(input: InputStream): PrefsDataStore { + override suspend fun readFrom(input: InputStream): AppDataStore { try { - return PrefsDataStore.parseFrom(input) + return AppDataStore.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto file.", exception) } } - override suspend fun writeTo(t: PrefsDataStore, output: OutputStream) = t.writeTo(output) + override suspend fun writeTo(t: AppDataStore, output: OutputStream) = t.writeTo(output) } -val Context.prefsDataStore: DataStore by dataStore( - fileName = "prefs_data_store.proto", +val Context.appDataStore: DataStore by dataStore( + fileName = "app_data_store.proto", serializer = PrefsDataStoreSerializer ) \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt index ff08552..56f26b9 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginPostResponse.kt @@ -1,9 +1,16 @@ package ing.bikeshedengineer.debtpirate.data.remote.model.auth +import com.google.gson.annotations.JsonAdapter +import ing.bikeshedengineer.debtpirate.domain.adapter.OffsetDateTimeAdapter +import java.time.OffsetDateTime + data class AuthLoginPostResponse( val userId: Int, + val auth: AuthLoginPostResponseTokenData, val session: AuthLoginPostResponseTokenData, - val auth: AuthLoginPostResponseTokenData ) -data class AuthLoginPostResponseTokenData(val token: String, val expiresAt: String) \ No newline at end of file +data class AuthLoginPostResponseTokenData( + val token: String, + @JsonAdapter(OffsetDateTimeAdapter::class) val expiresAt: OffsetDateTime +) \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt index 4322417..b84e3d2 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/user/UserCreatePostResponse.kt @@ -7,6 +7,6 @@ import java.time.OffsetDateTime data class UserCreatePostResponse( val userId: Int, @JsonAdapter(OffsetDateTimeAdapter::class) - val expiresAt: OffsetDateTime, + val expiresAt: OffsetDateTime? = null, val sessionToken: String? = null ) \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/PrefsDataStoreProvider.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/AppDataStoreProvider.kt similarity index 55% rename from app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/PrefsDataStoreProvider.kt rename to app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/AppDataStoreProvider.kt index 900d34a..4d6f9fe 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/PrefsDataStoreProvider.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/AppDataStoreProvider.kt @@ -6,17 +6,17 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import ing.bikeshedengineer.debtpirate.PrefsDataStore -import ing.bikeshedengineer.debtpirate.data.pref.prefsDataStore +import ing.bikeshedengineer.debtpirate.AppDataStore +import ing.bikeshedengineer.debtpirate.data.pref.appDataStore import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object PrefsDataStoreProvider { +object AppDataStoreProvider { @Provides @Singleton - fun providePrefsDataStore(application: Application): DataStore { - return application.prefsDataStore + fun provideAppDataStore(application: Application): DataStore { + return application.appDataStore } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt new file mode 100644 index 0000000..b8ce299 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/UseCaseModule.kt @@ -0,0 +1,19 @@ +package ing.bikeshedengineer.debtpirate.di + +import androidx.datastore.core.DataStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ing.bikeshedengineer.debtpirate.AppDataStore +import ing.bikeshedengineer.debtpirate.domain.usecase.UpdateStoreDataUseCase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object UseCaseModule { + + @Provides + @Singleton + fun provideUpdateStoreDataUseCase(store: DataStore) = UpdateStoreDataUseCase(store) +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt new file mode 100644 index 0000000..913eafe --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/usecase/UpdateStoreDataUseCase.kt @@ -0,0 +1,35 @@ +package ing.bikeshedengineer.debtpirate.domain.usecase + +import androidx.datastore.core.DataStore +import ing.bikeshedengineer.debtpirate.AppDataStore +import java.time.OffsetDateTime + +class UpdateStoreDataUseCase(val store: DataStore) { + suspend operator fun invoke( + userId: Int? = null, + authToken: String? = null, + authTokenExpiresAt: OffsetDateTime? = null, + sessionToken: String? = null, + sessionTokenExpiresAt: OffsetDateTime? = null + ) { + store.updateData { currentStore -> + currentStore.toBuilder().apply { + if (userId != null) { + this.setUserId(userId) + } + + if (authToken != null && authTokenExpiresAt != null) { + this.authToken.toBuilder().setToken(authToken).setExpiresAt(authTokenExpiresAt.toEpochSecond()) + .build() + } + + if (sessionToken != null && sessionTokenExpiresAt != null) { + this.sessionToken.toBuilder().setToken(sessionToken) + .setExpiresAt(sessionTokenExpiresAt.toEpochSecond()).build() + } + } + .build() + + } + } +} \ No newline at end of file diff --git a/app/app/src/main/proto/prefs_data_store.proto b/app/app/src/main/proto/app_data_store.proto similarity index 57% rename from app/app/src/main/proto/prefs_data_store.proto rename to app/app/src/main/proto/app_data_store.proto index 2d229c6..99e6ea7 100644 --- a/app/app/src/main/proto/prefs_data_store.proto +++ b/app/app/src/main/proto/app_data_store.proto @@ -8,9 +8,8 @@ message Token { int64 expires_at = 2; } -message PrefsDataStore { - string current_route = 1; - int32 user_id = 2; - Token auth_token = 3; - Token session_token = 4; +message AppDataStore { + int32 user_id = 1; + Token auth_token = 2; + Token session_token = 3; } \ No newline at end of file