mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
Add Organization events database (#1470)
This commit is contained in:
parent
a2f2216df2
commit
170db5077d
9 changed files with 439 additions and 0 deletions
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "f40d7a933b2f353d8d5b5ca619f28e24",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "organization_events",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `organization_event_type` TEXT NOT NULL, `cipher_id` TEXT, `date` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationEventType",
|
||||
"columnName": "organization_event_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherId",
|
||||
"columnName": "cipher_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_organization_events_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_organization_events_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f40d7a933b2f353d8d5b5ca619f28e24')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
|
||||
/**
|
||||
* Primary access point for disk information related to event data.
|
||||
*/
|
||||
interface EventDiskSource {
|
||||
/**
|
||||
* Deletes all organization events associated with the given [userId].
|
||||
*/
|
||||
suspend fun deleteOrganizationEvents(userId: String)
|
||||
|
||||
/**
|
||||
* Adds a new organization event associated with the given [userId].
|
||||
*/
|
||||
suspend fun addOrganizationEvent(userId: String, event: OrganizationEvent)
|
||||
|
||||
/**
|
||||
* Retrieves all organization events associated with the given [userId].
|
||||
*/
|
||||
suspend fun getOrganizationEvents(userId: String): List<OrganizationEvent>
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.dao.OrganizationEventDao
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* The default implementation of [EventDiskSource].
|
||||
*/
|
||||
class EventDiskSourceImpl(
|
||||
private val organizationEventDao: OrganizationEventDao,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
private val json: Json,
|
||||
) : EventDiskSource {
|
||||
|
||||
override suspend fun deleteOrganizationEvents(userId: String) {
|
||||
organizationEventDao.deleteOrganizationEvents(userId = userId)
|
||||
}
|
||||
|
||||
override suspend fun addOrganizationEvent(userId: String, event: OrganizationEvent) {
|
||||
organizationEventDao.insertOrganizationEvent(
|
||||
event = OrganizationEventEntity(
|
||||
userId = userId,
|
||||
organizationEventType = withContext(context = dispatcherManager.default) {
|
||||
json.encodeToString(value = event.type)
|
||||
},
|
||||
cipherId = event.cipherId,
|
||||
date = event.date,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getOrganizationEvents(
|
||||
userId: String,
|
||||
): List<OrganizationEvent> =
|
||||
organizationEventDao
|
||||
.getOrganizationEvents(userId = userId)
|
||||
.map {
|
||||
OrganizationEvent(
|
||||
type = withContext(context = dispatcherManager.default) {
|
||||
json.decodeFromString<OrganizationEventType>(
|
||||
string = it.organizationEventType,
|
||||
)
|
||||
},
|
||||
cipherId = it.cipherId,
|
||||
date = it.date,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
|
||||
|
||||
/**
|
||||
* Provides methods for inserting, retrieving, and deleting events from the database using the
|
||||
* [OrganizationEventEntity].
|
||||
*/
|
||||
@Dao
|
||||
interface OrganizationEventDao {
|
||||
/**
|
||||
* Deletes all the stored events associated with the given [userId]. This will return the
|
||||
* number of rows deleted by this query.
|
||||
*/
|
||||
@Query("DELETE FROM organization_events WHERE user_id = :userId")
|
||||
suspend fun deleteOrganizationEvents(userId: String): Int
|
||||
|
||||
/**
|
||||
* Retrieves all events from the database for a given [userId].
|
||||
*/
|
||||
@Query("SELECT * FROM organization_events WHERE user_id = :userId")
|
||||
suspend fun getOrganizationEvents(userId: String): List<OrganizationEventEntity>
|
||||
|
||||
/**
|
||||
* Inserts an event into the database.
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertOrganizationEvent(event: OrganizationEventEntity)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.dao.OrganizationEventDao
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
|
||||
/**
|
||||
* Room database for storing any persisted data for platform data.
|
||||
*/
|
||||
@Database(
|
||||
entities = [
|
||||
OrganizationEventEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||
abstract class PlatformDatabase : RoomDatabase() {
|
||||
/**
|
||||
* Provides the DAO for accessing organization event data.
|
||||
*/
|
||||
abstract fun organizationEventDao(): OrganizationEventDao
|
||||
}
|
|
@ -3,21 +3,28 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.di
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.x8bit.bitwarden.data.platform.datasource.di.EncryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.dao.OrganizationEventDao
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.database.PlatformDatabase
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigratorImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorage
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigratorImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTypeConverter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
@ -44,6 +51,38 @@ object PlatformDiskModule {
|
|||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventDatabase(app: Application): PlatformDatabase =
|
||||
Room
|
||||
.databaseBuilder(
|
||||
context = app,
|
||||
klass = PlatformDatabase::class.java,
|
||||
name = "platform_database",
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.addTypeConverter(ZonedDateTimeTypeConverter())
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOrganizationEventDao(
|
||||
database: PlatformDatabase,
|
||||
): OrganizationEventDao = database.organizationEventDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEventDiskSource(
|
||||
organizationEventDao: OrganizationEventDao,
|
||||
dispatcherManager: DispatcherManager,
|
||||
json: Json,
|
||||
): EventDiskSource =
|
||||
EventDiskSourceImpl(
|
||||
organizationEventDao = organizationEventDao,
|
||||
dispatcherManager = dispatcherManager,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLegacySecureStorage(
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Entity representing an organization event in the database.
|
||||
*/
|
||||
@Entity(tableName = "organization_events")
|
||||
data class OrganizationEventEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id")
|
||||
val id: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "user_id", index = true)
|
||||
val userId: String,
|
||||
|
||||
@ColumnInfo(name = "organization_event_type")
|
||||
val organizationEventType: String,
|
||||
|
||||
@ColumnInfo(name = "cipher_id")
|
||||
val cipherId: String?,
|
||||
|
||||
@ColumnInfo(name = "date")
|
||||
val date: ZonedDateTime,
|
||||
)
|
|
@ -0,0 +1,142 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.dao.FakeOrganizationEventDao
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class EventDiskSourceTest {
|
||||
private val fixedClock: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val fakeOrganizationEventDao = FakeOrganizationEventDao()
|
||||
private val fakeDispatcherManager = FakeDispatcherManager()
|
||||
private val json = PlatformNetworkModule.providesJson()
|
||||
|
||||
private val eventDiskSource: EventDiskSource = EventDiskSourceImpl(
|
||||
organizationEventDao = fakeOrganizationEventDao,
|
||||
dispatcherManager = fakeDispatcherManager,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `addOrganizationEvent should insert a new organization event`() = runTest {
|
||||
val userId = "userId-1"
|
||||
val organizationEvent = OrganizationEvent(
|
||||
type = OrganizationEventType.CIPHER_DELETED,
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
)
|
||||
|
||||
eventDiskSource.addOrganizationEvent(
|
||||
userId = userId,
|
||||
event = organizationEvent,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
OrganizationEventEntity(
|
||||
id = 0,
|
||||
userId = userId,
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
fakeOrganizationEventDao.storedEvents,
|
||||
)
|
||||
assertFalse(fakeOrganizationEventDao.isDeleteCalled)
|
||||
assertTrue(fakeOrganizationEventDao.isInsertCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deleteOrganizationEvents should delete all organization events`() = runTest {
|
||||
val userId = "userId-1"
|
||||
fakeOrganizationEventDao.storedEvents.addAll(
|
||||
listOf(
|
||||
OrganizationEventEntity(
|
||||
id = 1,
|
||||
userId = userId,
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
OrganizationEventEntity(
|
||||
id = 2,
|
||||
userId = "userId-2",
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
eventDiskSource.deleteOrganizationEvents(userId = userId)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
OrganizationEventEntity(
|
||||
id = 2,
|
||||
userId = "userId-2",
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
fakeOrganizationEventDao.storedEvents,
|
||||
)
|
||||
assertTrue(fakeOrganizationEventDao.isDeleteCalled)
|
||||
assertFalse(fakeOrganizationEventDao.isInsertCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOrganizationEvents should retrieve the correct organization events`() = runTest {
|
||||
val userId = "userId-1"
|
||||
fakeOrganizationEventDao.storedEvents.addAll(
|
||||
listOf(
|
||||
OrganizationEventEntity(
|
||||
id = 1,
|
||||
userId = userId,
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
OrganizationEventEntity(
|
||||
id = 2,
|
||||
userId = "userId-2",
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val result = eventDiskSource.getOrganizationEvents(userId = userId)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
OrganizationEvent(
|
||||
type = OrganizationEventType.CIPHER_DELETED,
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
result,
|
||||
)
|
||||
assertFalse(fakeOrganizationEventDao.isDeleteCalled)
|
||||
assertFalse(fakeOrganizationEventDao.isInsertCalled)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.dao
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
|
||||
|
||||
class FakeOrganizationEventDao : OrganizationEventDao {
|
||||
val storedEvents = mutableListOf<OrganizationEventEntity>()
|
||||
|
||||
var isDeleteCalled = false
|
||||
var isInsertCalled = false
|
||||
|
||||
override suspend fun deleteOrganizationEvents(userId: String): Int {
|
||||
val count = storedEvents.count { it.userId == userId }
|
||||
storedEvents.removeAll { it.userId == userId }
|
||||
isDeleteCalled = true
|
||||
return count
|
||||
}
|
||||
|
||||
override suspend fun getOrganizationEvents(
|
||||
userId: String,
|
||||
): List<OrganizationEventEntity> = storedEvents.filter { it.userId == userId }
|
||||
|
||||
override suspend fun insertOrganizationEvent(event: OrganizationEventEntity) {
|
||||
storedEvents.add(event)
|
||||
isInsertCalled = true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue