aboutsummaryrefslogtreecommitdiff
path: root/brep
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-09-13 22:46:48 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-09-24 11:23:30 +0200
commit75825230abcc5f303a29f1f74b29c36e6140a064 (patch)
tree00083fcb5bc44706fdaf85153f2a115f70a5c8c3 /brep
parent7747dde9fa64fe0b5838d730a1d892f3f49d2d5d (diff)
Implement package details page
Diffstat (limited to 'brep')
-rw-r--r--brep/buildfile2
-rw-r--r--brep/options.cli1
-rw-r--r--brep/package7
-rw-r--r--brep/package-version-search.cxx162
-rw-r--r--brep/page52
-rw-r--r--brep/page.cxx103
6 files changed, 324 insertions, 3 deletions
diff --git a/brep/buildfile b/brep/buildfile
index 9b64ed4..7e73d2b 100644
--- a/brep/buildfile
+++ b/brep/buildfile
@@ -22,7 +22,7 @@ libso{brep}: cxx.export.poptions = -I$out_root -I$src_root
import libs += libstudxml%lib{studxml}
brep = cxx{diagnostics module services package-search package-version-search \
- shared-database} cli.cxx{options}
+ shared-database page} cli.cxx{options}
web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding}
libso{brep-apache}: $brep $web libso{brep} $libs
diff --git a/brep/options.cli b/brep/options.cli
index 8062f59..a042f45 100644
--- a/brep/options.cli
+++ b/brep/options.cli
@@ -30,6 +30,7 @@ namespace brep
class package_version_search: module, db
{
std::uint16_t results-on-page = 10;
+ std::uint16_t pages-in-pager = 10;
};
}
diff --git a/brep/package b/brep/package
index 0132b73..0ef4407 100644
--- a/brep/package
+++ b/brep/package
@@ -482,6 +482,13 @@ namespace brep
#pragma db member(id) virtual(package_version::_id_type) \
get() set(_id (std::move (?)))
};
+
+ #pragma db view object(package_version)
+ struct package_version_count
+ {
+ #pragma db column("count(1)")
+ std::size_t count;
+ };
}
// Nested container emulation support for ODB.
diff --git a/brep/package-version-search.cxx b/brep/package-version-search.cxx
index a989434..d95b392 100644
--- a/brep/package-version-search.cxx
+++ b/brep/package-version-search.cxx
@@ -16,6 +16,7 @@
#include <web/module>
#include <web/mime-url-encoding>
+#include <brep/page>
#include <brep/package>
#include <brep/package-odb>
#include <brep/shared-database>
@@ -60,14 +61,171 @@ namespace brep
throw invalid_request (400, e.what ());
}
- const char* title ("Package");
+ const char* ident ("\n ");
+ const string title ("Package " + name);
serializer s (rs.content (), title);
s << HTML
<< HEAD
<< TITLE << title << ~TITLE
+ << CSS_STYLE << ident
+ << pager_style () << ident
+ << "a {text-decoration: none;}" << ident
+ << "a:hover {text-decoration: underline;}" << ident
+ << ".name {font-size: xx-large; font-weight: bold;}" << ident
+ << ".summary {font-size: x-large; margin: 0.2em 0 0;}" << ident
+ << ".url {font-size: small;}" << ident
+ << ".email {font-size: small;}" << ident
+ << ".description {margin: 0.5em 0 0;}" << ident
+ << ".tags {margin: 0.5em 0 0;}" << ident
+ << ".tag {padding: 0 0.3em 0 0;}" << ident
+ << ".versions {font-size: x-large; margin: 0.5em 0 0;}" << ident
+ << ".package_version {margin: 0.5em 0 0;}" << ident
+ << ".version {font-size: x-large;}" << ident
+ << ~CSS_STYLE
<< ~HEAD
- << BODY << name << ~BODY
+ << BODY;
+
+ transaction t (db_->begin ());
+
+ shared_ptr<package> p;
+
+ try
+ {
+ p = db_->load<package> (name);
+ }
+ catch (const object_not_persistent& )
+ {
+ throw invalid_request (404, "Package '" + name + "' not found");
+ }
+
+ s << DIV(CLASS="name")
+ << name
+ << ~DIV
+ << DIV(CLASS="summary")
+ << p->summary
+ << ~DIV;
+
+ s << DIV(CLASS="url")
+ << A << HREF << p->url << ~HREF << p->url << ~A
+ << ~DIV
+ << DIV(CLASS="email")
+ << A << HREF << "mailto:" << p->email << ~HREF << p->email << ~A
+ << ~DIV;
+
+ if (p->description)
+ s << DIV(CLASS="description")
+ << *p->description
+ << ~DIV;
+
+ if (!p->tags.empty ())
+ {
+ s << DIV(CLASS="tags");
+
+ for (const auto& t: p->tags)
+ s << SPAN(CLASS="tag") << t << ~SPAN << " ";
+
+ s << ~DIV;
+ }
+
+ // @@ Query will also include search criteria if specified.
+ //
+ size_t pvc (
+ db_->query_value<package_version_count> (
+ query<package_version_count>::id.data.package == name).count);
+
+ s << DIV(CLASS="versions")
+ << "Versions (" << pvc << ")"
+ << ~DIV;
+
+ if (p->package_url)
+ s << DIV(CLASS="url")
+ << A << HREF << *p->package_url << ~HREF << *p->package_url << ~A
+ << ~DIV;
+
+ if (p->package_email)
+ s << DIV(CLASS="email")
+ << A
+ << HREF << "mailto:" << *p->package_email << ~HREF
+ << *p->package_email
+ << ~A
+ << ~DIV;
+
+ size_t rop (options_->results_on_page ());
+
+ // @@ Use appropriate view when clarify which package version info to be
+ // displayed and search index structure get implemented. Query will also
+ // include search criteria if specified.
+ //
+ using query = query<package_version>;
+ auto r (
+ db_->query<package_version> ((query::id.data.package == name) +
+ "ORDER BY" + query::id.data.epoch + "DESC," +
+ query::id.data.canonical_upstream + "DESC," +
+ query::id.revision + "DESC " +
+ "OFFSET" + to_string (pr.page () * rop) +
+ "LIMIT" + to_string (rop)));
+
+ for (const auto& v: r)
+ {
+ static const strings priority_names (
+ {"low", "medium", "high", "security"});
+
+ assert (v.priority < priority_names.size ());
+
+ const string& vs (v.version.string ());
+
+ s << DIV(CLASS="package_version")
+ << DIV(CLASS="version")
+ << A
+ << HREF
+ << "/go/" << mime_url_encode (name) << "/" << vs << "/"
+ << ~HREF
+ << vs
+ << ~A
+ << ~DIV
+ << DIV(CLASS="priority")
+ << "Priority: " << priority_names[v.priority]
+ << ~DIV
+ << DIV(CLASS="licenses")
+ << "Licenses: ";
+
+ for (const auto& la: v.license_alternatives)
+ {
+ if (&la != &v.license_alternatives[0])
+ s << " or ";
+
+ for (const auto& l: la)
+ {
+ if (&l != &la[0])
+ s << ", ";
+
+ s << l;
+ }
+ }
+
+ s << ~DIV
+ << ~DIV;
+ }
+
+ t.commit ();
+
+ auto u (
+ [&name, &pr](size_t p)
+ {
+ string url ("/go/" + name + "/");
+ if (p > 0)
+ url += "?p=" + to_string (p);
+
+ if (!pr.query ().empty ())
+ url +=
+ string (p > 0 ? "&" : "?") + "q=" + mime_url_encode (pr.query ());
+
+ return url;
+ });
+
+ s << pager (pr.page (), pvc, rop, options_->pages_in_pager (), u)
+ << ~BODY
<< ~HTML;
}
}
diff --git a/brep/page b/brep/page
new file mode 100644
index 0000000..81b6da8
--- /dev/null
+++ b/brep/page
@@ -0,0 +1,52 @@
+// file : brep/page -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BREP_PAGE
+#define BREP_PAGE
+
+#include <string>
+#include <cstddef> // size_t
+#include <functional>
+
+#include <xml/forward>
+
+namespace brep
+{
+ // Page common building blocks.
+ //
+
+ // Generates paging element block.
+ //
+ class pager
+ {
+ public:
+ using get_url_type = std::function<std::string(std::size_t page)>;
+
+ pager (std::size_t current_page,
+ std::size_t item_count,
+ std::size_t item_per_page,
+ std::size_t page_number_count,
+ get_url_type get_url);
+
+ void
+ operator() (xml::serializer& s) const;
+
+ private:
+ std::size_t current_page_;
+ std::size_t item_count_;
+ std::size_t item_per_page_;
+ std::size_t page_number_count_;
+ get_url_type get_url_;
+ };
+
+ // Default pager element block style.
+ //
+ struct pager_style
+ {
+ void
+ operator() (xml::serializer& s) const;
+ };
+}
+
+#endif // BREP_PAGE
diff --git a/brep/page.cxx b/brep/page.cxx
new file mode 100644
index 0000000..70c6d8c
--- /dev/null
+++ b/brep/page.cxx
@@ -0,0 +1,103 @@
+// file : brep/page.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <brep/page>
+
+#include <utility> // move()
+#include <algorithm> // min()
+
+#include <xml/serializer>
+
+#include <web/xhtml>
+
+using namespace std;
+using namespace xml;
+using namespace web::xhtml;
+
+namespace brep
+{
+ // pager
+ //
+ pager::
+ pager (size_t current_page,
+ size_t item_count,
+ size_t item_per_page,
+ size_t page_number_count,
+ get_url_type get_url)
+ : current_page_ (current_page),
+ item_count_ (item_count),
+ item_per_page_ (item_per_page),
+ page_number_count_ (page_number_count),
+ get_url_ (move (get_url))
+ {
+ }
+
+ void pager::
+ operator() (serializer& s) const
+ {
+ if (item_count_ == 0 || item_per_page_ == 0)
+ return;
+
+ size_t pc (item_count_ / item_per_page_); // Page count.
+
+ if (item_count_ % item_per_page_)
+ ++pc;
+
+ if (pc > 1)
+ {
+ // Can consider customizing class names if use-case appear.
+ //
+ s << DIV(CLASS="pager");
+
+ if (current_page_ > 0)
+ s << A(CLASS="pg-prev")
+ << HREF << get_url_ (current_page_ - 1) << ~HREF
+ << "<<"
+ << ~A
+ << " ";
+
+ if (page_number_count_)
+ {
+ size_t offset (page_number_count_ / 2);
+ size_t fp (current_page_ > offset ? current_page_ - offset : 0);
+ size_t tp (min (fp + page_number_count_, pc));
+
+ for (size_t p (fp); p < tp; ++p)
+ {
+ if (p == current_page_)
+ s << SPAN(CLASS="pg-cpage")
+ << p + 1
+ << ~SPAN;
+ else
+ s << A(CLASS="pg-page")
+ << HREF << get_url_ (p) << ~HREF
+ << p + 1
+ << ~A;
+
+ s << " ";
+ }
+ }
+
+ if (current_page_ < pc - 1)
+ s << A(CLASS="pg-next")
+ << HREF << get_url_ (current_page_ + 1) << ~HREF
+ << ">>"
+ << ~A;
+
+ s << ~DIV;
+ }
+ }
+
+ // pager_style
+ //
+ void pager_style::
+ operator() (xml::serializer& s) const
+ {
+ const char* ident ("\n ");
+ s << ".pager {margin: 0.5em 0 0;}" << ident
+ << ".pg-prev {padding: 0 0.3em 0 0;}" << ident
+ << ".pg-page {padding: 0 0.3em 0 0;}" << ident
+ << ".pg-cpage {padding: 0 0.3em 0 0; font-weight: bold;}";
+ }
+}