Files
client/crates/forage-server/src/routes/platform.rs
2026-03-07 19:46:19 +01:00

167 lines
5.5 KiB
Rust

use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Response};
use axum::routing::get;
use axum::Router;
use forage_core::platform::validate_slug;
use forage_core::session::CachedOrg;
use minijinja::context;
use super::error_page;
use crate::auth::Session;
use crate::state::AppState;
pub fn router() -> Router<AppState> {
Router::new()
.route("/orgs/{org}/projects", get(projects_list))
.route("/orgs/{org}/projects/{project}", get(project_detail))
.route("/orgs/{org}/usage", get(usage))
}
fn orgs_context(orgs: &[CachedOrg]) -> Vec<minijinja::Value> {
orgs.iter()
.map(|o| context! { name => o.name, role => o.role })
.collect()
}
async fn projects_list(
State(state): State<AppState>,
session: Session,
Path(org): Path<String>,
) -> Result<Response, Response> {
if !validate_slug(&org) {
return Err(error_page(&state, StatusCode::BAD_REQUEST, "Invalid request", "Invalid organisation name."));
}
let orgs = &session.user.orgs;
if !orgs.iter().any(|o| o.name == org) {
return Err(error_page(&state, StatusCode::FORBIDDEN, "Access denied", "You don't have access to this organisation."));
}
let projects = state
.platform_client
.list_projects(&session.access_token, &org)
.await
.unwrap_or_default();
let html = state
.templates
.render(
"pages/projects.html.jinja",
context! {
title => format!("{org} - Projects - Forage"),
description => format!("Projects in {org}"),
user => context! { username => session.user.username },
csrf_token => &session.csrf_token,
current_org => &org,
orgs => orgs_context(orgs),
org_name => &org,
projects => projects,
},
)
.map_err(|e| {
tracing::error!("template error: {e:#}");
error_page(&state, StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong", "Please try again.")
})?;
Ok(Html(html).into_response())
}
async fn project_detail(
State(state): State<AppState>,
session: Session,
Path((org, project)): Path<(String, String)>,
) -> Result<Response, Response> {
if !validate_slug(&org) || !validate_slug(&project) {
return Err(error_page(&state, StatusCode::BAD_REQUEST, "Invalid request", "Invalid organisation or project name."));
}
let orgs = &session.user.orgs;
if !orgs.iter().any(|o| o.name == org) {
return Err(error_page(&state, StatusCode::FORBIDDEN, "Access denied", "You don't have access to this organisation."));
}
let artifacts = state
.platform_client
.list_artifacts(&session.access_token, &org, &project)
.await
.unwrap_or_default();
let html = state
.templates
.render(
"pages/project_detail.html.jinja",
context! {
title => format!("{project} - {org} - Forage"),
description => format!("Project {project} in {org}"),
user => context! { username => session.user.username },
csrf_token => &session.csrf_token,
current_org => &org,
orgs => orgs_context(orgs),
org_name => &org,
project_name => &project,
artifacts => artifacts.iter().map(|a| context! {
slug => a.slug,
title => a.context.title,
description => a.context.description,
created_at => a.created_at,
}).collect::<Vec<_>>(),
},
)
.map_err(|e| {
tracing::error!("template error: {e:#}");
error_page(&state, StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong", "Please try again.")
})?;
Ok(Html(html).into_response())
}
async fn usage(
State(state): State<AppState>,
session: Session,
Path(org): Path<String>,
) -> Result<Response, Response> {
if !validate_slug(&org) {
return Err(error_page(&state, StatusCode::BAD_REQUEST, "Invalid request", "Invalid organisation name."));
}
let orgs = &session.user.orgs;
let current_org_data = orgs.iter().find(|o| o.name == org);
let current_org_data = match current_org_data {
Some(o) => o,
None => return Err(error_page(&state, StatusCode::FORBIDDEN, "Access denied", "You don't have access to this organisation.")),
};
let projects = state
.platform_client
.list_projects(&session.access_token, &org)
.await
.unwrap_or_default();
let html = state
.templates
.render(
"pages/usage.html.jinja",
context! {
title => format!("Usage - {org} - Forage"),
description => format!("Usage and plan for {org}"),
user => context! { username => session.user.username },
csrf_token => &session.csrf_token,
current_org => &org,
orgs => orgs_context(orgs),
org_name => &org,
role => &current_org_data.role,
project_count => projects.len(),
},
)
.map_err(|e| {
tracing::error!("template error: {e:#}");
error_page(&state, StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong", "Please try again.")
})?;
Ok(Html(html).into_response())
}