Backup: versioned JSON serializer, SigV4 + S3 client, JCE crypto, daily worker

Milestone 5. One BackupManager entry point so export, import and
auto-backup share the same serializer and crypto by construction.
Explicit DTOs decouple the backup contract from the Room schema.
JCE baseline (PBKDF2-HMAC-SHA256 600k → AES-256-GCM) behind the
BackupCrypto interface with format auto-detection on decrypt; age
lands behind the same interface in milestone 6. SigV4 signer verified
against AWS's published documentation example signature. Settings in
SharedPreferences encrypted under a non-exportable AndroidKeyStore key
— replaces the now fully deprecated androidx security-crypto with the
same construction (~70 lines, zero deps); threat-model comments state
plainly that the stored auto-backup passphrase only protects against
bucket compromise. WorkManager daily periodic job, inexact by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-10 13:54:13 +02:00
commit 8f5b18cf68
14 changed files with 892 additions and 0 deletions

View file

@ -0,0 +1,51 @@
package no.naiv.meddetsamme.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
/**
* Bulk read/replace for backup. Inserts preserve original ids (autoGenerate
* only kicks in for id = 0), which keeps FK references in the file intact.
*/
@Dao
interface BackupDao {
@Query("SELECT * FROM medication")
suspend fun allMedications(): List<Medication>
@Query("SELECT * FROM dose_time")
suspend fun allDoseTimes(): List<DoseTime>
@Query("SELECT * FROM dose_log")
suspend fun allDoseLogs(): List<DoseLog>
@Query("DELETE FROM medication")
suspend fun clearMedications()
@Insert
suspend fun insertMedications(meds: List<Medication>)
@Insert
suspend fun insertDoseTimes(doseTimes: List<DoseTime>)
@Insert
suspend fun insertDoseLogs(logs: List<DoseLog>)
/**
* Replace-all restore. Deleting medication CASCADEs to dose_time and
* dose_log, so one delete clears everything; insertion order respects FKs.
*/
@Transaction
suspend fun replaceAll(
meds: List<Medication>,
doseTimes: List<DoseTime>,
logs: List<DoseLog>,
) {
clearMedications()
insertMedications(meds)
insertDoseTimes(doseTimes)
insertDoseLogs(logs)
}
}