From d63e34a7e8612dc69ae25f3d3903ba04cc808bf7 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 8 Sep 2018 17:46:57 +0300 Subject: Add support for root global and tenant views --- mod/mod-package-details.cxx | 2 +- mod/mod-package-search.cxx | 187 ------------------------------------------ mod/mod-package-search.hxx | 42 ---------- mod/mod-packages.cxx | 195 ++++++++++++++++++++++++++++++++++++++++++++ mod/mod-packages.hxx | 42 ++++++++++ mod/mod-repository-root.cxx | 112 ++++++++++++++++--------- mod/mod-repository-root.hxx | 4 +- mod/options.cli | 25 +++++- mod/page.cxx | 25 +++--- mod/page.hxx | 13 +-- 10 files changed, 359 insertions(+), 288 deletions(-) delete mode 100644 mod/mod-package-search.cxx delete mode 100644 mod/mod-package-search.hxx create mode 100644 mod/mod-packages.cxx create mode 100644 mod/mod-packages.hxx (limited to 'mod') diff --git a/mod/mod-package-details.cxx b/mod/mod-package-details.cxx index 6ec0b0f..a36d4c0 100644 --- a/mod/mod-package-details.cxx +++ b/mod/mod-package-details.cxx @@ -215,7 +215,7 @@ handle (request& rq, response& rs) package_db_->query_value ( search_params (squery, tenant, name))); - s << FORM_SEARCH (squery) + s << FORM_SEARCH (squery, "q") << DIV_COUNTER (pkg_count, "Version", "Versions"); // Enclose the subsequent tables to be able to use nth-child CSS selector. diff --git a/mod/mod-package-search.cxx b/mod/mod-package-search.cxx deleted file mode 100644 index 347abf1..0000000 --- a/mod/mod-package-search.cxx +++ /dev/null @@ -1,187 +0,0 @@ -// file : mod/mod-package-search.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -using namespace odb::core; -using namespace brep::cli; - -// While currently the user-defined copy constructor is not required (we don't -// need to deep copy nullptr's), it is a good idea to keep the placeholder -// ready for less trivial cases. -// -brep::package_search:: -package_search (const package_search& r) - : database_module (r), - options_ (r.initialized_ ? r.options_ : nullptr) -{ -} - -void brep::package_search:: -init (scanner& s) -{ - HANDLER_DIAG; - - options_ = make_shared ( - s, unknown_mode::fail, unknown_mode::fail); - - database_module::init (*options_, options_->package_db_retry ()); - - if (options_->root ().empty ()) - options_->root (dir_path ("/")); - - // Check that the database 'package' schema matches the current one. It's - // enough to perform the check in just a single module implementation (and we - // don't do in the dispatcher because it doesn't use the database). - // - // Note that the failure can be reported by each web server worker process. - // While it could be tempting to move the check to the - // repository_root::version() function, it would be wrong. The function can - // be called by a different process (usually the web server root one) not - // having the proper permissions to access the database. - // - const string ds ("package"); - if (schema_catalog::current_version (*package_db_, ds) != - package_db_->schema_version (ds)) - fail << "database 'package' schema differs from the current one (module " - << BREP_VERSION_ID << ")"; -} - -template -static inline query -search_param (const brep::string& q, const brep::string& t) -{ - using query = query; - return "(" + - (q.empty () - ? query ("NULL") - : "plainto_tsquery (" + query::_val (q) + ")") + - "," + - query::_val (t) + - ")"; -} - -bool brep::package_search:: -handle (request& rq, response& rs) -{ - using namespace web::xhtml; - - HANDLER_DIAG; - - const size_t res_page (options_->search_results ()); - const dir_path& root (options_->root ()); - const string& title (options_->search_title ()); - - params::package_search params; - - try - { - name_value_scanner s (rq.parameters (8 * 1024)); - params = params::package_search ( - s, unknown_mode::fail, unknown_mode::fail); - } - catch (const cli::exception& e) - { - throw invalid_request (400, e.what ()); - } - - size_t page (params.page ()); - const string& squery (params.query ()); - string squery_param (squery.empty () - ? "" - : "?q=" + web::mime_url_encode (squery)); - - xml::serializer s (rs.content (), title); - - s << HTML - << HEAD - << TITLE - << title; - - if (!squery.empty ()) - s << " " << squery; - - s << ~TITLE - << CSS_LINKS (path ("package-search.css"), root) - // - // This hack is required to avoid the "flash of unstyled content", which - // happens due to the presence of the autofocus attribute in the input - // element of the search form. The problem appears in Firefox and has a - // (4-year old, at the time of this writing) bug report: - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=712130 - // - // @@ An update: claimed to be fixed in Firefox 60 that is released in - // May 2018. Is it time to cleanup? Remember to cleanup in all places. - // - << SCRIPT << " " << ~SCRIPT - << ~HEAD - << BODY - << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) - << DIV(ID="content"); - - session sn; - transaction t (package_db_->begin ()); - - auto pkg_count ( - package_db_->query_value ( - search_param (squery, tenant))); - - s << FORM_SEARCH (squery) - << DIV_COUNTER (pkg_count, "Package", "Packages"); - - // Enclose the subsequent tables to be able to use nth-child CSS selector. - // - // @@ TENANT: use tenant for sorting when add support for global view. - // - s << DIV; - for (const auto& pr: - package_db_->query ( - search_param (squery, tenant) + - "ORDER BY rank DESC, name" + - "OFFSET" + to_string (page * res_page) + - "LIMIT" + to_string (res_page))) - { - shared_ptr p (package_db_->load (pr.id)); - - s << TABLE(CLASS="proplist package") - << TBODY - << TR_NAME (p->name, squery_param, root, tenant) - << TR_SUMMARY (p->summary) - << TR_LICENSE (p->license_alternatives) - << TR_TAGS (p->project, p->tags, root, tenant) - << TR_DEPENDS (p->dependencies, root, tenant) - << TR_REQUIRES (p->requirements) - << ~TBODY - << ~TABLE; - } - s << ~DIV; - - t.commit (); - - s << DIV_PAGER (page, pkg_count, res_page, options_->search_pages (), - tenant_dir (root, tenant).string () + squery_param) - << ~DIV - << ~BODY - << ~HTML; - - return true; -} diff --git a/mod/mod-package-search.hxx b/mod/mod-package-search.hxx deleted file mode 100644 index 406476e..0000000 --- a/mod/mod-package-search.hxx +++ /dev/null @@ -1,42 +0,0 @@ -// file : mod/mod-package-search.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef MOD_MOD_PACKAGE_SEARCH_HXX -#define MOD_MOD_PACKAGE_SEARCH_HXX - -#include -#include - -#include -#include - -namespace brep -{ - class package_search: public database_module - { - public: - package_search () = default; - - // Create a shallow copy (handling instance) if initialized and a deep - // copy (context exemplar) otherwise. - // - explicit - package_search (const package_search&); - - virtual bool - handle (request&, response&); - - virtual const cli::options& - cli_options () const {return options::package_search::description ();} - - private: - virtual void - init (cli::scanner&); - - private: - shared_ptr options_; - }; -} - -#endif // MOD_MOD_PACKAGE_SEARCH_HXX diff --git a/mod/mod-packages.cxx b/mod/mod-packages.cxx new file mode 100644 index 0000000..1157378 --- /dev/null +++ b/mod/mod-packages.cxx @@ -0,0 +1,195 @@ +// file : mod/mod-packages.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +using namespace odb::core; +using namespace brep::cli; + +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::packages:: +packages (const packages& r) + : database_module (r), + options_ (r.initialized_ ? r.options_ : nullptr) +{ +} + +void brep::packages:: +init (scanner& s) +{ + HANDLER_DIAG; + + options_ = make_shared ( + s, unknown_mode::fail, unknown_mode::fail); + + database_module::init (*options_, options_->package_db_retry ()); + + if (options_->root ().empty ()) + options_->root (dir_path ("/")); + + // Check that the database 'package' schema matches the current one. It's + // enough to perform the check in just a single module implementation (and we + // don't do in the dispatcher because it doesn't use the database). + // + // Note that the failure can be reported by each web server worker process. + // While it could be tempting to move the check to the + // repository_root::version() function, it would be wrong. The function can + // be called by a different process (usually the web server root one) not + // having the proper permissions to access the database. + // + const string ds ("package"); + if (schema_catalog::current_version (*package_db_, ds) != + package_db_->schema_version (ds)) + fail << "database 'package' schema differs from the current one (module " + << BREP_VERSION_ID << ")"; +} + +template +static inline query +search_param (const brep::string& q, const brep::string& t) +{ + using query = query; + return "(" + + (q.empty () + ? query ("NULL") + : "plainto_tsquery (" + query::_val (q) + ")") + + "," + + query::_val (t) + + ")"; +} + +bool brep::packages:: +handle (request& rq, response& rs) +{ + using namespace web::xhtml; + + HANDLER_DIAG; + + const size_t res_page (options_->search_results ()); + const dir_path& root (options_->root ()); + const string& title (options_->search_title ()); + + params::packages params; + + try + { + name_value_scanner s (rq.parameters (8 * 1024)); + params = params::packages (s, unknown_mode::fail, unknown_mode::fail); + } + catch (const cli::exception& e) + { + throw invalid_request (400, e.what ()); + } + + size_t page (params.page ()); + const string& squery (params.q ()); + string equery (web::mime_url_encode (squery)); + + xml::serializer s (rs.content (), title); + + s << HTML + << HEAD + << TITLE + << title; + + if (!squery.empty ()) + s << " " << squery; + + s << ~TITLE + << CSS_LINKS (path ("package-search.css"), root) + // + // This hack is required to avoid the "flash of unstyled content", which + // happens due to the presence of the autofocus attribute in the input + // element of the search form. The problem appears in Firefox and has a + // (4-year old, at the time of this writing) bug report: + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=712130 + // + // @@ An update: claimed to be fixed in Firefox 60 that is released in + // May 2018. Is it time to cleanup? Remember to cleanup in all places. + // + << SCRIPT << " " << ~SCRIPT + << ~HEAD + << BODY + << DIV_HEADER (options_->logo (), options_->menu (), root, tenant) + << DIV(ID="content"); + + session sn; + transaction t (package_db_->begin ()); + + auto pkg_count ( + package_db_->query_value ( + search_param (squery, tenant))); + + s << FORM_SEARCH (squery, "packages") + << DIV_COUNTER (pkg_count, "Package", "Packages"); + + // Enclose the subsequent tables to be able to use nth-child CSS selector. + // + // @@ TENANT: use tenant for sorting when add support for global view. + // + s << DIV; + for (const auto& pr: + package_db_->query ( + search_param (squery, tenant) + + "ORDER BY rank DESC, name" + + "OFFSET" + to_string (page * res_page) + + "LIMIT" + to_string (res_page))) + { + shared_ptr p (package_db_->load (pr.id)); + + s << TABLE(CLASS="proplist package") + << TBODY + << TR_NAME (p->name, equery, root, tenant) + << TR_SUMMARY (p->summary) + << TR_LICENSE (p->license_alternatives) + << TR_TAGS (p->project, p->tags, root, tenant) + << TR_DEPENDS (p->dependencies, root, tenant) + << TR_REQUIRES (p->requirements) + << ~TBODY + << ~TABLE; + } + s << ~DIV; + + t.commit (); + + string url (tenant_dir (root, tenant).string () + "?packages"); + + if (!equery.empty ()) + { + url += '='; + url += equery; + } + + s << DIV_PAGER (page, + pkg_count, + res_page, + options_->search_pages (), + url) + << ~DIV + << ~BODY + << ~HTML; + + return true; +} diff --git a/mod/mod-packages.hxx b/mod/mod-packages.hxx new file mode 100644 index 0000000..dd11136 --- /dev/null +++ b/mod/mod-packages.hxx @@ -0,0 +1,42 @@ +// file : mod/mod-packages.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef MOD_MOD_PACKAGES_HXX +#define MOD_MOD_PACKAGES_HXX + +#include +#include + +#include +#include + +namespace brep +{ + class packages: public database_module + { + public: + packages () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + packages (const packages&); + + virtual bool + handle (request&, response&); + + virtual const cli::options& + cli_options () const {return options::packages::description ();} + + private: + virtual void + init (cli::scanner&); + + private: + shared_ptr options_; + }; +} + +#endif // MOD_MOD_PACKAGES_HXX diff --git a/mod/mod-repository-root.cxx b/mod/mod-repository-root.cxx index b8777fd..b825ea1 100644 --- a/mod/mod-repository-root.cxx +++ b/mod/mod-repository-root.cxx @@ -7,6 +7,7 @@ #include // tzset() #include +#include // find() #include @@ -16,11 +17,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -105,7 +106,7 @@ namespace brep // repository_root:: repository_root () - : package_search_ (make_shared ()), + : packages_ (make_shared ()), package_details_ (make_shared ()), package_version_details_ (make_shared ()), repository_details_ (make_shared ()), @@ -126,10 +127,10 @@ namespace brep // Deep/shallow-copy sub-handlers depending on whether this is an // exemplar/handler. // - package_search_ ( + packages_ ( r.initialized_ - ? r.package_search_ - : make_shared (*r.package_search_)), + ? r.packages_ + : make_shared (*r.packages_)), package_details_ ( r.initialized_ ? r.package_details_ @@ -185,7 +186,7 @@ namespace brep options () { option_descriptions r (handler::options ()); - append (r, package_search_->options ()); + append (r, packages_->options ()); append (r, package_details_->options ()); append (r, package_version_details_->options ()); append (r, repository_details_->options ()); @@ -229,7 +230,7 @@ namespace brep // Initialize sub-handlers. // - sub_init (*package_search_, "package_search"); + sub_init (*packages_, "packages"); sub_init (*package_details_, "package_details"); sub_init (*package_version_details_, "package_version_details"); sub_init (*repository_details_, "repository_details"); @@ -255,6 +256,19 @@ namespace brep options_ = make_shared ( s, unknown_mode::fail, unknown_mode::fail); + // Verify that the root default views are properly configured. + // + auto verify = [&fail] (const string& v, const char* what) + { + cstrings vs ({"packages", "builds", "about", "submit", "ci"}); + + if (find (vs.begin (), vs.end (), v) == vs.end ()) + fail << what << " value '" << v << "' is invalid"; + }; + + verify (options_->root_global_view (), "root-global-view"); + verify (options_->root_tenant_view (), "root-tenant-view"); + if (options_->root ().empty ()) options_->root (dir_path ("/")); @@ -340,73 +354,97 @@ namespace brep // if (lpath.empty ()) { - // Dispatch request handling to the repository_details or the one of - // build_* handlers depending on the function name passed as a first HTTP - // request parameter (example: cppget.org/?about). Dispatch to the - // package_search handler if the function name is unavailable (no - // parameters) or is not recognized. + // Dispatch request handling to one of the sub-handlers depending on the + // function name passed as a first HTTP request parameter (example: + // cppget.org/?about). If it doesn't denote a handler or there are no + // parameters, then dispatch to the default handler. // const name_values& params (rq.parameters (0 /* limit */, true /* url_only */)); - if (!params.empty ()) + + auto dispatch = [&handle, this] (const string& func, + bool param) -> optional { - const string& fn (params.front ().name); + // When adding a new handler don't forget to check if need to add it + // to the default view list in the init() function. + // + if (func == "build-task") + { + if (handler_ == nullptr) + handler_.reset (new build_task (*build_task_)); - if (fn == "about") + return handle ("build_task", param); + } + else if (func == "build-result") { if (handler_ == nullptr) - handler_.reset (new repository_details (*repository_details_)); + handler_.reset (new build_result (*build_result_)); - return handle ("repository_details", true); + return handle ("build_result", param); } - else if (fn == "build-task") + else if (func == "build-force") { if (handler_ == nullptr) - handler_.reset (new build_task (*build_task_)); + handler_.reset (new build_force (*build_force_)); - return handle ("build_task", true); + return handle ("build_force", param); } - else if (fn == "build-result") + else if (func == "builds") { if (handler_ == nullptr) - handler_.reset (new build_result (*build_result_)); + handler_.reset (new builds (*builds_)); - return handle ("build_result", true); + return handle ("builds", param); } - else if (fn == "build-force") + else if (func == "packages") { if (handler_ == nullptr) - handler_.reset (new build_force (*build_force_)); + handler_.reset (new packages (*packages_)); - return handle ("build_force", true); + return handle ("packages", param); } - else if (fn == "builds") + else if (func == "about") { if (handler_ == nullptr) - handler_.reset (new builds (*builds_)); + handler_.reset (new repository_details (*repository_details_)); - return handle ("builds", true); + return handle ("repository_details", param); } - else if (fn == "submit") + else if (func == "submit") { if (handler_ == nullptr) handler_.reset (new submit (*submit_)); - return handle ("submit", true); + return handle ("submit", param); } - else if (fn == "ci") + else if (func == "ci") { if (handler_ == nullptr) handler_.reset (new ci (*ci_)); - return handle ("ci", true); + return handle ("ci", param); } - } + else + return nullopt; + }; + + optional r; + + if (!params.empty () && + (r = dispatch (params.front ().name, true /* param */))) + return *r; - if (handler_ == nullptr) - handler_.reset (new package_search (*package_search_)); + const string& view (!tenant.empty () + ? options_->root_tenant_view () + : options_->root_global_view ()); + + r = dispatch (view, false /* param */); + + // The default views are verified in the init() function. + // + assert (r); - return handle ("package_search"); + return *r; } else { diff --git a/mod/mod-repository-root.hxx b/mod/mod-repository-root.hxx index 9a71849..a46f43f 100644 --- a/mod/mod-repository-root.hxx +++ b/mod/mod-repository-root.hxx @@ -13,7 +13,7 @@ namespace brep { - class package_search; + class packages; class package_details; class package_version_details; class repository_details; @@ -58,7 +58,7 @@ namespace brep version (); private: - shared_ptr package_search_; + shared_ptr packages_; shared_ptr package_details_; shared_ptr package_version_details_; shared_ptr repository_details_; diff --git a/mod/options.cli b/mod/options.cli index 619df66..12f577c 100644 --- a/mod/options.cli +++ b/mod/options.cli @@ -311,7 +311,8 @@ namespace brep // Handler options. // - class package_search: search, package_db, page, handler + + class packages: search, package_db, page, handler { string search-title = "Packages" { @@ -532,6 +533,21 @@ namespace brep class repository_root: handler { + string root-global-view = "packages" + { + "", + "The default view to display for the global repository root. The + argument is one of the supported services (\c{packages}, + \c{builds}, \c{submit}, \c{ci}, etc)." + } + + string root-tenant-view = "packages" + { + "" + "The default view to display for the tenant repository root. The + argument is one of the supported services (\c{packages}, + \c{builds}, \c{submit}, \c{ci}, etc)." + } }; } @@ -542,7 +558,7 @@ namespace brep // Use parameters long names in the C++ code, short aliases (if present) // in HTTP URL. // - class package_search + class packages { // Display package search result list starting from this page. // @@ -550,7 +566,10 @@ namespace brep // Package search criteria. // - string query | q; + // Note that the packages parameter is renamed to '_' by the root + // handler (see the request_proxy class for details). + // + string q | _; }; class package_details diff --git a/mod/page.cxx b/mod/page.cxx index 46f4879..ce12da6 100644 --- a/mod/page.cxx +++ b/mod/page.cxx @@ -98,7 +98,8 @@ namespace brep << TBODY << TR << TD(ID="search-txt") - << *INPUT(TYPE="search", NAME="q", VALUE=query_, AUTOFOCUS="") + << *INPUT(TYPE="search", NAME=name_, VALUE=query_, + AUTOFOCUS="") << ~TD << TD(ID="search-btn") << *INPUT(TYPE="submit", VALUE="Search") @@ -192,14 +193,15 @@ namespace brep << SPAN(CLASS="value") << A << HREF - - // Propagate search criteria to the package details page. - // << tenant_dir (root_, tenant_) / - path (mime_url_encode (name_.string (), false)) - << query_param_ + path (mime_url_encode (name_.string (), false)); - << ~HREF + // Propagate search criteria to the package details page. + // + if (!query_.empty ()) + s << "?q=" << query_; + + s << ~HREF << name_ << ~A << ~SPAN @@ -255,7 +257,7 @@ namespace brep << SPAN(CLASS="value") << A << HREF - << tenant_dir (root_, tenant_) << "?q=" + << tenant_dir (root_, tenant_) << "?packages=" << mime_url_encode (project_.string ()) << ~HREF << project_ @@ -356,7 +358,8 @@ namespace brep { s << A << HREF - << tenant_dir (root_, tenant_) << "?q=" << mime_url_encode (t) + << tenant_dir (root_, tenant_) << "?packages=" + << mime_url_encode (t) << ~HREF << t << ~A; @@ -500,8 +503,8 @@ namespace brep if (r.empty ()) { - // If there is no requirement alternatives specified, then - // print the comment first word. + // If there is no requirement alternatives specified, then print the + // comment first word. // const auto& c (r.comment); if (!c.empty ()) diff --git a/mod/page.hxx b/mod/page.hxx index d3e10db..d9f4249 100644 --- a/mod/page.hxx +++ b/mod/page.hxx @@ -60,18 +60,20 @@ namespace brep const string& tenant_; }; - // Generates package search form element. + // Generates package search form element with the specified query input + // element name. // class FORM_SEARCH { public: - FORM_SEARCH (const string& q): query_ (q) {} + FORM_SEARCH (const string& q, const string& n): query_ (q), name_ (n) {} void operator() (xml::serializer&) const; private: const string& query_; + const string& name_; }; // Generates counter element. @@ -160,7 +162,8 @@ namespace brep const vector>& options_; }; - // Generates package name element. + // Generates package name element with an optional search criteria. The + // search string should be url-encoded, if specified. // class TR_NAME { @@ -169,14 +172,14 @@ namespace brep const string& q, const dir_path& r, const string& t) - : name_ (n), query_param_ (q), root_ (r), tenant_ (t) {} + : name_ (n), query_ (q), root_ (r), tenant_ (t) {} void operator() (xml::serializer&) const; private: const package_name& name_; - const string& query_param_; + const string& query_; const dir_path& root_; const string& tenant_; }; -- cgit v1.1