Skip to content

Commit 3e1a077

Browse files
authored
Merge pull request #94 from ericpp/filter-interface
Add podcast filter
2 parents d15f0a1 + 5a662c5 commit 3e1a077

File tree

9 files changed

+404
-132
lines changed

9 files changed

+404
-132
lines changed

dbif/src/lib.rs

Lines changed: 152 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use rusqlite::{params, Connection, Error::QueryReturnedNoRows};
1+
use rusqlite::{params, Connection, Statement, Error::QueryReturnedNoRows};
22
use std::error::Error;
33
use std::fmt;
44
use serde::{Deserialize, Serialize};
55
use serde_json::Value;
66
use std::os::unix::fs::PermissionsExt;
77
use chrono::DateTime;
8+
use std::collections::HashMap;
89

910
#[derive(Serialize, Deserialize, Debug)]
1011
pub struct NodeInfoRecord {
@@ -106,6 +107,17 @@ pub struct SettingsRecord {
106107
pub custom_pew_file: Option<String>,
107108
}
108109

110+
#[derive(Debug, Default)]
111+
pub struct BoostFilters {
112+
pub podcast: Option<String>,
113+
}
114+
115+
impl BoostFilters {
116+
pub fn new() -> Self {
117+
Default::default()
118+
}
119+
}
120+
109121
#[derive(Debug)]
110122
struct HydraError(String);
111123
impl fmt::Display for HydraError {
@@ -165,6 +177,20 @@ fn table_exists(conn: &Connection, table_name: &str) -> Result<bool, Box<dyn Err
165177
Ok(rows.next().is_some())
166178
}
167179

180+
//Bind a query parameter by param name and desired value
181+
fn bind_query_param(stmt: &mut Statement, name: &str, value: &str) -> Result<(), Box<dyn Error>> {
182+
let idx = match stmt.parameter_index(name)? {
183+
Some(num) => num,
184+
None => {
185+
return Err(format!("{} param not found", name).into());
186+
}
187+
};
188+
189+
stmt.raw_bind_parameter(idx, value)?;
190+
191+
Ok(())
192+
}
193+
168194
//Create or update a new database file if needed
169195
pub fn create_database(filepath: &String) -> Result<bool, Box<dyn Error>> {
170196
let conn = connect_to_database(true, filepath)?;
@@ -569,36 +595,64 @@ pub fn mark_boost_as_replied(filepath: &String, index: u64) -> Result<bool, Box<
569595
}
570596

571597
//Get all of the invoices from the database
572-
pub fn get_invoices_from_db(filepath: &String, invtype: &str, index: u64, max: u64, direction: bool, escape_html: bool) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
598+
pub fn get_invoices_from_db(filepath: &String, invtype: &str, index: u64, max: u64, direction: bool, escape_html: bool, filters: BoostFilters) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
573599
let conn = connect_to_database(false, filepath)?;
574600
let mut boosts: Vec<BoostRecord> = Vec::new();
575601

576-
let mut ltgt = ">=";
577-
if direction {
578-
ltgt = "<=";
579-
}
602+
let mut conditions = String::new();
603+
let mut bindings: HashMap<&str, &str> = HashMap::new();
580604

581-
let action = match invtype {
582-
"boost" => " AND action IN (2, 4)",
583-
"stream" => " AND action NOT IN (2, 4)",
584-
_ => "",
605+
let ltgt = if direction {
606+
"<="
607+
} else {
608+
">="
585609
};
586610

611+
conditions.push_str(&format!("idx {} :idx", ltgt));
612+
613+
let strindex = index.to_string();
614+
bindings.insert(":idx", &strindex);
615+
616+
if invtype == "boost" {
617+
conditions.push_str(" AND action IN (2, 4)");
618+
}
619+
else if invtype == "stream" {
620+
conditions.push_str(" AND action NOT IN (2, 4)");
621+
}
622+
623+
if let Some(podcast) = &filters.podcast {
624+
conditions.push_str(" AND podcast = :podcast");
625+
bindings.insert(":podcast", podcast);
626+
}
627+
628+
let strmax = max.to_string();
629+
bindings.insert(":max", &strmax);
630+
587631
//Query for boosts and automated boosts
588632
let sqltxt = format!("
589-
SELECT idx, time, value_msat, value_msat_total, action, sender, app, message, podcast, episode, tlv, remote_podcast, remote_episode, reply_sent, custom_key, custom_value
590-
FROM boosts
633+
SELECT
634+
idx, time, value_msat, value_msat_total, action, sender, app, message, podcast, episode, tlv, remote_podcast, remote_episode, reply_sent, custom_key, custom_value
635+
FROM
636+
boosts
591637
WHERE
592-
idx {} :index
593638
{}
594-
ORDER BY idx DESC
595-
LIMIT :max
596-
", ltgt, action);
639+
ORDER BY
640+
idx DESC
641+
LIMIT
642+
:max
643+
", conditions);
597644

598645
//Prepare and execute the query
599646
let mut stmt = conn.prepare(sqltxt.as_str())?;
600-
let rows = stmt.query_map(&[(":index", index.to_string().as_str()), (":max", max.to_string().as_str())], |row| {
601-
Ok(BoostRecord {
647+
648+
for (name, value) in &bindings {
649+
bind_query_param(&mut stmt, name, value)?;
650+
}
651+
652+
let mut rows = stmt.raw_query();
653+
654+
while let Some(row) = rows.next()? {
655+
let boost = BoostRecord {
602656
index: row.get(0)?,
603657
time: row.get(1)?,
604658
value_msat: row.get(2)?,
@@ -616,12 +670,7 @@ pub fn get_invoices_from_db(filepath: &String, invtype: &str, index: u64, max: u
616670
custom_key: row.get(14).ok(),
617671
custom_value: row.get(15).ok(),
618672
payment_info: None,
619-
})
620-
}).unwrap();
621-
622-
//Parse the results
623-
for row in rows {
624-
let boost: BoostRecord = row.unwrap();
673+
};
625674

626675
//Some things like text output don't need to be html entity escaped
627676
//so only do it if asked for
@@ -648,7 +697,8 @@ pub fn get_invoices_from_db(filepath: &String, invtype: &str, index: u64, max: u
648697
}
649698

650699
pub fn get_single_invoice_from_db(filepath: &String, invtype: &str, index: u64, escape_html: bool) -> Result<Option<BoostRecord>, Box<dyn Error>> {
651-
let invoices = get_invoices_from_db(filepath, invtype, index, 1, true, escape_html)?;
700+
let filters = BoostFilters::new();
701+
let invoices = get_invoices_from_db(filepath, invtype, index, 1, true, escape_html, filters)?;
652702

653703
if !invoices.is_empty() && invoices[0].index == index {
654704
Ok(Some(invoices[0].clone()))
@@ -658,13 +708,13 @@ pub fn get_single_invoice_from_db(filepath: &String, invtype: &str, index: u64,
658708
}
659709
}
660710

661-
pub fn get_boosts_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
662-
get_invoices_from_db(filepath, "boost", index, max, direction, escape_html)
711+
pub fn get_boosts_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool, filters: BoostFilters) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
712+
get_invoices_from_db(filepath, "boost", index, max, direction, escape_html, filters)
663713
}
664714

665715
//Get all of the non-boosts from the database
666-
pub fn get_streams_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
667-
get_invoices_from_db(filepath, "stream", index, max, direction, escape_html)
716+
pub fn get_streams_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool, filters: BoostFilters) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
717+
get_invoices_from_db(filepath, "stream", index, max, direction, escape_html, filters)
668718
}
669719

670720
//Get the last boost index number from the database
@@ -721,6 +771,25 @@ pub fn get_last_boost_index_from_db(filepath: &String) -> Result<u64, Box<dyn Er
721771
Ok(0)
722772
}
723773

774+
//Get podcasts that received boosts to this node
775+
pub fn get_podcasts_from_db(filepath: &String) -> Result<Vec<String>, Box<dyn Error>> {
776+
let conn = connect_to_database(false, filepath)?;
777+
778+
let query = "SELECT DISTINCT podcast FROM boosts WHERE podcast <> '' ORDER BY podcast".to_string();
779+
780+
let mut stmt = conn.prepare(&query)?;
781+
let mut rows = stmt.raw_query();
782+
783+
//Parse the results
784+
let mut podcasts = Vec::new();
785+
786+
while let Some(row) = rows.next()? {
787+
podcasts.push(row.get(0)?);
788+
}
789+
790+
Ok(podcasts)
791+
}
792+
724793
//Set/Get the wallet balance from the database in sats
725794
pub fn add_wallet_balance_to_db(filepath: &String, balance: i64) -> Result<bool, Box<dyn Error>> {
726795
let conn = connect_to_database(false, filepath)?;
@@ -758,16 +827,33 @@ pub fn get_wallet_balance_from_db(filepath: &String) -> Result<i64, Box<dyn Erro
758827
}
759828

760829
//Get all of the sent boosts from the database
761-
pub fn get_payments_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
830+
pub fn get_payments_from_db(filepath: &String, index: u64, max: u64, direction: bool, escape_html: bool, filters: BoostFilters) -> Result<Vec<BoostRecord>, Box<dyn Error>> {
762831
let conn = connect_to_database(false, filepath)?;
763832
let mut boosts: Vec<BoostRecord> = Vec::new();
764833

765-
let mut ltgt = ">=";
766-
if direction {
767-
ltgt = "<=";
834+
let mut conditions = String::new();
835+
let mut bindings: HashMap<&str, &str> = HashMap::new();
836+
837+
let ltgt = if direction {
838+
"<="
839+
} else {
840+
">="
841+
};
842+
843+
conditions.push_str(&format!("idx {} :idx", ltgt));
844+
845+
let strindex = index.to_string();
846+
bindings.insert(":idx", &strindex);
847+
848+
if let Some(podcast) = &filters.podcast {
849+
conditions.push_str(" AND podcast = :podcast");
850+
bindings.insert(":podcast", podcast);
768851
}
769852

770-
//Build the query
853+
let strmax = max.to_string();
854+
bindings.insert(":max", &strmax);
855+
856+
//Query for boosts and automated boosts
771857
let sqltxt = format!(
772858
"SELECT
773859
idx,
@@ -792,19 +878,27 @@ pub fn get_payments_from_db(filepath: &String, index: u64, max: u64, direction:
792878
FROM
793879
sent_boosts
794880
WHERE
795-
idx {} :index
881+
{}
796882
ORDER BY
797883
idx DESC
798884
LIMIT
799885
:max
800886
",
801-
ltgt
887+
conditions
802888
);
803889

804890
//Prepare and execute the query
805891
let mut stmt = conn.prepare(sqltxt.as_str())?;
806-
let rows = stmt.query_map(&[(":index", index.to_string().as_str()), (":max", max.to_string().as_str())], |row| {
807-
Ok(BoostRecord {
892+
893+
for (name, value) in &bindings {
894+
bind_query_param(&mut stmt, name, value)?;
895+
}
896+
897+
let mut rows = stmt.raw_query();
898+
899+
//Parse the results
900+
while let Some(row) = rows.next()? {
901+
let boost = BoostRecord {
808902
index: row.get(0)?,
809903
time: row.get(1)?,
810904
value_msat: row.get(2)?,
@@ -829,12 +923,7 @@ pub fn get_payments_from_db(filepath: &String, index: u64, max: u64, direction:
829923
fee_msat: row.get(17)?,
830924
reply_to_idx: row.get(18)?,
831925
}),
832-
})
833-
}).unwrap();
834-
835-
//Parse the results
836-
for row in rows {
837-
let boost: BoostRecord = row.unwrap();
926+
};
838927

839928
//Some things like text output don't need to be html entity escaped
840929
//so only do it if asked for
@@ -862,7 +951,6 @@ pub fn get_payments_from_db(filepath: &String, index: u64, max: u64, direction:
862951
} else {
863952
boosts.push(boost);
864953
}
865-
866954
}
867955

868956
Ok(boosts)
@@ -949,6 +1037,25 @@ pub fn add_payment_to_db(filepath: &String, boost: &BoostRecord) -> Result<bool,
9491037
Ok(true)
9501038
}
9511039

1040+
//Get podcasts that were send boosts from this node
1041+
pub fn get_sent_podcasts_from_db(filepath: &String) -> Result<Vec<String>, Box<dyn Error>> {
1042+
let conn = connect_to_database(false, filepath)?;
1043+
1044+
let query = "SELECT DISTINCT podcast FROM sent_boosts WHERE podcast <> '' ORDER BY podcast".to_string();
1045+
1046+
let mut stmt = conn.prepare(&query)?;
1047+
let mut rows = stmt.raw_query();
1048+
1049+
//Parse the results
1050+
let mut podcasts = Vec::new();
1051+
1052+
while let Some(row) = rows.next()? {
1053+
podcasts.push(row.get(0)?);
1054+
}
1055+
1056+
Ok(podcasts)
1057+
}
1058+
9521059
pub fn get_webhooks_from_db(filepath: &String, enabled: Option<bool>) -> Result<Vec<WebhookRecord>, Box<dyn Error>> {
9531060
let conn = connect_to_database(false, filepath)?;
9541061
let mut webhooks: Vec<WebhookRecord> = Vec::new();

0 commit comments

Comments
 (0)