Skip to content

Commit 9d31aa8

Browse files
authored
Email Notifications Sending (#619)
1 parent e15c86f commit 9d31aa8

File tree

19 files changed

+382
-284
lines changed

19 files changed

+382
-284
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* Email Notifications
55
* Add email notifications API
66
* Add email notifications registration API
7+
* Add email notifications sending logic
8+
* Fix issue where the notification sender defaulted to the personal identity instead of the signer identity
79
* Added `app_url` property to config - defaults to `https://bitcredit-dev.minibill.tech` (config break)
810
* Small fix to WASM build addressing the rustwasm organization archiving
911

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.4"
2+
version = "0.4.5"
33
edition = "2024"
44
license = "MIT"
55

crates/bcr-ebill-api/src/external/email.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,11 @@ impl EmailClientApi for EmailClient {
153153
.to_bech32()
154154
.map_err(|_| Error::NostrKey)?;
155155

156-
let payload = SendEmailNotificationPayload {
156+
let payload = NotificationSendPayload {
157157
kind: kind.to_string(),
158158
id: id.to_string(),
159-
sender: sender_npub,
160159
receiver: receiver.npub().to_bech32().map_err(|_| Error::NostrKey)?,
160+
sender: sender_npub,
161161
};
162162

163163
let key_pair = Keypair::from_secret_key(SECP256K1, private_key);
@@ -206,14 +206,14 @@ pub struct RegisterEmailNotificationResponse {
206206

207207
#[derive(Debug, Serialize)]
208208
pub struct SendEmailNotificationRequest {
209-
pub payload: SendEmailNotificationPayload,
209+
pub payload: NotificationSendPayload,
210210
pub signature: String,
211211
}
212212

213213
#[derive(Debug, Serialize, BorshSerialize)]
214-
pub struct SendEmailNotificationPayload {
214+
pub struct NotificationSendPayload {
215215
pub kind: String,
216216
pub id: String,
217-
pub sender: String,
218217
pub receiver: String,
218+
pub sender: String,
219219
}

crates/bcr-ebill-api/src/service/bill_service/issue.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ impl BillService {
156156
}
157157

158158
let mut bill_files: Vec<File> = vec![];
159+
// TODO(multi-relay): don't default to first
159160
if let Some(nostr_relay) = nostr_relays.first() {
160161
for file_upload_id in data.file_upload_ids.iter() {
161162
let (file_name, file_bytes) = &self

crates/bcr-ebill-api/src/service/bill_service/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3948,8 +3948,11 @@ pub mod tests {
39483948
always(),
39493949
eq(ActionType::AcceptBill),
39503950
recipient_check.clone(),
3951+
always(),
3952+
always(),
3953+
always(),
39513954
)
3952-
.returning(|_, _, _, _, _| Ok(()));
3955+
.returning(|_, _, _, _, _, _, _, _| Ok(()));
39533956

39543957
// send pay timeout notification
39553958
ctx.notification_service
@@ -3960,8 +3963,11 @@ pub mod tests {
39603963
always(),
39613964
eq(ActionType::PayBill),
39623965
recipient_check,
3966+
always(),
3967+
always(),
3968+
always(),
39633969
)
3964-
.returning(|_, _, _, _, _| Ok(()));
3970+
.returning(|_, _, _, _, _, _, _, _| Ok(()));
39653971

39663972
// marks accept bill timeout as sent
39673973
ctx.notification_service

crates/bcr-ebill-api/src/service/bill_service/payment.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use super::Result;
22
use super::service::BillService;
3-
use crate::service::bill_service::{BillAction, BillServiceApi};
3+
use crate::service::{
4+
bill_service::{BillAction, BillServiceApi},
5+
notification_service::event::BillChainEvent,
6+
};
47
use bcr_ebill_core::{
58
NodeId,
6-
bill::{BillId, PaymentState, RecourseReason},
9+
bill::{BillId, BillKeys, BitcreditBill, PaymentState, RecourseReason},
710
blockchain::{
811
Blockchain,
912
bill::{
10-
BillOpCode, OfferToSellWaitingForPayment, RecourseWaitingForPayment,
13+
BillBlockchain, BillOpCode, OfferToSellWaitingForPayment, RecourseWaitingForPayment,
1114
block::BillRecourseReasonBlockData,
1215
},
1316
},
@@ -72,6 +75,14 @@ impl BillService {
7275
.await?;
7376
// invalidate bill cache, so payment state is updated on next fetch
7477
self.store.invalidate_bill_in_cache(bill_id).await?;
78+
// the bill is paid now - trigger notification
79+
if let PaymentState::PaidConfirmed(_) = payment_state
80+
&& let Err(e) = self
81+
.trigger_is_paid_notification(identity, &chain, &bill_keys, &bill)
82+
.await
83+
{
84+
log::error!("Could not send is-paid notification for {bill_id}: {e}");
85+
}
7586
}
7687
}
7788
Err(e) => {
@@ -81,6 +92,26 @@ impl BillService {
8192
Ok(())
8293
}
8394

95+
async fn trigger_is_paid_notification(
96+
&self,
97+
identity: &Identity,
98+
blockchain: &BillBlockchain,
99+
bill_keys: &BillKeys,
100+
last_version_bill: &BitcreditBill,
101+
) -> Result<()> {
102+
let chain_event = BillChainEvent::new(
103+
last_version_bill,
104+
blockchain,
105+
bill_keys,
106+
true,
107+
&identity.node_id, // TODO(company-notifications): how to handle jobs as company participant?
108+
)?;
109+
self.notification_service
110+
.send_bill_is_paid_event(&chain_event)
111+
.await?;
112+
Ok(())
113+
}
114+
84115
pub(super) async fn check_bill_in_recourse_payment(
85116
&self,
86117
bill_id: &BillId,

crates/bcr-ebill-api/src/service/bill_service/propagation.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ impl BillService {
2020
bill_action: &BillAction,
2121
identity: &Identity,
2222
contacts: &HashMap<NodeId, Contact>,
23+
signer_node_id: &NodeId,
2324
) -> Result<()> {
2425
let last_version_bill = self
2526
.get_last_version_bill(blockchain, bill_keys, identity, contacts)
@@ -30,7 +31,7 @@ impl BillService {
3031
blockchain,
3132
bill_keys,
3233
true,
33-
&identity.node_id,
34+
signer_node_id,
3435
)?;
3536

3637
match bill_action {

crates/bcr-ebill-api/src/service/bill_service/service.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ use bcr_ebill_core::bill::{
3535
BillIssueData, BillValidateActionData, PastPaymentDataPayment, PastPaymentDataRecourse,
3636
PastPaymentDataSell, PastPaymentResult, PastPaymentStatus, PaymentState,
3737
};
38-
use bcr_ebill_core::blockchain::bill::block::BillParticipantBlockData;
38+
use bcr_ebill_core::blockchain::bill::block::{
39+
BillParticipantBlockData, BillRequestRecourseBlockData,
40+
};
3941
use bcr_ebill_core::blockchain::bill::create_bill_to_share_with_external_party;
4042
use bcr_ebill_core::company::{Company, CompanyKeys};
4143
use bcr_ebill_core::constants::{
@@ -215,6 +217,7 @@ impl BillService {
215217
let bill_keys = self.store.get_keys(bill_id).await?;
216218
let latest_ts = chain.get_latest_block().timestamp;
217219
let contacts = self.contact_store.get_map().await?;
220+
let mut recoursee = None;
218221

219222
if let Some(action) = match chain.get_latest_block().op_code {
220223
BillOpCode::RequestToPay | BillOpCode::OfferToSell
@@ -226,6 +229,12 @@ impl BillService {
226229
Some(ActionType::AcceptBill)
227230
}
228231
BillOpCode::RequestRecourse if (latest_ts + RECOURSE_DEADLINE_SECONDS <= now) => {
232+
if let Ok(recourse_block) = chain
233+
.get_latest_block()
234+
.get_decrypted_block::<BillRequestRecourseBlockData>(&bill_keys)
235+
{
236+
recoursee = Some(recourse_block.recoursee.node_id());
237+
}
229238
Some(ActionType::RecourseBill)
230239
}
231240
_ => None,
@@ -256,6 +265,9 @@ impl BillService {
256265
.get_last_version_bill(&chain, &bill_keys, &identity, &contacts)
257266
.await?;
258267

268+
let holder = bill.endorsee.as_ref().unwrap_or(&bill.payee).node_id();
269+
let drawee = &bill.drawee.node_id;
270+
259271
for node_id in participants {
260272
if let Some(contact) = self.contact_store.get(&node_id).await? {
261273
recipient_options.push(Some(contact.try_into()?));
@@ -269,11 +281,14 @@ impl BillService {
269281

270282
self.notification_service
271283
.send_request_to_action_timed_out_event(
272-
&identity.node_id,
284+
&identity.node_id, // TODO(company-notifications): how to handle jobs as company participant?
273285
bill_id,
274286
Some(bill.sum),
275287
action.to_owned(),
276288
recipients,
289+
&holder,
290+
drawee,
291+
&recoursee,
277292
)
278293
.await?;
279294

@@ -934,6 +949,7 @@ impl BillServiceApi for BillService {
934949
debug!("getting file {} for bill with id: {bill_id}", file.name);
935950
validate_bill_id_network(bill_id)?;
936951
let nostr_relays = get_config().nostr_config.relays.clone();
952+
// TODO(multi-relay): don't default to first
937953
if let Some(nostr_relay) = nostr_relays.first() {
938954
let file_bytes = self
939955
.file_upload_client
@@ -1028,6 +1044,7 @@ impl BillServiceApi for BillService {
10281044
&bill_action,
10291045
&identity.identity,
10301046
&contacts,
1047+
&signer_public_data.node_id(),
10311048
)
10321049
.await?;
10331050

crates/bcr-ebill-api/src/service/company_service.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ impl CompanyServiceApi for CompanyService {
300300
));
301301
}
302302

303+
// TODO(multi-relay): don't default to first
303304
let (proof_of_registration_file, logo_file) = match nostr_relays.first() {
304305
Some(nostr_relay) => {
305306
// Save the files locally with the identity public key
@@ -522,6 +523,7 @@ impl CompanyServiceApi for CompanyService {
522523
return Ok(());
523524
}
524525

526+
// TODO(multi-relay): don't default to first
525527
let (logo_file, proof_of_registration_file) = match nostr_relays.first() {
526528
Some(nostr_relay) => {
527529
let logo_file = if ignore_logo_file_upload_id {
@@ -820,6 +822,7 @@ impl CompanyServiceApi for CompanyService {
820822
debug!("getting file {file_name} for company with id: {id}",);
821823
validate_node_id_network(id)?;
822824
let nostr_relays = get_config().nostr_config.relays.clone();
825+
// TODO(multi-relay): don't default to first
823826
if let Some(nostr_relay) = nostr_relays.first() {
824827
let mut file = None;
825828
if let Some(logo_file) = company.logo_file

crates/bcr-ebill-api/src/service/contact_service.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ impl ContactServiceApi for ContactService {
372372
return Ok(());
373373
}
374374

375+
// TODO(multi-relay): don't default to first
375376
if let Some(nostr_relay) = nostr_relays.first() {
376377
if !ignore_avatar_file_upload_id {
377378
let avatar_file = self
@@ -447,6 +448,7 @@ impl ContactServiceApi for ContactService {
447448

448449
let contact = match t {
449450
ContactType::Company | ContactType::Person => {
451+
// TODO(multi-relay): don't default to first
450452
let (avatar_file, proof_document_file) = match nostr_relays.first() {
451453
Some(nostr_relay) => {
452454
let avatar_file = self
@@ -564,6 +566,7 @@ impl ContactServiceApi for ContactService {
564566

565567
let identity_public_key = self.identity_store.get_key_pair().await?.pub_key();
566568

569+
// TODO(multi-relay): don't default to first
567570
let (avatar_file, proof_document_file) = match nostr_relays.first() {
568571
Some(nostr_relay) => {
569572
let avatar_file = self
@@ -649,6 +652,7 @@ impl ContactServiceApi for ContactService {
649652
debug!("getting file {file_name} for contact with id: {node_id}",);
650653
validate_node_id_network(node_id)?;
651654
let nostr_relays = contact.nostr_relays.clone();
655+
// TODO(multi-relay): don't default to first
652656
if let Some(nostr_relay) = nostr_relays.first() {
653657
let mut file = None;
654658
if let Some(avatar_file) = contact.avatar_file

0 commit comments

Comments
 (0)