From 0e543539cff24e26c0b6a7d6dbd86d8a86bbb117 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 17 Mar 2025 11:15:56 +0100 Subject: [PATCH] feat: implemented sync device API routes --- src/server/gpodder/advanced/mod.rs | 2 + src/server/gpodder/advanced/sync.rs | 91 +++++++++++++++++++++++++++++ src/server/gpodder/models.rs | 14 +++++ 3 files changed, 107 insertions(+) create mode 100644 src/server/gpodder/advanced/sync.rs diff --git a/src/server/gpodder/advanced/mod.rs b/src/server/gpodder/advanced/mod.rs index a1f596c..c9ca0cd 100644 --- a/src/server/gpodder/advanced/mod.rs +++ b/src/server/gpodder/advanced/mod.rs @@ -2,6 +2,7 @@ mod auth; mod devices; mod episodes; mod subscriptions; +mod sync; use axum::Router; @@ -13,4 +14,5 @@ pub fn router(ctx: Context) -> Router { .nest("/devices", devices::router(ctx.clone())) .nest("/subscriptions", subscriptions::router(ctx.clone())) .nest("/episodes", episodes::router(ctx.clone())) + .nest("/sync-devices", sync::router(ctx.clone())) } diff --git a/src/server/gpodder/advanced/sync.rs b/src/server/gpodder/advanced/sync.rs new file mode 100644 index 0000000..fc97869 --- /dev/null +++ b/src/server/gpodder/advanced/sync.rs @@ -0,0 +1,91 @@ +use axum::{ + extract::{Path, State}, + middleware, + routing::get, + Extension, Json, Router, +}; + +use crate::{ + gpodder, + server::{ + error::{AppError, AppResult}, + gpodder::{ + auth_middleware, + format::{Format, StringWithFormat}, + models::{SyncStatus, SyncStatusDelta}, + }, + Context, + }, +}; + +pub fn router(ctx: Context) -> Router { + Router::new() + .route( + "/{username}", + get(get_sync_status).post(post_sync_status_changes), + ) + .layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware)) +} + +pub async fn get_sync_status( + State(ctx): State, + Path(username): Path, + Extension(user): Extension, +) -> AppResult> { + if username.format != Format::Json { + return Err(AppError::NotFound); + } + + if *username != user.username { + return Err(AppError::BadRequest); + } + + Ok( + tokio::task::spawn_blocking(move || ctx.store.devices_by_sync_group(&user)) + .await + .unwrap() + .map(|(not_synchronized, synchronized)| { + Json(SyncStatus { + synchronized, + not_synchronized, + }) + })?, + ) +} + +pub async fn post_sync_status_changes( + State(ctx): State, + Path(username): Path, + Extension(user): Extension, + Json(delta): Json, +) -> AppResult> { + if username.format != Format::Json { + return Err(AppError::NotFound); + } + + if *username != user.username { + return Err(AppError::BadRequest); + } + + Ok(tokio::task::spawn_blocking(move || { + ctx.store.update_device_sync_status( + &user, + delta + .synchronize + .iter() + .map(|v| v.iter().map(|s| s.as_ref()).collect()) + .collect(), + delta.stop_synchronize.iter().map(|s| s.as_ref()).collect(), + )?; + + ctx.store.devices_by_sync_group(&user) + }) + .await + .unwrap() + .map(|(not_synchronized, synchronized)| { + Json(SyncStatus { + synchronized, + not_synchronized, + }) + })?) +} diff --git a/src/server/gpodder/models.rs b/src/server/gpodder/models.rs index 6098a9f..20d459f 100644 --- a/src/server/gpodder/models.rs +++ b/src/server/gpodder/models.rs @@ -73,6 +73,20 @@ pub struct EpisodeAction { pub action: EpisodeActionType, } +#[derive(Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct SyncStatus { + pub synchronized: Vec>, + pub not_synchronized: Vec, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct SyncStatusDelta { + pub synchronize: Vec>, + pub stop_synchronize: Vec, +} + impl From for DeviceType { fn from(value: gpodder::DeviceType) -> Self { match value {