mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 03:20:33 +08:00
Migrate /projects: root, project, commit, activity handlers
Signed-off-by: xphoniex <dj.2dixx@gmail.com>
This commit is contained in:
@@ -18,6 +18,7 @@ logfmt = [
|
||||
anyhow = { version = "1" }
|
||||
axum = { version = "0.5.16", default-features = false, features = ["json", "headers", "query"] }
|
||||
axum-server = { version = "0.4.2", default-features = false }
|
||||
chrono = { version = "0.4.22" }
|
||||
ethers-core = { version = "1.0" }
|
||||
fastrand = { version = "1.7.0" }
|
||||
flate2 = { version = "1" }
|
||||
@@ -29,7 +30,7 @@ siwe = { version = "0.5" }
|
||||
thiserror = { version = "1" }
|
||||
time = { version = "0.3.17" }
|
||||
tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
tower-http = { version = "0.3.4", default-features = false, features = ["trace", "cors"] }
|
||||
tower-http = { version = "0.3.4", default-features = false, features = ["trace", "cors", "set-header"] }
|
||||
tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] }
|
||||
tracing-logfmt = { version = "0.2", optional = true }
|
||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["std", "ansi", "fmt"] }
|
||||
@@ -37,3 +38,8 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["s
|
||||
[dependencies.radicle]
|
||||
path = "../radicle"
|
||||
version = "0.2.0"
|
||||
|
||||
[dependencies.radicle-surf]
|
||||
git = "https://github.com/radicle-dev/radicle-git"
|
||||
features = ["serialize"]
|
||||
rev = "d3115a22158c8395705babefdc89049f7510d32d"
|
||||
|
||||
@@ -16,6 +16,9 @@ use tower_http::cors::{self, CorsLayer};
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::Span;
|
||||
|
||||
use radicle::cob::issue::Issues;
|
||||
use radicle::identity::{Doc, Id};
|
||||
use radicle::storage::{ReadRepository, WriteStorage};
|
||||
use radicle::Profile;
|
||||
|
||||
mod auth;
|
||||
@@ -41,6 +44,22 @@ impl Context {
|
||||
sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_info(&self, id: Id) -> Result<project::Info, error::Error> {
|
||||
let storage = &self.profile.storage;
|
||||
let repo = storage.repository(id)?;
|
||||
let (_, head) = repo.head()?;
|
||||
let Doc { payload, .. } = repo.project_of(self.profile.id())?;
|
||||
let issues = (Issues::open(self.profile.public_key, &repo)?).count()?;
|
||||
|
||||
Ok(project::Info {
|
||||
payload,
|
||||
head,
|
||||
issues,
|
||||
patches: 0,
|
||||
id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
@@ -115,3 +134,23 @@ pub struct PaginationQuery {
|
||||
pub page: Option<usize>,
|
||||
pub per_page: Option<usize>,
|
||||
}
|
||||
|
||||
mod project {
|
||||
use radicle::git::Oid;
|
||||
use radicle::identity::project::Payload;
|
||||
use radicle::identity::Id;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Project info.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Info {
|
||||
/// Project metadata.
|
||||
#[serde(flatten)]
|
||||
pub payload: Payload,
|
||||
pub head: Oid,
|
||||
pub patches: usize,
|
||||
pub issues: usize,
|
||||
pub id: Id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,19 @@ pub enum Error {
|
||||
|
||||
/// Storage error.
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] radicle::storage::Error),
|
||||
Storage(#[from] radicle::storage::Error),
|
||||
|
||||
/// Cob store error.
|
||||
#[error(transparent)]
|
||||
CobStore(#[from] radicle::cob::store::Error),
|
||||
|
||||
/// Git project error.
|
||||
#[error(transparent)]
|
||||
GitProject(#[from] radicle::storage::git::ProjectError),
|
||||
|
||||
/// Surf error.
|
||||
#[error(transparent)]
|
||||
SurfCommit(#[from] radicle_surf::commit::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod delegates;
|
||||
mod node;
|
||||
mod projects;
|
||||
mod sessions;
|
||||
|
||||
use axum::Router;
|
||||
@@ -10,7 +11,8 @@ pub fn router(ctx: Context) -> Router {
|
||||
let routes = Router::new()
|
||||
.merge(node::router(ctx.clone()))
|
||||
.merge(sessions::router(ctx.clone()))
|
||||
.merge(delegates::router(ctx));
|
||||
.merge(delegates::router(ctx.clone()))
|
||||
.merge(projects::router(ctx));
|
||||
|
||||
Router::new().nest("/v1", routes)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::{Extension, Json, Router};
|
||||
use serde::Serialize;
|
||||
|
||||
use radicle::cob::store::Store;
|
||||
use radicle::git::Oid;
|
||||
use radicle::identity::project::Payload;
|
||||
use radicle::cob::issue::Issues;
|
||||
use radicle::identity::{Did, Doc};
|
||||
use radicle::storage::{ReadRepository, WriteStorage};
|
||||
|
||||
use crate::api::axum_extra::{Path, Query};
|
||||
use crate::api::error::Error;
|
||||
use crate::api::project::Info;
|
||||
use crate::api::Context;
|
||||
use crate::api::PaginationQuery;
|
||||
|
||||
/// Project info.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Info {
|
||||
/// Project metadata.
|
||||
#[serde(flatten)]
|
||||
pub payload: Payload,
|
||||
pub head: Oid,
|
||||
pub patches: usize,
|
||||
pub issues: usize,
|
||||
}
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
@@ -58,15 +44,14 @@ async fn delegates_projects_handler(
|
||||
return None;
|
||||
}
|
||||
|
||||
let Ok(cobs) = Store::open(ctx.profile.public_key, &repo) else { return None };
|
||||
let Ok(issues) = cobs.issues().count() else { return None };
|
||||
let Ok(patches) = cobs.patches().count() else { return None };
|
||||
let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None };
|
||||
let Ok(issues) = (*issues).count() else { return None };
|
||||
|
||||
Some(Info {
|
||||
payload,
|
||||
head,
|
||||
issues,
|
||||
patches,
|
||||
patches: 0,
|
||||
id,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use axum::handler::Handler;
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::{Extension, Json, Router};
|
||||
use hyper::StatusCode;
|
||||
use serde_json::json;
|
||||
use tower_http::set_header::SetResponseHeaderLayer;
|
||||
|
||||
use radicle::cob::issue::Issues;
|
||||
use radicle::identity::{Doc, Id};
|
||||
use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage};
|
||||
use radicle_surf::git::History;
|
||||
|
||||
use crate::api::axum_extra::{Path, Query};
|
||||
use crate::api::error::Error;
|
||||
use crate::api::project::Info;
|
||||
use crate::api::{Context, PaginationQuery};
|
||||
|
||||
const CACHE_1_HOUR: &str = "public, max-age=3600, must-revalidate";
|
||||
|
||||
pub fn router(ctx: Context) -> Router {
|
||||
Router::new()
|
||||
.route("/projects", get(project_root_handler))
|
||||
.route("/projects/:project", get(project_handler))
|
||||
.route("/projects/:project/commits/:sha", get(commit_handler))
|
||||
.route(
|
||||
"/projects/:project/activity",
|
||||
get(
|
||||
activity_handler.layer(SetResponseHeaderLayer::if_not_present(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(CACHE_1_HOUR),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.layer(Extension(ctx))
|
||||
}
|
||||
|
||||
/// List all projects.
|
||||
/// `GET /projects`
|
||||
async fn project_root_handler(
|
||||
Extension(ctx): Extension<Context>,
|
||||
Query(qs): Query<PaginationQuery>,
|
||||
) -> impl IntoResponse {
|
||||
let PaginationQuery { page, per_page } = qs;
|
||||
let page = page.unwrap_or(0);
|
||||
let per_page = per_page.unwrap_or(10);
|
||||
let storage = &ctx.profile.storage;
|
||||
let projects = storage
|
||||
.projects()?
|
||||
.into_iter()
|
||||
.filter_map(|id| {
|
||||
let Ok(repo) = storage.repository(id) else { return None };
|
||||
let Ok((_, head)) = repo.head() else { return None };
|
||||
let Ok(Doc { payload, .. }) = repo.project_of(ctx.profile.id()) else { return None };
|
||||
let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None };
|
||||
let Ok(issues) = (*issues).count() else { return None };
|
||||
|
||||
Some(Info {
|
||||
payload,
|
||||
head,
|
||||
issues,
|
||||
patches: 0,
|
||||
id,
|
||||
})
|
||||
})
|
||||
.skip(page * per_page)
|
||||
.take(per_page)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok::<_, Error>(Json(projects))
|
||||
}
|
||||
|
||||
/// Get project metadata.
|
||||
/// `GET /projects/:project`
|
||||
async fn project_handler(
|
||||
Extension(ctx): Extension<Context>,
|
||||
Path(id): Path<Id>,
|
||||
) -> impl IntoResponse {
|
||||
let info = ctx.project_info(id)?;
|
||||
|
||||
Ok::<_, Error>(Json(info))
|
||||
}
|
||||
|
||||
/// Get project commit.
|
||||
/// `GET /projects/:project/commits/:sha`
|
||||
async fn commit_handler(
|
||||
Extension(ctx): Extension<Context>,
|
||||
Path((project, sha)): Path<(Id, Oid)>,
|
||||
) -> impl IntoResponse {
|
||||
let storage = &ctx.profile.storage;
|
||||
let repo = storage.repository(project)?;
|
||||
let commit = radicle_surf::commit(&repo.raw().into(), sha)?;
|
||||
|
||||
Ok::<_, Error>(Json(json!(commit)))
|
||||
}
|
||||
|
||||
/// Get project activity for the past year.
|
||||
/// `GET /projects/:project/activity`
|
||||
async fn activity_handler(
|
||||
Extension(ctx): Extension<Context>,
|
||||
Path(project): Path<Id>,
|
||||
) -> impl IntoResponse {
|
||||
let current_date = chrono::Utc::now().timestamp();
|
||||
let one_year_ago = chrono::Duration::weeks(52);
|
||||
let storage = &ctx.profile.storage;
|
||||
let repo = storage.repository(project)?;
|
||||
let (_, head) = repo.head()?;
|
||||
let timestamps = History::new(repo.raw().into(), head)
|
||||
.unwrap()
|
||||
.filter_map(|a| {
|
||||
if let Ok(a) = a {
|
||||
let seconds = a.committer.time.seconds();
|
||||
if seconds > current_date - one_year_ago.num_seconds() {
|
||||
return Some(seconds);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect::<Vec<i64>>();
|
||||
|
||||
Ok::<_, Error>((StatusCode::OK, Json(json!({ "activity": timestamps }))))
|
||||
}
|
||||
Reference in New Issue
Block a user