84 lines
2.8 KiB
Rust
84 lines
2.8 KiB
Rust
use axum::extract::State;
|
|
use axum::Json;
|
|
use serde::Serialize;
|
|
use utoipa::ToSchema;
|
|
|
|
use crate::movies::{ApiError, ErrorBody};
|
|
use crate::state::AppState;
|
|
|
|
/// A root folder joined with its policy, enough for the add flow to pre-fill
|
|
/// root and policy (DESIGN.md §9.2) without a second request.
|
|
#[derive(Debug, Clone, Serialize, ToSchema)]
|
|
pub struct Root {
|
|
pub id: i64,
|
|
pub kind: String,
|
|
pub audience: String,
|
|
pub path: String,
|
|
pub policy_id: i64,
|
|
pub policy_name: String,
|
|
}
|
|
|
|
#[utoipa::path(
|
|
get, path = "/api/roots", tag = "roots",
|
|
responses(
|
|
(status = 200, body = [Root]),
|
|
(status = 500, body = ErrorBody),
|
|
(status = 503, body = ErrorBody)
|
|
)
|
|
)]
|
|
pub async fn list(State(state): State<AppState>) -> Result<Json<Vec<Root>>, ApiError> {
|
|
let database = state.database().ok_or(ApiError::Unavailable)?;
|
|
let roots = sqlx::query_as!(
|
|
Root,
|
|
r#"SELECT r.id AS "id!: i64", r.kind AS "kind!: String", r.audience AS "audience!: String", r.path AS "path!: String", p.id AS "policy_id!: i64", p.name AS "policy_name!: String" FROM roots r JOIN policies p ON p.id = r.policy_id ORDER BY r.id"#
|
|
)
|
|
.fetch_all(database.pool())
|
|
.await?;
|
|
Ok(Json(roots))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::state::Upstreams;
|
|
use crate::{router, AppState};
|
|
|
|
#[tokio::test]
|
|
async fn roots_carry_their_policy_name() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let database = arr_db::Db::connect(dir.path().join("arr.db"))
|
|
.await
|
|
.expect("database");
|
|
database.migrate().await.expect("migrate");
|
|
let state = AppState::new(Upstreams::new(
|
|
"http://127.0.0.1:1".into(),
|
|
"http://127.0.0.1:1".into(),
|
|
))
|
|
.expect("state")
|
|
.with_database(database);
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
|
|
.await
|
|
.expect("bind");
|
|
let address = listener.local_addr().expect("address");
|
|
let app = router(state);
|
|
tokio::spawn(async move { axum::serve(listener, app).await.expect("serve") });
|
|
|
|
let roots: Vec<serde_json::Value> = reqwest::get(format!("http://{address}/api/roots"))
|
|
.await
|
|
.expect("roots")
|
|
.json()
|
|
.await
|
|
.expect("json");
|
|
assert_eq!(roots.len(), 4, "the seeded movie and TV roots: {roots:?}");
|
|
assert_eq!(roots[0]["audience"], "main");
|
|
assert_eq!(roots[0]["policy_name"], "Movies — main");
|
|
assert_eq!(roots[1]["audience"], "kids");
|
|
assert!(roots[1]["path"]
|
|
.as_str()
|
|
.is_some_and(|p| p.contains("kids")));
|
|
assert_eq!(roots[2]["kind"], "tv");
|
|
assert_eq!(roots[2]["policy_name"], "TV — main");
|
|
assert_eq!(roots[3]["kind"], "tv");
|
|
assert_eq!(roots[3]["policy_name"], "TV — kids");
|
|
}
|
|
}
|