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 { 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 { orgs.iter() .map(|o| context! { name => o.name, role => o.role }) .collect() } async fn projects_list( State(state): State, session: Session, Path(org): Path, ) -> Result { 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, session: Session, Path((org, project)): Path<(String, String)>, ) -> Result { 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::>(), }, ) .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, session: Session, Path(org): Path, ) -> Result { 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 => ¤t_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()) }