use super::manager::{RdfEventsStore, RdfStoreError};
use super::vars as SPVars;
use crate::err::LDError;
use crate::niri::ToNamedNode;
use derive_builder::Builder;
use language::Language;
use nostr::{EventId, PublicKey, Timestamp};
use oxigraph::model::*;
use std::time::Duration;
impl RdfEventsStore {
pub fn literal_for_subject_predicate(
&self,
subject_node: NamedNodeRef,
pred: NamedNodeRef,
) -> Result<Literal, RdfStoreError> {
self.store
.quads_for_pattern(
Some(subject_node.into()),
Some(pred),
None,
None,
)
.filter_map(|quad| match &quad.ok()?.object {
Term::Literal(lit) => Some(lit.clone()),
_ => None,
})
.next()
.ok_or("No quad found")
.map_err(|_| RdfStoreError::QuadError)
}
pub fn string_for_subject_predicate(
&self,
subject_node: NamedNodeRef,
pred: NamedNodeRef,
) -> Result<String, RdfStoreError> {
self.literal_for_subject_predicate(subject_node, pred)
.map(|l| l.to_string())
}
pub fn rm_quads_pattern(
&self,
subject: Option<SubjectRef<'_>>,
predicate: Option<NamedNodeRef<'_>>,
object: Option<TermRef<'_>>,
graph_name: Option<GraphNameRef<'_>>,
) -> Result<u32, RdfStoreError> {
let mut removed: u32 = 0;
for q in self
.store
.quads_for_pattern(subject, predicate, object, graph_name).flatten()
{
self.store.remove(&q)?;
removed += 1;
}
Ok(removed)
}
}
pub fn subl(
varname: &str,
literal: Literal,
) -> Result<(Variable, Term), LDError> {
Ok((
Variable::new(varname).map_err(|_| LDError::SubstitutionError)?,
literal.into(),
))
}
pub fn subnn(
varname: &str,
nn: NamedNode,
) -> Result<(Variable, Term), LDError> {
Ok((
Variable::new(varname).map_err(|_| LDError::SubstitutionError)?,
nn.into(),
))
}
#[derive(Builder, Debug, Default)]
#[builder(default)]
pub struct QuerySubs {
#[builder(setter(strip_option))]
event_id: Option<EventId>,
#[builder(setter(strip_option))]
replies_to_event_id: Option<EventId>,
#[builder(setter(strip_option))]
in_forum: Option<EventId>,
#[builder(setter(strip_option))]
since: Option<Duration>,
#[builder(setter(strip_option))]
author: Option<PublicKey>,
#[builder(setter(strip_option))]
notifications_for: Option<PublicKey>,
#[builder(setter(strip_option))]
identity_nip21: Option<PublicKey>,
#[builder(setter(strip_option))]
nip21: Option<PublicKey>,
#[builder(setter(strip_option))]
lang: Option<Language>,
#[builder(setter(strip_option))]
hashtag: Option<String>,
#[builder(setter(strip_option))]
only_followees: Option<i16>,
#[builder(setter(strip_option))]
feed: Option<NamedNode>,
#[builder(setter(strip_option))]
trust_score_min: Option<f32>,
}
impl QuerySubs {
#[deprecated]
pub fn subs(&self) -> Result<Vec<(Variable, Term)>, LDError> {
let mut subs_v = Vec::new();
if let Some(id) = self.event_id {
subs_v.push(subl(SPVars::EVENT_ID, id.to_hex().into())?);
}
Ok(subs_v)
}
pub fn subs_with_ckey(
&self,
) -> Result<(Vec<(Variable, Term)>, String), LDError> {
let mut cache_key = String::new();
let mut subs_v = Vec::new();
if let Some(pk) = self.identity_nip21 {
subs_v.push(subnn("identity_nip21", pk.named_node().unwrap())?);
}
if let Some(pk) = self.nip21 {
subs_v.push(subnn("nip21", pk.named_node().unwrap())?);
cache_key.push_str(&format!("nip21_{}", pk.to_hex()));
}
if let Some(pk) = self.notifications_for {
subs_v.push(subnn("notifications_for", pk.named_node().unwrap())?);
cache_key.push_str(&format!("notif_{}", pk.to_hex()));
}
if let Some(id) = self.event_id {
subs_v.push(subl(SPVars::EVENT_ID, id.to_hex().into())?);
cache_key.push_str(&format!("ev_{}", id.to_hex()));
}
if let Some(ev_id) = self.replies_to_event_id {
subs_v.push(subl("e_tag_value", Literal::from(ev_id.to_hex()))?);
subs_v.push(subl("filter_e_tag", Literal::from(1))?);
cache_key.push_str(&format!("_etag_{}", ev_id.to_hex()));
} else {
subs_v.push(subl("filter_e_tag", Literal::from(0))?);
}
if let Some(since_dur) = self.since {
let created_since = Timestamp::now() - since_dur;
subs_v.push(subl(
"created_at_since",
Literal::from(created_since.as_secs()),
)?);
cache_key.push_str(&format!("since_{}", since_dur.as_secs()));
}
if let Some(score) = self.trust_score_min {
subs_v.push(subl(
"trust_score_min",
Literal::from(score),
)?);
cache_key.push_str(&format!("tsm_{}", score));
}
if let Some(ref hashtag) = self.hashtag {
subs_v.push(subl("query_hashtag", Literal::from(hashtag.clone()))?);
cache_key.push_str(&format!("_ht{}", hashtag));
}
if let Some(lang) = self.lang {
let lang_tag = lang.as_tag();
subs_v.push(subl(
"lang_tag_filter",
Literal::from(lang_tag.to_string()),
)?);
} else {
subs_v.push(subl("lang_tag_filter", Literal::from(""))?);
}
if let Some(event_id) = &self.in_forum {
subs_v.push(subl("f_tag_value", Literal::from(event_id.to_hex()))?);
subs_v.push(subl("filter_f_tag", Literal::from(1))?);
cache_key.push_str(&format!("_ftag_{}", event_id.to_hex()));
} else {
subs_v.push(subl("filter_f_tag", Literal::from(0))?);
}
if let Some(only_f) = self.only_followees {
subs_v.push(subl("only_followees", Literal::from(only_f))?);
}
if let Some(feed_node) = &self.feed {
subs_v.push(subl("feed_mode", Literal::from(1))?);
subs_v.push(subnn("feed", feed_node.clone())?);
cache_key.push_str(&format!("_feed_{}", feed_node));
}
Ok((subs_v, cache_key))
}
}
#[derive(Builder, Debug, Default)]
#[builder(default)]
pub struct SearchQuerySubs<'a> {
#[builder(setter(strip_option))]
event_id: Option<EventId>,
#[builder(setter(strip_option))]
since_ts: Option<Timestamp>,
#[builder(setter(strip_option))]
since_duration: Option<Duration>,
#[builder(setter(strip_option))]
author_name: Option<&'a str>,
#[builder(setter(strip_option))]
nip21: Option<PublicKey>,
#[builder(setter(strip_option))]
feed: Option<NamedNode>,
#[builder(setter(strip_option))]
content_search_regex: Option<&'a str>,
}
impl<'a> SearchQuerySubs<'a> {
pub fn subs_with_ckey(
&self,
) -> Result<(Vec<(Variable, Term)>, String), LDError> {
let mut cache_key = String::from("search_");
let mut subs_v = Vec::new();
if let Some(regex) = self.content_search_regex {
subs_v.push(subl("content_search_regex", Literal::from(regex))?);
cache_key.push_str(regex);
}
if let Some(name) = self.author_name {
subs_v.push(subl("author_name", Literal::from(name))?);
cache_key.push_str(name);
}
if let Some(pk) = self.nip21 {
subs_v.push(subnn("nip21", pk.named_node().unwrap())?);
cache_key.push_str(&format!("nip21_{}", pk.to_hex()));
}
if let Some(since_ts) = self.since_ts {
subs_v.push(subl(
"created_at_since",
Literal::from(since_ts.as_secs()),
)?);
cache_key.push_str(&format!("since_{}", since_ts.as_secs()));
}
if let Some(since_dur) = self.since_duration {
let created_since = Timestamp::now() - since_dur;
subs_v.push(subl(
"created_at_since",
Literal::from(created_since.as_secs()),
)?);
cache_key.push_str(&format!("since_{}", since_dur.as_secs()));
}
Ok((subs_v, cache_key))
}
}
#[test]
fn test_subs() -> Result<(), LDError> {
let (_, _) = QuerySubsBuilder::default()
.event_id(EventId::parse("5d35413cad64e6c4e9cd9788b1d82b935744a36582239d753cd4dadca045580a").unwrap())
.lang(Language::EnGb)
.build().map_err(|_| LDError::EventBuildError)?
.subs_with_ckey()?;
Ok(())
}