johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
//! Making repositories from the pages: `dev.knotless.createRepo` and
//! `forkRepo`, as the account logged in to them, through the same code as
//! `knotless repo create` and `knotless repo fork`; and what every write from the
//! pages to a repository shares.
use super::stars::logged_in;
use super::*;
use crate::viewer::Viewer;
use axum::Extension;
use knotless::manage::{self, NewRepo};
/// A write the crate refused, in its words: what was asked could not be
/// done, and what to do about it on the pages.
pub(super) fn refused(e: anyhow::Error) -> Shown {
Shown(StatusCode::BAD_REQUEST, crate::shown(&e))
}
/// What a write from the pages did to `did`'s records, taken in as an
/// event from its PDS would be, but at once: each `(action, collection,
/// rkey)`, a record created, updated or deleted, indexed as its PDS now
/// has it, so the page after shows it; and the account's mirrors
/// refreshed.
pub(super) async fn wrote(app: &Arc<App>, did: &str, ops: &[(&str, &str, &str)]) -> Result<()> {
app.touch(did);
for index in app.index.iter() {
for (action, collection, rkey) in ops {
let op = crate::firehose::Op {
action: action.to_string(),
collection: collection.to_string(),
rkey: rkey.to_string(),
record: None,
};
let indexed = crate::index_op(&app.ctx, index, did, &op, None).await;
indexed.map_err(AppError::internal)?;
}
}
Ok(())
}
/// [`wrote`], of `repo`'s own records: none of them, for a private one,
/// which no public index holds, and its PDS shows no one.
pub(super) async fn wrote_of(
app: &Arc<App>,
repo: &Repo,
ops: &[(&str, &str, &str)],
) -> Result<()> {
let public = if repo.in_space.is_some() {
&[][..]
} else {
ops
};
wrote(app, &repo.did, public).await
}
/// [`wrote_of`], for a change to `repo`'s record: for a private one, into
/// this server's index of its space, as the viewer reads it there now.
pub(super) async fn rewrote(app: &Arc<App>, repo: &Repo) -> Result<()> {
wrote_of(app, repo, &[("update", nsid::REPO, repo.name.as_str())]).await?;
let uri = repo.uri();
let private = repo
.in_space
.is_some()
.then_some(super::threads::reindex(app, repo, &uri));
futures::future::OptionFuture::from(private)
.await
.transpose()?;
Ok(())
}
/// `dev.knotless.defs#repoMade`.
#[derive(Serialize)]
pub struct RepoMade {
uri: String,
name: String,
}
impl RepoMade {
pub(super) fn new(did: &str, name: &str) -> RepoMade {
RepoMade {
uri: forge::repo_uri(did, name),
name: name.to_string(),
}
}
}
/// `did`'s repository `name`, as a URL, or why that is no name.
fn named(did: &str, name: &str) -> Result<RepoUrl> {
if !knotless::keys::valid_name(name) {
let why = format!(
"{name:?} is no repository's name: up to 100 letters, digits, `.`, `-`, `_` and `~`"
);
return Err(repo::invalid(why));
}
RepoUrl::parse(&format!("knotless://{did}/{name}")).map_err(refused)
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateInput {
name: String,
description: Option<String>,
default_branch: Option<String>,
#[serde(default)]
private: bool,
}
/// `dev.knotless.createRepo`.
pub async fn create_repo(
State(app): State<Arc<App>>,
Extension(viewer): Extension<Viewer>,
headers: HeaderMap,
axum::Json(input): axum::Json<CreateInput>,
) -> Result<Response> {
let did = logged_in(&app, viewer, &headers, "create a repository")?;
let url = named(&did, input.name.trim())?;
let given = |s: Option<String>| s.map(|s| s.trim().to_string()).filter(|s| !s.is_empty());
let new = NewRepo {
private: input.private,
description: given(input.description),
default_branch: given(input.default_branch),
..Default::default()
};
let ctx = crate::login::ctx_for(&app, &did);
manage::create(&ctx, &url, &new).await.map_err(refused)?;
// A private one is in a space of its own, which no public index holds,
// and in this server's own as it answers.
let made = [("create", nsid::REPO, url.name.as_str())];
let public = if new.private { &[][..] } else { &made[..] };
wrote(&app, &did, public).await?;
if new.private {
let space = knotless::space::uri(&did, &url.name);
app.keep(&space, &did).await?;
}
Ok(answer(RepoMade::new(&did, &url.name)))
}
#[derive(Deserialize)]
pub struct ForkInput {
repo: String,
name: Option<String>,
}
/// `dev.knotless.forkRepo`.
pub async fn fork_repo(
State(app): State<Arc<App>>,
Extension(viewer): Extension<Viewer>,
headers: HeaderMap,
axum::Json(input): axum::Json<ForkInput>,
) -> Result<Response> {
let did = logged_in(&app, viewer, &headers, "fork")?;
let repo = repo::found(&app, &input.repo).await?;
let name = input
.name
.as_deref()
.map(str::trim)
.filter(|n| !n.is_empty());
let dest = named(&did, name.unwrap_or(&repo.name))?;
let source = RepoUrl {
authority: repo.did.clone(),
name: repo.name.clone(),
};
let ctx = crate::login::ctx_for(&app, &did);
let forked = forge::fork(&ctx, &source, Some(&dest.name)).await;
let url = forked.map_err(refused)?;
// A private repository's fork is private, its record in a space of its
// own, which no public index holds.
let made = [("create", nsid::REPO, url.name.as_str())];
let public = if repo.in_space.is_some() {
&[][..]
} else {
&made[..]
};
wrote(&app, &did, public).await?;
Ok(answer(RepoMade::new(&did, &url.name)))
}