feat: implement basic devices list endpoint
parent
22e01d10dc
commit
d6fb4573d0
|
@ -33,7 +33,7 @@ impl ServeCommand {
|
||||||
let pool = db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), true).unwrap();
|
let pool = db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), true).unwrap();
|
||||||
|
|
||||||
let ctx = server::Context { pool };
|
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()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
|
|
|
@ -16,21 +16,21 @@ use crate::db::{schema::*, DbPool, DbResult};
|
||||||
#[diesel(table_name = devices)]
|
#[diesel(table_name = devices)]
|
||||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
pub struct Device {
|
pub struct Device {
|
||||||
id: i64,
|
pub id: i64,
|
||||||
device_id: String,
|
pub device_id: String,
|
||||||
user_id: i64,
|
pub user_id: i64,
|
||||||
caption: String,
|
pub caption: String,
|
||||||
type_: DeviceType,
|
pub type_: DeviceType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Insertable)]
|
#[derive(Deserialize, Insertable)]
|
||||||
#[diesel(table_name = devices)]
|
#[diesel(table_name = devices)]
|
||||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
pub struct NewDevice {
|
pub struct NewDevice {
|
||||||
device_id: String,
|
pub device_id: String,
|
||||||
user_id: i64,
|
pub user_id: i64,
|
||||||
caption: String,
|
pub caption: String,
|
||||||
type_: DeviceType,
|
pub type_: DeviceType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, FromSqlRow, Debug, AsExpression, Clone)]
|
#[derive(Serialize, Deserialize, FromSqlRow, Debug, AsExpression, Clone)]
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod devices;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
http::{HeaderName, HeaderValue},
|
http::{HeaderName, HeaderValue},
|
||||||
|
@ -8,9 +9,10 @@ use tower_http::set_header::SetResponseHeaderLayer;
|
||||||
|
|
||||||
use super::Context;
|
use super::Context;
|
||||||
|
|
||||||
pub fn router() -> Router<Context> {
|
pub fn router(ctx: Context) -> Router<Context> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest("/auth", auth::router())
|
.nest("/auth", auth::router())
|
||||||
|
.nest("/devices", devices::router(ctx))
|
||||||
// https://gpoddernet.readthedocs.io/en/latest/api/reference/general.html#cors
|
// 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
|
// All endpoints should send this CORS header value so the endpoints can be used from web
|
||||||
// applications
|
// applications
|
||||||
|
|
|
@ -9,8 +9,9 @@ pub struct Context {
|
||||||
pub pool: crate::db::DbPool,
|
pub pool: crate::db::DbPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app() -> Router<Context> {
|
pub fn app(ctx: Context) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest("/api/2", gpodder::router())
|
.nest("/api/2", gpodder::router(ctx.clone()))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.with_state(ctx)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue