From 4d37ddb780616996e7368d6fc15f8cbdf06fb227 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 23 Feb 2025 21:54:34 +0100 Subject: [PATCH] feat: implement device update POST route --- README.md | 4 +- src/db/mod.rs | 2 +- src/db/models/device.rs | 46 ++++++++++++++++++ src/server/gpodder/devices.rs | 89 +++++++++++++++++++++++++++++++++-- 4 files changed, 133 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 213d16b..ee890c2 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ on later, no guarantees. * Suggestions API - [ ] Retrieve Suggested Podcasts * Device API - - [ ] Update Device Data - - [ ] List Devices + - [x] Update Device Data + - [-] List Devices - [ ] Get Device Updates * Subscriptions API - [ ] Get Subscriptions of Device diff --git a/src/db/mod.rs b/src/db/mod.rs index d435e9e..68c1607 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,7 +1,7 @@ pub mod models; mod schema; -pub use models::device::{Device, NewDevice}; +pub use models::device::{Device, DeviceType, NewDevice}; pub use models::session::Session; pub use models::user::{NewUser, User}; diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 3cd3663..c4b1238 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -51,6 +51,52 @@ impl Device { .filter(devices::user_id.eq(user_id)) .get_results(&mut pool.get()?)?) } + + pub fn by_device_id(pool: &DbPool, user_id: i64, device_id: &str) -> DbResult> { + Ok(devices::dsl::devices + .select(Self::as_select()) + .filter( + devices::user_id + .eq(user_id) + .and(devices::device_id.eq(device_id)), + ) + .get_result(&mut pool.get()?) + .optional()?) + } + + pub fn update(&self, pool: &DbPool) -> DbResult<()> { + Ok(diesel::update( + devices::table.filter( + devices::user_id + .eq(self.user_id) + .and(devices::device_id.eq(&self.device_id)), + ), + ) + .set(( + devices::caption.eq(&self.caption), + devices::type_.eq(&self.type_), + )) + .execute(&mut pool.get()?) + .map(|_| ())?) + } +} + +impl NewDevice { + pub fn new(user_id: i64, device_id: String, caption: String, type_: DeviceType) -> Self { + Self { + device_id, + user_id, + caption, + type_, + } + } + + pub fn insert(self, pool: &DbPool) -> DbResult { + Ok(diesel::insert_into(devices::table) + .values(&self) + .returning(Device::as_returning()) + .get_result(&mut pool.get()?)?) + } } impl fmt::Display for DeviceType { diff --git a/src/server/gpodder/devices.rs b/src/server/gpodder/devices.rs index 5cbd669..d336b56 100644 --- a/src/server/gpodder/devices.rs +++ b/src/server/gpodder/devices.rs @@ -1,10 +1,10 @@ use axum::{ extract::{Path, State}, middleware, - routing::get, + routing::{get, post}, Extension, Json, Router, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use crate::{ db::{self, User}, @@ -19,14 +19,49 @@ use super::auth::auth_middleware; pub fn router(ctx: Context) -> Router { Router::new() .route("/{username}", get(get_devices)) + .route("/{username}/{id}", post(post_device)) .layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware)) } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DeviceType { + Desktop, + Laptop, + Mobile, + Server, + Other, +} + +impl From for db::DeviceType { + fn from(value: DeviceType) -> Self { + match value { + DeviceType::Desktop => Self::Desktop, + DeviceType::Laptop => Self::Laptop, + DeviceType::Mobile => Self::Mobile, + DeviceType::Server => Self::Server, + DeviceType::Other => Self::Other, + } + } +} + +impl From for DeviceType { + fn from(value: db::DeviceType) -> Self { + match value { + db::DeviceType::Desktop => Self::Desktop, + db::DeviceType::Laptop => Self::Laptop, + db::DeviceType::Mobile => Self::Mobile, + db::DeviceType::Server => Self::Server, + db::DeviceType::Other => Self::Other, + } + } +} + #[derive(Serialize)] pub struct Device { id: String, caption: String, - r#type: String, + r#type: DeviceType, subscriptions: i64, } @@ -50,12 +85,56 @@ async fn get_devices( .map(|d| Device { id: d.device_id, caption: d.caption, - r#type: d.type_.to_string(), + r#type: d.type_.into(), // TODO implement subscription count subscriptions: 0, }) .collect(); Ok(Json(devices)) - // let devices: Vec = devices.iter +} + +#[derive(Deserialize)] +pub struct DevicePatch { + caption: Option, + r#type: Option, +} + +async fn post_device( + State(ctx): State, + Path((_username, id)): Path<(String, String)>, + Extension(user): Extension, + Json(patch): Json, +) -> AppResult<()> { + let id = id + .strip_suffix(".json") + .ok_or(AppError::NotFound)? + .to_string(); + + tokio::task::spawn_blocking(move || { + if let Some(mut device) = db::Device::by_device_id(&ctx.pool, user.id, &id)? { + if let Some(caption) = patch.caption { + device.caption = caption; + } + + if let Some(type_) = patch.r#type { + device.type_ = type_.into(); + } + + device.update(&ctx.pool) + } else { + db::NewDevice::new( + user.id, + id.to_string(), + patch.caption.unwrap_or(String::new()), + patch.r#type.unwrap_or(DeviceType::Other).into(), + ) + .insert(&ctx.pool) + .map(|_| ()) + } + }) + .await + .unwrap()?; + + Ok(()) }