Skip to content

Commit 2371de5

Browse files
authored
Add base64 response for files (#588)
1 parent f6edbe6 commit 2371de5

File tree

9 files changed

+179
-110
lines changed

9 files changed

+179
-110
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.4.3
2+
3+
* Add endpoints to fetch files as base64 for identity, contacts, companies and bills
4+
15
# 0.4.2
26

37
* Add logic to get endorsees from plaintext chain

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace.package]
2-
version = "0.4.2"
2+
version = "0.4.3"
33
edition = "2024"
44
license = "MIT"
55

crates/bcr-ebill-wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ gloo-timers = { version = "0.3.0", features = ["futures"] }
3333
tsify = { version = "0.4.5", features = ["js"] }
3434
bcr-ebill-api = { path = "../bcr-ebill-api" }
3535
bcr-ebill-transport.workspace = true
36+
base64.workspace = true

crates/bcr-ebill-wasm/main.js

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ let config = {
5656
job_runner_check_interval_seconds: 600,
5757
// default_mint_url: "http://localhost:4343",
5858
default_mint_url: "https://wildcat-dev-docker.minibill.tech",
59-
// default_mint_node_id: "bitcrt038d1bd3e2e3a01f20c861f18eb456cc33f869c9aaa5dec685f7f7d8c40ea3b3c7",
60-
default_mint_node_id: "bitcrt0372422bfa5c9b60ef2f10ced26e764983b2e675cd7fac374f5f223616530ce3fb", // dev mint
59+
// default_mint_node_id: "bitcrt038d1bd3e2e3a01f20c861f18eb456cc33f869c9aaa5dec685f7f7d8c40ea3b3c7",
60+
default_mint_node_id: "bitcrt03cbb9254a24df6bad6243227cadf257c25eb10c2177c1ee85bfaefde3bf532ab6", // dev mint
6161
};
6262

