feat: implemented sync device API routes

signup-links
Jef Roosens 2025-03-17 11:15:56 +01:00
parent f42c708cc6
commit 0e543539cf
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
3 changed files with 107 additions and 0 deletions

View File

@ -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<Context> {
.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()))
}

View File

@ -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<Context> {
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<Context>,
Path(username): Path<StringWithFormat>,
Extension(user): Extension<gpodder::User>,
) -> AppResult<Json<SyncStatus>> {
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<Context>,
Path(username): Path<StringWithFormat>,
Extension(user): Extension<gpodder::User>,
Json(delta): Json<SyncStatusDelta>,
) -> AppResult<Json<SyncStatus>> {
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,
})
})?)
}

View File

@ -73,6 +73,20 @@ pub struct EpisodeAction {
pub action: EpisodeActionType,
}
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SyncStatus {
pub synchronized: Vec<Vec<String>>,
pub not_synchronized: Vec<String>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SyncStatusDelta {
pub synchronize: Vec<Vec<String>>,
pub stop_synchronize: Vec<String>,
}
impl From<gpodder::DeviceType> for DeviceType {
fn from(value: gpodder::DeviceType) -> Self {
match value {