Something went wrong. Try again.
Chris’s PDS implementation
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145//! Discovery, health, and session endpoints.
use crate::http::request::{JsonRequest, QueryRequest};use atrium_api::{ com::atproto::{ identity::resolve_handle, server::{deactivate_account, get_session}, }, did_doc::DidDocument, types::string::{Did, Handle},};use axum::{ Json, extract::{Query, State}, http::{HeaderMap, StatusCode}, response::{Html, IntoResponse as _},};use serde::Serialize;
use crate::{ AppState, error::{Error, ErrorContext as _},};
use super::{ApiResult, auth};
pub async fn index() -> Html<&'static str> { Html( "<!doctype html><html><head><title>cpds</title></head>\ <body><main><h1>cpds</h1><p>A small Personal Data Server.</p></main></body></html>", )}
pub async fn health() -> &'static str { "ok\n"}
pub async fn ready(State(state): State<AppState>) -> axum::response::Response { match state.repository.check().await { Ok(()) => (axum::http::StatusCode::OK, "ready\n").into_response(), Err(error) => { tracing::warn!(%error, "readiness check failed"); super::error::response( axum::http::StatusCode::SERVICE_UNAVAILABLE, "ServiceUnavailable", "server is not ready", ) } }}
pub async fn did_document(State(state): State<AppState>) -> Json<DidDocument> { Json(state.identity.local_document().clone())}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct DescribeServer { did: String, available_user_domains: Vec<String>, invite_code_required: bool,}
pub async fn describe_server(State(state): State<AppState>) -> Json<DescribeServer> { Json(DescribeServer { did: state.config.account.did.clone(), available_user_domains: Vec::new(), invite_code_required: true, })}
pub async fn get_session( State(state): State<AppState>, headers: HeaderMap,) -> ApiResult<Json<get_session::OutputData>> { let (token, status) = auth::token_with_status(&headers, &state)?; let account = token.account; let did = Did::new(account.did).context(Error::Internal, "invalid stored account DID")?; let handle = Handle::new(account.handle).context(Error::Internal, "invalid stored account handle")?; Ok(Json(get_session::OutputData { active: Some(status.is_active()), did, did_doc: None, email: None, email_auth_factor: None, email_confirmed: None, handle, status: status.inactive_reason().map(ToOwned::to_owned), }))}
pub async fn activate_account( State(state): State<AppState>, headers: HeaderMap,) -> ApiResult<StatusCode> { let (token, _) = auth::token_with_status(&headers, &state)?; state.repository.activate_account(&token.account.did)?; Ok(StatusCode::OK)}
pub async fn deactivate_account( (State(state), headers, Json(_input)): JsonRequest<deactivate_account::InputData>,) -> ApiResult<StatusCode> { let (token, _) = auth::token_with_status(&headers, &state)?; state.repository.deactivate_account(&token.account.did)?; Ok(StatusCode::OK)}
pub async fn resolve_handle( (State(state), Query(query)): QueryRequest<resolve_handle::ParametersData>,) -> ApiResult<Json<resolve_handle::OutputData>> { let did = state.identity.resolve_handle(&query.handle).await?; Ok(Json(resolve_handle::OutputData { did }))}
#[cfg(test)]mod tests { use super::{get_session, resolve_handle}; use atrium_api::types::Object; use serde_json::json;
#[test] fn server_response_data_matches_empty_extension_objects() { let did = "did:web:alice.test"; let handle: resolve_handle::OutputData = serde_json::from_value(json!({"did": did})).unwrap(); assert_eq!( serde_json::to_vec(&handle).unwrap(), serde_json::to_vec(&Object::from(handle.clone())).unwrap(), ); for (active, status) in [(true, None), (false, Some("deactivated"))] { let session: get_session::OutputData = serde_json::from_value(json!({ "active": active, "did": did, "handle": "alice.test", "status": status, "didDoc": null, "email": null, "emailAuthFactor": null, "emailConfirmed": null, })) .unwrap(); assert_eq!( serde_json::to_vec(&session).unwrap(), serde_json::to_vec(&Object::from(session.clone())).unwrap(), ); } }}