Added UserRepository and ExpenseShareRepository

Implemented `UserRepository` and `ExpenseShareRepository` to handle data fetching with a caching strategy (local DAO + remote API).

Specific changes include:
- Added `getUserById` to `UserDao` and updated `getShareById` in `ExpenseShareDao` to support nullable returns.
- Updated `APIService` and `ComDataTypes` to include endpoints and data models for User info, Expense Shares, and pluralized Expense responses.
- Refactored `ExpenseRepository` to use the updated API naming conventions and removed debug print statements.
This commit is contained in:
2026-03-04 12:28:12 +01:00
parent 3d789c0352
commit 4104930ea5
7 changed files with 107 additions and 8 deletions

View File

@@ -24,7 +24,7 @@ interface ExpenseShareDao {
fun getAllShares(): Flow<List<ExpenseShare>> fun getAllShares(): Flow<List<ExpenseShare>>
@Query("SELECT * FROM expense_shares WHERE id = :shareId") @Query("SELECT * FROM expense_shares WHERE id = :shareId")
fun getShareById(shareId: UUID): Flow<ExpenseShare> fun getShareById(shareId: UUID): Flow<ExpenseShare?>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertShare(share: ExpenseShare) suspend fun insertShare(share: ExpenseShare)

View File

@@ -22,6 +22,9 @@ interface UserDao {
@Query("SELECT * FROM users") @Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>> fun getAllUsers(): Flow<List<User>>
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserById(userId: UUID): Flow<User?>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User) suspend fun insertUser(user: User)

View File

@@ -21,7 +21,7 @@ interface APIService {
// Expenses // Expenses
@GET("api/expenses") @GET("api/expenses")
suspend fun expenseGet(@Header("Authorization") token: String): Response<ExpenseResponse> suspend fun expensesGet(@Header("Authorization") token: String): Response<ExpensesResponse>
@POST("api/expenses") @POST("api/expenses")
suspend fun expenseCreate(@Header("Authorization") token: String) suspend fun expenseCreate(@Header("Authorization") token: String)
@PUT("api/expenses") @PUT("api/expenses")
@@ -29,7 +29,13 @@ interface APIService {
@DELETE("api/expenses") @DELETE("api/expenses")
suspend fun expenseDelete(@Header("Authorization") token: String) suspend fun expenseDelete(@Header("Authorization") token: String)
// Shares
@GET("api/shares")
suspend fun sharesGet(@Header("Authorization") token: String): Response<ExpenseSharesResponse>
@GET("api/shares")
suspend fun shareGet(@Header("Authorization") token: String, @Query("id") shareId: UUID): Response<ExpenseShareResponse>
// User // User
@GET("api/userinfo") @GET("api/userinfo")
suspend fun userinfo(@Header("Authorization") token: String, @Query("id") userId: UUID) suspend fun userinfo(@Header("Authorization") token: String, @Query("id") userId: UUID): Response<UserinfoResponse>
} }

View File

@@ -1,6 +1,8 @@
package de.miaurizius.shap_planner.network package de.miaurizius.shap_planner.network
import de.miaurizius.shap_planner.entities.Expense import de.miaurizius.shap_planner.entities.Expense
import de.miaurizius.shap_planner.entities.ExpenseShare
import de.miaurizius.shap_planner.entities.User
// Login // Login
data class LoginRequest(val username: String, val password: String) data class LoginRequest(val username: String, val password: String)
@@ -12,4 +14,11 @@ data class RefreshRequest(val refresh_token: String)
data class RefreshResponse(val access_token: String, val refresh_token: String) data class RefreshResponse(val access_token: String, val refresh_token: String)
// Expenses // Expenses
data class ExpenseResponse(val expenses: List<Expense>) data class ExpensesResponse(val expenses: List<Expense>)
// ExpenseShares
data class ExpenseSharesResponse(val shares: List<ExpenseShare>)
data class ExpenseShareResponse(val share: ExpenseShare)
// User
data class UserinfoResponse(val user: User)

View File

@@ -13,18 +13,15 @@ class ExpenseRepository(
) { ) {
fun getExpenses(token: String, forceRefresh: Boolean = false): Flow<Resource<List<Expense>>> = flow { fun getExpenses(token: String, forceRefresh: Boolean = false): Flow<Resource<List<Expense>>> = flow {
val cachedExpense = dao.getAllExpenses().first() val cachedExpense = dao.getAllExpenses().first()
println("CachedExpense: $cachedExpense")
emit(Resource.Loading(cachedExpense)) emit(Resource.Loading(cachedExpense))
if(cachedExpense.isEmpty() || forceRefresh) { if(cachedExpense.isEmpty() || forceRefresh) {
try { try {
val response = api.expenseGet("Bearer $token") val response = api.expensesGet("Bearer $token")
if(response.isSuccessful) { if(response.isSuccessful) {
val remoteExpense = response.body()?.expenses ?: emptyList() val remoteExpense = response.body()?.expenses ?: emptyList()
println("Fetched expenses: $remoteExpense")
remoteExpense.forEach { remoteExpense.forEach {
dao.insertExpense(it) dao.insertExpense(it)
println("Added $it")
} }
} }
} catch(e: Exception) { } catch(e: Exception) {

View File

@@ -0,0 +1,53 @@
package de.miaurizius.shap_planner.repository
import de.miaurizius.shap_planner.entities.ExpenseShare
import de.miaurizius.shap_planner.entities.ExpenseShareDao
import de.miaurizius.shap_planner.network.APIService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import java.util.UUID
class ExpenseShareRepository(
private val dao: ExpenseShareDao,
private val api: APIService
) {
fun getShares(token: String, forceRefresh: Boolean = false): Flow<Resource<List<ExpenseShare>>> = flow {
val cachedData = dao.getAllShares().first()
emit(Resource.Loading(cachedData))
if(cachedData.isEmpty() || forceRefresh) {
try {
val response = api.sharesGet("Bearer $token")
if(response.isSuccessful) {
val remoteShare = response.body()?.shares ?: emptyList()
remoteShare.forEach {
dao.insertShare(it)
}
}
} catch(e: Exception) {
emit(Resource.Error("Network Error: ${e.localizedMessage}", cachedData))
}
}
dao.getAllShares().collect { emit(Resource.Success(it)) }
}
fun getShareById(token: String, shareId: UUID, forceRefresh: Boolean = false): Flow<Resource<ExpenseShare>> = flow {
val cached = dao.getShareById(shareId).first()
emit(Resource.Loading(cached))
if(cached == null || forceRefresh) {
try {
val response = api.shareGet("Bearer $token", shareId)
if(response.isSuccessful) {
response.body()?.share?.let { remoteShare -> dao.insertShare(remoteShare) }
}
} catch(e: Exception) {
emit(Resource.Error("Network-Error: ${e.localizedMessage}", cached))
}
}
dao.getShareById(shareId).collect { share ->
if(share != null) emit(Resource.Success(share))
else emit(Resource.Error("Share nicht gefunden", null))
}
}
}

View File

@@ -0,0 +1,31 @@
package de.miaurizius.shap_planner.repository
import de.miaurizius.shap_planner.entities.User
import de.miaurizius.shap_planner.entities.UserDao
import de.miaurizius.shap_planner.network.APIService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import java.util.UUID
class UserRepository(
private val dao: UserDao,
private val api: APIService
) {
fun getUser(token: String, userId: UUID, forceRefresh: Boolean = false): Flow<Resource<User>> = flow {
val cached = dao.getUserById(userId).first()
emit(Resource.Loading(cached))
if(cached == null || forceRefresh) {
try {
val response = api.userinfo("Bearer $token", userId)
if(response.isSuccessful) {
response.body()?.user?.let { remoteUser -> dao.insertUser(remoteUser) }
}
} catch(e: Exception) {
emit(Resource.Error("Network-Error: ${e.localizedMessage}", cached))
}
}
dao.getUserById(userId).collect { user -> if(user != null) emit(Resource.Success(user)) else emit(
Resource.Error("User nicht gefunden", null)) }
}
}