Something went wrong. Try again.
[ARCHIVED] A PLC Mirror written in Rust
Something went wrong. Try again.
4.8 kB · 166 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167use crate::types::{DidDocument, JsonCid, PlcEntry, PlcOperationType};use crate::{ApiContext, db};use dropshot::{ Body, ClientErrorStatusCode, HttpError, HttpResponseOk, Path, RequestContext, endpoint,};use hyper::Response;use ipld_core::cid::Cid;use schemars::JsonSchema;use serde::Deserialize;use std::str::FromStr;
fn err_did_not_found(did: &str) -> HttpError { HttpError::for_not_found(None, format!("DID not registered: {did}"))}
fn err_did_tombstoned(did: &str) -> HttpError { HttpError::for_client_error( None, ClientErrorStatusCode::GONE, format!("DID not available: {did}"), )}
#[endpoint{ method = GET, path = "/",}]// strictly this isn't the correct type, but it workspub async fn index(_rqctx: RequestContext<ApiContext>) -> Result<Response<Body>, HttpError> { Ok(Response::builder() .status(200) .header("Content-Type", "text/plain") .body(Body::with_content(include_str!("./index.txt")))?)}
#[derive(Debug, JsonSchema, Deserialize)]pub struct DidPathParams { pub did: String,}
#[endpoint { method = GET, path = "/{did}",}]pub async fn resolve_did( rqctx: RequestContext<ApiContext>, path: Path<DidPathParams>,) -> Result<HttpResponseOk<DidDocument>, HttpError> { let conn = rqctx.context().get_conn().await?; let did = path.into_inner().did;
if did == "favicon.ico" { return Err(HttpError::for_not_found(None, Default::default())); }
if !did.starts_with("did:plc:") { return Err(HttpError::for_bad_request(None, "Invalid DID".to_string())); }
let op = db::get_latest_operation(&conn, &did) .await .map_err(|v| HttpError::for_internal_error(v.to_string()))? .ok_or(err_did_not_found(&did))?;
let decoded: PlcOperationType = serde_json::from_value(op.operation) .map_err(|v| HttpError::for_internal_error(v.to_string()))?;
let doc = match decoded { PlcOperationType::Tombstone(_) => return Err(err_did_tombstoned(&did)), PlcOperationType::Create(op) => DidDocument::from_create(&did, op), PlcOperationType::Operation(op) => DidDocument::from_plc_op(&did, op), };
Ok(HttpResponseOk(doc))}
#[endpoint { method = GET, path = "/{did}/log",}]pub async fn get_plc_op_log( rqctx: RequestContext<ApiContext>, path: Path<DidPathParams>,) -> Result<HttpResponseOk<Vec<PlcOperationType>>, HttpError> { let conn = rqctx.context().get_conn().await?; let did = path.into_inner().did;
let ops = db::get_operations(&conn, &did) .await .map_err(|v| HttpError::for_internal_error(v.to_string()))?;
if ops.is_empty() { return Err(err_did_not_found(&did)); }
let ops = ops .into_iter() .map(|op| serde_json::from_value(op.operation)) .collect::<Result<Vec<_>, _>>() .map_err(|v| HttpError::for_internal_error(v.to_string()))?;
Ok(HttpResponseOk(ops))}
#[endpoint { method = GET, path = "/{did}/log/audit",}]pub async fn get_plc_audit_log( rqctx: RequestContext<ApiContext>, path: Path<DidPathParams>,) -> Result<HttpResponseOk<Vec<PlcEntry>>, HttpError> { let conn = rqctx.context().get_conn().await?; let did = path.into_inner().did;
let ops = db::get_operations(&conn, &did) .await .map_err(|v| HttpError::for_internal_error(v.to_string()))?;
if ops.is_empty() { return Err(err_did_not_found(&did)); }
let entries = ops .into_iter() .map(|op| { let operation = serde_json::from_value(op.operation)?; let cid = Cid::from_str(&op.hash).unwrap();
let entry = PlcEntry { did: did.clone(), operation, cid: JsonCid(cid), nullified: op.nullified, created_at: op.created_at, };
Ok(entry) }) .collect::<Result<Vec<_>, _>>() .map_err(|v: eyre::Report| HttpError::for_internal_error(v.to_string()))?;
Ok(HttpResponseOk(entries))}
#[endpoint { method = GET, path = "/{did}/log/last",}]pub async fn get_last_op( rqctx: RequestContext<ApiContext>, path: Path<DidPathParams>,) -> Result<HttpResponseOk<PlcOperationType>, HttpError> { let conn = rqctx.context().get_conn().await?; let did = path.into_inner().did;
let op = db::get_latest_operation(&conn, &did) .await .map_err(|v| HttpError::for_internal_error(v.to_string()))? .ok_or(err_did_not_found(&did))?;
let decoded = serde_json::from_value(op.operation) .map_err(|v| HttpError::for_internal_error(v.to_string()))?;
Ok(HttpResponseOk(decoded))}