diff --git a/src/main/kotlin/core/DatabaseFactory.kt b/src/main/kotlin/core/DatabaseFactory.kt index 9ea601c..25a4101 100644 --- a/src/main/kotlin/core/DatabaseFactory.kt +++ b/src/main/kotlin/core/DatabaseFactory.kt @@ -4,6 +4,8 @@ import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import org.flywaydb.core.Flyway import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger object DatabaseFactory { @@ -24,12 +26,17 @@ object DatabaseFactory { val dataSource = HikariDataSource(hikariConfig) Flyway.configure() - .dataSource(dataSource) - .locations("classpath:db/migration") - .load() - .migrate() + .dataSource(dataSource) + .locations("classpath:db/migration") + .load() + .migrate() + + val exposedDbConfig = DatabaseConfig { + sqlLogger = Slf4jSqlDebugLogger + keepLoadedReferencesOutOfTransaction = true + } - return Database.connect(dataSource) + return Database.connect(datasource = dataSource, databaseConfig = exposedDbConfig) } } \ No newline at end of file diff --git a/src/main/kotlin/core/configs/DatabaseConfig.kt b/src/main/kotlin/core/configs/DatabaseConfig.kt index fecf655..c0805d9 100644 --- a/src/main/kotlin/core/configs/DatabaseConfig.kt +++ b/src/main/kotlin/core/configs/DatabaseConfig.kt @@ -8,5 +8,5 @@ data class DatabaseConfig( val maximumPoolSize: Int, val isAutoCommit: Boolean, val isReadOnly: Boolean, - val transactionIsolation: String + val transactionIsolation: String, ) \ No newline at end of file diff --git a/src/main/kotlin/core/models/UiModel.kt b/src/main/kotlin/core/models/UiModel.kt index 78d23ca..98c0e69 100644 --- a/src/main/kotlin/core/models/UiModel.kt +++ b/src/main/kotlin/core/models/UiModel.kt @@ -1,6 +1,9 @@ package core.models import androidx.compose.ui.graphics.Color +import repository.creditcard.CreditCard +import repository.password.Password +import java.util.UUID enum class NotificationType(val color: Color) { ERROR(Color.Red), @@ -9,11 +12,17 @@ enum class NotificationType(val color: Color) { } data class CredentialDisplay( + val id: UUID, val title: String, val description: String, val favorite: Boolean ) +data class SelectedCredential( + val password: Password?, + val creditCard: CreditCard? +) + enum class CredentialSort(val value: String) { NAME("Name"), FAVORITE("Favorite"), diff --git a/src/main/kotlin/repository/creditcard/CreditCardRepository.kt b/src/main/kotlin/repository/creditcard/CreditCardRepository.kt index cfb5ab6..13869ed 100644 --- a/src/main/kotlin/repository/creditcard/CreditCardRepository.kt +++ b/src/main/kotlin/repository/creditcard/CreditCardRepository.kt @@ -4,9 +4,12 @@ import core.models.Result import core.models.criteria.CredentialSearchCriteria import core.models.dto.CreditCardDto import repository.creditcard.projection.CreditCardSummary +import java.util.* interface CreditCardRepository { + suspend fun findById(id: UUID): Result + suspend fun findSummaries(searchCriteria: CredentialSearchCriteria): Result> suspend fun save(creditCardDto: CreditCardDto): Result diff --git a/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt b/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt index fd98a5f..1ee4cf1 100644 --- a/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt +++ b/src/main/kotlin/repository/creditcard/impl/CreditCardRepositoryImpl.kt @@ -5,6 +5,7 @@ import core.models.Result import core.models.criteria.CredentialSearchCriteria import core.models.dto.CreditCardDto import kotlinx.coroutines.delay +import org.jetbrains.exposed.dao.load import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Expression import org.jetbrains.exposed.sql.SortOrder @@ -16,12 +17,28 @@ import repository.creditcard.CreditCard import repository.creditcard.CreditCardRepository import repository.creditcard.CreditCardTable import repository.creditcard.projection.CreditCardSummary +import java.util.* class CreditCardRepositoryImpl( private val db: Database, private val logger: Logger ) : CreditCardRepository { + override suspend fun findById(id: UUID): Result { + return try { + return transaction(db) { + CreditCard.findById(id)?.load(CreditCard::owner, CreditCard::user)?.apply { + owner + user + } + + }?.let { Result.Success(it) } ?: Result.Error("Password not found") + } catch (e: Exception) { + logger.error(e.message, e) + Result.Error(DatabaseError.fromException(e).extractMessage()) + } + } + override suspend fun findSummaries(searchCriteria: CredentialSearchCriteria): Result> { delay(550) return try { diff --git a/src/main/kotlin/repository/password/PasswordRepository.kt b/src/main/kotlin/repository/password/PasswordRepository.kt index 50d5c20..2818a18 100644 --- a/src/main/kotlin/repository/password/PasswordRepository.kt +++ b/src/main/kotlin/repository/password/PasswordRepository.kt @@ -4,9 +4,12 @@ import core.models.Result import core.models.criteria.CredentialSearchCriteria import core.models.dto.PasswordDto import repository.password.projection.PasswordSummary +import java.util.UUID interface PasswordRepository { + suspend fun findById(id: UUID): Result + suspend fun findSummaries(searchCriteria: CredentialSearchCriteria): Result> suspend fun save(password: PasswordDto): Result diff --git a/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt b/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt index 4b302e8..52cb721 100644 --- a/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt +++ b/src/main/kotlin/repository/password/impl/PasswordRepositoryImpl.kt @@ -23,6 +23,17 @@ class PasswordRepositoryImpl( private val logger: Logger ) : PasswordRepository { + override suspend fun findById(id: UUID): Result { + return try { + return transaction(db) { + Password.findById(id) + }?.let { Result.Success(it) } ?: Result.Error("Password not found") + } catch (e: Exception) { + logger.error(e.message, e) + Result.Error(DatabaseError.fromException(e).extractMessage()) + } + } + override suspend fun findSummaries(searchCriteria: CredentialSearchCriteria): Result> { delay(550) return try { diff --git a/src/main/kotlin/ui/components/secvault/passwordinfo/CredentialForms.kt b/src/main/kotlin/ui/components/secvault/passwordinfo/CredentialForms.kt index b705f98..1507d83 100644 --- a/src/main/kotlin/ui/components/secvault/passwordinfo/CredentialForms.kt +++ b/src/main/kotlin/ui/components/secvault/passwordinfo/CredentialForms.kt @@ -6,10 +6,27 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.* import androidx.compose.ui.Modifier import ui.components.UnderLineTextFiled +import viewmodel.SecVaultScreenModel @Composable -fun PasswordCredentialForm() { - var userName by remember { mutableStateOf("") } +fun PasswordCredentialForm(screenModel: SecVaultScreenModel) { + val selectedCredential by screenModel.selectedCredential.collectAsState() + + var userName by remember(selectedCredential) { + mutableStateOf(selectedCredential.password?.username ?: "") + } + + var password by remember(selectedCredential) { + mutableStateOf(selectedCredential.password?.password ?: "") + } + + var email by remember(selectedCredential) { + mutableStateOf(selectedCredential.password?.email ?: "") + } + + var website by remember(selectedCredential) { + mutableStateOf(selectedCredential.password?.website ?: "") + } Column() { Row(modifier = Modifier.weight(1f)) { @@ -23,8 +40,8 @@ fun PasswordCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = password, + onFieldChange = { password = it }, label = "Password", modifier = Modifier.fillMaxWidth(), isPassword = true @@ -33,8 +50,8 @@ fun PasswordCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = email, + onFieldChange = { email = it }, label = "Email", modifier = Modifier.fillMaxWidth() ) @@ -42,8 +59,8 @@ fun PasswordCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = website, + onFieldChange = { website = it }, label = "Website", modifier = Modifier.fillMaxWidth() ) @@ -53,14 +70,42 @@ fun PasswordCredentialForm() { } @Composable -fun CreditCardCredentialForm() { - var userName by remember { mutableStateOf("") } +fun CreditCardCredentialForm(screenModel: SecVaultScreenModel) { + val selectedCredential by screenModel.selectedCredential.collectAsState() + + var bankName by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.name ?: "") + } + + var cardOwner by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.owner?.userName ?: "") + } + + var cardNumber by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.number ?: "") + } + + var cvc by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.cvc ?: "") + } + + var pin by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.pin ?: "") + } + + var expiryDate by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.expiryDate ?: "") + } + + var notes by remember(selectedCredential) { + mutableStateOf(selectedCredential.creditCard?.notes ?: "") + } Column() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = bankName, + onFieldChange = { bankName = it }, label = "Bank Name", modifier = Modifier.fillMaxWidth() ) @@ -68,8 +113,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = cardOwner, + onFieldChange = { cardOwner = it }, label = "Card Owner", modifier = Modifier.fillMaxWidth() ) @@ -77,8 +122,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = cardNumber, + onFieldChange = { cardNumber = it }, label = "Card Number", modifier = Modifier.fillMaxWidth() ) @@ -86,8 +131,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = cvc.toString(), + onFieldChange = { cvc = it }, label = "CVC", modifier = Modifier.fillMaxWidth() ) @@ -95,8 +140,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = pin.toString(), + onFieldChange = { pin = it }, label = "Pin", modifier = Modifier.fillMaxWidth() ) @@ -104,8 +149,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = expiryDate, + onFieldChange = { expiryDate = it }, label = "Expiry Date", modifier = Modifier.fillMaxWidth() ) @@ -113,8 +158,8 @@ fun CreditCardCredentialForm() { Row(modifier = Modifier.weight(1f)) { UnderLineTextFiled( - field = userName, - onFieldChange = { userName = it }, + field = notes, + onFieldChange = { notes = it }, label = "Notes", modifier = Modifier.fillMaxWidth(), singleLine = false diff --git a/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordForm.kt b/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordForm.kt index a58c0ef..8e6346e 100644 --- a/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordForm.kt +++ b/src/main/kotlin/ui/components/secvault/passwordinfo/PasswordForm.kt @@ -52,11 +52,11 @@ fun PasswordForm(screenModel: SecVaultScreenModel) { Row(modifier = Modifier.weight(9f)) { when (menuItem.value) { DefaultMenuItem.PASSWORDS -> { - PasswordCredentialForm() + PasswordCredentialForm(screenModel) } DefaultMenuItem.CREDIT_CARD -> { - CreditCardCredentialForm() + CreditCardCredentialForm(screenModel) } DefaultMenuItem.NOTES -> { diff --git a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt index 958515a..db1920d 100644 --- a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt +++ b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordItem.kt @@ -21,12 +21,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import core.models.CredentialDisplay -import repository.password.projection.PasswordSummary import ui.theme.Font import ui.theme.PasswordColors +import java.util.* @Composable -fun PasswordItem(credentialDisplay: CredentialDisplay) { +fun PasswordItem(credentialDisplay: CredentialDisplay, onClick: (id: UUID) -> Unit = {}) { val interactionSource = remember { MutableInteractionSource() } val isHovered by interactionSource.collectIsHoveredAsState() @@ -37,7 +37,7 @@ fun PasswordItem(credentialDisplay: CredentialDisplay) { shape = RoundedCornerShape(6.dp) ) .padding(PaddingValues(start = 5.dp, end = 5.dp)) - .clickable(onClick = {}, indication = null, interactionSource = interactionSource) + .clickable(onClick = { onClick(credentialDisplay.id) }, indication = null, interactionSource = interactionSource) .hoverable(interactionSource), verticalAlignment = Alignment.CenterVertically ) diff --git a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt index fb293bd..27ff73f 100644 --- a/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt +++ b/src/main/kotlin/ui/components/secvault/passwordlayout/PasswordLayout.kt @@ -12,6 +12,7 @@ import core.models.CredentialDisplay import core.models.DefaultMenuItem import core.models.UiState import viewmodel.SecVaultScreenModel +import java.util.* @Composable fun PasswordLayout(screenModel: SecVaultScreenModel) { @@ -27,8 +28,8 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { Row( modifier = Modifier.fillMaxWidth() - .fillMaxHeight() - .weight(1.6f) + .fillMaxHeight() + .weight(1.6f) ) { PasswordFilterHeader(screenModel) @@ -36,8 +37,8 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { Row( modifier = Modifier.fillMaxWidth() - .fillMaxHeight() - .weight(8.4f) + .fillMaxHeight() + .weight(8.4f) ) { LazyColumn( @@ -59,11 +60,15 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { DefaultMenuItem.PASSWORDS -> { if (passwordItems.isNotEmpty()) { items(passwordItems) { item -> - PasswordItem(CredentialDisplay( - item.name, - if (item.email?.isEmpty() == true) item.username!! else item.email!!, - favorite = item.favorite - )) + PasswordItem( + CredentialDisplay( + item.id, + item.name, + if (item.email?.isEmpty() == true) item.username!! else item.email!!, + favorite = item.favorite, + ), + onClick = { id: UUID -> screenModel.loadSelectedCredential(id) } + ) } } else { items(24) { @@ -75,11 +80,15 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { DefaultMenuItem.CREDIT_CARD -> { if (creditCards.isNotEmpty()) { items(creditCards) { item -> - PasswordItem(CredentialDisplay( - item.name, - item.number, - favorite = item.favorite - )) + PasswordItem( + CredentialDisplay( + item.id, + item.name, + item.number, + favorite = item.favorite + ), + onClick = { id: UUID -> screenModel.loadSelectedCredential(id) } + ) } } else { items(24) { @@ -87,6 +96,7 @@ fun PasswordLayout(screenModel: SecVaultScreenModel) { } } } + DefaultMenuItem.NOTES -> TODO() } } diff --git a/src/main/kotlin/viewmodel/SecVaultScreenModel.kt b/src/main/kotlin/viewmodel/SecVaultScreenModel.kt index ff832b6..7f4a9bf 100644 --- a/src/main/kotlin/viewmodel/SecVaultScreenModel.kt +++ b/src/main/kotlin/viewmodel/SecVaultScreenModel.kt @@ -17,6 +17,7 @@ import repository.creditcard.CreditCardRepository import repository.creditcard.projection.CreditCardSummary import repository.password.PasswordRepository import repository.password.projection.PasswordSummary +import java.util.* class SecVaultScreenModel( private val passwordRepository: PasswordRepository, @@ -48,6 +49,9 @@ class SecVaultScreenModel( private val _creditCardItems = MutableStateFlow>(emptyList()) val creditCardItems: StateFlow> = _creditCardItems.asStateFlow() + private val _selectedCredential = MutableStateFlow(SelectedCredential(null, null)) + val selectedCredential: StateFlow = _selectedCredential.asStateFlow() + init { handleEvents() } @@ -77,11 +81,35 @@ class SecVaultScreenModel( } fun onScreenShown() { - screenModelScope.launch { + screenModelScope.launch(dispatcher) { eventChannel.send(SecVaultEvent.LoadCredentials) } } + fun loadSelectedCredential(id: UUID) { + screenModelScope.launch(dispatcher) { + when (_selectedMenuItem.value) { + DefaultMenuItem.PASSWORDS -> { + passwordRepository.findById(id).let { result -> + if (result is Result.Success) { + _selectedCredential.value = SelectedCredential(result.data, null) + } + } + } + + DefaultMenuItem.CREDIT_CARD -> { + creditCardRepository.findById(id).let { result -> + if (result is Result.Success) { + _selectedCredential.value = SelectedCredential(null, result.data) + } + } + } + + DefaultMenuItem.NOTES -> TODO() + } + } + } + private suspend fun loadPasswords(sort: CredentialSort) { val criteria = CredentialSearchCriteria(appState.getAuthenticatedUser?.id?.value, sort) _passwordItems.value = when (val passwords = passwordRepository.findSummaries(criteria)) {