From 542fbb0941f15bc57e4a6e0293b89d9c218c410f Mon Sep 17 00:00:00 2001 From: Jens Reinemann Date: Mon, 18 May 2026 22:10:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(app):=20add=20ResourceEntity,=20Dao,=20Rep?= =?UTF-8?q?ository=20+=20DB=20migration=208=E2=86=929?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #121 --- .../bollwerk/app/data/db/BollwerkDatabase.kt | 10 ++- .../bollwerk/app/data/db/dao/ResourceDao.kt | 24 +++++ .../app/data/db/entity/ResourceEntity.kt | 23 +++++ .../data/repository/ResourceRepositoryImpl.kt | 88 +++++++++++++++++++ .../java/de/bollwerk/app/di/DatabaseModule.kt | 4 + .../de/bollwerk/app/di/RepositoryModule.kt | 6 ++ .../domain/repository/ResourceRepository.kt | 10 +++ 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/de/bollwerk/app/data/db/dao/ResourceDao.kt create mode 100644 app/src/main/java/de/bollwerk/app/data/db/entity/ResourceEntity.kt create mode 100644 app/src/main/java/de/bollwerk/app/data/repository/ResourceRepositoryImpl.kt create mode 100644 app/src/main/java/de/bollwerk/app/domain/repository/ResourceRepository.kt diff --git a/app/src/main/java/de/bollwerk/app/data/db/BollwerkDatabase.kt b/app/src/main/java/de/bollwerk/app/data/db/BollwerkDatabase.kt index ad27ef5..6869902 100644 --- a/app/src/main/java/de/bollwerk/app/data/db/BollwerkDatabase.kt +++ b/app/src/main/java/de/bollwerk/app/data/db/BollwerkDatabase.kt @@ -9,22 +9,25 @@ import de.bollwerk.app.data.db.dao.ItemDao import de.bollwerk.app.data.db.dao.LocationDao import de.bollwerk.app.data.db.dao.MessageDao import de.bollwerk.app.data.db.dao.PendingSyncOpDao +import de.bollwerk.app.data.db.dao.ResourceDao import de.bollwerk.app.data.db.dao.SettingsDao import de.bollwerk.app.data.db.entity.CategoryEntity import de.bollwerk.app.data.db.entity.ItemEntity import de.bollwerk.app.data.db.entity.LocationEntity import de.bollwerk.app.data.db.entity.MessageEntity import de.bollwerk.app.data.db.entity.PendingSyncOpEntity +import de.bollwerk.app.data.db.entity.ResourceEntity import de.bollwerk.app.data.db.entity.SettingsEntity @Database( - entities = [CategoryEntity::class, LocationEntity::class, ItemEntity::class, SettingsEntity::class, PendingSyncOpEntity::class, MessageEntity::class], - version = 8, + entities = [CategoryEntity::class, LocationEntity::class, ItemEntity::class, SettingsEntity::class, PendingSyncOpEntity::class, MessageEntity::class, ResourceEntity::class], + version = 9, exportSchema = true, autoMigrations = [ AutoMigration(from = 5, to = 6), AutoMigration(from = 6, to = 7), - AutoMigration(from = 7, to = 8) + AutoMigration(from = 7, to = 8), + AutoMigration(from = 8, to = 9) ] ) @TypeConverters(LocalDateConverter::class) @@ -35,4 +38,5 @@ internal abstract class BollwerkDatabase : RoomDatabase() { abstract fun settingsDao(): SettingsDao abstract fun pendingSyncOpDao(): PendingSyncOpDao abstract fun messageDao(): MessageDao + abstract fun resourceDao(): ResourceDao } diff --git a/app/src/main/java/de/bollwerk/app/data/db/dao/ResourceDao.kt b/app/src/main/java/de/bollwerk/app/data/db/dao/ResourceDao.kt new file mode 100644 index 0000000..35d698d --- /dev/null +++ b/app/src/main/java/de/bollwerk/app/data/db/dao/ResourceDao.kt @@ -0,0 +1,24 @@ +package de.bollwerk.app.data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import de.bollwerk.app.data.db.entity.ResourceEntity +import kotlinx.coroutines.flow.Flow + +@Dao +internal interface ResourceDao { + + @Query("SELECT * FROM resources") + fun getAll(): Flow> + + @Query("SELECT * FROM resources WHERE guid = :guid") + suspend fun getByGuid(guid: String): ResourceEntity? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(resources: List) + + @Query("DELETE FROM resources") + suspend fun deleteAll() +} diff --git a/app/src/main/java/de/bollwerk/app/data/db/entity/ResourceEntity.kt b/app/src/main/java/de/bollwerk/app/data/db/entity/ResourceEntity.kt new file mode 100644 index 0000000..9221d7d --- /dev/null +++ b/app/src/main/java/de/bollwerk/app/data/db/entity/ResourceEntity.kt @@ -0,0 +1,23 @@ +package de.bollwerk.app.data.db.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "resources") +internal data class ResourceEntity( + @PrimaryKey val guid: String, + @ColumnInfo(name = "title") val title: String, + @ColumnInfo(name = "description") val description: String, + @ColumnInfo(name = "tags") val tags: String, // JSON array string + @ColumnInfo(name = "file_format") val fileFormat: String, + @ColumnInfo(name = "mime_type") val mimeType: String, + @ColumnInfo(name = "file_size") val fileSize: Long, + @ColumnInfo(name = "release_date") val releaseDate: String?, + @ColumnInfo(name = "created_at") val createdAt: Long, + @ColumnInfo(name = "updated_at") val updatedAt: Long, + @ColumnInfo(name = "author") val author: String?, + @ColumnInfo(name = "language") val language: String?, + @ColumnInfo(name = "edition") val edition: String?, + @ColumnInfo(name = "download_url") val downloadUrl: String +) diff --git a/app/src/main/java/de/bollwerk/app/data/repository/ResourceRepositoryImpl.kt b/app/src/main/java/de/bollwerk/app/data/repository/ResourceRepositoryImpl.kt new file mode 100644 index 0000000..625bf8a --- /dev/null +++ b/app/src/main/java/de/bollwerk/app/data/repository/ResourceRepositoryImpl.kt @@ -0,0 +1,88 @@ +package de.bollwerk.app.data.repository + +import de.bollwerk.app.data.db.dao.ResourceDao +import de.bollwerk.app.data.db.entity.ResourceEntity +import de.bollwerk.app.domain.model.SettingsKey.StringKey +import de.bollwerk.app.domain.repository.ResourceRepository +import de.bollwerk.app.domain.repository.SettingsRepository +import de.bollwerk.shared.model.ResourceDto +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.statement.bodyAsBytes +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import javax.inject.Inject + +internal class ResourceRepositoryImpl @Inject constructor( + private val dao: ResourceDao, + private val httpClient: HttpClient, + private val settingsRepository: SettingsRepository +) : ResourceRepository { + + override fun getAll(): Flow> = + dao.getAll().map { entities -> entities.map { it.toDto() } } + + override suspend fun refreshFromServer() = withContext(Dispatchers.IO) { + val serverUrl = settingsRepository.getString(StringKey.ServerUrl) + val token = settingsRepository.getString(StringKey.AuthAccessToken) + if (serverUrl.isBlank() || token.isBlank()) return@withContext + + val response = httpClient.get("$serverUrl/api/resources") { + header("Authorization", "Bearer $token") + } + val resources: List = response.body() + dao.deleteAll() + dao.insertAll(resources.map { it.toEntity() }) + } + + override suspend fun downloadResource(guid: String): ByteArray = withContext(Dispatchers.IO) { + val serverUrl = settingsRepository.getString(StringKey.ServerUrl) + val token = settingsRepository.getString(StringKey.AuthAccessToken) + + val response = httpClient.get("$serverUrl/api/resources/$guid/download") { + header("Authorization", "Bearer $token") + } + response.bodyAsBytes() + } + + private fun ResourceEntity.toDto() = ResourceDto( + guid = guid, + title = title, + description = description, + tags = try { Json.decodeFromString>(tags) } catch (_: Exception) { emptyList() }, + fileFormat = fileFormat, + mimeType = mimeType, + fileSize = fileSize, + releaseDate = releaseDate, + createdAt = createdAt, + updatedAt = updatedAt, + author = author, + language = language, + edition = edition, + downloadUrl = downloadUrl + ) + + private fun ResourceDto.toEntity() = ResourceEntity( + guid = guid, + title = title, + description = description, + tags = Json.encodeToString(ListSerializer(String.serializer()), tags), + fileFormat = fileFormat, + mimeType = mimeType, + fileSize = fileSize, + releaseDate = releaseDate, + createdAt = createdAt, + updatedAt = updatedAt, + author = author, + language = language, + edition = edition, + downloadUrl = downloadUrl + ) +} diff --git a/app/src/main/java/de/bollwerk/app/di/DatabaseModule.kt b/app/src/main/java/de/bollwerk/app/di/DatabaseModule.kt index c2e4857..1c21b3b 100644 --- a/app/src/main/java/de/bollwerk/app/di/DatabaseModule.kt +++ b/app/src/main/java/de/bollwerk/app/di/DatabaseModule.kt @@ -16,6 +16,7 @@ import de.bollwerk.app.data.db.dao.ItemDao import de.bollwerk.app.data.db.dao.LocationDao import de.bollwerk.app.data.db.dao.MessageDao import de.bollwerk.app.data.db.dao.PendingSyncOpDao +import de.bollwerk.app.data.db.dao.ResourceDao import de.bollwerk.app.data.db.dao.SettingsDao import de.bollwerk.app.data.export.DatabaseTransaction import javax.inject.Singleton @@ -67,6 +68,9 @@ internal object DatabaseModule { @Provides fun provideMessageDao(db: BollwerkDatabase): MessageDao = db.messageDao() + @Provides + fun provideResourceDao(db: BollwerkDatabase): ResourceDao = db.resourceDao() + @Provides @Singleton fun provideDatabaseTransaction(db: BollwerkDatabase): DatabaseTransaction = diff --git a/app/src/main/java/de/bollwerk/app/di/RepositoryModule.kt b/app/src/main/java/de/bollwerk/app/di/RepositoryModule.kt index 3688095..ebe172d 100644 --- a/app/src/main/java/de/bollwerk/app/di/RepositoryModule.kt +++ b/app/src/main/java/de/bollwerk/app/di/RepositoryModule.kt @@ -10,6 +10,7 @@ import de.bollwerk.app.data.repository.CategoryRepositoryImpl import de.bollwerk.app.data.repository.ItemRepositoryImpl import de.bollwerk.app.data.repository.LocationRepositoryImpl import de.bollwerk.app.data.repository.MessageRepositoryImpl +import de.bollwerk.app.data.repository.ResourceRepositoryImpl import de.bollwerk.app.data.repository.SettingsRepositoryImpl import de.bollwerk.app.data.repository.UpdateRepositoryImpl import de.bollwerk.app.domain.repository.CategoryRepository @@ -17,6 +18,7 @@ import de.bollwerk.app.domain.repository.ImportExportRepository import de.bollwerk.app.domain.repository.ItemRepository import de.bollwerk.app.domain.repository.LocationRepository import de.bollwerk.app.domain.repository.MessageRepository +import de.bollwerk.app.domain.repository.ResourceRepository import de.bollwerk.app.domain.repository.SettingsRepository import de.bollwerk.app.domain.repository.UpdateRepository import de.bollwerk.app.domain.usecase.ApkInstaller @@ -57,4 +59,8 @@ internal abstract class RepositoryModule { @Binds @Singleton abstract fun bindApkInstaller(impl: ApkInstallerImpl): ApkInstaller + + @Binds + @Singleton + abstract fun bindResourceRepository(impl: ResourceRepositoryImpl): ResourceRepository } diff --git a/app/src/main/java/de/bollwerk/app/domain/repository/ResourceRepository.kt b/app/src/main/java/de/bollwerk/app/domain/repository/ResourceRepository.kt new file mode 100644 index 0000000..273a6b3 --- /dev/null +++ b/app/src/main/java/de/bollwerk/app/domain/repository/ResourceRepository.kt @@ -0,0 +1,10 @@ +package de.bollwerk.app.domain.repository + +import de.bollwerk.shared.model.ResourceDto +import kotlinx.coroutines.flow.Flow + +internal interface ResourceRepository { + fun getAll(): Flow> + suspend fun refreshFromServer() + suspend fun downloadResource(guid: String): ByteArray +}