commit de58457ef924b2d42e3a9c3583a9690008227b4b Author: Senad Uka Date: Mon Aug 13 11:25:01 2018 +0200 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..3145f33 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +########################## +# Services +########################## +TREE_SOURCE_API_HOSTNAME=https://kf6xwyykee.execute-api.us-east-1.amazonaws.com/production +RESTCLIENT_LOG=stdout diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45d62d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sw? diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d879829 --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +source 'https://rubygems.org' + +gem 'sinatra', "~> 2.0" +gem "retries", "~> 0.0.5" + +gem "dotenv", "~> 2.5" + +gem "rack-indifferent", "~> 1.2" + +gem "sinatra-router", "~> 0.2.4" + +gem "rest-client", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..384bd80 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,48 @@ +GEM + remote: https://rubygems.org/ + specs: + domain_name (0.5.20180417) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.5.0) + http-cookie (1.0.3) + domain_name (~> 0.5) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mustermann (1.0.2) + netrc (0.11.0) + rack (2.0.5) + rack-indifferent (1.2.0) + rack (>= 1.5) + rack-protection (2.0.3) + rack + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retries (0.0.5) + sinatra (2.0.3) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.3) + tilt (~> 2.0) + sinatra-router (0.2.4) + sinatra (>= 1.4, < 3.0) + tilt (2.0.8) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.5) + +PLATFORMS + ruby + +DEPENDENCIES + dotenv (~> 2.5) + rack-indifferent (~> 1.2) + rest-client (~> 2.0) + retries (~> 0.0.5) + sinatra (~> 2.0) + sinatra-router (~> 0.2.4) + +BUNDLED WITH + 1.16.2 diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..b6cc0be --- /dev/null +++ b/config.ru @@ -0,0 +1,15 @@ +require 'bundler/setup' +require 'dotenv/load' +require 'rack/indifferent' +require 'sinatra/router' + +require_relative 'lib/api/tree.rb' + +app = Sinatra::Router.new do + mount Pruning::API::Tree +end + +map '/' do + run app +end + diff --git a/lib/api/app.rb b/lib/api/app.rb new file mode 100644 index 0000000..6a12a50 --- /dev/null +++ b/lib/api/app.rb @@ -0,0 +1,23 @@ +require 'json' +require 'sinatra/base' +require_relative '../http/query' + +module Pruning + module API + class App < Sinatra::Base + before { content_type :json } + after { serialise_response } + + private + + def serialise_response + return unless content_type == 'application/json' + response.body = [JSON(response.body)] + end + + def query + Pruning::HTTP::Query.new(params) + end + end + end +end diff --git a/lib/api/tree.rb b/lib/api/tree.rb new file mode 100644 index 0000000..f22734c --- /dev/null +++ b/lib/api/tree.rb @@ -0,0 +1,17 @@ +require 'rest-client' +require_relative 'app' +require_relative '../repos/tree' +require_relative '../pruner' + +module Pruning + module API + class Tree < App + get '/tree/:name' do + tree_repo = Pruning::Repos::Tree.new(RestClient, ENV['TREE_SOURCE_API_HOSTNAME']) + complete_tree = tree_repo.get(query.name) + pruner = Pruning::Processing::Pruner.new(complete_tree) + pruner.prune_tree(query.indicator_ids) + end + end + end +end diff --git a/lib/http/query.rb b/lib/http/query.rb new file mode 100644 index 0000000..4519524 --- /dev/null +++ b/lib/http/query.rb @@ -0,0 +1,18 @@ +module Pruning + module HTTP + class Query < Struct.new(:name, :indicator_ids) + def initialize(params = {}) + values = members.map do |member| + value = params.fetch(member, nil) + next if value.nil? + case member + when :indicator_ids then value.map(&:to_i) # break on purpose if indicator_ids is not an array + when :name then value.to_s.gsub(/[^A-Za-z]/,'') + else value + end + end + super(*values) + end + end + end +end diff --git a/lib/pruner.rb b/lib/pruner.rb new file mode 100644 index 0000000..f7e19c0 --- /dev/null +++ b/lib/pruner.rb @@ -0,0 +1,32 @@ +module Pruning + module Processing + class Pruner + + def initialize(tree, indicator_ids) + @tree = tree + end + + def prune_tree(nodes, indicator_ids) + nodes.delete_if do |node| + unwanted_indicator = indicator_node?(node) && !indicator_ids.include?(node['id']) + has_no_wanted_indicators_in_children = prune_tree(children(node), indicator_ids) + + unwanted_indicator && has_no_wanted_indicators_in_children + end + nodes.empty? + end + + private + def children(node) + node.get('sub-themes', false) || + node.get('categories', false) || + node.get('indicators', false) || + [] + end + + def indicator_node?(node) + children(node).empty? + end + end + end +end diff --git a/lib/repos/tree.rb b/lib/repos/tree.rb new file mode 100644 index 0000000..4a3d225 --- /dev/null +++ b/lib/repos/tree.rb @@ -0,0 +1,24 @@ +require 'rest-client' +require 'retries' +require 'json' + +module Pruning + module Repos + class Tree + def initialize(client=RestClient, base_url) + @client = client + @base_url = base_url + end + + def get(name) + resp = @client.get(url(name)) + JSON(resp.body) + end + + private + def url(name) + "#{@base_url}/tree/#{name}" + end + end + end +end