Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Add different file size limits for pictures (avatar/logo - 20k) and documents (invoices, registration, passport - 1mb) as well as an upper limit for bill files (100)
* This limit is checked at creation/update time, not at the time of uploading a temporary file
* Add the address of the signer for the calls to `endorsements` and `past_endorsees`
* Add api call `active_notifications_for_node_ids` on `notification` API, which returns for a set of node ids, whether they have active notifications
* If the set of node ids is empty, only the node ids that have active notifications are returned

# 0.4.2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,17 @@ impl NotificationServiceApi for DefaultNotificationService {
.collect()
}

async fn get_active_notification_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>> {
Ok(self
.notification_store
.get_active_status_for_node_ids(node_ids)
.await
.unwrap_or_default())
Comment thread
zupzup marked this conversation as resolved.
}

async fn check_bill_notification_sent(
&self,
bill_id: &BillId,
Expand Down
8 changes: 8 additions & 0 deletions crates/bcr-ebill-api/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ pub mod tests {

#[async_trait]
impl NotificationStoreApi for NotificationStoreApiMock {
async fn get_active_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>>;
async fn add(&self, notification: Notification) -> Result<Notification>;
async fn list(&self, filter: NotificationFilter) -> Result<Vec<Notification>>;
async fn get_latest_by_references(
Expand Down Expand Up @@ -426,6 +430,10 @@ pub mod tests {
async fn mark_notification_as_done(&self, notification_id: &str) -> bcr_ebill_transport::Result<()>;
async fn get_active_bill_notification(&self, bill_id: &BillId) -> Option<Notification>;
async fn get_active_bill_notifications(&self, bill_ids: &[BillId]) -> HashMap<BillId, Notification>;
async fn get_active_notification_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> bcr_ebill_transport::Result<HashMap<NodeId, bool>>;
async fn check_bill_notification_sent(
&self,
bill_id: &BillId,
Expand Down
120 changes: 119 additions & 1 deletion crates/bcr-ebill-persistence/src/db/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ impl ServiceTraitBounds for SurrealNotificationStore {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NotificationStoreApi for SurrealNotificationStore {
/// Returns node ids with an active notification for the given node ids
async fn get_active_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>> {
let mut bindings = Bindings::default();
bindings.add("table", Self::TABLE)?;
bindings.add("node_ids", node_ids.to_owned())?;

let node_id_filter = if node_ids.is_empty() {
""
} else {
"and node_id in $node_ids"
};

let result: Vec<NodeIdDb> = self.db.query(&format!("SELECT node_id from notifications where active = true {node_id_filter} GROUP BY node_id"), bindings).await?;
let mut res: HashMap<NodeId, bool> = HashMap::new();

if node_ids.is_empty() {
for node_id_db in result {
res.insert(node_id_db.node_id.to_owned(), true);
}
} else {
for node_id in node_ids {
res.insert(
node_id.to_owned(),
result.iter().any(|n| n.node_id == *node_id),
);
}
}
Ok(res)
}

/// Stores a new notification into the database
async fn add(&self, notification: Notification) -> Result<Notification> {
let id = notification.id.to_owned();
Expand Down Expand Up @@ -205,6 +238,11 @@ impl NotificationStoreApi for SurrealNotificationStore {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeIdDb {
pub node_id: NodeId,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct NotificationDb {
pub id: Thing,
Expand Down Expand Up @@ -274,7 +312,7 @@ mod tests {
use super::*;
use crate::{
db::get_memory_db,
tests::tests::{bill_id_test, bill_id_test_other, node_id_test},
tests::tests::{bill_id_test, bill_id_test_other, node_id_test, node_id_test_other},
util::date::now,
};

Expand Down Expand Up @@ -533,6 +571,86 @@ mod tests {
});
}

#[tokio::test]
async fn test_returns_active_status_for_node_ids() {
let store = get_store().await;
let notification1 = test_notification(&bill_id_test(), Some(test_payload()));
let mut notification2 = test_notification(&bill_id_test_other(), Some(test_payload()));
notification2.node_id = Some(node_id_test_other());
let notification3 = test_general_notification();
let _ = store
.add(notification1.clone())
.await
.expect("notification created");
let _ = store
.add(notification2.clone())
.await
.expect("notification created");
let _ = store
.add(notification3.clone())
.await
.expect("notification created");

let status = store
.get_active_status_for_node_ids(&[])
.await
.expect("returns status");

assert_eq!(status.len(), 2, "should have all node ids in list");
assert!(status.get(&node_id_test()).unwrap());
assert!(status.get(&node_id_test_other()).unwrap());

let status = store
.get_active_status_for_node_ids(&[node_id_test()])
.await
.expect("returns status");

assert_eq!(
status.len(),
1,
"should have all given node ids in the list"
);
assert!(status.get(&node_id_test()).unwrap());

store
.mark_as_done(&notification2.clone().id)
.await
.expect("notification marked done");

let status = store
.get_active_status_for_node_ids(&[node_id_test_other()])
.await
.expect("returns status");

assert_eq!(
status.len(),
1,
"should have all given node ids in the list"
);
assert!(!status.get(&node_id_test_other()).unwrap());

let status = store
.get_active_status_for_node_ids(&[node_id_test(), node_id_test_other()])
.await
.expect("returns status");

assert_eq!(status.len(), 2, "should have all given node ids in list");
assert!(status.get(&node_id_test()).unwrap());
assert!(!status.get(&node_id_test_other()).unwrap());

let status = store
.get_active_status_for_node_ids(&[])
.await
.expect("returns status");

assert_eq!(
status.len(),
1,
"should have all active notif node ids in list"
);
assert!(status.get(&node_id_test()).unwrap());
}

fn test_notification(bill_id: &BillId, payload: Option<Value>) -> Notification {
Notification::new_bill_notification(bill_id, &node_id_test(), "test_notification", payload)
}
Expand Down
5 changes: 5 additions & 0 deletions crates/bcr-ebill-persistence/src/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ use bcr_ebill_core::{
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NotificationStoreApi: ServiceTraitBounds {
/// Returns node ids with an active notification for the given node ids
async fn get_active_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>>;
/// Stores a new notification into the database
async fn add(&self, notification: Notification) -> Result<Notification>;
/// Returns all currently active notifications from the database
Expand Down
4 changes: 4 additions & 0 deletions crates/bcr-ebill-transport/src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ mod test_utils {

#[async_trait]
impl NotificationStoreApi for NotificationStore {
async fn get_active_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>>;
async fn add(&self, notification: Notification) -> Result<Notification>;
async fn list(&self, filter: NotificationFilter) -> Result<Vec<Notification>>;
async fn get_latest_by_references(
Expand Down
5 changes: 5 additions & 0 deletions crates/bcr-ebill-transport/src/notification_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ pub trait NotificationServiceApi: ServiceTraitBounds {
bill_ids: &[BillId],
) -> HashMap<BillId, Notification>;

async fn get_active_notification_status_for_node_ids(
&self,
node_ids: &[NodeId],
) -> Result<HashMap<NodeId, bool>>;

/// Returns whether a notification was already sent for the given bill id and action
async fn check_bill_notification_sent(
&self,
Expand Down
1 change: 1 addition & 0 deletions crates/bcr-ebill-wasm/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ <h2>Uploads</h2>
<h2>Notification Testing</h2>
<div>
<button type="button" id="notif">trigger local push notif</button>
<button type="button" id="get_active_notif_status">Get Active Notification Status for Node Ids</button>
</div>
</div>
<div>
Expand Down
8 changes: 8 additions & 0 deletions crates/bcr-ebill-wasm/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as wasm from '../pkg/index.js';

document.getElementById("fileInput").addEventListener("change", uploadFile);
document.getElementById("notif").addEventListener("click", triggerNotif);
document.getElementById("get_active_notif_status").addEventListener("click", getActiveNotif);
document.getElementById("company_create").addEventListener("click", createCompany);
document.getElementById("contact_test").addEventListener("click", triggerContact);
document.getElementById("contact_test_anon").addEventListener("click", triggerAnonContact);
Expand Down Expand Up @@ -591,3 +592,10 @@ async function deleteContact() {
await measured();
}

async function getActiveNotif() {
let measured = measure(async () => {
return await notificationTriggerApi.active_notifications_for_node_ids([]);
});
await measured();
}

25 changes: 23 additions & 2 deletions crates/bcr-ebill-wasm/src/api/notification.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use super::Result;
use crate::{
context::get_ctx,
data::{NotificationFilters, notification::NotificationWeb},
data::{
NotificationFilters,
notification::{NotificationStatusWeb, NotificationWeb},
},
};
use bcr_ebill_api::NotificationFilter;
use bcr_ebill_api::{NotificationFilter, data::NodeId};
use log::{error, info};
use wasm_bindgen::prelude::*;

Expand All @@ -17,6 +20,24 @@ impl Notification {
Notification
}

#[wasm_bindgen(unchecked_return_type = "NotificationStatusWeb[]")]
pub async fn active_notifications_for_node_ids(
&self,
#[wasm_bindgen(unchecked_param_type = "Vec<String>")] node_ids: JsValue,
) -> Result<JsValue> {
let node_ids_parsed: Vec<NodeId> = serde_wasm_bindgen::from_value(node_ids)?;
let notification_status = get_ctx()
.notification_service
.get_active_notification_status_for_node_ids(&node_ids_parsed)
.await?;
let web: Vec<NotificationStatusWeb> = notification_status
.into_iter()
.map(|(node_id, active)| NotificationStatusWeb { node_id, active })
.collect();
let res = serde_wasm_bindgen::to_value(&web)?;
Ok(res)
}

#[wasm_bindgen]
pub async fn subscribe(&self, callback: js_sys::Function) {
wasm_bindgen_futures::spawn_local(async move {
Expand Down
8 changes: 8 additions & 0 deletions crates/bcr-ebill-wasm/src/data/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ use serde_json::Value;
use tsify::Tsify;
use wasm_bindgen::prelude::*;

#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct NotificationStatusWeb {
#[tsify(type = "string")]
pub node_id: NodeId,
pub active: bool,
}

#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct NotificationWeb {
Expand Down
Loading