diff --git a/lib/facts/blob-service.js b/lib/facts/blob-service.js index 9c4ffb606..22aa92112 100644 --- a/lib/facts/blob-service.js +++ b/lib/facts/blob-service.js @@ -1,47 +1,51 @@ // @flow import { isDev } from '../utils/dev-utils.js'; type BlobServicePath = '/blob/:blobHash' | '/blob' | '/holders'; export type BlobServiceHTTPEndpoint = { +path: BlobServicePath, +method: 'GET' | 'POST' | 'PUT' | 'DELETE', }; type BlobServiceConfig = { +url: string, +httpEndpoints: { +[endpoint: string]: BlobServiceHTTPEndpoint }, }; const httpEndpoints = Object.freeze({ GET_BLOB: { path: '/blob/:blobHash', method: 'GET', }, ASSIGN_HOLDER: { path: '/blob', method: 'POST', }, ASSIGN_MULTIPLE_HOLDERS: { path: '/holders', method: 'POST', }, UPLOAD_BLOB: { path: '/blob', method: 'PUT', }, DELETE_BLOB: { path: '/blob', method: 'DELETE', }, + REMOVE_MULTIPLE_HOLDERS: { + path: '/holders', + method: 'DELETE', + }, }); const config: BlobServiceConfig = { url: isDev ? 'https://blob.staging.commtechnologies.org' : 'https://blob.commtechnologies.org', httpEndpoints, }; export default config; diff --git a/lib/types/blob-service-types.js b/lib/types/blob-service-types.js new file mode 100644 index 000000000..396d24ec2 --- /dev/null +++ b/lib/types/blob-service-types.js @@ -0,0 +1,78 @@ +// @flow + +import t, { type TInterface } from 'tcomb'; + +import { tShape } from '../utils/validation-utils.js'; + +/* + * This file defines types and validation for HTTP requests and responses + * for the Blob Service. The definitions in this file should remain in sync + * with the structures defined in the `http` submodule of the corresponding + * Rust file at `shared/comm-lib/src/blob/types.rs`. + * + * If you edit the definitions in one file, + * please make sure to update the corresponding definitions in the other. + */ + +export type BlobInfo = { + +blobHash: string, + +holder: string, +}; +export const blobInfoValidator: TInterface = tShape({ + blobHash: t.String, + holder: t.String, +}); + +export type HolderAssignmentResult = $ReadOnly<{ + ...BlobInfo, + // `true` if adding this holder was successful. + // Also true when `holderAlreadyExists` is true. + +success: boolean, + // `true` when given holder already existed + +holderAlreadyExists: boolean, + // `true` if blob hash has been uploaded before. + +dataExists: boolean, +}>; +export const holderAssignmentResultValidator: TInterface = + tShape({ + blobHash: t.String, + holder: t.String, + success: t.Boolean, + holderAlreadyExists: t.Boolean, + dataExists: t.Boolean, + }); + +export type AssignHoldersRequest = { + +requests: $ReadOnlyArray, +}; + +export type AssignHoldersResponse = { + +results: $ReadOnlyArray, +}; +export const assignHoldersResponseValidator: TInterface = + tShape({ + results: t.list(holderAssignmentResultValidator), + }); + +type RemoveHolderItemsRequest = { + +requests: $ReadOnlyArray, + // Whether to instantly delete blob after last holder is removed, without + // waiting for cleanup. Defaults to `false` if not provided. + +instantDelete?: boolean, +}; +type RemoveHoldersByTagRequest = { + +tags: $ReadOnlyArray, +}; +export type RemoveHoldersRequest = + | RemoveHolderItemsRequest + | RemoveHoldersByTagRequest; + +export type RemoveHoldersResponse = { + // Holder removal requests that failed server-side are returned here. + // This can be passed into retry request body. + +failedRequests: $ReadOnlyArray, +}; +export const removeHoldersResponseValidator: TInterface = + tShape({ + failedRequests: t.list(blobInfoValidator), + }); diff --git a/shared/comm-lib/src/blob/types.rs b/shared/comm-lib/src/blob/types.rs index 0480543f3..b3aaa2a3c 100644 --- a/shared/comm-lib/src/blob/types.rs +++ b/shared/comm-lib/src/blob/types.rs @@ -1,134 +1,141 @@ use derive_more::Constructor; use hex::ToHex; use sha2::{Digest, Sha256}; +/// This module defines structures for HTTP requests and responses +/// for the Blob Service. The definitions in this file should remain in sync +/// with the types and validators defined in the corresponding +/// JavaScript file at `lib/types/blob-service-types.js`. +/// +/// If you edit the definitions in one file, +/// please make sure to update the corresponding definitions in the other. pub mod http { use serde::{Deserialize, Serialize}; pub use super::BlobInfo; // Assign multiple holders #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AssignHoldersRequest { pub requests: Vec, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct HolderAssignmentResult { #[serde(flatten)] pub request: BlobInfo, pub success: bool, pub data_exists: bool, pub holder_already_exists: bool, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AssignHoldersResponse { pub results: Vec, } // Remove multiple holders #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RemoveHoldersRequest { pub requests: Vec, #[serde(default)] pub instant_delete: bool, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RemoveHoldersResponse { pub failed_requests: Vec, } // Single holder endpoint types #[derive(Serialize, Deserialize, Debug)] pub struct AssignHolderRequest { pub blob_hash: String, pub holder: String, } #[derive(Serialize, Deserialize, Debug)] pub struct AssignHolderResponse { pub data_exists: bool, } #[derive(Serialize, Deserialize, Debug)] pub struct RemoveHolderRequest { pub blob_hash: String, pub holder: String, /// If true, the blob will be deleted intantly /// after the last holder is revoked. #[serde(default)] pub instant_delete: bool, } } /// Blob owning information - stores both blob_hash and holder #[derive(Clone, Debug, Constructor, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct BlobInfo { pub blob_hash: String, pub holder: String, } impl BlobInfo { pub fn from_bytes(data: &[u8]) -> Self { Self { blob_hash: Sha256::digest(data).encode_hex(), holder: uuid::Uuid::new_v4().to_string(), } } } #[cfg(feature = "aws")] mod db_conversions { use super::*; use crate::database::{AttributeTryInto, DBItemError, TryFromAttribute}; use aws_sdk_dynamodb::types::AttributeValue; use std::collections::HashMap; const BLOB_HASH_DDB_MAP_KEY: &str = "blob_hash"; const HOLDER_DDB_MAP_KEY: &str = "holder"; impl From for AttributeValue { fn from(value: BlobInfo) -> Self { let map = HashMap::from([ ( BLOB_HASH_DDB_MAP_KEY.to_string(), AttributeValue::S(value.blob_hash), ), ( HOLDER_DDB_MAP_KEY.to_string(), AttributeValue::S(value.holder), ), ]); AttributeValue::M(map) } } impl From<&BlobInfo> for AttributeValue { fn from(value: &BlobInfo) -> Self { AttributeValue::from(value.to_owned()) } } impl TryFromAttribute for BlobInfo { fn try_from_attr( attribute_name: impl Into, attribute: Option, ) -> Result { let attr_name: String = attribute_name.into(); let mut inner_map: HashMap = attribute.attr_try_into(&attr_name)?; let blob_hash = inner_map .remove("blob_hash") .attr_try_into(format!("{attr_name}.blob_hash"))?; let holder = inner_map .remove("holder") .attr_try_into(format!("{attr_name}.holder"))?; Ok(BlobInfo { blob_hash, holder }) } } }