Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) mod session_store;
pub(crate) mod proxy;

use crate::{clients::Auth0Client, routes, routes::axum_routes, Config, Crypter, Db, FeatureFlags};
use trillium_client::Client;

use axum::extract::{DefaultBodyLimit, FromRef};
use axum::http::{header, HeaderValue};
Expand Down Expand Up @@ -66,6 +67,7 @@ pub struct AxumAppState {
pub(crate) oauth_client: OauthClient,
pub(crate) crypter: Crypter,
pub(crate) feature_flags: FeatureFlags,
pub(crate) client: Client,
}

impl FromRef<AxumAppState> for Db {
Expand Down Expand Up @@ -104,6 +106,12 @@ impl FromRef<AxumAppState> for FeatureFlags {
}
}

impl FromRef<AxumAppState> for Client {
fn from_ref(state: &AxumAppState) -> Self {
state.client.clone()
}
}

#[derive(Handler, Debug)]
pub struct DivviupApi {
#[handler(except = init)]
Expand Down Expand Up @@ -137,6 +145,7 @@ impl DivviupApi {
oauth_client: OauthClient::new(&config.oauth_config()),
crypter: config.crypter.clone(),
feature_flags: config.feature_flags(),
client: config.client.clone(),
};
// Middleware stack in logical order (outermost first), matching the
// Trillium api() handler chain.
Expand Down
53 changes: 25 additions & 28 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod users;

use crate::{
clients::Auth0Client,
handler::{actor_required, admin_required, ReplaceMimeTypes},
handler::{actor_required, ReplaceMimeTypes},
};
pub use health_check::health_check;
use trillium::{
Expand Down Expand Up @@ -38,37 +38,15 @@ fn api_routes() -> impl Handler {
(
ReplaceMimeTypes,
api(actor_required),
router()
.get("/tasks/:task_id", api(tasks::show))
.patch("/tasks/:task_id", api(tasks::update))
.delete("/tasks/:task_id", api(tasks::delete))
.patch("/aggregators/:aggregator_id", api(aggregators::update))
.get("/aggregators/:aggregator_id", api(aggregators::show))
.delete("/aggregators/:aggregator_id", api(aggregators::delete))
.get("/aggregators", api(aggregators::index))
.post(
"/aggregators",
(api(admin_required), api(aggregators::admin_create)),
)
.any(
&[Patch, Get, Post],
"/accounts/:account_id/*",
accounts_routes(),
)
.all("/admin/*", admin::routes()),
router().all("/admin/*", admin::routes()),
)
}

fn accounts_routes() -> impl Handler {
router()
.get("/tasks", api(tasks::index))
.post("/tasks", api(tasks::create))
.post("/aggregators", api(aggregators::create))
.get("/aggregators", api(aggregators::index))
}

pub(crate) mod axum_routes {
use super::{accounts, api_tokens, collector_credentials, memberships, users};
use super::{
accounts, aggregators::axum_handler as aggregators, api_tokens, collector_credentials,
memberships, tasks::axum_handler as tasks, users,
};
use crate::handler::AxumAppState;
use axum::routing::{delete, get};

Expand Down Expand Up @@ -96,6 +74,20 @@ pub(crate) mod axum_routes {
.get(collector_credentials::show)
.patch(collector_credentials::update),
)
.route(
"/aggregators",
get(aggregators::index_shared).post(aggregators::admin_create),
)
.route(
"/aggregators/{aggregator_id}",
get(aggregators::show)
.patch(aggregators::update)
.delete(aggregators::delete),
)
.route(
"/tasks/{task_id}",
get(tasks::show).patch(tasks::update).delete(tasks::delete),
)
.nest(
"/accounts/{account_id}",
axum::Router::new()
Expand All @@ -111,6 +103,11 @@ pub(crate) mod axum_routes {
.route(
"/collector_credentials",
get(collector_credentials::index).post(collector_credentials::create),
)
.route("/tasks", get(tasks::index).post(tasks::create))
.route(
"/aggregators",
get(aggregators::index_for_account).post(aggregators::create),
),
)
}
Expand Down
3 changes: 2 additions & 1 deletion src/routes/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use axum::{
http::{header, request::Parts, StatusCode},
response::IntoResponse,
};
use httpdate::fmt_http_date;
use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait};
use trillium::Conn;
use trillium_api::FromConn;
Expand Down Expand Up @@ -49,7 +50,7 @@ where
}

pub async fn show(account: Account) -> impl IntoResponse {
let last_modified = httpdate::fmt_http_date(account.updated_at.into());
let last_modified = fmt_http_date(account.updated_at.into());
([(header::LAST_MODIFIED, last_modified)], Json(account))
}

Expand Down
197 changes: 106 additions & 91 deletions src/routes/aggregators.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use crate::{
config::FeatureFlags,
entity::{Account, Aggregator, AggregatorColumn, Aggregators, NewAggregator, UpdateAggregator},
handler::extract::extract_entity,
Db, Error, Permissions, PermissionsActor,
handler::extract::{extract_entity, Json},
Crypter, Db, Error, Permissions, PermissionsActor,
};
use axum::extract::{FromRef, FromRequestParts};
use axum::http::request::Parts;
use axum::extract::{FromRef, FromRequestParts, State};
use axum::http::{request::Parts, StatusCode};
use axum::response::IntoResponse;
use sea_orm::{
sea_query::{all, any},
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter,
};
use trillium::{Conn, Handler, Status};
use trillium_api::{FromConn, Json, State};

use trillium::Conn;
use trillium_api::FromConn;
use trillium_client::Client;
use trillium_router::RouterConnExt;
use uuid::Uuid;

Expand Down Expand Up @@ -63,96 +64,110 @@ impl Permissions for Aggregator {
}
}

