use std::collections::BTreeMap;
use serde_json::{Value, json};
use super::{
COLLABORATOR, Hosted, MANIFEST, Out, Refused, Route, Social, SocialRecord, WHOLE_LIST, count,
keys, no_count, no_items, spellings,
};
const BY_NAME: Route = Route {
nsid: "org.tangled.temp.repo.getRepoByName",
source: "$arg_owner$arg_ownerDid|$arg_name",
var: "$ks_soc_by_name",
miss: "not_found",
empty: None,
refused: &[],
ignored: &[],
};
const BY_DID: Route = Route {
nsid: "org.tangled.repo.getRepoByRepoDid",
source: "$arg_repoDid",
var: "$ks_soc_by_did",
miss: "not_found",
empty: None,
refused: &[],
ignored: &[],
};
const BY_DIDS: Route = Route {
nsid: "org.tangled.repo.getReposByRepoDids",
source: "$arg_dids$arg_repoDids",
var: "$ks_soc_by_dids",
miss: "invalid_request",
empty: Some(no_items),
refused: &[Refused::Distinct("dids"), Refused::Distinct("repoDids")],
ignored: &[],
};
const LIST: Route = Route {
nsid: "org.tangled.repo.listRepos",
source: "$arg_subject$arg_owner",
var: "$ks_soc_repos",
miss: "invalid_request",
empty: Some(no_items),
refused: WHOLE_LIST,
ignored: &["limit", "offset", "order"],
};
const COLLABORATORS: Route = Route {
nsid: "org.tangled.repo.listCollaborators",
source: "$arg_subject$arg_repo",
var: "$ks_soc_collaborators",
miss: "invalid_request",
empty: Some(no_items),
refused: WHOLE_LIST,
ignored: &["limit", "offset", "order"],
};
const ARTIFACTS: Route = Route {
nsid: "org.tangled.repo.listArtifacts",
source: "$arg_subject$arg_repo",
var: "$ks_soc_artifacts",
miss: "invalid_request",
empty: Some(no_items),
refused: WHOLE_LIST,
ignored: &["limit", "offset", "order"],
};
const LABELS: Route = Route {
nsid: "org.tangled.label.listDefinitions",
source: "$arg_subject$arg_repo",
var: "$ks_soc_labels",
miss: "invalid_request",
empty: Some(no_items),
refused: WHOLE_LIST,
ignored: &["limit", "offset", "order"],
};
macro_rules! count_route {
($name:ident, $nsid:literal, $var:literal) => {
const $name: Route = Route {
nsid: $nsid,
source: "$arg_subject$arg_repo",
var: $var,
miss: "invalid_request",
empty: Some(no_count),
refused: &[],
ignored: &[],
};
};
}
count_route!(STARS, "org.tangled.feed.countStars", "$ks_soc_n_stars");
count_route!(FORKS, "org.tangled.repo.countForks", "$ks_soc_n_forks");
count_route!(
N_COLLABORATORS,
"org.tangled.repo.countCollaborators",
"$ks_soc_n_collaborators"
);
count_route!(
N_LABELS,
"org.tangled.label.countDefinitions",
"$ks_soc_n_labels"
);
count_route!(
N_ARTIFACTS,
"org.tangled.repo.countArtifacts",
"$ks_soc_n_artifacts"
);
pub(super) const ROUTES: &[Route] = &[
BY_NAME,
BY_DID,
BY_DIDS,
LIST,
COLLABORATORS,
ARTIFACTS,
LABELS,
STARS,
FORKS,
N_COLLABORATORS,
N_LABELS,
N_ARTIFACTS,
];
pub(super) fn view(hosted: &Hosted, social: &Social) -> Option<Value> {
let manifest = hosted.manifest?;
let mut view = json!({
"uri": format!("at://{}/{MANIFEST}/self", hosted.did),
"cid": manifest.cid,
"value": manifest.value,
"knot": social.hostname,
});
if let Some(refs) = hosted.refs {
view["git"] = refs.value.clone();
}
Some(view)
}
/// the repoViewBasic shape that ticket views embed.
pub(super) fn basic(hosted: &Hosted, social: &Social, forks: usize) -> Value {
let open = |pulls: bool| {
hosted
.tickets
.iter()
.filter(|ticket| super::ticket::is_pull(ticket) == pulls)
.filter(|ticket| super::ticket::state(ticket) == "open")
.count()
};
let mut basic = json!({
"did": hosted.did,
"owner": social.profile(hosted.owner().unwrap_or_default()),
"slug": hosted.name().unwrap_or_default(),
"createdAt": hosted.manifest.map(SocialRecord::created_at).unwrap_or_default(),
"forkCount": forks,
"starCount": 0,
"openIssueCount": open(false),
"openPullCount": open(true),
});
if let Some(description) = hosted.manifest.and_then(|m| m.value.get("description")) {
basic["description"] = description.clone();
}
basic
}
pub(super) fn forks<'a>(hosted: &'a [Hosted<'a>]) -> BTreeMap<&'a str, usize> {
let mut forks = BTreeMap::new();
for repo in hosted {
let source = repo
.manifest
.and_then(|m| m.value["source"]["did"].as_str());
if let Some(source) = source.filter(|source| *source != repo.did) {
*forks.entry(source).or_default() += 1;
}
}
forks
}
fn items(records: &[&SocialRecord]) -> Value {
json!({
"items": records.iter().map(|record| record.view()).collect::<Vec<_>>(),
"total": records.len(),
})
}
pub(super) fn emit(out: &mut Out, hosted: &[Hosted], social: &Social) -> anyhow::Result<()> {
let forks = forks(hosted);
let mut owned: BTreeMap<&str, Vec<(&str, Value)>> = BTreeMap::new();
for repo in hosted {
let subjects = repo.subjects();
let forked = forks.get(repo.did).copied().unwrap_or_default();
out.answer(&STARS, subjects.clone(), &no_count())?;
out.answer(
&FORKS,
subjects.clone(),
&json!({ "count": forked, "distinctAuthors": forked }),
)?;
out.answer(&N_LABELS, subjects.clone(), &count(&repo.labels))?;
out.answer(&N_ARTIFACTS, subjects.clone(), &count(&repo.artifacts))?;
out.answer(&LABELS, subjects.clone(), &items(&repo.labels))?;
out.answer(&ARTIFACTS, subjects.clone(), &items(&repo.artifacts))?;
let owner = repo.owner().unwrap_or_default();
let collaborators: Vec<Value> = repo
.collaborators
.iter()
.filter(|grant| grant.collection == COLLABORATOR)
.map(|grant| {
json!({
"subject": grant.rkey,
"addedBy": owner,
"createdAt": grant.created_at(),
"effectiveSince": grant.created_at(),
"uri": grant.uri,
"cid": grant.cid,
})
})
.collect();
out.answer(
&N_COLLABORATORS,
subjects.clone(),
&json!({ "count": collaborators.len(), "distinctAuthors": collaborators.len() }),
)?;
out.answer(
&COLLABORATORS,
subjects,
&json!({ "total": collaborators.len(), "items": collaborators }),
)?;
let Some(view) = view(repo, social) else {
continue;
};
let dids = spellings(repo.did);
out.answer(&BY_DID, dids.clone(), &view)?;
out.answer(&BY_DIDS, dids, &json!({ "items": [view] }))?;
let names = spellings(repo.name().unwrap_or_default());
out.answer(&BY_NAME, keys(&[&spellings(owner), &names]), &view)?;
owned.entry(owner).or_default().push((
repo.manifest
.map(SocialRecord::created_at)
.unwrap_or_default(),
view,
));
}
for (owner, mut views) in owned {
views.sort_by(|a, b| b.0.cmp(a.0));
let views: Vec<Value> = views.into_iter().map(|(_, view)| view).collect();
out.answer(
&LIST,
spellings(owner),
&json!({ "total": views.len(), "items": views }),
)?;
}
Ok(())
}