diff --git a/Cargo.toml b/Cargo.toml index a366162d..e10aab0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,9 @@ default = [] api-mocks = ["dep:trillium-testing"] integration-testing = [] # Enables a non-production axum middleware that reads an `X-Integration-Testing-User` -# header and injects the decoded [`User`] into request extensions. Strictly for -# use by the test harness (`test-support`); never enable in deployed builds. -# TODO: fold into `integration-testing` once Trillium is removed — the blanket -# User injection in the Trillium api() handler currently makes them incompatible. +# header and injects the decoded [`User`] into request extensions. Used by +# `test-support` to impersonate specific users in tests; never enable in deployed +# builds. TODO(Part 9): fold into `integration-testing` when test-support is rewritten. test-header-injection = [] otlp-trace = ["opentelemetry/trace", "opentelemetry-otlp", "opentelemetry_sdk/trace", "trillium-opentelemetry/trace"] diff --git a/src/entity/queue.rs b/src/entity/queue.rs index 8b2f6033..e4afca0a 100644 --- a/src/entity/queue.rs +++ b/src/entity/queue.rs @@ -34,10 +34,13 @@ pub struct Model { #[sea_orm(rs_type = "i32", db_type = "Integer")] pub enum JobStatus { #[sea_orm(num_value = 0)] + #[serde(alias = "pending")] Pending, #[sea_orm(num_value = 1)] + #[serde(alias = "success")] Success, #[sea_orm(num_value = 2)] + #[serde(alias = "failed")] Failed, } diff --git a/src/handler.rs b/src/handler.rs index b6b5909c..1f1a3720 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,7 +6,6 @@ pub(crate) mod custom_mime_types; pub(crate) mod error; pub(crate) mod extract; pub(crate) mod logger; -pub(crate) mod misc; pub(crate) mod oauth2; pub(crate) mod opentelemetry; pub(crate) mod origin_router; @@ -19,41 +18,32 @@ use trillium_client::Client; use axum::extract::{DefaultBodyLimit, FromRef}; use axum::http::{header, HeaderValue}; -use cors::{axum_cors_layer, cors_headers}; +use cors::axum_cors_layer; use error::ErrorHandler; use logger::logger; use oauth2::OauthClient; use proxy::AxumProxy; -use session_store::{axum_session_layer, SessionStore}; +use session_store::axum_session_layer; use std::{borrow::Cow, net::Ipv6Addr, net::SocketAddr, sync::Arc}; use tokio::net::TcpListener; use tower::ServiceBuilder; use tower_http::compression::CompressionLayer; use tower_http::set_header::SetResponseHeaderLayer; use tower_http::trace::TraceLayer; -use trillium::{state, Handler, Info}; -use trillium_caching_headers::{ - cache_control, caching_headers, - CacheControlDirective::{MustRevalidate, Private}, -}; -use trillium_compression::compression; +use trillium::{Handler, Info}; +use trillium_caching_headers::caching_headers; use trillium_conn_id::conn_id; -use trillium_cookies::cookies; use trillium_forwarding::Forwarding; use trillium_macros::Handler; -use trillium_sessions::sessions; - -pub(crate) use custom_mime_types::ReplaceMimeTypes; -pub(crate) use misc::*; pub use error::Error; pub use origin_router::origin_router; use self::opentelemetry::opentelemetry; -#[cfg(feature = "otlp-trace")] +#[cfg(all(assets, feature = "otlp-trace"))] use trillium_opentelemetry::global::instrument_handler; -#[cfg(not(feature = "otlp-trace"))] +#[cfg(all(assets, not(feature = "otlp-trace")))] fn instrument_handler(handler: impl Handler) -> impl Handler { handler } @@ -160,10 +150,13 @@ impl DivviupApi { .layer(axum_cors_layer(&config)) .layer(axum_session_layer(db.clone(), &config.session_secrets)); - #[cfg(feature = "test-header-injection")] + #[cfg(feature = "integration-testing")] let middleware = middleware.layer(axum::middleware::from_fn(inject_integration_testing_user)); + #[cfg(feature = "test-header-injection")] + let middleware = middleware.layer(axum::middleware::from_fn(inject_test_header_user)); + let axum_router = axum::Router::new() // Temporary test endpoint to verify the proxy bridge works. // TODO: Remove once enough routes have migrated to make it redundant. @@ -175,7 +168,7 @@ impl DivviupApi { .route("/login", axum::routing::get(oauth2::redirect)) .route("/logout", axum::routing::get(oauth2::logout)) .route("/callback", axum::routing::get(oauth2::callback)) - .nest("/api", axum_routes::api_router()) + .nest("/api", axum_routes::api_router(&axum_state)) .layer(middleware) .with_state(axum_state); let axum_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 0)) @@ -203,7 +196,8 @@ impl DivviupApi { logger(), #[cfg(assets)] instrument_handler(assets::static_assets(&config)), - instrument_handler(api(&db, &config, auth0_client)), + #[cfg(feature = "test-header-injection")] + inject_test_user_trillium, proxy, ErrorHandler, )), @@ -237,23 +231,13 @@ impl AsRef for DivviupApi { } } -/// Trillium-side test shim that bridges user injection between the Trillium -/// and Axum worlds during the migration: -/// -/// - If the `X-Integration-Testing-User` header is present (set by -/// `TestExt::with_user`), deserialize the user into connection state so -/// `actor_required` passes. -/// - If a `User` was injected via `.with_state()` (legacy test pattern), -/// serialize it into the header so the proxy forwards it to Axum. +/// Trillium-side test shim: if a `User` was injected via `.with_state()` +/// (legacy test pattern), serialize it into the `X-Integration-Testing-User` +/// header so the proxy forwards it to Axum. +// TODO: remove in Part 8 (Trillium removal) #[cfg(feature = "test-header-injection")] async fn inject_test_user_trillium(mut conn: trillium::Conn) -> trillium::Conn { - if let Some(user) = conn - .request_headers() - .get_str("x-integration-testing-user") - .and_then(|v| serde_json::from_str::(v).ok()) - { - conn.insert_state(user); - } else if let Some(json) = conn + if let Some(json) = conn .state::() .and_then(|u| serde_json::to_string(u).ok()) { @@ -263,15 +247,32 @@ async fn inject_test_user_trillium(mut conn: trillium::Conn) -> trillium::Conn { conn } -/// Axum-side test shim: if the request carries an `X-Integration-Testing-User` -/// header with a JSON-encoded [`crate::User`], place the user in request -/// extensions so [`crate::User`]'s extractor picks it up without a real -/// session. +/// Axum middleware that unconditionally injects an admin +/// [`User`](crate::User) into every request. This is the Axum equivalent of +/// the Trillium `state(User::for_integration_testing())` that was in the old +/// `api()` handler chain. +/// +/// Only compiled under `--features integration-testing` (enabled by +/// `compose.dev.override.yaml`). Never compiled into deployed builds. +#[cfg(feature = "integration-testing")] +async fn inject_integration_testing_user( + mut request: axum::extract::Request, + next: axum::middleware::Next, +) -> axum::response::Response { + request + .extensions_mut() + .insert(crate::User::for_integration_testing()); + next.run(request).await +} + +/// Axum middleware that reads an `X-Integration-Testing-User` header and +/// injects the decoded [`User`](crate::User) into request extensions. +/// Used by `test-support` to impersonate specific users in tests. /// /// Only compiled under `--features test-header-injection` (enabled by /// `test-support`). Never compiled into deployed builds. #[cfg(feature = "test-header-injection")] -async fn inject_integration_testing_user( +async fn inject_test_header_user( mut request: axum::extract::Request, next: axum::middleware::Next, ) -> axum::response::Response { @@ -296,32 +297,3 @@ impl NamedHandler { Self(handler, name.into()) } } - -fn api(db: &Db, config: &Config, auth0_client: Auth0Client) -> impl Handler { - NamedHandler::new( - "api", - ( - instrument_handler(compression()), - #[cfg(feature = "integration-testing")] - state(crate::User::for_integration_testing()), - #[cfg(feature = "test-header-injection")] - inject_test_user_trillium, - instrument_handler(cookies()), - instrument_handler( - sessions( - SessionStore::new(db.clone()), - &config.session_secrets.current, - ) - .with_cookie_name(session_store::SESSION_COOKIE_NAME) - .with_older_secrets(&config.session_secrets.older), - ), - state(config.client.clone()), - state(config.crypter.clone()), - state(config.feature_flags()), - instrument_handler(cors_headers(config)), - cache_control([Private, MustRevalidate]), - db.clone(), - instrument_handler(routes(auth0_client)), - ), - ) -} diff --git a/src/handler/cors.rs b/src/handler/cors.rs index b236bdf0..ee1f78a4 100644 --- a/src/handler/cors.rs +++ b/src/handler/cors.rs @@ -53,6 +53,7 @@ impl CorsHeaders { } } +#[expect(dead_code)] // TODO: remove in Part 8 (Trillium removal) pub fn cors_headers(config: &Config) -> impl Handler { CorsHeaders::new(config) } diff --git a/src/handler/custom_mime_types.rs b/src/handler/custom_mime_types.rs index f5e86469..a144e338 100644 --- a/src/handler/custom_mime_types.rs +++ b/src/handler/custom_mime_types.rs @@ -12,9 +12,9 @@ use trillium::{ }; pub const DIVVIUP_API_MEDIA_TYPE: &str = "application/vnd.divviup+json;version=0.1"; -#[cfg_attr(not(test), expect(dead_code))] // Used once ReplaceMimeTypesLayer is wired; see TODO in routes.rs. const APPLICATION_JSON: header::HeaderValue = header::HeaderValue::from_static("application/json"); +#[expect(dead_code)] // TODO: remove in Part 8 (Trillium removal) pub struct ReplaceMimeTypes; #[trillium::async_trait] @@ -50,7 +50,6 @@ impl Handler for ReplaceMimeTypes { /// This replicates the Trillium [`ReplaceMimeTypes`] handler for Axum routes: /// requests with the custom content type (or no content type) have their headers /// normalized to `application/json`; responses get the custom content type set. -#[cfg_attr(not(test), expect(dead_code))] // Wired once the Trillium proxy is removed; see TODO in routes.rs. #[derive(Clone, Debug)] pub struct ReplaceMimeTypesLayer; @@ -62,7 +61,6 @@ impl Layer for ReplaceMimeTypesLayer { } /// Tower [`Service`] produced by [`ReplaceMimeTypesLayer`]. -#[cfg_attr(not(test), expect(dead_code))] // Constructed by ReplaceMimeTypesLayer. #[derive(Clone, Debug)] pub struct ReplaceMimeTypesService { inner: S, diff --git a/src/handler/misc.rs b/src/handler/misc.rs deleted file mode 100644 index e3130f3a..00000000 --- a/src/handler/misc.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::PermissionsActor; -use trillium::{Conn, Handler, Status}; -use trillium_api::Halt; - -pub async fn actor_required(_: &mut Conn, actor: Option) -> impl Handler { - if actor.is_none() { - Some((Status::Forbidden, Halt)) - } else { - None - } -} - -pub async fn admin_required(_: &mut Conn, actor: Option) -> impl Handler { - if matches!(actor, Some(actor) if actor.is_admin()) { - None - } else { - // we return not found instead of forbidden so as to not - // reveal what admin endpoints exist - Some((Status::NotFound, Halt)) - } -} diff --git a/src/handler/session_store.rs b/src/handler/session_store.rs index ab046530..341e7690 100644 --- a/src/handler/session_store.rs +++ b/src/handler/session_store.rs @@ -25,11 +25,13 @@ use tower_sessions::{ // note will no longer apply. pub const SESSION_COOKIE_NAME: &str = "divviup.sid"; +#[allow(dead_code)] // TODO: remove in Part 8 (Trillium removal) #[derive(Debug, Clone)] pub struct SessionStore { db: Db, } +#[allow(dead_code)] // TODO: remove in Part 8 (Trillium removal) impl SessionStore { pub fn new(db: Db) -> Self { Self { db } diff --git a/src/lib.rs b/src/lib.rs index 23ebf4f9..3891dc94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,9 +27,8 @@ pub use crypter::Crypter; pub use db::Db; pub use handler::{custom_mime_types::DIVVIUP_API_MEDIA_TYPE, AxumAppState, DivviupApi, Error}; pub use opentelemetry; -pub use permissions::{Permissions, PermissionsActor}; +pub use permissions::{AdminPermissionsActor, Permissions, PermissionsActor}; pub use queue::Queue; -pub use routes::routes; use serde::{Deserialize, Deserializer}; pub use user::{User, USER_SESSION_KEY}; diff --git a/src/permissions.rs b/src/permissions.rs index 02e3f0d5..00955a6b 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -139,6 +139,30 @@ where } } +/// A [`PermissionsActor`] that has been verified to be an admin. +/// +/// Use this as an Axum extractor on admin-only routes. Returns +/// [`Error::NotFound`] for non-admins, hiding the endpoint's existence. +#[derive(Debug, Clone)] +pub struct AdminPermissionsActor(pub PermissionsActor); + +impl FromRequestParts for AdminPermissionsActor +where + Db: FromRef, + S: Send + Sync, +{ + type Rejection = Error; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let actor = PermissionsActor::from_request_parts(parts, state).await?; + if actor.is_admin() { + Ok(Self(actor)) + } else { + Err(Error::NotFound) + } + } +} + pub trait Permissions { fn allow_read(&self, actor: &PermissionsActor) -> bool { self.allow_write(actor) diff --git a/src/routes.rs b/src/routes.rs index 2995220c..1c222128 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -8,58 +8,18 @@ mod memberships; mod tasks; mod users; -use crate::{ - clients::Auth0Client, - handler::{actor_required, ReplaceMimeTypes}, -}; pub use health_check::health_check; -use trillium::{ - state, Handler, - Method::{Delete, Get, Patch, Post}, -}; -use trillium_api::api; -use trillium_router::router; - -pub fn routes(auth0_client: Auth0Client) -> impl Handler { - router().any( - &[Get, Post, Delete, Patch], - "/api/*", - (state(auth0_client), api_routes()), - ) -} - -fn api_routes() -> impl Handler { - // ReplaceMimeTypes stays in the outer chain because the trillium-router's - // before_send does not replay path adjustments, so handlers inside route - // entries never get their before_send called for wildcard matches. Keeping - // it here means it also transforms headers for requests that fall through - // to the Axum proxy, but that's harmless: Trillium already validated them, - // and the Axum side accepts pre-transformed application/json. - ( - ReplaceMimeTypes, - api(actor_required), - router().all("/admin/*", admin::routes()), - ) -} pub(crate) mod axum_routes { use super::{ - accounts, aggregators::axum_handler as aggregators, api_tokens, collector_credentials, - memberships, tasks::axum_handler as tasks, users, + accounts, admin::axum_handler as admin, aggregators::axum_handler as aggregators, + api_tokens, collector_credentials, memberships, tasks::axum_handler as tasks, users, }; - use crate::handler::AxumAppState; + use crate::handler::{custom_mime_types::ReplaceMimeTypesLayer, AxumAppState}; use axum::routing::{delete, get}; /// Axum sub-router for `/api` routes. - /// - /// During the proxy migration, Trillium's `ReplaceMimeTypes` handler in - /// the outer chain already validates/normalizes request headers and sets - /// the response Content-Type via `before_send`. So we do NOT apply - /// `ReplaceMimeTypesLayer` here — it would reject the already-normalized - /// `application/json` Content-Type that Trillium forwarded. - /// - /// TODO: wire `ReplaceMimeTypesLayer` once the Trillium proxy is removed. - pub fn api_router() -> axum::Router { + pub fn api_router(state: &AxumAppState) -> axum::Router { axum::Router::new() .route("/users/me", get(users::show)) .route("/accounts", get(accounts::index).post(accounts::create)) @@ -88,6 +48,16 @@ pub(crate) mod axum_routes { "/tasks/{task_id}", get(tasks::show).patch(tasks::update).delete(tasks::delete), ) + .nest( + "/admin", + axum::Router::new() + .route("/queue", get(admin::index)) + .route("/queue/{job_id}", get(admin::show).delete(admin::delete)) + .route_layer(axum::middleware::from_fn_with_state( + state.clone(), + admin::require_admin, + )), + ) .nest( "/accounts/{account_id}", axum::Router::new() @@ -110,5 +80,6 @@ pub(crate) mod axum_routes { get(aggregators::index_for_account).post(aggregators::create), ), ) + .layer(ReplaceMimeTypesLayer) } } diff --git a/src/routes/accounts.rs b/src/routes/accounts.rs index a1eb675d..e9016245 100644 --- a/src/routes/accounts.rs +++ b/src/routes/accounts.rs @@ -9,11 +9,7 @@ use axum::{ response::IntoResponse, }; use httpdate::fmt_http_date; -use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait}; -use trillium::Conn; -use trillium_api::FromConn; -use trillium_router::RouterConnExt; -use uuid::Uuid; +use sea_orm::{ActiveModelTrait, TransactionTrait}; impl Permissions for Account { fn allow_write(&self, actor: &PermissionsActor) -> bool { @@ -21,23 +17,6 @@ impl Permissions for Account { } } -#[trillium::async_trait] -impl FromConn for Account { - async fn from_conn(conn: &mut Conn) -> Option { - let actor = PermissionsActor::from_conn(conn).await?; - let db: &Db = conn.state()?; - let account_id = conn.param("account_id")?.parse::().ok()?; - match Accounts::find_by_id(account_id).one(db).await { - Ok(Some(account)) => actor.if_allowed(conn.method(), account), - Ok(None) => None, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } - } -} - impl FromRequestParts for Account where Db: FromRef, diff --git a/src/routes/admin.rs b/src/routes/admin.rs index ea28ed8a..ba2671e5 100644 --- a/src/routes/admin.rs +++ b/src/routes/admin.rs @@ -1,77 +1,87 @@ use crate::{ entity::queue::{self, Column, Entity, JobStatus, Model}, - handler::{admin_required, Error}, - Db, + handler::extract::Json, + Db, Error, PermissionsActor, }; -use querystrong::QueryStrong; +use axum::extract::{FromRef, FromRequestParts, Path, Query, Request, State}; +use axum::http::{header, request::Parts, StatusCode}; +use axum::middleware::Next; +use axum::response::IntoResponse; +use httpdate::fmt_http_date; use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryOrder, QuerySelect}; -use trillium::{async_trait, Conn, Handler, Status}; -use trillium_api::{api, FromConn, Json}; -use trillium_caching_headers::CachingHeadersExt; -use trillium_router::{router, RouterConnExt}; +use serde::Deserialize; +use std::collections::HashMap; use uuid::Uuid; -pub fn routes() -> impl Handler { - ( - api(admin_required), - router() - .get("/queue", api(index)) - .get("/queue/:job_id", api(show)) - .delete("/queue/:job_id", api(delete)), - ) -} +impl FromRequestParts for queue::Model +where + Db: FromRef, + S: Send + Sync, +{ + type Rejection = Error; + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let Path(params) = Path::>::from_request_parts(parts, state) + .await + .map_err(|_| Error::NotFound)?; -#[async_trait] -impl FromConn for queue::Model { - async fn from_conn(conn: &mut Conn) -> Option { - let db = Db::from_conn(conn).await?; - let id: Uuid = conn.param("job_id")?.parse().ok()?; + let id = params + .get("job_id") + .and_then(|s| s.parse::().ok()) + .ok_or(Error::NotFound)?; - match Entity::find_by_id(id).one(&db).await { - Ok(job) => job, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } + let db = Db::from_ref(state); + Entity::find_by_id(id) + .one(&db) + .await? + .ok_or(Error::NotFound) } } -async fn index(conn: &mut Conn, db: Db) -> Result>, Error> { - let params = QueryStrong::parse(conn.querystring()); - let mut find = Entity::find(); - let query = QuerySelect::query(&mut find); - match params.get_str("status") { - Some("pending") => { - query.cond_where(Column::Status.eq(JobStatus::Pending)); - } +#[derive(Deserialize)] +pub struct IndexParams { + status: Option, +} - Some("success") => { - query.cond_where(Column::Status.eq(JobStatus::Success)); - } +pub mod axum_handler { + use super::*; - Some("failed") => { - query.cond_where(Column::Status.eq(JobStatus::Failed)); + pub async fn require_admin( + actor: PermissionsActor, + request: Request, + next: Next, + ) -> axum::response::Response { + if actor.is_admin() { + next.run(request).await + } else { + StatusCode::NOT_FOUND.into_response() } - - _ => {} } - find.order_by_desc(Column::UpdatedAt) - .limit(100) - .all(&db) - .await - .map(Json) - .map_err(Error::from) -} + pub async fn index( + State(db): State, + Query(params): Query, + ) -> Result>, Error> { + let mut find = Entity::find(); + if let Some(status) = params.status { + let query = QuerySelect::query(&mut find); + query.cond_where(Column::Status.eq(status)); + } -async fn show(conn: &mut Conn, queue_job: Model) -> Json { - conn.set_last_modified(queue_job.updated_at.into()); + Ok(Json( + find.order_by_desc(Column::UpdatedAt) + .limit(100) + .all(&db) + .await?, + )) + } - Json(queue_job) -} + pub async fn show(queue_job: Model) -> impl IntoResponse { + let last_modified = fmt_http_date(queue_job.updated_at.into()); + ([(header::LAST_MODIFIED, last_modified)], Json(queue_job)) + } -async fn delete(_: &mut Conn, (queue_job, db): (Model, Db)) -> Result { - queue_job.delete(&db).await?; - Ok(Status::NoContent) + pub async fn delete(queue_job: Model, State(db): State) -> Result { + queue_job.delete(&db).await?; + Ok(StatusCode::NO_CONTENT) + } } diff --git a/src/routes/aggregators.rs b/src/routes/aggregators.rs index 899d7160..61f869c1 100644 --- a/src/routes/aggregators.rs +++ b/src/routes/aggregators.rs @@ -2,7 +2,7 @@ use crate::{ config::FeatureFlags, entity::{Account, Aggregator, AggregatorColumn, Aggregators, NewAggregator, UpdateAggregator}, handler::extract::{extract_entity, Json}, - Crypter, Db, Error, Permissions, PermissionsActor, + AdminPermissionsActor, Crypter, Db, Error, Permissions, PermissionsActor, }; use axum::extract::{FromRef, FromRequestParts, State}; use axum::http::{request::Parts, StatusCode}; @@ -11,29 +11,7 @@ use sea_orm::{ sea_query::{all, any}, ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, }; -use trillium::Conn; -use trillium_api::FromConn; use trillium_client::Client; -use trillium_router::RouterConnExt; -use uuid::Uuid; - -#[trillium::async_trait] -impl FromConn for Aggregator { - async fn from_conn(conn: &mut Conn) -> Option { - let actor = PermissionsActor::from_conn(conn).await?; - let db: &Db = conn.state()?; - let id = conn.param("aggregator_id")?.parse::().ok()?; - let aggregator = Aggregators::find_by_id(id).one(db).await; - match aggregator { - Ok(Some(aggregator)) => actor.if_allowed(conn.method(), aggregator), - Ok(None) => None, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } - } -} impl FromRequestParts for Aggregator where @@ -147,17 +125,13 @@ pub mod axum_handler { } pub async fn admin_create( - actor: PermissionsActor, + _admin: AdminPermissionsActor, State(db): State, State(client): State, State(crypter): State, State(feature_flags): State, Json(new_aggregator): Json, ) -> Result { - if !actor.is_admin() { - return Err(Error::NotFound); - } - let aggregator = new_aggregator .build( None, diff --git a/src/routes/api_tokens.rs b/src/routes/api_tokens.rs index cd97e070..7cff60b4 100644 --- a/src/routes/api_tokens.rs +++ b/src/routes/api_tokens.rs @@ -8,28 +8,7 @@ use axum::{ http::{request::Parts, StatusCode}, response::IntoResponse, }; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, QueryOrder}; -use trillium::Conn; -use trillium_api::FromConn; -use trillium_router::RouterConnExt; -use uuid::Uuid; - -#[trillium::async_trait] -impl FromConn for ApiToken { - async fn from_conn(conn: &mut Conn) -> Option { - let actor = PermissionsActor::from_conn(conn).await?; - let id = conn.param("api_token_id")?.parse::().ok()?; - let db: &Db = conn.state()?; - match ApiTokens::find_by_id(id).one(db).await { - Ok(Some(api_token)) => actor.if_allowed(conn.method(), api_token), - Ok(None) => None, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } - } -} +use sea_orm::{ActiveModelTrait, ColumnTrait, ModelTrait, QueryFilter, QueryOrder}; impl FromRequestParts for ApiToken where diff --git a/src/routes/collector_credentials.rs b/src/routes/collector_credentials.rs index d4c24fcb..7ae87a30 100644 --- a/src/routes/collector_credentials.rs +++ b/src/routes/collector_credentials.rs @@ -12,31 +12,7 @@ use axum::{ response::IntoResponse, }; use httpdate::fmt_http_date; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter}; -use trillium::Conn; -use trillium_api::FromConn; -use trillium_router::RouterConnExt; -use uuid::Uuid; - -#[trillium::async_trait] -impl FromConn for CollectorCredential { - async fn from_conn(conn: &mut Conn) -> Option { - let actor = PermissionsActor::from_conn(conn).await?; - let id = conn - .param("collector_credential_id")? - .parse::() - .ok()?; - let db: &Db = conn.state()?; - match CollectorCredentials::find_by_id(id).one(db).await { - Ok(Some(collector_credential)) => actor.if_allowed(conn.method(), collector_credential), - Ok(None) => None, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } - } -} +use sea_orm::{ActiveModelTrait, ColumnTrait, ModelTrait, QueryFilter}; impl FromRequestParts for CollectorCredential where diff --git a/src/routes/tasks.rs b/src/routes/tasks.rs index b8262036..121da190 100644 --- a/src/routes/tasks.rs +++ b/src/routes/tasks.rs @@ -19,10 +19,7 @@ use std::time::Duration; use time::OffsetDateTime; use tokio::join; use tracing::warn; -use trillium::Conn; -use trillium_api::FromConn; use trillium_client::Client; -use trillium_router::RouterConnExt; impl Permissions for Task { fn allow_write(&self, actor: &PermissionsActor) -> bool { @@ -30,24 +27,6 @@ impl Permissions for Task { } } -#[trillium::async_trait] -impl FromConn for Task { - async fn from_conn(conn: &mut Conn) -> Option { - let actor = PermissionsActor::from_conn(conn).await?; - let db: &Db = conn.state()?; - let id = conn.param("task_id")?; - - match Tasks::find_by_id(id).one(db).await { - Ok(Some(task)) => actor.if_allowed(conn.method(), task), - Ok(None) => None, - Err(error) => { - conn.insert_state(Error::from(error)); - None - } - } - } -} - impl FromRequestParts for Task where Db: FromRef,