A PLC Mirror written in Rust
at main 4.8 kB view raw
1use crate::types::{DidDocument, JsonCid, PlcEntry, PlcOperationType}; 2use crate::{ApiContext, db}; 3use dropshot::{ 4 Body, ClientErrorStatusCode, HttpError, HttpResponseOk, Path, RequestContext, endpoint, 5}; 6use hyper::Response; 7use ipld_core::cid::Cid; 8use schemars::JsonSchema; 9use serde::Deserialize; 10use std::str::FromStr; 11 12fn err_did_not_found(did: &str) -> HttpError { 13 HttpError::for_not_found(None, format!("DID not registered: {did}")) 14} 15 16fn err_did_tombstoned(did: &str) -> HttpError { 17 HttpError::for_client_error( 18 None, 19 ClientErrorStatusCode::GONE, 20 format!("DID not available: {did}"), 21 ) 22} 23 24#[endpoint{ 25 method = GET, 26 path = "/", 27}] 28// strictly this isn't the correct type, but it works 29pub async fn index(_rqctx: RequestContext<ApiContext>) -> Result<Response<Body>, HttpError> { 30 Ok(Response::builder() 31 .status(200) 32 .header("Content-Type", "text/plain") 33 .body(Body::with_content(include_str!("./index.txt")))?) 34} 35 36#[derive(Debug, JsonSchema, Deserialize)] 37pub struct DidPathParams { 38 pub did: String, 39} 40 41#[endpoint { 42 method = GET, 43 path = "/{did}", 44}] 45pub async fn resolve_did( 46 rqctx: RequestContext<ApiContext>, 47 path: Path<DidPathParams>, 48) -> Result<HttpResponseOk<DidDocument>, HttpError> { 49 let conn = rqctx.context().get_conn().await?; 50 let did = path.into_inner().did; 51 52 if did == "favicon.ico" { 53 return Err(HttpError::for_not_found(None, Default::default())); 54 } 55 56 if !did.starts_with("did:plc:") { 57 return Err(HttpError::for_bad_request(None, "Invalid DID".to_string())); 58 } 59 60 let op = db::get_latest_operation(&conn, &did) 61 .await 62 .map_err(|v| HttpError::for_internal_error(v.to_string()))? 63 .ok_or(err_did_not_found(&did))?; 64 65 let decoded: PlcOperationType = serde_json::from_value(op.operation) 66 .map_err(|v| HttpError::for_internal_error(v.to_string()))?; 67 68 let doc = match decoded { 69 PlcOperationType::Tombstone(_) => return Err(err_did_tombstoned(&did)), 70 PlcOperationType::Create(op) => DidDocument::from_create(&did, op), 71 PlcOperationType::Operation(op) => DidDocument::from_plc_op(&did, op), 72 }; 73 74 Ok(HttpResponseOk(doc)) 75} 76 77#[endpoint { 78 method = GET, 79 path = "/{did}/log", 80}] 81pub async fn get_plc_op_log( 82 rqctx: RequestContext<ApiContext>, 83 path: Path<DidPathParams>, 84) -> Result<HttpResponseOk<Vec<PlcOperationType>>, HttpError> { 85 let conn = rqctx.context().get_conn().await?; 86 let did = path.into_inner().did; 87 88 let ops = db::get_operations(&conn, &did) 89 .await 90 .map_err(|v| HttpError::for_internal_error(v.to_string()))?; 91 92 if ops.is_empty() { 93 return Err(err_did_not_found(&did)); 94 } 95 96 let ops = ops 97 .into_iter() 98 .map(|op| serde_json::from_value(op.operation)) 99 .collect::<Result<Vec<_>, _>>() 100 .map_err(|v| HttpError::for_internal_error(v.to_string()))?; 101 102 Ok(HttpResponseOk(ops)) 103} 104 105#[endpoint { 106 method = GET, 107 path = "/{did}/log/audit", 108}] 109pub async fn get_plc_audit_log( 110 rqctx: RequestContext<ApiContext>, 111 path: Path<DidPathParams>, 112) -> Result<HttpResponseOk<Vec<PlcEntry>>, HttpError> { 113 let conn = rqctx.context().get_conn().await?; 114 let did = path.into_inner().did; 115 116 let ops = db::get_operations(&conn, &did) 117 .await 118 .map_err(|v| HttpError::for_internal_error(v.to_string()))?; 119 120 if ops.is_empty() { 121 return Err(err_did_not_found(&did)); 122 } 123 124 let entries = ops 125 .into_iter() 126 .map(|op| { 127 let operation = serde_json::from_value(op.operation)?; 128 let cid = Cid::from_str(&op.hash).unwrap(); 129 130 let entry = PlcEntry { 131 did: did.clone(), 132 operation, 133 cid: JsonCid(cid), 134 nullified: op.nullified, 135 created_at: op.created_at, 136 }; 137 138 Ok(entry) 139 }) 140 .collect::<Result<Vec<_>, _>>() 141 .map_err(|v: eyre::Report| HttpError::for_internal_error(v.to_string()))?; 142 143 Ok(HttpResponseOk(entries)) 144} 145 146#[endpoint { 147 method = GET, 148 path = "/{did}/log/last", 149}] 150pub async fn get_last_op( 151 rqctx: RequestContext<ApiContext>, 152 path: Path<DidPathParams>, 153) -> Result<HttpResponseOk<PlcOperationType>, HttpError> { 154 let conn = rqctx.context().get_conn().await?; 155 let did = path.into_inner().did; 156 157 let op = db::get_latest_operation(&conn, &did) 158 .await 159 .map_err(|v| HttpError::for_internal_error(v.to_string()))? 160 .ok_or(err_did_not_found(&did))?; 161 162 let decoded = serde_json::from_value(op.operation) 163 .map_err(|v| HttpError::for_internal_error(v.to_string()))?; 164 165 Ok(HttpResponseOk(decoded)) 166}