feat: implement basic devices list endpoint

episode-actions
Jef Roosens 2025-02-23 21:04:44 +01:00
parent 22e01d10dc
commit d6fb4573d0
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
5 changed files with 77 additions and 13 deletions

View File

@ -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()

View File

@ -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)]

View File

@ -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<Context> {
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<Context>,
Path(username): Path<String>,
Extension(user): Extension<User>,
) -> AppResult<Json<Vec<Device>>> {
// 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<Device> = devices.iter
}

View File

@ -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<Context> {
pub fn router(ctx: Context) -> Router<Context> {
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

View File

@ -9,8 +9,9 @@ pub struct Context {
pub pool: crate::db::DbPool,
}
pub fn app() -> Router<Context> {
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)
}