use 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 works pub async fn index(_rqctx: RequestContext) -> Result, 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, path: Path, ) -> Result, 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, path: Path, ) -> Result>, 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::, _>>() .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, path: Path, ) -> Result>, 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::, _>>() .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, path: Path, ) -> Result, 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)) }