pub async fn show(_: &mut Conn, aggregator: Aggregator) -> Json<Aggregator> {
Json(aggregator)
}
pub mod axum_handler {
use super::*;

pub async fn index(
conn: &mut Conn,
(db, account): (Db, Option<Account>),
) -> Result<Json<Vec<Aggregator>>, Error> {
if conn.param("account_id").is_some() && account.is_none() {
return Err(Error::NotFound);
pub async fn show(aggregator: Aggregator) -> Json<Aggregator> {
Json(aggregator)
}

Aggregators::find()
.filter(all![
match account {
Some(account) => any![
AggregatorColumn::AccountId.eq(account.id),
AggregatorColumn::AccountId.is_null()
],
None => any![AggregatorColumn::AccountId.is_null()],
},
AggregatorColumn::DeletedAt.is_null()
])
.all(&db)
.await
.map(Json)
.map_err(Error::from)
}
pub async fn index_shared(
_actor: PermissionsActor,
Comment thread
divergentdave marked this conversation as resolved.
State(db): State<Db>,
) -> Result<Json<Vec<Aggregator>>, Error> {
Ok(Json(
Aggregators::find()
.filter(all![
AggregatorColumn::AccountId.is_null(),
AggregatorColumn::DeletedAt.is_null()
])
.all(&db)
.await?,
))
}

pub async fn create(
conn: &mut Conn,
(db, account, Json(new_aggregator), State(feature_flags)): (
Db,
Account,
Json<NewAggregator>,
State<FeatureFlags>,
),
) -> Result<impl Handler, Error> {
let client = conn.take_state().unwrap();
let crypter = conn.take_state().unwrap();
pub async fn index_for_account(
account: Account,
State(db): State<Db>,
) -> Result<Json<Vec<Aggregator>>, Error> {
Ok(Json(
Aggregators::find()
.filter(all![
any![
AggregatorColumn::AccountId.eq(account.id),
AggregatorColumn::AccountId.is_null()
],
AggregatorColumn::DeletedAt.is_null()
])
.all(&db)
.await?,
))
}

new_aggregator
.build(
Some(&account),
client,
&crypter,
feature_flags.ssrf_validation_enabled,
)
.await?
.insert(&db)
.await
.map_err(Error::from)
.map(|agg| (Json(agg), Status::Created))
}
pub async fn create(
account: Account,
State(db): State<Db>,
State(client): State<Client>,
State(crypter): State<Crypter>,
State(feature_flags): State<FeatureFlags>,
Json(new_aggregator): Json<NewAggregator>,
) -> Result<impl IntoResponse, Error> {
let aggregator = new_aggregator
.build(
Some(&account),
client,
&crypter,
feature_flags.ssrf_validation_enabled,
)
.await?
.insert(&db)
.await?;
Ok((StatusCode::CREATED, Json(aggregator)))
}

pub async fn delete(_: &mut Conn, (db, aggregator): (Db, Aggregator)) -> Result<Status, Error> {
aggregator.tombstone().update(&db).await?;
Ok(Status::NoContent)
}
pub async fn update(
aggregator: Aggregator,
State(db): State<Db>,
State(client): State<Client>,
State(crypter): State<Crypter>,
Json(update_aggregator): Json<UpdateAggregator>,
) -> Result<Json<Aggregator>, Error> {
Ok(Json(
update_aggregator
.build(aggregator, client, &crypter)
.await?
.update(&db)
.await?,
))
}

pub async fn update(
conn: &mut Conn,
(db, aggregator, Json(update_aggregator)): (Db, Aggregator, Json<UpdateAggregator>),
) -> Result<Json<Aggregator>, Error> {
let client = conn.take_state().unwrap();
let crypter = conn.state().unwrap();
update_aggregator
.build(aggregator, client, crypter)
.await?
.update(&db)
.await
.map_err(Error::from)
.map(Json)
}
pub async fn delete(aggregator: Aggregator, State(db): State<Db>) -> Result<StatusCode, Error> {
aggregator.tombstone().update(&db).await?;
Ok(StatusCode::NO_CONTENT)
}

pub async fn admin_create(
conn: &mut Conn,
(db, Json(new_aggregator), State(feature_flags)): (
Db,
Json<NewAggregator>,
State<FeatureFlags>,
),
) -> Result<impl Handler, Error> {
let client = conn.take_state().unwrap();
let crypter = conn.state().unwrap();
new_aggregator
.build(None, client, crypter, feature_flags.ssrf_validation_enabled)
.await?
.insert(&db)
.await
.map_err(Error::from)
.map(|agg| (Json(agg), Status::Created))
pub async fn admin_create(
actor: PermissionsActor,
State(db): State<Db>,
State(client): State<Client>,
State(crypter): State<Crypter>,
State(feature_flags): State<FeatureFlags>,
Json(new_aggregator): Json<NewAggregator>,
) -> Result<impl IntoResponse, Error> {
if !actor.is_admin() {
return Err(Error::NotFound);
}
Comment on lines +157 to +159
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm following the model above of inline checking is_admin, but in 7C I am going to create a thin AdminPermissionsActor newtype extractor that wraps PermissionsActor, checks is_admin()and returns Error::NotFound on failure. The handlers then will just extract AdminPermissionsActor instead of PermissionsActor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually decided that was brittle while mucking with it, and the correct way to do this was a route_layer, which you can see here: https://github.com/divviup/divviup-api/pull/2247/changes#diff-2f935612004a95b0b20ea32f896554a8d594d2fd9faa19319e37470a9ff34689R56


let aggregator = new_aggregator
.build(
None,
client,
&crypter,
feature_flags.ssrf_validation_enabled,
)
.await?
.insert(&db)
.await?;
Ok((StatusCode::CREATED, Json(aggregator)))
}
}
3 changes: 2 additions & 1 deletion src/routes/collector_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use axum::{
http::{header, request::Parts, StatusCode},
response::IntoResponse,
};
use httpdate::fmt_http_date;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, ModelTrait, QueryFilter};
use trillium::Conn;
use trillium_api::FromConn;
Expand Down Expand Up @@ -68,7 +69,7 @@ pub async fn index(
}

pub async fn show(collector_credential: CollectorCredential) -> impl IntoResponse {
let last_modified = httpdate::fmt_http_date(collector_credential.updated_at.into());
let last_modified = fmt_http_date(collector_credential.updated_at.into());
(
[(header::LAST_MODIFIED, last_modified)],
Json(collector_credential),
Expand Down
Loading
Loading