Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle: pinned repositories configuration
Fintan Halpenny committed 2 years ago
commit 49584f4e732fb0039d2089f3c39fd56fc34a2ee3
parent d722638905888f7d50502e6ac37aa2011b659a26
11 files changed +119 -29
modified radicle-cli/examples/rad-config.md
@@ -6,6 +6,11 @@ $ rad config
{
  "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
  "preferredSeeds": [],
+
  "web": {
+
    "pinned": {
+
      "repositories": []
+
    }
+
  },
  "cli": {
    "hints": false
  },
modified radicle-httpd/src/api.rs
@@ -125,10 +125,20 @@ async fn root_handler() -> impl IntoResponse {
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PaginationQuery {
+
    #[serde(default)]
+
    pub show: ProjectQuery,
    pub page: Option<usize>,
    pub per_page: Option<usize>,
}

+
#[derive(Serialize, Deserialize, Clone, Default)]
+
#[serde(rename_all = "camelCase")]
+
pub enum ProjectQuery {
+
    All,
+
    #[default]
+
    Pinned,
+
}
+

#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RawQuery {
modified radicle-httpd/src/api/v1/delegates.rs
@@ -14,7 +14,7 @@ use radicle::storage::{ReadRepository, ReadStorage};
use crate::api::error::Error;
use crate::api::project::Info;
use crate::api::Context;
-
use crate::api::PaginationQuery;
+
use crate::api::{PaginationQuery, ProjectQuery};
use crate::axum_extra::{Path, Query};

pub fn router(ctx: Context) -> Router {
@@ -34,19 +34,28 @@ async fn delegates_projects_handler(
    Path(delegate): Path<Did>,
    Query(qs): Query<PaginationQuery>,
) -> impl IntoResponse {
-
    let PaginationQuery { page, per_page } = qs;
+
    let PaginationQuery {
+
        show,
+
        page,
+
        per_page,
+
    } = qs;
    let page = page.unwrap_or(0);
    let per_page = per_page.unwrap_or(10);
    let storage = &ctx.profile.storage;
    let db = &ctx.profile.database()?;
-
    let mut projects = storage
-
        .repositories()?
-
        .into_iter()
-
        .filter(|id| match &id.doc.visibility {
-
            Visibility::Private { .. } => addr.ip().is_loopback(),
-
            Visibility::Public => true,
-
        })
-
        .collect::<Vec<_>>();
+
    let pinned = &ctx.profile.config.web.pinned;
+

+
    let mut projects = match show {
+
        ProjectQuery::All => storage
+
            .repositories()?
+
            .into_iter()
+
            .filter(|repo| match &repo.doc.visibility {
+
                Visibility::Private { .. } => addr.ip().is_loopback(),
+
                Visibility::Public => true,
+
            })
+
            .collect::<Vec<_>>(),
+
        ProjectQuery::Pinned => storage.repositories_by_id(pinned.repositories.iter())?,
+
    };
    projects.sort_by_key(|p| p.rid);

    let infos = projects
@@ -116,11 +125,16 @@ mod routes {
            .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))));
        let response = get(
            &app,
-
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects",
+
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all",
        )
        .await;

-
        assert_eq!(response.status(), StatusCode::OK);
+
        assert_eq!(
+
            response.status(),
+
            StatusCode::OK,
+
            "failed response: {:?}",
+
            response.json().await
+
        );
        assert_eq!(
            response.json().await,
            json!([
@@ -179,11 +193,16 @@ mod routes {
        ))));
        let response = get(
            &app,
-
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects",
+
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects?show=all",
        )
        .await;

-
        assert_eq!(response.status(), StatusCode::OK);
+
        assert_eq!(
+
            response.status(),
+
            StatusCode::OK,
+
            "failed response: {:?}",
+
            response.json().await
+
        );
        assert_eq!(
            response.json().await,
            json!([
modified radicle-httpd/src/api/v1/profile.rs
@@ -75,6 +75,7 @@ mod routes {
                "preferredSeeds": [
                  "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776"
                ],
+
                "web": { "pinned": { "repositories": [] } },
                "cli": {
                  "hints": true
                },
modified radicle-httpd/src/api/v1/projects.rs
@@ -24,7 +24,7 @@ use radicle_surf::{diff, Glob, Oid, Repository};

use crate::api::error::Error;
use crate::api::project::Info;
-
use crate::api::{self, announce_refs, CobsQuery, Context, PaginationQuery};
+
use crate::api::{self, announce_refs, CobsQuery, Context, PaginationQuery, ProjectQuery};
use crate::axum_extra::{Path, Query};

const CACHE_1_HOUR: &str = "public, max-age=3600, must-revalidate";
@@ -79,19 +79,28 @@ async fn project_root_handler(
    State(ctx): State<Context>,
    Query(qs): Query<PaginationQuery>,
) -> impl IntoResponse {
-
    let PaginationQuery { page, per_page } = qs;
+
    let PaginationQuery {
+
        show,
+
        page,
+
        per_page,
+
    } = qs;
    let page = page.unwrap_or(0);
    let per_page = per_page.unwrap_or(10);
    let storage = &ctx.profile.storage;
    let db = &ctx.profile.database()?;
-
    let mut projects = storage
-
        .repositories()?
-
        .into_iter()
-
        .filter(|id| match &id.doc.visibility {
-
            Visibility::Private { .. } => addr.ip().is_loopback(),
-
            Visibility::Public => true,
-
        })
-
        .collect::<Vec<_>>();
+
    let pinned = &ctx.profile.config.web.pinned;
+

+
    let mut projects = match show {
+
        ProjectQuery::All => storage
+
            .repositories()?
+
            .into_iter()
+
            .filter(|repo| match &repo.doc.visibility {
+
                Visibility::Private { .. } => addr.ip().is_loopback(),
+
                Visibility::Public => true,
+
            })
+
            .collect::<Vec<_>>(),
+
        ProjectQuery::Pinned => storage.repositories_by_id(pinned.repositories.iter())?,
+
    };
    projects.sort_by_key(|p| p.rid);

    let infos = projects
@@ -976,7 +985,7 @@ mod routes {
        let seed = seed(tmp.path());
        let app = super::router(seed.clone())
            .layer(MockConnectInfo(SocketAddr::from(([127, 0, 0, 1], 8080))));
-
        let response = get(&app, "/projects").await;
+
        let response = get(&app, "/projects?show=all").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -1035,7 +1044,7 @@ mod routes {
            [192, 168, 13, 37],
            8080,
        ))));
-
        let response = get(&app, "/projects").await;
+
        let response = get(&app, "/projects?show=all").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
modified radicle-node/src/test/environment.rs
@@ -13,7 +13,6 @@ use radicle::cob::issue;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::{KeyPair, Seed, Signer};
-
use radicle::git;
use radicle::git::refname;
use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress;
@@ -30,6 +29,7 @@ use radicle::test::fixtures;
use radicle::Storage;
use radicle::{cli, node};
use radicle::{cob, explorer};
+
use radicle::{git, web};

use crate::node::NodeId;
use crate::service::Event;
@@ -92,6 +92,7 @@ impl Environment {
            cli: cli::Config { hints: false },
            public_explorer: explorer::Explorer::default(),
            preferred_seeds: PreferredSeeds::from(vec![]),
+
            web: web::Config::default(),
        }
    }

modified radicle/src/cob/store.rs
@@ -389,7 +389,7 @@ pub mod test {
        let obj = history.traverse(initial, &children, |mut acc, _, entry| {
            match Op::try_from(entry) {
                Ok(op) => {
-
                    if let Err(err) = acc.op(op, [].into_iter(), repo) {
+
                    if let Err(err) = acc.op(op, [], repo) {
                        log::warn!("Error applying op to `{}` state: {err}", T::type_name());
                        return ControlFlow::Break(acc);
                    }
modified radicle/src/lib.rs
@@ -28,6 +28,7 @@ pub mod storage;
#[cfg(any(test, feature = "test"))]
pub mod test;
pub mod version;
+
pub mod web;

pub use cob::{issue, patch};
pub use node::Node;
modified radicle/src/profile.rs
@@ -27,7 +27,7 @@ use crate::prelude::Did;
use crate::prelude::NodeId;
use crate::storage::git::transport;
use crate::storage::git::Storage;
-
use crate::{cli, git, node};
+
use crate::{cli, git, node, web};

/// Environment variables used by radicle.
pub mod env {
@@ -123,6 +123,9 @@ pub struct Config {
    /// and in other situations when a seed needs to be chosen.
    #[serde(default)]
    pub preferred_seeds: PreferredSeeds,
+
    /// Web configuration.
+
    #[serde(default)]
+
    pub web: web::Config,
    /// CLI configuration.
    #[serde(default)]
    pub cli: cli::Config,
@@ -136,6 +139,7 @@ impl Config {
        Self {
            public_explorer: Explorer::default(),
            preferred_seeds: PreferredSeeds::default(),
+
            web: web::Config::default(),
            cli: cli::Config::default(),
            node: node::Config::new(alias),
        }
modified radicle/src/storage/git.rs
@@ -252,6 +252,24 @@ impl Storage {
        Ok(repos)
    }

+
    pub fn repositories_by_id<'a>(
+
        &self,
+
        mut rids: impl Iterator<Item = &'a RepoId>,
+
    ) -> Result<Vec<RepositoryInfo<Verified>>, RepositoryError> {
+
        rids.try_fold(Vec::new(), |mut infos, rid| {
+
            let repo = self.repository(*rid)?;
+
            let (_, head) = repo.head()?;
+
            let info = RepositoryInfo {
+
                rid: *rid,
+
                head,
+
                doc: repo.identity_doc()?.into(),
+
                refs: refs::SignedRefsAt::load(self.info.key, &repo)?,
+
            };
+
            infos.push(info);
+
            Ok(infos)
+
        })
+
    }
+

    pub fn inspect(&self) -> Result<(), Error> {
        for r in self.repositories()? {
            let rid = r.rid;
added radicle/src/web.rs
@@ -0,0 +1,22 @@
+
use std::collections::HashSet;
+

+
use serde::{Deserialize, Serialize};
+

+
use crate::prelude::RepoId;
+

+
/// Web configuration.
+
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Config {
+
    /// Pinned content.
+
    pub pinned: Pinned,
+
}
+

+
/// Pinned content. This can be used to pin certain content when
+
/// listing, e.g. pin repositories on a web client.
+
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
pub struct Pinned {
+
    /// Pinned repositories.
+
    pub repositories: HashSet<RepoId>,
+
}