Start saving data to proto datastore from successful rest api requests

This commit is contained in:
Z. Charles Dziura 2024-11-28 07:00:11 -05:00
parent 78a4b75acd
commit ece116a411
14 changed files with 131 additions and 59 deletions

View file

@ -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),
}
};

View file

@ -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<SystemTime>,
pub session_token: Option<String>,
}

View file

@ -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<PrefsDataStore>,
private val prefsStore: DataStore<AppDataStore>,
private val verifyNewUser: SubmitNewUserVerificationRequestUseCase
) : ViewModel() {
private var _isLoading = MutableStateFlow(true)

View file

@ -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(

View file

@ -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<PrefsDataStore>,
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")
}
}
}
}

View file

@ -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))

View file

@ -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...

View file

@ -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<PrefsDataStore> {
override val defaultValue: PrefsDataStore
get() = PrefsDataStore.getDefaultInstance()
object PrefsDataStoreSerializer : Serializer<AppDataStore> {
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<PrefsDataStore> by dataStore(
fileName = "prefs_data_store.proto",
val Context.appDataStore: DataStore<AppDataStore> by dataStore(
fileName = "app_data_store.proto",
serializer = PrefsDataStoreSerializer
)

View file

@ -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)
data class AuthLoginPostResponseTokenData(
val token: String,
@JsonAdapter(OffsetDateTimeAdapter::class) val expiresAt: OffsetDateTime
)

View file

@ -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
)

View file

@ -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<PrefsDataStore> {
return application.prefsDataStore
fun provideAppDataStore(application: Application): DataStore<AppDataStore> {
return application.appDataStore
}
}

View file

@ -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<AppDataStore>) = UpdateStoreDataUseCase(store)
}

View file

@ -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<AppDataStore>) {
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()
}
}
}

View file

@ -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;
}