//! Diffusion's views (#150, docs/diffusion.md): a repository's Diffusion //! tab (the consumers that pin it, and what it follows itself), and Admin › //! Diffusion across the hub. Each consumer shows green, amber, red, grey //! or up to date (`diffusion::Colour`). use super::{filters, lookup, Error, Page, RepoShell, Shell}; use crate::auth::Viewer; use crate::db::repos::RepoRow; use crate::diffusion::Consumer; use crate::Shared; use askama::Template; use axum::extract::{Path, State}; #[derive(Template)] #[template(path = "diffusion.html")] pub struct DiffusionPage { shell: Shell, repo: RepoShell, used_by: Vec, follows: Vec<(RepoRow, Consumer)>, /// This repository has no checks of its own: its bumps are amber. unverified: bool, /// The ripples backdrop's data (docs/backgrounds.md §2): this page's /// own repositories and pill tones, JSON safe in a script. backdrop: String, } /// The ripples' data: the repository, the upstreams it takes from (each in /// its upstream half's tone) and the consumers it diffuses to (each in its /// own half's tone), as `{scene, repo, upstreams: [[name, tone]], /// consumers: [[name, tone]]}`. fn ripples_json(slug: &str, used_by: &[Consumer], follows: &[(RepoRow, Consumer)]) -> String { let tone = |class: &str| class.trim_start_matches("df-").to_string(); let upstreams: Vec<(String, String)> = follows.iter().map(|(up, c)| (format!("{}/{}", up.owner, up.name), tone(c.upstream.class))).collect(); let consumers: Vec<(String, String)> = used_by.iter().map(|c| (format!("{}/{}", c.owner, c.name), tone(c.downstream.class))).collect(); serde_json::json!({ "scene": "ripples", "repo": slug, "upstreams": upstreams, "consumers": consumers }) .to_string() .replace('<', "\\u003c") } /// `GET /{owner}/{name}/diffusion`. pub async fn page( State(app): State, viewer: Viewer, Path((owner, name)): Path<(String, String)>, ) -> Result, Error> { let shell = Shell::new(&app, &format!("/{owner}/{name}/diffusion"), &viewer); let (_, row, role) = lookup(&app, &shell, &viewer, &owner, &name).await?; let used_by = crate::diffusion::consumers(&app, &row, viewer.account()).await.unwrap_or_default(); let follows = crate::diffusion::follows(&app, &row, viewer.account()).await; let unverified = crate::diffusion::unverified(&app, &row).await; let backdrop = ripples_json(&format!("{owner}/{name}"), &used_by, &follows); Ok(Page(DiffusionPage { shell, repo: RepoShell::new(&app, &row, "diffusion").with_role(role), used_by, follows, unverified, backdrop })) } #[derive(Template)] #[template(path = "admin_diffusion.html")] pub struct AdminPage { shell: Shell, upstreams: Vec<(RepoRow, Vec, bool)>, } /// `GET /admin/diffusion`: every upstream on the hub and its consumers. pub async fn admin(State(app): State, viewer: Viewer) -> Result, Error> { let shell = Shell::new(&app, "/admin/diffusion", &viewer); if !viewer.is_admin() { return Err(Error::not_found(shell)); } let upstreams = crate::diffusion::everything(&app, viewer.account()).await; Ok(Page(AdminPage { shell, upstreams })) }