radicle: pinned repositories configuration

Introduce pinned repositories to the `profile::Config`, i.e.
`config.json`.

This is used for pinning repositories in the httpd `/projects` handle.

To allow returning all repositories, an `all` query parameter is also
included.

This is non-backwards compatible change, since we introduce the `show`
query parameter which, if not supplied, will default to `pinned`.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny
2024-01-24 10:55:27 +01:00
committed by cloudhead
parent 92182a879b
commit 5b3d7b7652
4 changed files with 65 additions and 26 deletions
+10
View File
@@ -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 {
+33 -14
View File
@@ -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!([
+1
View File
@@ -75,6 +75,7 @@ mod routes {
"preferredSeeds": [
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776"
],
"web": { "pinned": { "repositories": [] } },
"cli": {
"hints": true
},
+21 -12
View File
@@ -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!(