feat: implement device update POST route

episode-actions
Jef Roosens 2025-02-23 21:54:34 +01:00
parent d6fb4573d0
commit 4d37ddb780
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
4 changed files with 133 additions and 8 deletions

View File

@ -18,8 +18,8 @@ on later, no guarantees.
* Suggestions API * Suggestions API
- [ ] Retrieve Suggested Podcasts - [ ] Retrieve Suggested Podcasts
* Device API * Device API
- [ ] Update Device Data - [x] Update Device Data
- [ ] List Devices - [-] List Devices
- [ ] Get Device Updates - [ ] Get Device Updates
* Subscriptions API * Subscriptions API
- [ ] Get Subscriptions of Device - [ ] Get Subscriptions of Device

View File

@ -1,7 +1,7 @@
pub mod models; pub mod models;
mod schema; mod schema;
pub use models::device::{Device, NewDevice}; pub use models::device::{Device, DeviceType, NewDevice};
pub use models::session::Session; pub use models::session::Session;
pub use models::user::{NewUser, User}; pub use models::user::{NewUser, User};

View File

@ -51,6 +51,52 @@ impl Device {
.filter(devices::user_id.eq(user_id)) .filter(devices::user_id.eq(user_id))
.get_results(&mut pool.get()?)?) .get_results(&mut pool.get()?)?)
} }
pub fn by_device_id(pool: &DbPool, user_id: i64, device_id: &str) -> DbResult<Option<Self>> {
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<Device> {
Ok(diesel::insert_into(devices::table)
.values(&self)
.returning(Device::as_returning())
.get_result(&mut pool.get()?)?)
}
} }
impl fmt::Display for DeviceType { impl fmt::Display for DeviceType {

View File

@ -1,10 +1,10 @@
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
middleware, middleware,
routing::get, routing::{get, post},
Extension, Json, Router, Extension, Json, Router,
}; };
use serde::Serialize; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
db::{self, User}, db::{self, User},
@ -19,14 +19,49 @@ use super::auth::auth_middleware;
pub fn router(ctx: Context) -> Router<Context> { pub fn router(ctx: Context) -> Router<Context> {
Router::new() Router::new()
.route("/{username}", get(get_devices)) .route("/{username}", get(get_devices))
.route("/{username}/{id}", post(post_device))
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware)) .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<DeviceType> 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<db::DeviceType> 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)] #[derive(Serialize)]
pub struct Device { pub struct Device {
id: String, id: String,
caption: String, caption: String,
r#type: String, r#type: DeviceType,
subscriptions: i64, subscriptions: i64,
} }
@ -50,12 +85,56 @@ async fn get_devices(
.map(|d| Device { .map(|d| Device {
id: d.device_id, id: d.device_id,
caption: d.caption, caption: d.caption,
r#type: d.type_.to_string(), r#type: d.type_.into(),
// TODO implement subscription count // TODO implement subscription count
subscriptions: 0, subscriptions: 0,
}) })
.collect(); .collect();
Ok(Json(devices)) Ok(Json(devices))
// let devices: Vec<Device> = devices.iter }
#[derive(Deserialize)]
pub struct DevicePatch {
caption: Option<String>,
r#type: Option<DeviceType>,
}
async fn post_device(
State(ctx): State<Context>,
Path((_username, id)): Path<(String, String)>,
Extension(user): Extension<User>,
Json(patch): Json<DevicePatch>,
) -> 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(())
} }