lib/intranet/pictures/responder.rb in intranet-pictures-1.1.0 vs lib/intranet/pictures/responder.rb in intranet-pictures-2.0.0

- old
+ new

@@ -7,43 +7,50 @@ require_relative 'version' module Intranet module Pictures # The responder for the Pictures monitor module of the Intranet. - class Responder < AbstractResponder + class Responder < AbstractResponder # rubocop:disable Metrics/ClassLength include Core::HamlWrapper # 'inherits' from methods of HamlWrapper # Returns the name of the module. # @return [String] The name of the module. def self.module_name NAME end - # The version of the module, according to semantic versionning. + # Returns the version of the module, according to semantic versionning. # @return [String] The version of the module. def self.module_version VERSION end - # The homepage of the module. + # Returns the homepage URL of the module. # @return [String] The homepage URL of the module. def self.module_homepage HOMEPAGE_URL end # Initializes a new Pictures responder instance. - # @param provider [#title,#group_types,#list_groups,#group_thumbnail,#list_pictures,#picture] + # @param provider [#title,#list_pictures,#group_thumbnail,#group_brief,#picture] # The pictures provider. # @see Intranet::Pictures::JsonDbProvider The specification of the provider, and in particular # the minimal mandatory elements that must be returned by the operations. - # @param recents [Array<Hash{group_type:String, sort_by:String, asc:Boolean, limit:Integer}>] - # The description of the recent pictures album to be displayed on the module home page (all - # keys except +group_type+ may be omitted). - # @param home_groups [Array<Hash{group_type:String, sort_by:String, asc:Boolean, - # browse:String, browse_sort_by:String, browse_asc:Boolean}>] - # The description of the groups to be displayed on the module home page. All groups of the - # +group_type+ will be displayed and link to a page showing all groups of the +browse+ type. + # @param recents [Array<Hash{group_by:String, sort_by:String, sort_order:String, + # limit:Integer}>] + # The description of the recent pictures to be displayed on the module home page. Pictures + # will first be sorted according to +sort_by+ and +sort_order+, then grouped by +group_by+, + # keeping only the first +limit+ elements, or all if +limit+ is zero. All keys except + # +group_by+ may be omitted. + # @param home_groups [Array<Hash{group_by:String, sort_by:String, sort_order:String, + # browse_group_by:String, browse_sort_by:String, browse_sort_order:String}>] + # The description of the pictures groups to be displayed on the module home page, after the + # +recents+. Pictures will first be sorted according to +sort_by+ and +sort_order+, then + # grouped by +group_by+. The obtained groups will be displayed with their thumbnail, + # (optional) brief text and a link to display images of that group sorted according to + # +browse_sort_by+ and +browse_sort_order+ and grouped by +browse_group_by+. All keys except + # +group_by+ and +browse_group_by+ may be omitted. # @param in_menu [Boolean] Whether the module instance should be displayed in the main # navigation menu or not. def initialize(provider, recents = [], home_groups = [], in_menu = true) @provider = provider @recents = recents @@ -63,63 +70,78 @@ def resources_dir File.absolute_path(File.join('..', 'resources'), __dir__) end # Generates the HTML content associated to the given +path+ and +query+. - # === REST API Description: - # * Read-only access to pictures listings under */api/pictures* using +GET+ method, response - # is in JSON format with the following structure: + # @param path [String] The requested URI, relative to that module root URI. + # @param query [Hash<String,String>] The URI variable/value pairs, if any. + # @return [Integer, String, String] The HTTP return code, the MIME type and the answer body. + # + # Relevant queries are as follows: + # + # * If +path+ is +/browse.html+, key/value pairs may be used to restrict the displayed images + # to those satisfying all the pairs. Moreover, +sort_by+ (followed by a key), +sort_order+ + # (followed by either +asc+ or +desc+) and +group_by+ may be used to alter the way images + # are displayed. + # * If +path+ is +/api/group_thumbnail+ or +/api/group_brief+, query must contain only a + # single key/value pair designating the pictures group. + # * If +path+ is +/api/pictures+, key/value pairs may be used to restrict the displayed images + # to those satisfying all the pairs. Moreover, +sort_by+ (followed by a key), and + # +sort_order+ (followed by either +asc+ or +desc+) may be used to alter the order in which + # images are returned. + # * If +path+ is +api/picture+, key/value pairs must be used to select a single image from the + # gallery. + # + # When +path+ is +/api/pictures+, the selected pictures are returned in JSON format (REST API) + # with the following structure: # [ # { "id": "...", "height": 480, "width": 640, "title": "...", "datetime": "...", ... }, # { ... } # ] - # * Read-only access to groups listings under */api/groups/*+group_type+ using +GET+ method, - # response is in JSON format with the following structure: - # [ - # { "id": "...", "title": "...", ... }, - # { ... } - # ] - # @param path [String] The requested URI, relative to that module root URI. - # @param query [Hash<String,String>] The URI variable/value pairs, if any. - # @return [Integer, String, String] The HTTP return code, the MIME type and the answer body. def generate_page(path, query) case path - when %r{^/index\.html$} then serve_home - when %r{^/browse_\w+\.html$} - serve_groups(path.gsub(%r{^/browse_(\w+)\.html$}, '\\1'), query) + when %r{^/index\.html$} then serve_home + when %r{^/browse\.html$} then serve_browse(query) when %r{^/api/} then serve_api(path.gsub(%r{^/api}, ''), query) when %r{^/i18n\.js$} then serve_i18n_js else super(path, query) end end - # The title of the Pictures module, as displayed on the web page. + # Returns the title of the Pictures module, as displayed on the web page. # @return [String] The title of the Pictures module web page. def title @provider.title end private # Extract a selector from the given +query+. - # @return [Hash<String,String>] The picture or group selector. + # @return [Hash<String,String>] The picture selector. def selector(query) - query.except('sort_by', 'sort_order') + query.except('group_by', 'sort_by', 'sort_order') end - # Extract the sort criteria from the given +query+. - # @return [String] The key to use to sort pictures or groups, or nil if no sort order is - # specified in +query+. + # Extract the grouping criteria from the given +query+, ie. the key that will be used to group + # the selected pictures. + # @return [String] The key to use to group pictures. + # @raise KeyError If no grouping criteria is specified. + def group_by(query) + query.fetch('group_by') + end + + # Extract the sorting criteria from the given +query+, ie. the key that will be used to sort + # the selected pictures. + # @return [String] The key to use to sort pictures, or nil if no sorting is specified. def sort_by(query) query.fetch('sort_by') rescue KeyError nil end - # Extract the sort order from the given +query+. - # @return [Boolean] False if the pictures or groups should be sorted in descending order, - # True otherwise. + # Extract the sorting order from the given +query+. + # @return [Boolean] True if the pictures should be sorted in ascending order, False otherwise. # @raise KeyError If the query requests an invalid sort order. def sort_order(query) return false if query['sort_order'] == 'desc' return true if query['sort_order'].nil? || query['sort_order'] == 'asc' @@ -141,69 +163,72 @@ ########################################################################## ### Servicing of the HTML "display-able" content ### ########################################################################## - def active_filters(query) - selector(query).map do |k, v| - { k => @provider.list_groups(k, { k => v }).first.fetch('title') } - rescue KeyError - { k => v } - end.reduce({}, :merge) + def collect_groups(selector, group_by, sort_by, sort_order) + # Select all pictures, sort them & eventually keep only group names (first occurrence only) + @provider.list_pictures(selector, sort_by, sort_order).map do |picture| + picture.fetch(group_by) + end.uniq end + def hash_to_query(hash) + hash.map { |k, v| [k, v].join('=') }.join('&') + end + def recent_groups @recents.map do |recent| - groups = @provider.list_groups(recent[:group_type], {}, recent[:sort_by], recent[:asc]) + groups = collect_groups({}, group_by(recent), sort_by(recent), sort_order(recent)) see_more_url = '' - if recent[:limit].to_i.positive? - groups = groups.first(recent[:limit]) - see_more_url = "browse_#{recent[:group_type]}.html?sort_by=#{recent[:sort_by]}" - see_more_url += '&sort_order=desc' unless recent[:asc] + if recent['limit'].to_i.positive? + groups = groups.first(recent['limit'].to_i) + see_more_url = "browse.html?#{hash_to_query(recent.except('limit'))}" end - { group_type: recent[:group_type], groups: groups, see_more_url: see_more_url } + { group_key: group_by(recent), groups: groups, see_more_url: see_more_url } end end def all_groups - @home_groups.map do |section| - groups = @provider.list_groups(section[:group_type], {}, section[:sort_by], section[:asc]) - url_prefix = "browse_#{section[:browse]}.html?sort_by=#{section[:browse_sort_by]}" - url_prefix += '&sort_order=desc' unless section[:browse_asc] - { group_type: section[:group_type], groups: groups, url_prefix: url_prefix } + @home_groups.map do |sec| + groups = collect_groups({}, group_by(sec), sort_by(sec), sort_order(sec)) + url_prefix = "browse.html?group_by=#{sec['browse_group_by']}" + url_prefix += "&sort_by=#{sec['browse_sort_by']}" if sec['browse_sort_by'] + url_prefix += "&sort_order=#{sec['browse_sort_order']}" if sec['browse_sort_order'] + { group_key: group_by(sec), groups: groups, url_prefix: url_prefix } end end - def make_nav(group_type = nil, query = {}) + def make_nav(query = {}) h = { I18n.t('nav.home') => '/index.html', I18n.t('pictures.menu') => nil, title => nil } - unless group_type.nil? + unless query['group_by'].nil? h[title] = 'index.html' - extra_key = I18n.t("pictures.nav.#{group_type}") - filters = active_filters(query).values + extra_key = I18n.t("pictures.nav.#{group_by(query)}") + filters = selector(query).values extra_key += " (#{filters.join(', ')})" unless filters.empty? h.store(extra_key, nil) end h end - def gallery_url(group_type, group_id, filters = {}) - filters.store(group_type, group_id) + def gallery_url(key, value, filters = {}) + filters.store(key, value) filters.store('sort_by', 'datetime') - filters.map { |k, v| [k, v].join('=') }.join('&') + hash_to_query(filters) end def serve_home content = to_markup('pictures_home', nav: make_nav) [206, 'text/html', { content: content, title: title, stylesheets: stylesheets, scripts: scripts }] rescue KeyError [404, '', ''] end - def serve_groups(type, query) - groups = @provider.list_groups(type, selector(query), sort_by(query), sort_order(query)) - content = to_markup('pictures_browse', nav: make_nav(type, query), group_type: type, + def serve_browse(query) + groups = collect_groups(selector(query), group_by(query), sort_by(query), sort_order(query)) + content = to_markup('pictures_browse', nav: make_nav(query), group_key: group_by(query), filters: selector(query), groups: groups) [206, 'text/html', { content: content, title: title, stylesheets: stylesheets, scripts: scripts }] rescue KeyError [404, '', ''] @@ -220,31 +245,35 @@ ########################################################################## ### Servicing of the REST API (raw JSON data & pictures) ### ########################################################################## - def api_list_groups(path, query) - group_type = path.split('/')[2].to_s - @provider.list_groups(group_type, selector(query), sort_by(query), sort_order(query)) - end + def api_group_thumbnail(query) + raise KeyError unless query.size == 1 - def api_group_thumbnail(path, query) - group_type = path.split('/')[2].to_s - pic = @provider.group_thumbnail(group_type, selector(query)) + key, value = query.first + pic = @provider.group_thumbnail(key, value) if pic.nil? pic = ['image/svg+xml', File.read(File.join(resources_dir, 'www', 'group_thumbnail.svg'))] end pic end + def api_group_brief(query) + raise KeyError unless query.size == 1 + + key, value = query.first + @provider.group_brief(key, value) + end + def api_list_pictures(query) @provider.list_pictures(selector(query), sort_by(query), sort_order(query)) end def serve_api(path, query) case path - when %r{^/groups/} then [200, 'application/json', api_list_groups(path, query).to_json] - when %r{^/group/} then [200, api_group_thumbnail(path, query)].flatten + when %r{^/group_thumbnail$} then [200, api_group_thumbnail(query)].flatten + when %r{^/group_brief$} then [200, 'application/json', api_group_brief(query).to_json] when %r{^/pictures$} then [200, 'application/json', api_list_pictures(query).to_json] when %r{^/picture$} then [200, @provider.picture(query)].flatten else [404, '', ''] end rescue KeyError