Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-gateway): Catalyst signed docs endpoints initial implementation #1510

Merged
merged 34 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9ad8d59
feat(cat-gateway): Add document get endpoint
stevenj Jan 6, 2025
ce2189e
feat(cat-gateway): Add generic cbor payload type
stevenj Jan 6, 2025
a97c2db
fix(docs): spelling
stevenj Jan 6, 2025
6970364
fix(cat-gateway): code format
stevenj Jan 6, 2025
fd95bac
feat(cat-gateway): Add document put endpoint
stevenj Jan 7, 2025
1e9191b
Merge branch 'main' into feat/add-doc-endpoints
stevenj Jan 8, 2025
92b4296
fix(cat-gateway): fix documentation for the error responses.
stevenj Jan 8, 2025
f5ef33e
Merge branch 'feat/add-doc-endpoints' of github.com:input-output-hk/c…
stevenj Jan 8, 2025
195f879
refactor RetryAfterOption
Mr-Leshiy Jan 9, 2025
8bd207a
fix documentation for the error responses.
Mr-Leshiy Jan 9, 2025
0487269
update full signed doc query
Mr-Leshiy Jan 9, 2025
7d5b3f4
Merge branch 'main' into feat/add-doc-endpoints
Mr-Leshiy Jan 9, 2025
54f0075
add put documents endpoint initial impl
Mr-Leshiy Jan 10, 2025
6676eaf
update put_document impl and get_document impl
Mr-Leshiy Jan 13, 2025
40d5da4
fix CI checks
Mr-Leshiy Jan 13, 2025
fb8b365
updating author field to be an array of TEXT
Mr-Leshiy Jan 13, 2025
693e8ec
feat(general): Bump cat-ci to v3.2.31 to get documentation updates
stevenj Jan 14, 2025
a086f0d
feat(docs): Add alternative OpenAPI viewers because Elements has issues
stevenj Jan 14, 2025
d5d5780
add authors
Mr-Leshiy Jan 14, 2025
0bee3fc
wip
Mr-Leshiy Jan 14, 2025
7182732
Merge branch 'main' into feat/add-doc-endpoints
stevenj Jan 14, 2025
5259c12
Merge branch 'feat/add-doc-endpoints' into feat/put-doc-endpoint
Mr-Leshiy Jan 14, 2025
f320366
fix error handling
Mr-Leshiy Jan 14, 2025
8be1bb7
Merge branch 'main' into feat/put-doc-endpoint
Mr-Leshiy Jan 15, 2025
ad80f63
fix
Mr-Leshiy Jan 15, 2025
b64ac3d
fix
Mr-Leshiy Jan 15, 2025
b3fd211
Merge branch 'main' into feat/put-doc-endpoint
Mr-Leshiy Jan 15, 2025
f9b69cf
fix errors
Mr-Leshiy Jan 16, 2025
9a40deb
update catalyst-signed-doc crate ver
Mr-Leshiy Jan 16, 2025
336ebf3
fix
Mr-Leshiy Jan 16, 2025
52c23c5
revert
Mr-Leshiy Jan 16, 2025
6b68573
fix
Mr-Leshiy Jan 16, 2025
c2d04df
fix sqlfluff
Mr-Leshiy Jan 16, 2025
1c68d5e
Merge branch 'main' into feat/put-doc-endpoint
Mr-Leshiy Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions catalyst-gateway/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ workspace = true
cardano-chain-follower = {version = "0.0.6", git = "https://github.com/input-output-hk/catalyst-libs.git", tag="v0.0.10" }
c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.3" }
rbac-registration = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "v0.0.8" }
catalyst-signed-doc = { version = "0.0.1", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250116-00" }


pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
pallas-traverse = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" }
Expand Down
28 changes: 18 additions & 10 deletions catalyst-gateway/bin/src/db/event/signed_docs/full_signed_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ impl FullSignedDoc {

/// Returns the document author.
#[allow(dead_code)]
pub(crate) fn author(&self) -> &String {
self.body.author()
pub(crate) fn authors(&self) -> &Vec<String> {
self.body.authors()
}

/// Returns the `SignedDocBody`.
Expand All @@ -57,7 +57,15 @@ impl FullSignedDoc {
&self.body
}

/// Returns the document raw bytes.
#[allow(dead_code)]
pub(crate) fn raw(&self) -> &Vec<u8> {
&self.raw
}

/// Uploads a `FullSignedDoc` to the event db.
/// Returns `true` if document was added into the db, `false` if it was already added
/// previously.
///
/// Make an insert query into the `event-db` by adding data into the `signed_docs`
/// table.
Expand All @@ -73,21 +81,21 @@ impl FullSignedDoc {
/// - `ver` is a UUID v7
/// - `doc_type` is a UUID v4
#[allow(dead_code)]
pub(crate) async fn store(&self) -> anyhow::Result<()> {
pub(crate) async fn store(&self) -> anyhow::Result<bool> {
match Self::retrieve(self.id(), Some(self.ver())).await {
Ok(res_doc) => {
anyhow::ensure!(
&res_doc == self,
"Document with the same `id` and `ver` already exists"
);
return Ok(());
Ok(false)
},
Err(err) if err.is::<NotFoundError>() => {},
Err(err) => return Err(err),
Err(err) if err.is::<NotFoundError>() => {
EventDB::modify(INSERT_SIGNED_DOCS, &self.postgres_db_fields()).await?;
Ok(true)
},
Err(err) => Err(err),
}

EventDB::modify(INSERT_SIGNED_DOCS, &self.postgres_db_fields()).await?;
Ok(())
}

/// Loads a `FullSignedDoc` from the event db.
Expand Down Expand Up @@ -146,7 +154,7 @@ impl FullSignedDoc {
*id,
ver,
row.try_get("type")?,
row.try_get("author")?,
row.try_get("authors")?,
row.try_get("metadata")?,
),
payload: row.try_get("payload")?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub(crate) enum DocsQueryFilter {
DocId(uuid::Uuid),
/// Select docs with the specific `id` and `ver` field
DocVer(uuid::Uuid, uuid::Uuid),
/// Select docs with the specific `author` field
/// Select docs with the specific `authors` field
Author(String),
}

Expand All @@ -26,7 +26,7 @@ impl Display for DocsQueryFilter {
Self::DocVer(id, ver) => {
write!(f, "signed_docs.id = '{id}' AND signed_docs.ver = '{ver}'")
},
Self::Author(author) => write!(f, "signed_docs.author = '{author}'"),
Self::Author(author) => write!(f, "signed_docs.authors @> '{{ \"{author}\" }}'"),
}
}
}
21 changes: 10 additions & 11 deletions catalyst-gateway/bin/src/db/event/signed_docs/signed_doc_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ pub(crate) struct SignedDocBody {
ver: uuid::Uuid,
/// `signed_doc` table `type` field
doc_type: uuid::Uuid,
/// `signed_doc` table `author` field
author: String,
/// `signed_doc` table `authors` field
authors: Vec<String>,
/// `signed_doc` table `metadata` field
metadata: Option<serde_json::Value>,
}
Expand All @@ -40,9 +40,9 @@ impl SignedDocBody {
&self.ver
}

/// Returns the document author.
pub(crate) fn author(&self) -> &String {
&self.author
/// Returns the document authors.
pub(crate) fn authors(&self) -> &Vec<String> {
&self.authors
}

/// Returns all signed document fields for the event db queries
Expand All @@ -51,22 +51,21 @@ impl SignedDocBody {
&self.id,
&self.ver,
&self.doc_type,
&self.author,
&self.authors,
&self.metadata,
]
}

/// Creates a `SignedDocBody` instance.
#[allow(dead_code)]
pub(crate) fn new(
id: uuid::Uuid, ver: uuid::Uuid, doc_type: uuid::Uuid, author: String,
id: uuid::Uuid, ver: uuid::Uuid, doc_type: uuid::Uuid, authors: Vec<String>,
metadata: Option<serde_json::Value>,
) -> Self {
Self {
id,
ver,
doc_type,
author,
authors,
metadata,
}
}
Expand All @@ -91,13 +90,13 @@ impl SignedDocBody {
let id = row.try_get("id")?;
let ver = row.try_get("ver")?;
let doc_type = row.try_get("type")?;
let author = row.try_get("author")?;
let authors = row.try_get("authors")?;
let metadata = row.try_get("metadata")?;
Ok(Self {
id,
ver,
doc_type,
author,
authors,
metadata,
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SELECT
signed_docs.id,
signed_docs.ver,
signed_docs.type,
signed_docs.author,
signed_docs.authors,
signed_docs.metadata
FROM signed_docs
WHERE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ INSERT INTO signed_docs
id,
ver,
type,
author,
authors,
metadata,
payload,
raw
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SELECT
{% if not ver %} signed_docs.ver, {% endif %}
signed_docs.type,
signed_docs.author,
signed_docs.authors,
signed_docs.metadata,
signed_docs.payload,
signed_docs.raw
Expand Down
20 changes: 13 additions & 7 deletions catalyst-gateway/bin/src/db/event/signed_docs/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async fn queries_test() {
uuid::Uuid::now_v7(),
uuid::Uuid::now_v7(),
doc_type,
"Alex".to_string(),
vec!["Alex".to_string()],
Some(serde_json::Value::Null),
),
Some(serde_json::Value::Null),
Expand All @@ -29,7 +29,7 @@ async fn queries_test() {
uuid::Uuid::now_v7(),
uuid::Uuid::now_v7(),
doc_type,
"Steven".to_string(),
vec!["Steven".to_string()],
Some(serde_json::Value::Null),
),
Some(serde_json::Value::Null),
Expand All @@ -40,7 +40,7 @@ async fn queries_test() {
uuid::Uuid::now_v7(),
uuid::Uuid::now_v7(),
doc_type,
"Sasha".to_string(),
vec!["Sasha".to_string()],
None,
),
None,
Expand All @@ -49,12 +49,18 @@ async fn queries_test() {
];

for doc in &docs {
doc.store().await.unwrap();
assert!(doc.store().await.unwrap());
// try to insert the same data again
doc.store().await.unwrap();
assert!(!doc.store().await.unwrap());
// try another doc with the same `id` and `ver` and with different other fields
let another_doc = FullSignedDoc::new(
SignedDocBody::new(*doc.id(), *doc.ver(), doc_type, "Neil".to_string(), None),
SignedDocBody::new(
*doc.id(),
*doc.ver(),
doc_type,
vec!["Neil".to_string()],
None,
),
None,
vec![],
);
Expand Down Expand Up @@ -87,7 +93,7 @@ async fn queries_test() {
assert!(res_docs.try_next().await.unwrap().is_none());

let mut res_docs = SignedDocBody::retrieve(
&DocsQueryFilter::Author(doc.author().clone()),
&DocsQueryFilter::Author(doc.authors().first().unwrap().clone()),
&QueryLimits::ALL,
)
.await
Expand Down
17 changes: 10 additions & 7 deletions catalyst-gateway/bin/src/service/api/documents/get_document.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Implementation of the GET `/document` endpoint

use poem::Body;
use poem_openapi::ApiResponse;

use crate::service::common::{responses::WithErrorResponses, types::payload::cbor::Cbor};
use crate::{
db::event::{error::NotFoundError, signed_docs::FullSignedDoc},
service::common::{responses::WithErrorResponses, types::payload::cbor::Cbor},
};

/// Endpoint responses.
#[derive(ApiResponse)]
Expand All @@ -13,7 +15,7 @@ pub(crate) enum Responses {
///
/// The Document that was requested.
#[oai(status = 200)]
Ok(Cbor<Body>),
Ok(Cbor<Vec<u8>>),
/// ## Not Found
///
/// The document could not be found.
Expand All @@ -27,8 +29,9 @@ pub(crate) type AllResponses = WithErrorResponses<Responses>;
/// # GET `/document`
#[allow(clippy::unused_async, clippy::no_effect_underscore_binding)]
pub(crate) async fn endpoint(document_id: uuid::Uuid, version: Option<uuid::Uuid>) -> AllResponses {
let _doc = document_id;
let _ver = version;

Responses::NotFound.into()
match FullSignedDoc::retrieve(&document_id, version.as_ref()).await {
Ok(doc) => Responses::Ok(Cbor(doc.raw().clone())).into(),
Err(err) if err.is::<NotFoundError>() => Responses::NotFound.into(),
Err(err) => AllResponses::handle_error(&err),
}
}
20 changes: 12 additions & 8 deletions catalyst-gateway/bin/src/service/api/documents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ use put_document::{bad_put_request::PutDocumentBadRequest, MAXIMUM_DOCUMENT_SIZE

use crate::service::{
common::{
self,
auth::{none_or_rbac::NoneOrRBAC, rbac::scheme::CatalystRBACSecurityScheme},
auth::rbac::scheme::CatalystRBACSecurityScheme,
tags::ApiTags,
types::{generic::uuidv7::UUIDv7, payload::cbor::Cbor},
types::{
generic::{
query::pagination::{Limit, Page},
uuidv7::UUIDv7,
},
payload::cbor::Cbor,
},
},
utilities::middleware::schema_validation::schema_version_validation,
};
Expand Down Expand Up @@ -46,7 +51,7 @@ impl DocumentApi {
/// version.
version: Query<Option<UUIDv7>>,
/// No Authorization required, but Token permitted.
_auth: NoneOrRBAC,
_auth: CatalystRBACSecurityScheme,
) -> get_document::AllResponses {
let Ok(doc_id) = document_id.0.try_into() else {
let err = anyhow!("Invalid UUIDv7"); // Should not happen as UUIDv7 is validating.
Expand Down Expand Up @@ -76,9 +81,9 @@ impl DocumentApi {
_auth: CatalystRBACSecurityScheme,
) -> put_document::AllResponses {
match document.0.into_bytes_limit(MAXIMUM_DOCUMENT_SIZE).await {
Ok(document) => put_document::endpoint(document).await,
Ok(doc_bytes) => put_document::endpoint(doc_bytes.to_vec()).await,
Err(ReadBodyError::PayloadTooLarge) => put_document::Responses::PayloadTooLarge.into(),
Err(_err) => {
Err(_) => {
put_document::Responses::BadRequest(Json(PutDocumentBadRequest::new(
"Failed to read document from the request",
)))
Expand All @@ -103,8 +108,7 @@ impl DocumentApi {
async fn post_document(
&self, /// The Query Filter Specification
query: Json<DocumentIndexQueryFilterBody>,
page: Query<Option<common::types::generic::query::pagination::Page>>,
limit: Query<Option<common::types::generic::query::pagination::Limit>>,
page: Query<Option<Page>>, limit: Query<Option<Limit>>,
/// Authorization required.
_auth: CatalystRBACSecurityScheme,
) -> post_document_index_query::AllResponses {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use poem_openapi::{payload::Json, ApiResponse, Object};
use query_filter::DocumentIndexQueryFilter;
use response::DocumentIndexListDocumented;

use super::common;
use super::{Limit, Page};
use crate::service::common::responses::WithErrorResponses;

pub(crate) mod query_filter;
Expand Down Expand Up @@ -39,9 +39,7 @@ pub(crate) struct QueryDocumentIndex {
/// # POST `/document/index`
#[allow(clippy::unused_async, clippy::no_effect_underscore_binding)]
pub(crate) async fn endpoint(
filter: DocumentIndexQueryFilter,
page: Option<common::types::generic::query::pagination::Page>,
limit: Option<common::types::generic::query::pagination::Limit>,
filter: DocumentIndexQueryFilter, page: Option<Page>, limit: Option<Limit>,
) -> AllResponses {
let _filter = filter;
let _page = page;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

use poem_openapi::{types::Example, NewType, Object};

use super::common::types::document::ver::EqOrRangedVerDocumented;
use crate::service::common::types::document::{
doc_ref::IdAndVerRefDocumented, doc_type::DocumentType, id::EqOrRangedIdDocumented,
ver::EqOrRangedVerDocumented,
};

/// Query Filter for the generation of a signed document index.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ pub(crate) struct PutDocumentBadRequest {

impl PutDocumentBadRequest {
/// Create a new instance of `ConfigBadRequest`.
pub(crate) fn new(error: &str) -> Self {
pub(crate) fn new(error: &(impl ToString + ?Sized)) -> Self {
Self {
error: error.to_owned(),
error: error.to_string(),
}
}
}
Expand Down
Loading
Loading