OXYGENHUB
# 2cbf6f348595 oxygenhub / crates / hub / src / web / diffusion.rs
3.2 KiB · Rust · last changed feat(web): the mark, ripples, the swarm and adrift (closes #167) 22 days ago source blame history raw
1
//! Diffusion's views (#150, docs/diffusion.md): a repository's Diffusion
2
//! tab (the consumers that pin it, and what it follows itself), and Admin ›
3
//! Diffusion across the hub. Each consumer shows green, amber, red, grey
4
//! or up to date (`diffusion::Colour`).
5
6
use super::{filters, lookup, Error, Page, RepoShell, Shell};
7
use crate::auth::Viewer;
8
use crate::db::repos::RepoRow;
9
use crate::diffusion::Consumer;
10
use crate::Shared;
11
use askama::Template;
12
use axum::extract::{Path, State};
13
14
#[derive(Template)]
15
#[template(path = "diffusion.html")]
16
pub struct DiffusionPage {
17
    shell: Shell,
18
    repo: RepoShell,
19
    used_by: Vec<Consumer>,
20
    follows: Vec<(RepoRow, Consumer)>,
21
    /// This repository has no checks of its own: its bumps are amber.
22
    unverified: bool,
23
    /// The ripples backdrop's data (docs/backgrounds.md §2): this page's
24
    /// own repositories and pill tones, JSON safe in a script.
25
    backdrop: String,
26
}
27
28
/// The ripples' data: the repository, the upstreams it takes from (each in
29
/// its upstream half's tone) and the consumers it diffuses to (each in its
30
/// own half's tone), as `{scene, repo, upstreams: [[name, tone]],
31
/// consumers: [[name, tone]]}`.
32
fn ripples_json(slug: &str, used_by: &[Consumer], follows: &[(RepoRow, Consumer)]) -> String {
33
    let tone = |class: &str| class.trim_start_matches("df-").to_string();
34
    let upstreams: Vec<(String, String)> =
35
        follows.iter().map(|(up, c)| (format!("{}/{}", up.owner, up.name), tone(c.upstream.class))).collect();
36
    let consumers: Vec<(String, String)> = used_by.iter().map(|c| (format!("{}/{}", c.owner, c.name), tone(c.downstream.class))).collect();
37
    serde_json::json!({ "scene": "ripples", "repo": slug, "upstreams": upstreams, "consumers": consumers })
38
        .to_string()
39
        .replace('<', "\\u003c")
40
}
41
42
/// `GET /{owner}/{name}/diffusion`.
43
pub async fn page(
44
    State(app): State<Shared>,
45
    viewer: Viewer,
46
    Path((owner, name)): Path<(String, String)>,
47
) -> Result<Page<DiffusionPage>, Error> {
48
    let shell = Shell::new(&app, &format!("/{owner}/{name}/diffusion"), &viewer);
49
    let (_, row, role) = lookup(&app, &shell, &viewer, &owner, &name).await?;
50
    let used_by = crate::diffusion::consumers(&app, &row, viewer.account()).await.unwrap_or_default();
51
    let follows = crate::diffusion::follows(&app, &row, viewer.account()).await;
52
    let unverified = crate::diffusion::unverified(&app, &row).await;
53
    let backdrop = ripples_json(&format!("{owner}/{name}"), &used_by, &follows);
54
    Ok(Page(DiffusionPage { shell, repo: RepoShell::new(&app, &row, "diffusion").with_role(role), used_by, follows, unverified, backdrop }))
55
}
56
57
#[derive(Template)]
58
#[template(path = "admin_diffusion.html")]
59
pub struct AdminPage {
60
    shell: Shell,
61
    upstreams: Vec<(RepoRow, Vec<Consumer>, bool)>,
62
}
63
64
/// `GET /admin/diffusion`: every upstream on the hub and its consumers.
65
pub async fn admin(State(app): State<Shared>, viewer: Viewer) -> Result<Page<AdminPage>, Error> {
66
    let shell = Shell::new(&app, "/admin/diffusion", &viewer);
67
    if !viewer.is_admin() {
68
        return Err(Error::not_found(shell));
69
    }
70
    let upstreams = crate::diffusion::everything(&app, viewer.account()).await;
71
    Ok(Page(AdminPage { shell, upstreams }))
72
}