diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 460d92765..dc95add3d 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -722,6 +722,8 @@ std::unique_ptr InternalServer::handle_catalog_v2(const RequestContext if (url == "root.xml") { return handle_catalog_v2_root(request); + } else if (url == "categories") { + return handle_catalog_v2_categories(request); } else { return Response::build_404(*this, request, ""); } @@ -744,6 +746,34 @@ std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestCo ); } +std::unique_ptr InternalServer::handle_catalog_v2_categories(const RequestContext& request) +{ + const std::string root_url = normalizeRootUrl(m_root); + const auto now = gen_date_str(); + kainjow::mustache::list categoryData; + for ( const auto& category : mp_library->getBooksCategories() ) { + const auto urlencodedCategoryName = urlEncode(category); + categoryData.push_back(kainjow::mustache::object{ + {"name", category}, + {"urlencoded_name", urlencodedCategoryName}, + {"updated", now}, + {"id", gen_uuid(m_server_id + "/categories/" + urlencodedCategoryName)} + }); + } + + return ContentResponse::build( + *this, + RESOURCE::catalog_v2_categories_xml, + kainjow::mustache::object{ + {"date", now}, + {"endpoint_root", root_url + "/catalog/v2"}, + {"feed_id", gen_uuid(m_server_id + "/categories")}, + {"categories", categoryData } + }, + "application/atom+xml;profile=opds-catalog;kind=navigation" + ); +} + namespace { diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 7e98638a3..d57ac6ed9 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -75,6 +75,7 @@ class InternalServer { std::unique_ptr handle_catalog(const RequestContext& request); std::unique_ptr handle_catalog_v2(const RequestContext& request); std::unique_ptr handle_catalog_v2_root(const RequestContext& request); + std::unique_ptr handle_catalog_v2_categories(const RequestContext& request); std::unique_ptr handle_meta(const RequestContext& request); std::unique_ptr handle_search(const RequestContext& request); std::unique_ptr handle_suggest(const RequestContext& request); diff --git a/static/catalog_v2_categories.xml b/static/catalog_v2_categories.xml new file mode 100644 index 000000000..5592849e6 --- /dev/null +++ b/static/catalog_v2_categories.xml @@ -0,0 +1,24 @@ + + + {{feed_id}} + + + List of categories + {{date}} + + {{#categories}} + + {{name}} + + {{updated}} + {{id}} + All entries with category of '{{name}}'. + + {{/categories}} + diff --git a/static/resources_list.txt b/static/resources_list.txt index 5b6341a37..9691e80e5 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -38,3 +38,4 @@ templates/external_blocker_part.html templates/captured_external.html opensearchdescription.xml catalog_v2_root.xml +catalog_v2_categories.xml diff --git a/test/server.cpp b/test/server.cpp index 9c2575023..bcc2e7e05 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -941,3 +941,43 @@ R"( )" ); } + +TEST_F(LibraryServerTest, catalog_v2_categories) +{ + const auto r = zfs1_->GET("/catalog/v2/categories"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), +R"( + + 12345678-90ab-cdef-1234-567890abcdef + + + List of categories + YYYY-MM-DDThh:mm:ssZ + + + jazz + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + All entries with category of 'jazz'. + + + wikipedia + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + All entries with category of 'wikipedia'. + + +)" + ); +}