diff --git a/src/cli/serve.rs b/src/cli/serve.rs index 48a6c5b..f329858 100644 --- a/src/cli/serve.rs +++ b/src/cli/serve.rs @@ -33,7 +33,7 @@ impl ServeCommand { let pool = db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), true).unwrap(); let ctx = server::Context { pool }; - let app = server::app().with_state(ctx); + let app = server::app(ctx); let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/src/db/models/device.rs b/src/db/models/device.rs index d5dbb28..3cd3663 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -16,21 +16,21 @@ use crate::db::{schema::*, DbPool, DbResult}; #[diesel(table_name = devices)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] pub struct Device { - id: i64, - device_id: String, - user_id: i64, - caption: String, - type_: DeviceType, + pub id: i64, + pub device_id: String, + pub user_id: i64, + pub caption: String, + pub type_: DeviceType, } #[derive(Deserialize, Insertable)] #[diesel(table_name = devices)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] pub struct NewDevice { - device_id: String, - user_id: i64, - caption: String, - type_: DeviceType, + pub device_id: String, + pub user_id: i64, + pub caption: String, + pub type_: DeviceType, } #[derive(Serialize, Deserialize, FromSqlRow, Debug, AsExpression, Clone)] diff --git a/src/server/gpodder/devices.rs b/src/server/gpodder/devices.rs new file mode 100644 index 0000000..5cbd669 --- /dev/null +++ b/src/server/gpodder/devices.rs @@ -0,0 +1,61 @@ +use axum::{ + extract::{Path, State}, + middleware, + routing::get, + Extension, Json, Router, +}; +use serde::Serialize; + +use crate::{ + db::{self, User}, + server::{ + error::{AppError, AppResult}, + Context, + }, +}; + +use super::auth::auth_middleware; + +pub fn router(ctx: Context) -> Router { + Router::new() + .route("/{username}", get(get_devices)) + .layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware)) +} + +#[derive(Serialize)] +pub struct Device { + id: String, + caption: String, + r#type: String, + subscriptions: i64, +} + +async fn get_devices( + State(ctx): State, + Path(username): Path, + Extension(user): Extension, +) -> AppResult>> { + // Check suffix is present and return 404 otherwise; axum doesn't support matching part of a + // route segment + let username = username.strip_suffix(".json").ok_or(AppError::NotFound)?; + + if username != user.username { + return Err(AppError::BadRequest); + } + + let devices = tokio::task::spawn_blocking(move || db::Device::for_user(&ctx.pool, user.id)) + .await + .unwrap()? + .into_iter() + .map(|d| Device { + id: d.device_id, + caption: d.caption, + r#type: d.type_.to_string(), + // TODO implement subscription count + subscriptions: 0, + }) + .collect(); + + Ok(Json(devices)) + // let devices: Vec = devices.iter +} diff --git a/src/server/gpodder/mod.rs b/src/server/gpodder/mod.rs index 690780b..6add829 100644 --- a/src/server/gpodder/mod.rs +++ b/src/server/gpodder/mod.rs @@ -1,4 +1,5 @@ mod auth; +mod devices; use axum::{ http::{HeaderName, HeaderValue}, @@ -8,9 +9,10 @@ use tower_http::set_header::SetResponseHeaderLayer; use super::Context; -pub fn router() -> Router { +pub fn router(ctx: Context) -> Router { Router::new() .nest("/auth", auth::router()) + .nest("/devices", devices::router(ctx)) // https://gpoddernet.readthedocs.io/en/latest/api/reference/general.html#cors // All endpoints should send this CORS header value so the endpoints can be used from web // applications diff --git a/src/server/mod.rs b/src/server/mod.rs index 2ebbc5f..a6f5250 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -9,8 +9,9 @@ pub struct Context { pub pool: crate::db::DbPool, } -pub fn app() -> Router { +pub fn app(ctx: Context) -> Router { Router::new() - .nest("/api/2", gpodder::router()) + .nest("/api/2", gpodder::router(ctx.clone())) .layer(TraceLayer::new_for_http()) + .with_state(ctx) }