+143
src/api.rs
+143
src/api.rs
···
1
+
use crate::types::{DidDocument, JsonCid, PlcEntry, PlcOperationType};
2
+
use crate::{ApiContext, db};
3
+
use dropshot::{ClientErrorStatusCode, HttpError, HttpResponseOk, Path, RequestContext, endpoint};
4
+
use ipld_core::cid::Cid;
5
+
use schemars::JsonSchema;
6
+
use serde::Deserialize;
7
+
use std::str::FromStr;
8
+
9
+
fn err_did_not_found(did: &str) -> HttpError {
10
+
HttpError::for_not_found(None, format!("DID not registered: {did}"))
11
+
}
12
+
13
+
fn err_did_tombstoned(did: &str) -> HttpError {
14
+
HttpError::for_client_error(
15
+
None,
16
+
ClientErrorStatusCode::GONE,
17
+
format!("DID not available: {did}"),
18
+
)
19
+
}
20
+
21
+
#[derive(Debug, JsonSchema, Deserialize)]
22
+
pub struct DidPathParams {
23
+
pub did: String,
24
+
}
25
+
26
+
#[endpoint {
27
+
method = GET,
28
+
path = "/{did}",
29
+
}]
30
+
pub async fn resolve_did(
31
+
rqctx: RequestContext<ApiContext>,
32
+
path: Path<DidPathParams>,
33
+
) -> Result<HttpResponseOk<DidDocument>, HttpError> {
34
+
let conn = rqctx.context().get_conn().await?;
35
+
let did = path.into_inner().did;
36
+
37
+
let op = db::get_latest_operation(&conn, &did)
38
+
.await
39
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?
40
+
.ok_or(err_did_not_found(&did))?;
41
+
42
+
let decoded: PlcOperationType = serde_json::from_value(op.operation)
43
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?;
44
+
45
+
let doc = match decoded {
46
+
PlcOperationType::Tombstone(_) => return Err(err_did_tombstoned(&did)),
47
+
PlcOperationType::Create(op) => DidDocument::from_create(&did, op),
48
+
PlcOperationType::Operation(op) => DidDocument::from_plc_op(&did, op),
49
+
};
50
+
51
+
Ok(HttpResponseOk(doc))
52
+
}
53
+
54
+
#[endpoint {
55
+
method = GET,
56
+
path = "/{did}/log",
57
+
}]
58
+
pub async fn get_plc_op_log(
59
+
rqctx: RequestContext<ApiContext>,
60
+
path: Path<DidPathParams>,
61
+
) -> Result<HttpResponseOk<Vec<PlcOperationType>>, HttpError> {
62
+
let conn = rqctx.context().get_conn().await?;
63
+
let did = path.into_inner().did;
64
+
65
+
let ops = db::get_operations(&conn, &did)
66
+
.await
67
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?;
68
+
69
+
if ops.is_empty() {
70
+
return Err(err_did_not_found(&did));
71
+
}
72
+
73
+
let ops = ops
74
+
.into_iter()
75
+
.map(|op| serde_json::from_value(op.operation))
76
+
.collect::<Result<Vec<_>, _>>()
77
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?;
78
+
79
+
Ok(HttpResponseOk(ops))
80
+
}
81
+
82
+
#[endpoint {
83
+
method = GET,
84
+
path = "/{did}/log/audit",
85
+
}]
86
+
pub async fn get_plc_audit_log(
87
+
rqctx: RequestContext<ApiContext>,
88
+
path: Path<DidPathParams>,
89
+
) -> Result<HttpResponseOk<Vec<PlcEntry>>, HttpError> {
90
+
let conn = rqctx.context().get_conn().await?;
91
+
let did = path.into_inner().did;
92
+
93
+
let ops = db::get_operations(&conn, &did)
94
+
.await
95
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?;
96
+
97
+
if ops.is_empty() {
98
+
return Err(err_did_not_found(&did));
99
+
}
100
+
101
+
let entries = ops
102
+
.into_iter()
103
+
.map(|op| {
104
+
let operation = serde_json::from_value(op.operation)?;
105
+
let cid = Cid::from_str(&op.hash).unwrap();
106
+
107
+
let entry = PlcEntry {
108
+
did: did.clone(),
109
+
operation,
110
+
cid: JsonCid(cid),
111
+
nullified: op.nullified,
112
+
created_at: op.created_at,
113
+
};
114
+
115
+
Ok(entry)
116
+
})
117
+
.collect::<Result<Vec<_>, _>>()
118
+
.map_err(|v: eyre::Report| HttpError::for_internal_error(v.to_string()))?;
119
+
120
+
Ok(HttpResponseOk(entries))
121
+
}
122
+
123
+
#[endpoint {
124
+
method = GET,
125
+
path = "/{did}/log/last",
126
+
}]
127
+
pub async fn get_last_op(
128
+
rqctx: RequestContext<ApiContext>,
129
+
path: Path<DidPathParams>,
130
+
) -> Result<HttpResponseOk<PlcOperationType>, HttpError> {
131
+
let conn = rqctx.context().get_conn().await?;
132
+
let did = path.into_inner().did;
133
+
134
+
let op = db::get_latest_operation(&conn, &did)
135
+
.await
136
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?
137
+
.ok_or(err_did_not_found(&did))?;
138
+
139
+
let decoded = serde_json::from_value(op.operation)
140
+
.map_err(|v| HttpError::for_internal_error(v.to_string()))?;
141
+
142
+
Ok(HttpResponseOk(decoded))
143
+
}
+56
src/db.rs
+56
src/db.rs
···
1
+
use chrono::{DateTime, Utc};
2
+
use deadpool_postgres::Object;
3
+
use serde_json::Value;
4
+
use tokio_postgres::Row;
5
+
6
+
pub struct Operation {
7
+
pub did: String,
8
+
pub hash: String,
9
+
pub prev: Option<String>,
10
+
pub sig: String,
11
+
pub nullified: bool,
12
+
pub operation: Value,
13
+
pub created_at: DateTime<Utc>,
14
+
}
15
+
16
+
impl From<Row> for Operation {
17
+
fn from(row: Row) -> Self {
18
+
Operation {
19
+
did: row.get(0),
20
+
hash: row.get(1),
21
+
prev: row.get(2),
22
+
sig: row.get(3),
23
+
nullified: row.get(4),
24
+
operation: row.get(5),
25
+
created_at: row.get(6),
26
+
}
27
+
}
28
+
}
29
+
30
+
pub async fn get_latest_operation(
31
+
conn: &Object,
32
+
did: &str,
33
+
) -> Result<Option<Operation>, tokio_postgres::Error> {
34
+
let maybe_op = conn
35
+
.query_opt(
36
+
"SELECT * FROM operations WHERE did=$1 ORDER BY created_at DESC LIMIT 1",
37
+
&[&did],
38
+
)
39
+
.await?;
40
+
41
+
Ok(maybe_op.map(Operation::from))
42
+
}
43
+
44
+
pub async fn get_operations(
45
+
conn: &Object,
46
+
did: &str,
47
+
) -> Result<Vec<Operation>, tokio_postgres::Error> {
48
+
let ops = conn
49
+
.query(
50
+
"SELECT * FROM operations WHERE did=$1 ORDER BY created_at",
51
+
&[&did],
52
+
)
53
+
.await?;
54
+
55
+
Ok(ops.into_iter().map(Operation::from).collect())
56
+
}
+15
-5
src/lib.rs
+15
-5
src/lib.rs
···
1
-
use deadpool_postgres::{Manager, Pool};
2
-
use dropshot::{ApiDescription, ConfigLogging, ConfigLoggingLevel};
1
+
use deadpool_postgres::{Manager, Object, Pool};
2
+
use dropshot::{ApiDescription, ConfigLogging, ConfigLoggingLevel, HttpError};
3
3
use eyre::Context;
4
4
use slog::Logger;
5
5
use std::env::var;
6
6
use std::str::FromStr;
7
7
use tokio_postgres::{Config, NoTls};
8
8
9
+
mod api;
9
10
mod db;
10
11
pub mod import;
11
12
mod types;
···
16
17
pub pool: Pool,
17
18
}
18
19
20
+
impl ApiContext {
21
+
pub async fn get_conn(&self) -> Result<Object, HttpError> {
22
+
self.pool.get().await.map_err(|err| HttpError::for_internal_error(err.to_string()))
23
+
}
24
+
}
25
+
19
26
pub fn create_logger() -> eyre::Result<Logger> {
20
27
let log = ConfigLogging::StderrTerminal { level: ConfigLoggingLevel::Info }
21
28
.to_logger("plc-mirror")?;
···
24
31
}
25
32
26
33
pub fn create_api() -> eyre::Result<ApiDescription<ApiContext>> {
27
-
let mut api = ApiDescription::new();
34
+
let mut api_desc = ApiDescription::new();
28
35
29
-
// TODO: api endpoints
36
+
api_desc.register(api::get_plc_op_log)?;
37
+
api_desc.register(api::get_plc_audit_log)?;
38
+
api_desc.register(api::get_last_op)?;
39
+
api_desc.register(api::resolve_did)?;
30
40
31
-
Ok(api)
41
+
Ok(api_desc)
32
42
}
33
43
34
44
pub async fn connect_db() -> eyre::Result<Pool> {