OXYGENHUB
# 3f02e5ea4f2d oxygenhub / crates / hub / src / web / home.rs
3.4 KiB · Rust · last changed Milestone 0.2: accounts and access 1 month ago source blame history raw
1
//! The front page, explore, owner pages and small endpoints.
2
3
use super::{filters, Error, Page, Shell};
4
use crate::auth::Viewer;
5
use crate::{db, Shared};
6
use askama::Template;
7
use axum::extract::{Path, State};
8
use axum::http::header;
9
use axum::response::{IntoResponse, Response};
10
use hub_api::Visibility;
11
12
#[derive(Template)]
13
#[template(path = "home.html")]
14
pub struct HomePage {
15
    shell: Shell,
16
    description: String,
17
    repos: Vec<db::repos::RepoRow>,
18
    owner: Option<String>,
19
}
20
21
pub async fn index(State(app): State<Shared>, viewer: Viewer) -> Result<Page<HomePage>, Error> {
22
    let shell = Shell::new(&app, "/", &viewer);
23
    let repos = visible(&app, &shell, &viewer, db::repos::list(&app.db).await.map_err(|e| Error::from_db(shell.clone(), e))?).await?;
24
    Ok(Page(HomePage { description: app.config.site.description.clone(), shell, repos, owner: None }))
25
}
26
27
pub async fn explore(State(app): State<Shared>, viewer: Viewer) -> Result<Page<HomePage>, Error> {
28
    let mut page = index(State(app.clone()), viewer.clone()).await?;
29
    page.0.shell = Shell::new(&app, "/explore", &viewer);
30
    Ok(page)
31
}
32
33
pub async fn owner(State(app): State<Shared>, viewer: Viewer, Path(owner): Path<String>) -> Result<Page<HomePage>, Error> {
34
    let shell = Shell::new(&app, &format!("/{owner}"), &viewer);
35
    if !crate::repos::names::valid_owner(&owner) {
36
        return Err(Error::not_found(shell));
37
    }
38
    let repos =
39
        visible(&app, &shell, &viewer, db::repos::list_by_owner(&app.db, &owner).await.map_err(|e| Error::from_db(shell.clone(), e))?)
40
            .await?;
41
    let known = db::accounts::account_by_name(&app.db, &owner).await.map_err(|e| Error::from_db(shell.clone(), e))?.is_some()
42
        || db::accounts::org_by_name(&app.db, &owner).await.map_err(|e| Error::from_db(shell.clone(), e))?.is_some();
43
    if repos.is_empty() && !known {
44
        return Err(Error::not_found(shell));
45
    }
46
    Ok(Page(HomePage { description: String::new(), shell, repos, owner: Some(owner) }))
47
}
48
49
/// The repositories among `rows` the viewer may see: public ones, internal
50
/// ones when signed in, and those the viewer owns or collaborates on.
51
async fn visible(app: &Shared, shell: &Shell, viewer: &Viewer, rows: Vec<db::repos::RepoRow>) -> Result<Vec<db::repos::RepoRow>, Error> {
52
    let mine: Vec<i64> = match viewer.account() {
53
        Some(a) if a.is_admin => rows.iter().map(|r| r.id).collect(),
54
        Some(a) => db::accounts::visible_repo_ids(&app.db, a.id).await.map_err(|e| Error::from_db(shell.clone(), e))?,
55
        None => vec![],
56
    };
57
    let signed_in = viewer.signed_in();
58
    Ok(rows
59
        .into_iter()
60
        .filter(|r| match r.visibility() {
61
            Visibility::Public => true,
62
            Visibility::Internal => signed_in,
63
            Visibility::Private => mine.contains(&r.id),
64
        })
65
        .collect())
66
}
67
68
pub async fn health(State(app): State<Shared>) -> Response {
69
    let body = serde_json::json!({
70
        "ok": true,
71
        "version": env!("CARGO_PKG_VERSION"),
72
        "uptime_s": app.started.elapsed().as_secs(),
73
    });
74
    axum::Json(body).into_response()
75
}
76
77
/// The hub delegate identity object (hub spec §15.5), as `oxy identity
78
/// export` writes one, so policy holders can admit it.
79
pub async fn hub_key(State(app): State<Shared>) -> Response {
80
    (
81
        [(header::CONTENT_TYPE, "application/oxy-identity"), (header::CONTENT_DISPOSITION, "attachment; filename=\"hub.identity\"")],
82
        app.hub_key.bytes.clone(),
83
    )
84
        .into_response()
85
}