From 7c62244e4990102bbc29522bb547e4b1e247e0f5 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Sat, 14 Mar 2026 10:43:52 +0100 Subject: [PATCH 1/4] Add makeRSSItem helper and test Extract the title/description/link/guid logic for RSS items into a standalone function so it can be tested without pugixml. Add tests for Document 2026D10396, Motie 2026D05246 and Schriftelijke vragen 2026D10272 to lock down current behaviour. --- search.cc | 11 +++++++++++ search.hh | 10 ++++++++++ testrunner.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ users.cc | 12 +++++------- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/search.cc b/search.cc index 69d1f77..ca2b3b8 100644 --- a/search.cc +++ b/search.cc @@ -1,7 +1,18 @@ #include "search.hh" #include "support.hh" +#include using namespace std; +RSSItem makeRSSItem(const SearchHelper::Result& r, const std::string& naam) +{ + RSSItem item; + item.title = r.onderwerp; + item.description = naam + " | " + r.titel + " " + r.onderwerp; + item.link = fmt::format("https://berthub.eu/tkconv/document.html?nummer={}", r.nummer); + item.guid = "tkconv_" + r.nummer; + return item; +} + std::vector SearchHelper::search(const std::string& query, const std::set& categories, const std::string& cutoff, unsigned int mseclimit, unsigned int itemlimit) { diff --git a/search.hh b/search.hh index a789e8f..d674440 100644 --- a/search.hh +++ b/search.hh @@ -43,4 +43,14 @@ struct SearchHelper SQLiteWriter& d_sqw; }; + +struct RSSItem +{ + std::string title; + std::string description; + std::string link; + std::string guid; +}; +RSSItem makeRSSItem(const SearchHelper::Result& r, const std::string& naam); + std::set> getZakenFromDocument(const std::string& id); diff --git a/testrunner.cc b/testrunner.cc index cdf5c90..6912844 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -90,7 +90,53 @@ TEST_CASE("Test timestamps") CHECK(then == 1737094027); } +TEST_CASE("makeRSSItem for Document 2026D10396") +{ + SearchHelper::Result r; + r.nummer = "2026D10396"; + r.categorie = "Document"; + r.onderwerp = "Minimum vermogensbelasting van 2% voor zeer vermogende personen"; + r.titel = "Herziening Belastingstelsel"; + r.soort = "Brief regering"; + + auto item = makeRSSItem(r, "vaste commissie voor Financiën"); + CHECK(item.title == "Minimum vermogensbelasting van 2% voor zeer vermogende personen"); + CHECK(item.description == "vaste commissie voor Financiën | Herziening Belastingstelsel Minimum vermogensbelasting van 2% voor zeer vermogende personen"); + CHECK(item.link == "https://berthub.eu/tkconv/document.html?nummer=2026D10396"); + CHECK(item.guid == "tkconv_2026D10396"); +} +TEST_CASE("makeRSSItem for Motie 2026D05246") +{ + SearchHelper::Result r; + r.nummer = "2026D05246"; + r.categorie = "Document"; + r.onderwerp = "Motie van het lid Vermeer"; + r.titel = "Wijziging van de Wet inkomstenbelasting 2001 om werkelijke inkomsten uit bezittingen en schulden in box 3 te belasten (Wet werkelijk rendement box 3)"; + r.soort = "Motie"; + + auto item = makeRSSItem(r, "vaste commissie voor Financiën"); + CHECK(item.title == "Motie van het lid Vermeer"); + CHECK(item.description == "vaste commissie voor Financiën | Wijziging van de Wet inkomstenbelasting 2001 om werkelijke inkomsten uit bezittingen en schulden in box 3 te belasten (Wet werkelijk rendement box 3) Motie van het lid Vermeer"); + CHECK(item.link == "https://berthub.eu/tkconv/document.html?nummer=2026D05246"); + CHECK(item.guid == "tkconv_2026D05246"); +} + +TEST_CASE("makeRSSItem for Schriftelijke vragen 2026D10272") +{ + SearchHelper::Result r; + r.nummer = "2026D10272"; + r.categorie = "Document"; + r.onderwerp = "Het terugkrijgen van belastingrente door belastingplichtigen die te veel hebben betaald in box 3"; + r.titel = ""; + r.soort = "Schriftelijke vragen"; + + auto item = makeRSSItem(r, ""); + CHECK(item.title == "Het terugkrijgen van belastingrente door belastingplichtigen die te veel hebben betaald in box 3"); + CHECK(item.description == " | Het terugkrijgen van belastingrente door belastingplichtigen die te veel hebben betaald in box 3"); + CHECK(item.link == "https://berthub.eu/tkconv/document.html?nummer=2026D10272"); + CHECK(item.guid == "tkconv_2026D10272"); +} TEST_CASE("Send email" * doctest::skip()) { diff --git a/users.cc b/users.cc index 05d522a..d196e9e 100644 --- a/users.cc +++ b/users.cc @@ -379,15 +379,13 @@ Goed inzicht in ons parlement is belangrijk, soms omdat er dingen in het nieuws } auto& r = docs[0]; pugi::xml_node item = channel.append_child("item"); - string onderwerp = eget(r, "onderwerp"); - item.append_child("title").append_child(pugi::node_pcdata).set_value(onderwerp.c_str()); - onderwerp = eget(r, "naam")+" | " + eget(r, "titel") + " " + onderwerp; - item.append_child("description").append_child(pugi::node_pcdata).set_value(onderwerp.c_str()); + auto rssItem = makeRSSItem(m, eget(r, "naam")); + item.append_child("title").append_child(pugi::node_pcdata).set_value(rssItem.title.c_str()); + item.append_child("description").append_child(pugi::node_pcdata).set_value(rssItem.description.c_str()); - item.append_child("link").append_child(pugi::node_pcdata).set_value( - fmt::format("https://berthub.eu/tkconv/document.html?nummer={}", eget(r,"nummer")).c_str()); - item.append_child("guid").append_child(pugi::node_pcdata).set_value(("tkconv_"+eget(r, "nummer")).c_str()); + item.append_child("link").append_child(pugi::node_pcdata).set_value(rssItem.link.c_str()); + item.append_child("guid").append_child(pugi::node_pcdata).set_value(rssItem.guid.c_str()); // 2024-12-06T06:01:10.2530000 string pubDate = eget(r, "bijgewerkt"); From 3919acd0c053d972278873ac5d96d2b33868be4d Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Sat, 14 Mar 2026 11:11:56 +0100 Subject: [PATCH 2/4] Use search result fields directly in RSS handler The RSS handler previously re-queried every Document field (onderwerp, titel, bijgewerkt) from the database even though SearchHelper::search() already populates these on each Result. Simplify the SQL to only fetch the committee name (ZaakActor) and read the remaining fields from the Result struct. Also drop the 'if (docs.empty()) skip' guard: SearchHelper::search() already skips any document not found in the Document table (search.cc line 65-66), so results that reach this loop are guaranteed to exist. --- users.cc | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/users.cc b/users.cc index d196e9e..caa3680 100644 --- a/users.cc +++ b/users.cc @@ -371,15 +371,13 @@ Goed inzicht in ons parlement is belangrijk, soms omdat er dingen in het nieuws for(auto& m : matches) { - auto docs = own.queryT("select Document.onderwerp, Document.titel titel, Document.nummer nummer, Document.bijgewerkt bijgewerkt, ZaakActor.naam naam, ZaakActor.afkorting afkorting from Document left join Link on link.van = document.id left join zaak on zaak.id = link.naar left join ZaakActor on ZaakActor.zaakId = zaak.id and relatie = 'Voortouwcommissie' where Document.nummer=?", {m.nummer}); + string naam; + auto docs = own.queryT("select ZaakActor.naam from Document left join Link on link.van = document.id left join zaak on zaak.id = link.naar left join ZaakActor on ZaakActor.zaakId = zaak.id and relatie = 'Voortouwcommissie' where Document.nummer=?", {m.nummer}); + if(!docs.empty()) + naam = eget(docs[0], "naam"); - if(docs.empty()) { - cout<<"No hits for "<< m.nummer<Fri, 13 Dec 2024 14:13:41 +0000 From e3f9fd0ae296d85acc88b153b09ec66a8fe5dfdb Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Sat, 14 Mar 2026 11:12:01 +0100 Subject: [PATCH 3/4] Extract searchResultMatchesSoorten helper Move the soorten filter logic out of the search POST handler in tkserv.cc into a testable free function in search.cc. This is needed in a later commit to filter the RSS feed by soorten. --- search.cc | 14 ++++++++++++++ search.hh | 2 ++ testrunner.cc | 21 +++++++++++++++++++++ tkserv.cc | 8 +------- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/search.cc b/search.cc index ca2b3b8..aa7e5f8 100644 --- a/search.cc +++ b/search.cc @@ -13,6 +13,20 @@ RSSItem makeRSSItem(const SearchHelper::Result& r, const std::string& naam) return item; } +bool searchResultMatchesSoorten(const SearchHelper::Result& r, const std::string& soorten) +{ + if(soorten == "documenten") + return r.categorie == "Document"; + if(soorten == "moties") + return r.soort == "Motie"; + if(soorten == "vragenantwoorden") { + return r.soort == "Schriftelijke vragen" || + r.soort == "Antwoord schriftelijke vragen" || + r.soort == "Antwoord schriftelijke vragen (nader)"; + } + return true; +} + std::vector SearchHelper::search(const std::string& query, const std::set& categories, const std::string& cutoff, unsigned int mseclimit, unsigned int itemlimit) { diff --git a/search.hh b/search.hh index d674440..4e7340a 100644 --- a/search.hh +++ b/search.hh @@ -53,4 +53,6 @@ struct RSSItem }; RSSItem makeRSSItem(const SearchHelper::Result& r, const std::string& naam); +bool searchResultMatchesSoorten(const SearchHelper::Result& r, const std::string& soorten); + std::set> getZakenFromDocument(const std::string& id); diff --git a/testrunner.cc b/testrunner.cc index 6912844..4895792 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -138,6 +138,27 @@ TEST_CASE("makeRSSItem for Schriftelijke vragen 2026D10272") CHECK(item.guid == "tkconv_2026D10272"); } +TEST_CASE("searchResultMatchesSoorten") +{ + SearchHelper::Result activiteit; + activiteit.categorie = "Activiteit"; + + SearchHelper::Result motie; + motie.categorie = "Document"; + motie.soort = "Motie"; + + SearchHelper::Result antwoord; + antwoord.categorie = "Document"; + antwoord.soort = "Antwoord schriftelijke vragen"; + + CHECK(searchResultMatchesSoorten(motie, "moties")); + CHECK_FALSE(searchResultMatchesSoorten(antwoord, "moties")); + CHECK(searchResultMatchesSoorten(antwoord, "vragenantwoorden")); + CHECK_FALSE(searchResultMatchesSoorten(activiteit, "vragenantwoorden")); + CHECK(searchResultMatchesSoorten(motie, "documenten")); + CHECK_FALSE(searchResultMatchesSoorten(activiteit, "documenten")); +} + TEST_CASE("Send email" * doctest::skip()) { string text("* Zoekopdracht motie paulusma:\nhttp://berthub.eu/tkconv/get/2024D49539: Voortgangsbrief beschikbaarheid geneesmiddelen\n\nDit was een bericht van https://berthub.eu/tkconv, ook bekend als OpenTK"); diff --git a/tkserv.cc b/tkserv.cc index af29fe0..1b31bc7 100644 --- a/tkserv.cc +++ b/tkserv.cc @@ -2009,13 +2009,7 @@ int main(int argc, char** argv) auto sres = sh.search(term, categories, limit, mseclimit, 280); nlohmann::json results = nlohmann::json::array(); for(const auto& r : sres) { - - if(soorten=="moties" && r.soort != "Motie") - continue; - else if(soorten=="vragenantwoorden" && - (r.soort != "Schriftelijke vragen" && - r.soort != "Antwoord schriftelijke vragen" && - r.soort != "Antwoord schriftelijke vragen (nader)")) + if(!searchResultMatchesSoorten(r, soorten)) continue; results.push_back(nlohmann::json({ From 6c93a946a782bebdcac7f2091785fcf5c1a960ae Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Sat, 14 Mar 2026 10:45:05 +0100 Subject: [PATCH 4/4] Make search RSS honor soorten filters Extend makeRSSItem to handle non-Document results (Activiteit, Verslag, Toezegging, PersoonGeschenk) with appropriate link/guid/description formats. For backward compatibility, treat the absence of a soorten parameter in RSS URLs as "documenten" so existing subscribers only see Documents. The search.html page defaults soorten to "alles" in its RSS tag, so new subscriptions explicitly opt-in to all result types. Pass the soorten parameter through to the search.html template so the filter carries over to the RSS URL. Add test case for Activiteit 2026A01281 (requires the new non-Document code path in makeRSSItem). --- html/logic.js | 1 + partials/search.html | 2 +- search.cc | 15 ++++++++++++--- testrunner.cc | 16 ++++++++++++++++ tkserv.cc | 2 ++ users.cc | 29 ++++++++++++++++++++++------- 6 files changed, 54 insertions(+), 11 deletions(-) diff --git a/html/logic.js b/html/logic.js index 4a2e64b..4e0dfa3 100644 --- a/html/logic.js +++ b/html/logic.js @@ -304,6 +304,7 @@ async function getSearchResults(f) f.foundDocs = data["results"]; const rssurl = new URL("https://berthub.eu/tkconv/search/index.xml"); rssurl.searchParams.set("q", f.searchQuery); + rssurl.searchParams.set("soorten", f.soorten); f.rssurl = rssurl.href f.message = `${data["milliseconds"]} milliseconden`; diff --git a/partials/search.html b/partials/search.html index e8ba90b..ad0da5b 100644 --- a/partials/search.html +++ b/partials/search.html @@ -21,7 +21,7 @@ {% endblock %} {% block extrameta %} - + {% endblock %} {% block customheader %} diff --git a/search.cc b/search.cc index aa7e5f8..19da94c 100644 --- a/search.cc +++ b/search.cc @@ -7,9 +7,18 @@ RSSItem makeRSSItem(const SearchHelper::Result& r, const std::string& naam) { RSSItem item; item.title = r.onderwerp; - item.description = naam + " | " + r.titel + " " + r.onderwerp; - item.link = fmt::format("https://berthub.eu/tkconv/document.html?nummer={}", r.nummer); - item.guid = "tkconv_" + r.nummer; + + if(r.categorie == "Document") { + item.description = naam + " | " + r.titel + " " + r.onderwerp; + item.link = fmt::format("https://berthub.eu/tkconv/document.html?nummer={}", r.nummer); + item.guid = "tkconv_" + r.nummer; + } else { + item.description = r.onderwerp; + if(!r.soort.empty()) + item.description = r.soort + " | " + item.description; + item.link = fmt::format("https://berthub.eu/tkconv/{}", r.relurl); + item.guid = "tkconv_" + r.relurl; + } return item; } diff --git a/testrunner.cc b/testrunner.cc index 4895792..12ced0f 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -159,6 +159,22 @@ TEST_CASE("searchResultMatchesSoorten") CHECK_FALSE(searchResultMatchesSoorten(activiteit, "documenten")); } +TEST_CASE("makeRSSItem for Activiteit 2026A01281") +{ + SearchHelper::Result r; + r.nummer = "2026A01281"; + r.categorie = "Activiteit"; + r.relurl = "activiteit.html?nummer=2026A01281"; + r.onderwerp = "Aanvang middagvergadering: STEMMINGEN (over de Wet werkelijk rendement box 3) en over moties ingediend bij het Tweeminutendebat Voorhang wijziging Postbesluit 2009)"; + r.soort = "Stemmingen"; + + auto item = makeRSSItem(r, ""); + CHECK(item.title == r.onderwerp); + CHECK(item.description == "Stemmingen | " + r.onderwerp); + CHECK(item.link == "https://berthub.eu/tkconv/activiteit.html?nummer=2026A01281"); + CHECK(item.guid == "tkconv_activiteit.html?nummer=2026A01281"); +} + TEST_CASE("Send email" * doctest::skip()) { string text("* Zoekopdracht motie paulusma:\nhttp://berthub.eu/tkconv/get/2024D49539: Voortgangsbrief beschikbaarheid geneesmiddelen\n\nDit was een bericht van https://berthub.eu/tkconv, ook bekend als OpenTK"); diff --git a/tkserv.cc b/tkserv.cc index 1b31bc7..c4ace02 100644 --- a/tkserv.cc +++ b/tkserv.cc @@ -733,12 +733,14 @@ int main(int argc, char** argv) sws.wrapGet({}, "/search.html", [&tp](auto& cr) { string q = cr.req.get_param_value("q"); + string soorten = cr.req.get_param_value("soorten"); nlohmann::json data; data["pagemeta"]["title"]="Zoek naar "+htmlEscape(q); data["og"]["title"] = "Zoek naar "+htmlEscape(q); data["og"]["description"] = "Zoek naar "+htmlEscape(q); data["og"]["imageurl"] = ""; data["q"] = urlEscape(q); + data["soorten"] = soorten.empty() ? "alles" : urlEscape(soorten); inja::Environment e; e.set_html_autoescape(false); // !! diff --git a/users.cc b/users.cc index caa3680..1cd2d5f 100644 --- a/users.cc +++ b/users.cc @@ -355,26 +355,41 @@ Goed inzicht in ons parlement is belangrijk, soms omdat er dingen in het nieuws // https://berthub.eu/tkconv/search.html?q=bert+hubert&twomonths=false&soorten=alles sws.wrapGet({}, "/search/index.xml", [](auto& cr) { string q = convertToSQLiteFTS5(cr.req.get_param_value("q")); - string categorie; + string soorten = cr.req.get_param_value("soorten"); + + // Backward compatibility: existing RSS URLs have no soorten parameter + // and historically only returned Documents. Treat absent soorten the + // same as the explicit "documenten" filter so new Activiteit items + // don't suddenly appear in existing subscribers' feeds. + if(soorten.empty()) + soorten = "documenten"; SQLiteWriter own("tkindex-small.sqlite3", SQLWFlag::ReadOnly); own.query("ATTACH database 'tk.sqlite3' as meta"); SearchHelper sh(own); - // for now we can't do the rest, only Document XXX - auto matches = sh.search(q, {"Document"}); + set categories; + if(soorten=="activiteiten") + categories.insert("Activiteit"); + + auto matches = sh.search(q, categories); cout<<"Have "<