mirror of https://github.com/kiwix/libkiwix.git
/catalog/v2/entries is also a search endpoint
This commit is contained in:
parent
b60e3ffb26
commit
07252a127a
|
@ -690,6 +690,13 @@ Filter get_search_filter(const RequestContext& request)
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
std::vector<T> subrange(const std::vector<T>& v, size_t s, size_t n)
|
||||||
|
{
|
||||||
|
const size_t e = std::min(v.size(), s+n);
|
||||||
|
return std::vector<T>(v.begin()+std::min(v.size(), s), v.begin()+e);
|
||||||
|
}
|
||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
|
@ -705,11 +712,7 @@ InternalServer::search_catalog(const RequestContext& request,
|
||||||
const auto totalResults = bookIdsToDump.size();
|
const auto totalResults = bookIdsToDump.size();
|
||||||
const size_t count = request.get_optional_param("count", 10UL);
|
const size_t count = request.get_optional_param("count", 10UL);
|
||||||
const size_t startIndex = request.get_optional_param("start", 0UL);
|
const size_t startIndex = request.get_optional_param("start", 0UL);
|
||||||
const auto s = std::min(startIndex, totalResults);
|
bookIdsToDump = subrange(bookIdsToDump, startIndex, count);
|
||||||
bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+s);
|
|
||||||
if (count>0 && bookIdsToDump.size() > count) {
|
|
||||||
bookIdsToDump.resize(count);
|
|
||||||
}
|
|
||||||
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
|
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
|
||||||
return bookIdsToDump;
|
return bookIdsToDump;
|
||||||
}
|
}
|
||||||
|
@ -761,7 +764,11 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const Reques
|
||||||
|
|
||||||
const auto now = gen_date_str();
|
const auto now = gen_date_str();
|
||||||
kainjow::mustache::list bookData;
|
kainjow::mustache::list bookData;
|
||||||
for ( const auto& bookId : mp_library->getBooksIds() ) {
|
const auto filter = get_search_filter(request);
|
||||||
|
const auto allMatchingEntries = mp_library->filter(filter);
|
||||||
|
const size_t count = request.get_optional_param("count", 10UL);
|
||||||
|
const size_t start = request.get_optional_param("start", 0UL);
|
||||||
|
for ( const auto& bookId : subrange(allMatchingEntries, start, count) ) {
|
||||||
const Book& book = mp_library->getBookById(bookId);
|
const Book& book = mp_library->getBookById(bookId);
|
||||||
const MustacheData bookUrl = book.getUrl().empty()
|
const MustacheData bookUrl = book.getUrl().empty()
|
||||||
? MustacheData(false)
|
? MustacheData(false)
|
||||||
|
@ -786,13 +793,18 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const Reques
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto query = request.get_query().empty()
|
||||||
|
? MustacheData(false)
|
||||||
|
: MustacheData(request.get_query());
|
||||||
|
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
*this,
|
*this,
|
||||||
RESOURCE::catalog_v2_entries_xml,
|
RESOURCE::catalog_v2_entries_xml,
|
||||||
kainjow::mustache::object{
|
kainjow::mustache::object{
|
||||||
{"date", now},
|
{"date", now},
|
||||||
{"endpoint_root", root_url + "/catalog/v2"},
|
{"endpoint_root", root_url + "/catalog/v2"},
|
||||||
{"feed_id", gen_uuid(m_library_id + "/entries")},
|
{"feed_id", gen_uuid(m_library_id + "/entries?"+request.get_query())},
|
||||||
|
{"filter", query},
|
||||||
{"books", bookData }
|
{"books", bookData }
|
||||||
},
|
},
|
||||||
"application/atom+xml;profile=opds-catalog;kind=acquisition"
|
"application/atom+xml;profile=opds-catalog;kind=acquisition"
|
||||||
|
|
|
@ -183,4 +183,14 @@ std::string RequestContext::get_header(const std::string& name) const {
|
||||||
return headers.at(lcAll(name));
|
return headers.at(lcAll(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string RequestContext::get_query() const {
|
||||||
|
std::string q;
|
||||||
|
const char* sep = "";
|
||||||
|
for ( const auto& a : arguments ) {
|
||||||
|
q += sep + a.first + '=' + a.second;
|
||||||
|
sep = "&";
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ class RequestContext {
|
||||||
std::string get_url() const;
|
std::string get_url() const;
|
||||||
std::string get_url_part(int part) const;
|
std::string get_url_part(int part) const;
|
||||||
std::string get_full_url() const;
|
std::string get_full_url() const;
|
||||||
|
std::string get_query() const;
|
||||||
|
|
||||||
ByteRange get_range() const;
|
ByteRange get_range() const;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
href="{{endpoint_root}}/root.xml"
|
href="{{endpoint_root}}/root.xml"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
|
||||||
<title>All Entries</title>
|
<title>{{^filter}}All Entries{{/filter}}{{#filter}}Filtered Entries ({{filter}}){{/filter}}</title>
|
||||||
<updated>{{date}}</updated>
|
<updated>{{date}}</updated>
|
||||||
|
|
||||||
{{#books}}
|
{{#books}}
|
||||||
|
|
|
@ -982,26 +982,30 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CATALOG_V2_ENTRIES_PREAMBLE \
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
|
||||||
|
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
|
||||||
|
" xmlns:opds=\"https://specs.opds.io/opds-1.2\">\n" \
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
||||||
|
"\n" \
|
||||||
|
" <link rel=\"self\"\n" \
|
||||||
|
" href=\"/catalog/v2/entries\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
||||||
|
" <link rel=\"start\"\n" \
|
||||||
|
" href=\"/catalog/v2/root.xml\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
|
" <link rel=\"up\"\n" \
|
||||||
|
" href=\"/catalog/v2/root.xml\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
|
"\n" \
|
||||||
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries)
|
TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries");
|
const auto r = zfs1_->GET("/catalog/v2/entries");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
CATALOG_V2_ENTRIES_PREAMBLE
|
||||||
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n"
|
|
||||||
" xmlns:opds=\"https://specs.opds.io/opds-1.2\">\n"
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
"\n"
|
|
||||||
" <link rel=\"self\"\n"
|
|
||||||
" href=\"/catalog/v2/entries\"\n"
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n"
|
|
||||||
" <link rel=\"start\"\n"
|
|
||||||
" href=\"/catalog/v2/root.xml\"\n"
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n"
|
|
||||||
" <link rel=\"up\"\n"
|
|
||||||
" href=\"/catalog/v2/root.xml\"\n"
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n"
|
|
||||||
"\n"
|
|
||||||
" <title>All Entries</title>\n"
|
" <title>All Entries</title>\n"
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -1011,3 +1015,62 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||||
"</feed>\n"
|
"</feed>\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/catalog/v2/entries?start=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE
|
||||||
|
" <title>Filtered Entries (start=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/catalog/v2/entries?count=2");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE
|
||||||
|
" <title>Filtered Entries (count=2)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/catalog/v2/entries?start=1&count=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE
|
||||||
|
" <title>Filtered Entries (count=1&start=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/catalog/v2/entries?q=\"ray%20charles\"");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE
|
||||||
|
" <title>Filtered Entries (q="ray charles")</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue