feat(sync): sync_targets CRUD in the storage layer (T9.1)
This commit is contained in:
1 parent
197092220c
commit
bb7f704e16
1 file changed
+116
@@ -128,6 +128,30 @@ pub struct Retention {
|
||||
pub max_size_gb: Option<u32>,
|
||||
}
|
||||
|
||||
/// A configured upload destination as stored in the DB (ADR-0010). Holds only a
|
||||
/// `credential_ref` into the OS credential store — never the secret itself
|
||||
/// (FR-SYNC-6). Maps 1:1 to the `sync_targets` table.
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub struct SyncTargetRow {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub kind: String, // webdav|onedrive|dropbox|box
|
||||
pub provider_hint: Option<String>,
|
||||
pub base_url: Option<String>,
|
||||
pub remote_base_path: String,
|
||||
pub username: Option<String>,
|
||||
pub credential_ref: String,
|
||||
pub enabled: bool,
|
||||
pub upload_transcript: bool,
|
||||
pub upload_notes: bool,
|
||||
pub upload_summary: bool,
|
||||
pub upload_recording: bool,
|
||||
pub trigger_on_finalize: bool,
|
||||
pub allow_plaintext_lan: bool,
|
||||
pub encrypt_before_upload: bool,
|
||||
pub created_at: i64,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Store: Send + Sync {
|
||||
async fn create_meeting(&self, m: NewMeeting) -> Result<MeetingId, StoreError>;
|
||||
@@ -226,6 +250,14 @@ pub trait Store: Send + Sync {
|
||||
/// Enforce retention; returns count removed. Skips in-progress meetings
|
||||
/// (anything whose `status` isn't a terminal `ready`/`error`).
|
||||
async fn enforce_retention(&self, policy: Retention) -> Result<u32, StoreError>;
|
||||
|
||||
// ---- Sync targets (Phase 9, ADR-0010) ----
|
||||
async fn add_sync_target(&self, row: SyncTargetRow) -> Result<(), StoreError>;
|
||||
/// Full-row replace (caller loads, edits, and preserves `credential_ref`).
|
||||
async fn update_sync_target(&self, row: SyncTargetRow) -> Result<(), StoreError>;
|
||||
async fn remove_sync_target(&self, id: &str) -> Result<(), StoreError>;
|
||||
async fn list_sync_targets(&self) -> Result<Vec<SyncTargetRow>, StoreError>;
|
||||
async fn get_sync_target(&self, id: &str) -> Result<SyncTargetRow, StoreError>;
|
||||
}
|
||||
|
||||
/// SQLite-backed store. Migrations live in `migrations/` (`sqlx::migrate!`).
|
||||
@@ -1189,6 +1221,90 @@ impl Store for SqliteStore {
|
||||
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
async fn add_sync_target(&self, row: SyncTargetRow) -> Result<(), StoreError> {
|
||||
sqlx::query(
|
||||
"INSERT INTO sync_targets (id, name, kind, provider_hint, base_url, remote_base_path, \
|
||||
username, credential_ref, enabled, upload_transcript, upload_notes, upload_summary, \
|
||||
upload_recording, trigger_on_finalize, allow_plaintext_lan, encrypt_before_upload, created_at) \
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
)
|
||||
.bind(&row.id)
|
||||
.bind(&row.name)
|
||||
.bind(&row.kind)
|
||||
.bind(&row.provider_hint)
|
||||
.bind(&row.base_url)
|
||||
.bind(&row.remote_base_path)
|
||||
.bind(&row.username)
|
||||
.bind(&row.credential_ref)
|
||||
.bind(row.enabled)
|
||||
.bind(row.upload_transcript)
|
||||
.bind(row.upload_notes)
|
||||
.bind(row.upload_summary)
|
||||
.bind(row.upload_recording)
|
||||
.bind(row.trigger_on_finalize)
|
||||
.bind(row.allow_plaintext_lan)
|
||||
.bind(row.encrypt_before_upload)
|
||||
.bind(row.created_at)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_sync_target(&self, row: SyncTargetRow) -> Result<(), StoreError> {
|
||||
sqlx::query(
|
||||
"UPDATE sync_targets SET name=?, kind=?, provider_hint=?, base_url=?, remote_base_path=?, \
|
||||
username=?, credential_ref=?, enabled=?, upload_transcript=?, upload_notes=?, \
|
||||
upload_summary=?, upload_recording=?, trigger_on_finalize=?, allow_plaintext_lan=?, \
|
||||
encrypt_before_upload=? WHERE id=?",
|
||||
)
|
||||
.bind(&row.name)
|
||||
.bind(&row.kind)
|
||||
.bind(&row.provider_hint)
|
||||
.bind(&row.base_url)
|
||||
.bind(&row.remote_base_path)
|
||||
.bind(&row.username)
|
||||
.bind(&row.credential_ref)
|
||||
.bind(row.enabled)
|
||||
.bind(row.upload_transcript)
|
||||
.bind(row.upload_notes)
|
||||
.bind(row.upload_summary)
|
||||
.bind(row.upload_recording)
|
||||
.bind(row.trigger_on_finalize)
|
||||
.bind(row.allow_plaintext_lan)
|
||||
.bind(row.encrypt_before_upload)
|
||||
.bind(&row.id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_sync_target(&self, id: &str) -> Result<(), StoreError> {
|
||||
// ON DELETE CASCADE clears this target's sync_jobs too.
|
||||
sqlx::query("DELETE FROM sync_targets WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_sync_targets(&self) -> Result<Vec<SyncTargetRow>, StoreError> {
|
||||
let rows = sqlx::query_as::<_, SyncTargetRow>(
|
||||
"SELECT * FROM sync_targets ORDER BY created_at ASC",
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
async fn get_sync_target(&self, id: &str) -> Result<SyncTargetRow, StoreError> {
|
||||
let row = sqlx::query_as::<_, SyncTargetRow>("SELECT * FROM sync_targets WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?
|
||||
.ok_or_else(|| StoreError::NotFound(format!("sync target {id}")))?;
|
||||
Ok(row)
|
||||
}
|
||||
}
|
||||
|
||||
fn dir_size(path: &str) -> u64 {
|
||||
|
||||
Reference in new issue
Block a user