6363
async function start() {
@@ -334,14 +334,8 @@ async function fetchTempFile() {
334334
async function fetchContactFile() {
335335
let node_id = document.getElementById("contact_id").value;
336336
let file_name = document.getElementById("contact_file_name").value;
337-
let file = await contactApi.file(node_id, file_name);
338-
let file_bytes = file.data;
339-
let arr = new Uint8Array(file_bytes);
340-
let blob = new Blob([arr], { type: file.content_type });
341-
let url = URL.createObjectURL(blob);
342-
343-
console.log("file", file, url, blob);
344-
document.getElementById("attached_file").src = url;
337+
let file = await contactApi.file_base64(node_id, file_name);
338+
document.getElementById("attached_file").src = `data:${file.content_type};base64,${file.data}`;
345339
}
346340

347341
async function switchIdentity() {
@@ -523,11 +517,8 @@ async function fetchBillFile() {
523517
let detail = await billApi.detail(bill_id);
524518

525519
if (detail.data.files.length > 0) {
526-
let file = await billApi.attachment(bill_id, detail.data.files[0].name);
527-
let file_bytes = file.data;
528-
let arr = new Uint8Array(file_bytes);
529-
let blob = new Blob([arr], { type: file.content_type });
530-
document.getElementById("bill_attached_file").src = URL.createObjectURL(blob);
520+
let file = await billApi.attachment_base64(bill_id, detail.data.files[0].name);
521+
document.getElementById("bill_attached_file").src = `data:${file.content_type};base64,${file.data}`;
531522
} else {
532523
console.log("Bill has no file");
533524
}

crates/bcr-ebill-wasm/src/api/bill.rs

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::str::FromStr;
22

33
use super::Result;
4+
use base64::{Engine as _, engine::general_purpose::STANDARD};
45
use bcr_ebill_api::{
56
data::{
67
NodeId,
@@ -25,7 +26,7 @@ use crate::{
2526
api::identity::get_current_identity_node_id,
2627
context::get_ctx,
2728
data::{
28-
BinaryFileResponse, UploadFile, UploadFileResponse,
29+
Base64FileResponse, BinaryFileResponse, UploadFile, UploadFileResponse,
2930
bill::{
3031
AcceptBitcreditBillPayload, BillCombinedBitcoinKeyWeb, BillIdResponse,
3132
BillNumbersToWordsForSum, BillsResponse, BillsSearchFilterPayload,
@@ -42,6 +43,44 @@ use crate::{
4243

4344
use super::identity::get_current_identity;
4445

46+
async fn get_attachment(bill_id: &str, file_name: &str) -> Result<(Vec<u8>, String)> {
47+
let parsed_bill_id = BillId::from_str(bill_id)?;
48+
let current_timestamp = util::date::now().timestamp() as u64;
49+
let identity = get_ctx().identity_service.get_identity().await?;
50+
// get bill
51+
let bill = get_ctx()
52+
.bill_service
53+
.get_detail(
54+
&parsed_bill_id,
55+
&identity,
56+
&get_current_identity_node_id().await?,
57+
current_timestamp,
58+
)
59+
.await?;
60+
61+
// check if this file even exists on the bill
62+
let file = match bill.data.files.iter().find(|f| f.name == file_name) {
63+
Some(f) => f,
64+
None => {
65+
return Err(bcr_ebill_api::service::bill_service::Error::NotFound.into());
66+
}
67+
};
68+
69+
// fetch the attachment
70+
let keys = get_ctx()
71+
.bill_service
72+
.get_bill_keys(&parsed_bill_id)
73+
.await?;
74+
let file_bytes = get_ctx()
75+
.bill_service
76+
.open_and_decrypt_attached_file(&parsed_bill_id, file, &keys.private_key)
77+
.await?;
78+
79+
let content_type = detect_content_type_for_bytes(&file_bytes)
80+
.ok_or(Error::Validation(ValidationError::InvalidContentType))?;
81+
Ok((file_bytes, content_type))
82+
}
83+
4584
#[wasm_bindgen]
4685
pub struct Bill;
4786

@@ -111,41 +150,7 @@ impl Bill {
111150

112151
#[wasm_bindgen(unchecked_return_type = "BinaryFileResponse")]
113152
pub async fn attachment(&self, bill_id: &str, file_name: &str) -> Result<JsValue> {
114-
let parsed_bill_id = BillId::from_str(bill_id)?;
115-
let current_timestamp = util::date::now().timestamp() as u64;
116-
let identity = get_ctx().identity_service.get_identity().await?;
117-
// get bill
118-
let bill = get_ctx()
119-
.bill_service
120-
.get_detail(
121-
&parsed_bill_id,
122-
&identity,
123-
&get_current_identity_node_id().await?,
124-
current_timestamp,
125-
)
126-
.await?;
127-
128-
// check if this file even exists on the bill
129-
let file = match bill.data.files.iter().find(|f| f.name == file_name) {
130-
Some(f) => f,
131-
None => {
132-
return Err(bcr_ebill_api::service::bill_service::Error::NotFound.into());
133-
}
134-
};
135-
136-
// fetch the attachment
137-
let keys = get_ctx()
138-
.bill_service
139-
.get_bill_keys(&parsed_bill_id)
140-
.await?;
141-
let file_bytes = get_ctx()
142-
.bill_service
143-
.open_and_decrypt_attached_file(&parsed_bill_id, file, &keys.private_key)
144-
.await?;
145-
146-
let content_type = detect_content_type_for_bytes(&file_bytes)
147-
.ok_or(Error::Validation(ValidationError::InvalidContentType))?;
148-
153+
let (file_bytes, content_type) = get_attachment(bill_id, file_name).await?;
149154
let res = serde_wasm_bindgen::to_value(&BinaryFileResponse {
150155
data: file_bytes,
151156
name: file_name.to_owned(),
@@ -154,6 +159,17 @@ impl Bill {
154159
Ok(res)
155160
}
156161

162+
#[wasm_bindgen(unchecked_return_type = "Base64FileResponse")]
163+
pub async fn attachment_base64(&self, bill_id: &str, file_name: &str) -> Result<JsValue> {
164+
let (file_bytes, content_type) = get_attachment(bill_id, file_name).await?;
165+
let res = serde_wasm_bindgen::to_value(&Base64FileResponse {
166+
data: STANDARD.encode(&file_bytes),
167+
name: file_name.to_owned(),
168+
content_type,
169+
})?;
170+
Ok(res)
171+
}
172+
157173
#[wasm_bindgen(unchecked_return_type = "UploadFileResponse")]
158174
pub async fn upload(
159175
&self,

crates/bcr-ebill-wasm/src/api/company.rs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::str::FromStr;
22

33
use super::Result;
4+
use base64::{Engine as _, engine::general_purpose::STANDARD};
45
use bcr_ebill_api::{
56
data::{NodeId, OptionalPostalAddress, PostalAddress},
67
external,
@@ -16,14 +17,37 @@ use wasm_bindgen::prelude::*;
1617
use crate::{
1718
context::get_ctx,
1819
data::{
19-
BinaryFileResponse, UploadFile, UploadFileResponse,
20+
Base64FileResponse, BinaryFileResponse, UploadFile, UploadFileResponse,
2021
company::{
2122
AddSignatoryPayload, CompaniesResponse, CompanyWeb, CreateCompanyPayload,
2223
EditCompanyPayload, ListSignatoriesResponse, RemoveSignatoryPayload, SignatoryResponse,
2324
},
2425
},
2526
};
2627

28+
async fn get_file(id: &str, file_name: &str) -> Result<(Vec<u8>, String)> {
29+
let parsed_id = NodeId::from_str(id)?;
30+
let company = get_ctx()
31+
.company_service
32+
.get_company_by_id(&parsed_id)
33+
.await?; // check if company exists
34+
let private_key = get_ctx()
35+
.identity_service
36+
.get_full_identity()
37+
.await?
38+
.key_pair
39+
.get_private_key();
40+
41+
let file_bytes = get_ctx()
42+
.company_service
43+
.open_and_decrypt_file(company, &parsed_id, file_name, &private_key)
44+
.await?;
45+
46+
let content_type = detect_content_type_for_bytes(&file_bytes)
47+
.ok_or(Error::Validation(ValidationError::InvalidContentType))?;
48+
Ok((file_bytes, content_type))
49+
}
50+
2751
#[wasm_bindgen]
2852
pub struct Company;
2953

@@ -36,26 +60,7 @@ impl Company {
3660

3761
#[wasm_bindgen(unchecked_return_type = "BinaryFileResponse")]
3862
pub async fn file(&self, id: &str, file_name: &str) -> Result<JsValue> {
39-
let parsed_id = NodeId::from_str(id)?;
40-
let company = get_ctx()
41-
.company_service
42-
.get_company_by_id(&parsed_id)
43-
.await?; // check if company exists
44-
let private_key = get_ctx()
45-
.identity_service
46-
.get_full_identity()
47-
.await?
48-
.key_pair
49-
.get_private_key();
50-
51-
let file_bytes = get_ctx()
52-
.company_service
53-
.open_and_decrypt_file(company, &parsed_id, file_name, &private_key)
54-
.await?;
55-
56-
let content_type = detect_content_type_for_bytes(&file_bytes)
57-
.ok_or(Error::Validation(ValidationError::InvalidContentType))?;
58-
63+
let (file_bytes, content_type) = get_file(id, file_name).await?;
5964
let res = serde_wasm_bindgen::to_value(&BinaryFileResponse {
6065
data: file_bytes,
6166
name: file_name.to_owned(),
@@ -64,6 +69,18 @@ impl Company {
6469
Ok(res)
6570
}
6671

72+
#[wasm_bindgen(unchecked_return_type = "Base64FileResponse")]
73+
pub async fn file_base64(&self, id: &str, file_name: &str) -> Result<JsValue> {
74+
let (file_bytes, content_type) = get_file(id, file_name).await?;
75+
76+
let res = serde_wasm_bindgen::to_value(&Base64FileResponse {
77+
data: STANDARD.encode(&file_bytes),
78+
name: file_name.to_owned(),
79+
content_type,
80+
})?;
81+
Ok(res)
82+
}
83+
6784
#[wasm_bindgen(unchecked_return_type = "UploadFileResponse")]
6885
pub async fn upload(
6986
&self,

crates/bcr-ebill-wasm/src/api/contact.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::str::FromStr;
33
use crate::data::contact::{
44
ContactTypeWeb, ContactWeb, ContactsResponse, EditContactPayload, NewContactPayload,
55
};
6-
use crate::data::{BinaryFileResponse, UploadFile, UploadFileResponse};
6+
use crate::data::{Base64FileResponse, BinaryFileResponse, UploadFile, UploadFileResponse};
77
use crate::{Result, context::get_ctx};
8+
use base64::{Engine as _, engine::general_purpose::STANDARD};
89
use bcr_ebill_api::data::contact::ContactType;
910
use bcr_ebill_api::data::{NodeId, OptionalPostalAddress, PostalAddress};
1011
use bcr_ebill_api::service;
@@ -15,6 +16,30 @@ use wasm_bindgen::prelude::*;
1516
#[wasm_bindgen]
1617
pub struct Contact;
1718

19+
async fn get_file(node_id: &str, file_name: &str) -> Result<(Vec<u8>, String)> {
20+
let parsed_node_id = NodeId::from_str(node_id)?;
21+
let contact = get_ctx()
22+
.contact_service
23+
.get_contact(&parsed_node_id)
24+
.await?; // check if contact exists
25+
26+
let private_key = get_ctx()
27+
.identity_service
28+
.get_full_identity()
29+
.await?
30+
.key_pair
31+
.get_private_key();
32+
33+
let file_bytes = get_ctx()
34+
.contact_service
35+
.open_and_decrypt_file(contact, &parsed_node_id, file_name, &private_key)
36+
.await?;
37+
let content_type = detect_content_type_for_bytes(&file_bytes).ok_or(
38+
service::Error::Validation(ValidationError::InvalidContentType),
39+
)?;
40+
Ok((file_bytes, content_type))
41+
}
42+
1843
#[wasm_bindgen]
1944
impl Contact {
2045
#[wasm_bindgen]
@@ -24,28 +49,7 @@ impl Contact {
2449

2550
#[wasm_bindgen(unchecked_return_type = "BinaryFileResponse")]
2651
pub async fn file(&self, node_id: &str, file_name: &str) -> Result<JsValue> {
27-
let parsed_node_id = NodeId::from_str(node_id)?;
28-
let contact = get_ctx()
29-
.contact_service
30-
.get_contact(&parsed_node_id)
31-
.await?; // check if contact exists
32-
33-
let private_key = get_ctx()
34-
.identity_service
35-
.get_full_identity()
36-
.await?
37-
.key_pair
38-
.get_private_key();
39-
40-
let file_bytes = get_ctx()
41-
.contact_service
42-
.open_and_decrypt_file(contact, &parsed_node_id, file_name, &private_key)
43-
.await?;
44-
45-
let content_type = detect_content_type_for_bytes(&file_bytes).ok_or(
46-
service::Error::Validation(ValidationError::InvalidContentType),
47-
)?;
48-
52+
let (file_bytes, content_type) = get_file(node_id, file_name).await?;
4953
let res = serde_wasm_bindgen::to_value(&BinaryFileResponse {
5054
data: file_bytes,
5155
name: file_name.to_owned(),
@@ -54,6 +58,18 @@ impl Contact {
5458
Ok(res)
5559
}
5660

61+
#[wasm_bindgen(unchecked_return_type = "Base64FileResponse")]
62+
pub async fn file_base64(&self, node_id: &str, file_name: &str) -> Result<JsValue> {
63+
let (file_bytes, content_type) = get_file(node_id, file_name).await?;
64+
65+
let res = serde_wasm_bindgen::to_value(&Base64FileResponse {
66+
data: STANDARD.encode(&file_bytes),
67+
name: file_name.to_owned(),
68+
content_type,
69+
})?;
70+
Ok(res)
71+
}
72+
5773
#[wasm_bindgen(unchecked_return_type = "UploadFileResponse")]
5874
pub async fn upload(
5975
&self,

0 commit comments

Comments
 (0)