thron 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +4 -0
  3. data/.gitignore +13 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +4 -0
  6. data/README.md +182 -0
  7. data/Rakefile +10 -0
  8. data/Vagrantfile +80 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/config/thron.yml +10 -0
  12. data/lib/thron.rb +3 -0
  13. data/lib/thron/circuit_breaker.rb +46 -0
  14. data/lib/thron/config.rb +35 -0
  15. data/lib/thron/entity/base.rb +78 -0
  16. data/lib/thron/entity/image.rb +30 -0
  17. data/lib/thron/gateway/access_manager.rb +69 -0
  18. data/lib/thron/gateway/apps.rb +91 -0
  19. data/lib/thron/gateway/apps_admin.rb +153 -0
  20. data/lib/thron/gateway/base.rb +41 -0
  21. data/lib/thron/gateway/category.rb +210 -0
  22. data/lib/thron/gateway/client.rb +59 -0
  23. data/lib/thron/gateway/comment.rb +76 -0
  24. data/lib/thron/gateway/contact.rb +120 -0
  25. data/lib/thron/gateway/content.rb +267 -0
  26. data/lib/thron/gateway/content_category.rb +32 -0
  27. data/lib/thron/gateway/content_list.rb +40 -0
  28. data/lib/thron/gateway/dashboard.rb +120 -0
  29. data/lib/thron/gateway/delivery.rb +122 -0
  30. data/lib/thron/gateway/device.rb +58 -0
  31. data/lib/thron/gateway/metadata.rb +68 -0
  32. data/lib/thron/gateway/publish_in_weebo_express.rb +39 -0
  33. data/lib/thron/gateway/publishing_process.rb +141 -0
  34. data/lib/thron/gateway/repository.rb +111 -0
  35. data/lib/thron/gateway/session.rb +15 -0
  36. data/lib/thron/gateway/users_group_manager.rb +117 -0
  37. data/lib/thron/gateway/v_user_manager.rb +195 -0
  38. data/lib/thron/logger.rb +25 -0
  39. data/lib/thron/pageable.rb +26 -0
  40. data/lib/thron/paginator.rb +82 -0
  41. data/lib/thron/response.rb +48 -0
  42. data/lib/thron/root.rb +9 -0
  43. data/lib/thron/routable.rb +80 -0
  44. data/lib/thron/route.rb +102 -0
  45. data/lib/thron/string_extensions.rb +23 -0
  46. data/lib/thron/user.rb +77 -0
  47. data/lib/thron/version.rb +3 -0
  48. data/log/.gitignore +4 -0
  49. data/thron.gemspec +26 -0
  50. metadata +176 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 828717f3d643a94ede20ded4f7d5ffec9515244c
4
+ data.tar.gz: 6ac41767ca932862c830b22933651e49b8d68e45
5
+ SHA512:
6
+ metadata.gz: a37a51677d948af316337b411a051add6b6d78717fb20d2531076912040559349f4a2ec7023523d12e93ce41eb48fd29c65f0f60cc421e86ea880045ab1bfe30
7
+ data.tar.gz: 6f21a2b5e729e4cef36063593218acad88c46c838c4ebc37a6bfa687a04823a7d7223d6b4b8f12cbfcd13b841e2e3e025477722824109c25e0bd9df90eade41c
@@ -0,0 +1,4 @@
1
+ THRON_CLIENT_ID=<the client id subscribed to Thron APIs>
2
+ VERBOSE=<a boolean indicating if the remote calls has to be printed on screen, useful when dealing with REPL>
3
+ LOGGER_LEVEL=<the logger level you want, default to WARN>
4
+ CIRCUIT_BREAKER_THRESHOLD=<the number of tries before circuit breaker open the circuit in case of communication issues, set to zero to disable the circuit breaker>
@@ -0,0 +1,13 @@
1
+ .env
2
+ /tags
3
+ /.vagrant/
4
+ /.bundle/
5
+ /.yardoc
6
+ /Gemfile.lock
7
+ /_yardoc/
8
+ /coverage/
9
+ /doc/
10
+ /pkg/
11
+ /spec/reports/
12
+ /tmp/
13
+ *.gem
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.8
4
+ - 2.2.1
5
+ - 2.3.0
6
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in thron.gemspec
4
+ gemspec
@@ -0,0 +1,182 @@
1
+ ## Table of Contents
2
+ * [Warning](#warning)
3
+ * [Scope](#scope)
4
+ * [Setup](#setup)
5
+ * [HTTPArty](#httparty)
6
+ * [Architecture](#architecture)
7
+ * [Gateways](#gateways)
8
+ * [Routing](#routing)
9
+ * [Paginator](#paginator)
10
+ * [Response](#response)
11
+ * [Dynamic Entities](#dynamic-entities)
12
+ * [User Aggregate](#user-aggregate)
13
+ * [Interface](#interface)
14
+ * [Access](#access)
15
+ * [Contents](#contents)
16
+ * [Groups](#groups)
17
+ * [Categories](#categories)
18
+ * [Disguise](#disguise)
19
+
20
+ ## Warning
21
+ I do not maintain this gem anymore. Please do copy with latest Thron APIs and integrate them into this facade if you need to use it.
22
+
23
+ ## Scope
24
+ This gem provides a simple Ruby client for the [Thron](https://developer.4me.it/index.php) (ex 4me) APIs.
25
+ The aim of this gem is to provide a simple interface for your ruby application
26
+ that need to communicate with Thron services.
27
+ I've also managed to keep the gem's dependencies footprint as small as possible (i
28
+ hate bulky Gemfile...).
29
+
30
+ ## Setup
31
+ This gem use at least a **Ruby 2.1** compatible parser.
32
+ Thron is a payment service, is assumed you have a valid account in order to use this gem.
33
+ Once you have valid credentials to access the Thron APIs, you have to enter the **THRON_CLIENT_ID** value
34
+ inside of your *.env* file, this way the gem can reads from it and configure itself properly.
35
+
36
+ ## HTTParty
37
+ This gem uses the [HTTParty](https://github.com/jnunemaker/httparty) library to communicate with the Thron APIs.
38
+ HTTParty has proven to be reliable and simple, so it's a natural candidate.
39
+
40
+ ## Architecture
41
+ This gem is architected on these concepts:
42
+
43
+ ### Gateways
44
+ Several gateways objects are used to communicate with the Thron APIs: each of them
45
+ mimic the original Thron API namespace (find a complete list on Thron site).
46
+ Each gateway derive from a basic class, which includes the HTTParty interface.
47
+
48
+ #### Routing
49
+ A simple routing engine is encapsulated into the gateway objects: a class-level
50
+ hash declares the routes that matches the API name, by specifying the
51
+ format and verb.
52
+ Some extra parameters are used to factory the route URL, in some cases lazily (by returning a proc object), since some APIs need to access the method arguments early to compose the URL.
53
+
54
+ #### Paginator
55
+ Thron APIs that return a list of results are limited to a maximum of **50**.
56
+ To avoid repeating the same call many times by passing an augmented offset,
57
+ is possible to call a wrapper method on the gateway objects that returns a paginator object.
58
+ Once the paginator is loaded, it allows to navigate the results by using the following interface:
59
+ * **next**: loads the first offset and move forward, returning last when max offset is reached
60
+ * **prev**: move backwards from the current offset, returning first when minimum offset is reached
61
+ * **preload**: does not move the offset, but indeed preload the specified number of
62
+ data by performing an asynchronous call (wrapped in a thread).
63
+
64
+ Paginator keeps an internal cache to avoid hitting the remote service more than
65
+ once. The same is used when preloading results. Keep that in mind when you need to get fresh data.
66
+
67
+ ### Response
68
+ The HTTParty response has been wrapped in order to return a logical object that wraps the APIs return values.
69
+ The main attributes are:
70
+ * http_code: the HTTP code of the response
71
+ * body: the body of the response, in case the result is JSON data, it contains the data parsed into an appropriate entity (read below)
72
+ * total: in case the API returns a list of results, it indicates the total number of records; it's used by paginator
73
+ * error: the error message, if any, returned by the API
74
+
75
+ ### Dynamic Entities
76
+ Some of the Thron APIs return a JSON representation of an entity. I have initially
77
+ considered wrapping each entity into its own PORO object, but gave up after few
78
+ days since the quality of the returned entities is very large.
79
+ I opted instead to wrap returned data into a sub-class of the OpenStruct object, having the same
80
+ dynamic behaviour while adding some sugar features:
81
+ * it converts the lower-camel-case parameters into (more rubesque) snake-case
82
+ * it does recursive mapping of nested attributes
83
+ * it converts time and date values to appropriate Ruby objects
84
+ The same object can be used when passing complex parameters to some of the APIs: in this case is sufficent to call the *#to_payload* method on the entity
85
+ to convert the object in an hash with lower-camle-case keys (see examples below).
86
+
87
+ ### User Aggregate
88
+ To avoid accessing the multitude of Thron APIs via several objects, an aggregate has been created (DDD anyone?).
89
+ The **User** aggregate delegates most of its methods directly to the gateway objects (it uses the *Forwardable* module).
90
+ It keeps a registry of the gateway objects in order to refresh them on login: this
91
+ is requested to update the token id that identifies the current session and that is
92
+ stored internally by the gateway objects.
93
+
94
+ To create the user aggregate simply instantiate it without arguments:
95
+ ```ruby
96
+ user = Thron::User::new
97
+ ```
98
+
99
+ ## Interface
100
+ The following examples illustrates how to use this library.
101
+ Thron APIs include a broad range of methods, for a complete list of them please consult the [official APIs documentation](https://developer.thron.com/index.php).
102
+ For the APIs that accepts composed arguments simply use the dynamic entity described
103
+ above: just be aware to name its attributes by snake-case and not as the
104
+ lower-camel-case specified in the Thron documentation.
105
+
106
+ ### Access
107
+ To get a valid session token just login with your credentials:
108
+ ```ruby
109
+ user.login(username: '<your_username>', password: '<your_password>')
110
+ ```
111
+ From here you can check current user with the available methods, for example:
112
+ ```ruby
113
+ user.validate_token
114
+ ```
115
+ Or you can query the APIs to return other details:
116
+ ```ruby
117
+ user.user_detail(username: '<a_username>')
118
+ ```
119
+ Uploads the avatar image for a user (it relies on Linux *file* system call):
120
+ ```ruby
121
+ avatar = Thron::Entity::Image::new(path: '<path_to_an_image>').to_payload
122
+ user.update_image(username: '<a_username>', image: avatar)
123
+ ```
124
+
125
+ ### Contents
126
+ Thron is all about managing users contents, so no surprise there is a plethora of
127
+ methods at your disposal:
128
+
129
+ Find the contents by using the paginator object:
130
+ ```ruby
131
+ paginator = user.find_contents_paginator
132
+ paginator.preload(10) # preload the first 10 calls
133
+ paginator.next # fetch first result set from preloaded cache
134
+ ```
135
+ Show the contents by category (slightly more efficient):
136
+ ```ruby
137
+ user.show_contents(category_id: '<a_category_id>')
138
+ ```
139
+ Load specific content detail:
140
+ ```ruby
141
+ user.content_detail(content_id: '<a_content_id>')
142
+ ```
143
+
144
+ ### Groups
145
+ Users are arranged into different groups.
146
+
147
+ Create a new group:
148
+ ```ruby
149
+ group = Thron::Entity::Base::new(active: true, name: 'my new group').to_payload
150
+ user.create_group(data: group)
151
+ ```
152
+ List existing groups:
153
+ ```ruby
154
+ paginator = user.find_gropus
155
+ paginator.next
156
+ ```
157
+
158
+ ### Categories
159
+ Thron contents are organized by categories.
160
+
161
+ List existing categories (without paginator):
162
+ ```ruby
163
+ user.find_categories
164
+ ```
165
+ Create a new locale for a category:
166
+ ```ruby
167
+ locale = Thron::Entity::Base::new(name: 'photos', description: 'JPG and PNG images', locale: 'EN').to_payload
168
+ user.create_category_locale(category_id: '<a_category_id>', locale: locale)
169
+ ```
170
+
171
+ ### Disguise
172
+ Thron APIs allow to disguise another user via its apps sub-system.
173
+
174
+ Disguising only works inside the block:
175
+ ```ruby
176
+ user.disguise(app_id: '<app_id_that_can_disguise>', username: '<username_to_disguise>') do
177
+ # load the disguised user contents, each gateway will now use the disguised token id
178
+ contents = user.find_contents
179
+ # do something with contents
180
+ end
181
+ # finished disguising, it returns disguised token id
182
+ ```
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:spec) do |t|
5
+ t.libs << 'spec'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,80 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # All Vagrant configuration is done below. The "2" in Vagrant.configure
5
+ # configures the configuration version (we support older styles for
6
+ # backwards compatibility). Please don't change it unless you know what
7
+ # you're doing.
8
+ Vagrant.configure(2) do |config|
9
+ # The most common configuration options are documented and commented below.
10
+ # For a complete reference, please see the online documentation at
11
+ # https://docs.vagrantup.com.
12
+
13
+ # Every Vagrant development environment requires a box. You can search for
14
+ # boxes at https://atlas.hashicorp.com/search.
15
+ config.vm.box = "ubuntu/trusty64"
16
+
17
+ # Disable automatic box update checking. If you disable this, then
18
+ # boxes will only be checked for updates when the user runs
19
+ # `vagrant box outdated`. This is not recommended.
20
+ config.vm.box_check_update = false
21
+
22
+ # Create a forwarded port mapping which allows access to a specific port
23
+ # within the machine from a port on the host machine. In the example below,
24
+ # accessing "localhost:8080" will access port 80 on the guest machine.
25
+ # config.vm.network "forwarded_port", guest: 80, host: 8080
26
+
27
+ # Create a private network, which allows host-only access to the machine
28
+ # using a specific IP.
29
+ config.vm.network "private_network", ip: "192.168.33.22"
30
+
31
+ # Create a public network, which generally matched to bridged network.
32
+ # Bridged networks make the machine appear as another physical device on
33
+ # your network.
34
+ # config.vm.network "public_network"
35
+
36
+ # Share an additional folder to the guest VM. The first argument is
37
+ # the path on the host to the actual folder. The second argument is
38
+ # the path on the guest to mount the folder. And the optional third
39
+ # argument is a set of non-required options.
40
+ # config.vm.synced_folder "../data", "/vagrant_data"
41
+
42
+ # Provider-specific configuration so you can fine-tune various
43
+ # backing providers for Vagrant. These expose provider-specific options.
44
+ # Example for VirtualBox:
45
+ #
46
+ config.vm.provider "virtualbox" do |vb|
47
+ # Display the VirtualBox GUI when booting the machine
48
+ #vb.gui = true
49
+
50
+ # Customize the amount of memory on the VM:
51
+ vb.memory = "6144"
52
+ vb.cpus = 3
53
+ end
54
+ #
55
+ # View the documentation for the provider you are using for more
56
+ # information on available options.
57
+
58
+ # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
59
+ # such as FTP and Heroku are also available. See the documentation at
60
+ # https://docs.vagrantup.com/v2/push/atlas.html for more information.
61
+ # config.push.define "atlas" do |push|
62
+ # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
63
+ # end
64
+
65
+ # Enable provisioning with a shell script. Additional provisioners such as
66
+ # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
67
+ # documentation for more information about their specific syntax and use.
68
+ # config.vm.provision "shell", inline: <<-SHELL
69
+ # sudo apt-get update
70
+ # sudo apt-get install -y apache2
71
+ # SHELL
72
+ $script = [
73
+ "sudo apt-add-repository ppa:brightbox/ruby-ng",
74
+ "sudo apt-get update",
75
+ "sudo apt-get -y -q install build-essential libssl-dev git",
76
+ "sudo apt-get -y -q install ruby2.3 ruby2.3-dev",
77
+ "sudo gem install bundler"]
78
+
79
+ config.vm.provision "shell", inline: $script.join(" && ")
80
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "thron"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ logger:
2
+ level: <%= ENV.fetch('LOGGER_LEVEL') { 'warn' } %>
3
+ verbose: <%= ENV.fetch('VERBOSE') { false } %>
4
+
5
+ circuit_breaker:
6
+ threshold: <%= ENV.fetch('CIRCUIT_BREAKER_THRESHOLD') { 0 } %>
7
+
8
+ thron:
9
+ client_id: <%= ENV.fetch('THRON_CLIENT_ID') { 'none' } %>
10
+ protocol: <%= ENV.fetch('THRON_PROTOCOL') { 'https' } %>
@@ -0,0 +1,3 @@
1
+ require 'thron/version'
2
+ require 'thron/root'
3
+ require 'thron/user'
@@ -0,0 +1,46 @@
1
+ module Thron
2
+ class CircuitBreaker
3
+ class OpenError < StandardError; end
4
+
5
+ %w[open closed].each do |name|
6
+ const_set(name.upcase.to_sym, name.to_sym)
7
+ end
8
+
9
+ def initialize(options = {})
10
+ @state = CLOSED
11
+ @threshold = options.fetch(:threshold) { 5 }
12
+ @error_count = 0
13
+ @ignored = options[:ignored].to_a
14
+ end
15
+
16
+ def monitor
17
+ return yield if @threshold.zero?
18
+ fail OpenError, 'the circuit breaker is open!' if open?
19
+ result = yield
20
+ handle_success
21
+ result
22
+ rescue OpenError
23
+ raise
24
+ rescue => error
25
+ handle_error(error) unless @ignored.include?(error.class)
26
+ raise
27
+ end
28
+
29
+ def open?
30
+ @state == OPEN
31
+ end
32
+
33
+ private
34
+
35
+ def handle_success
36
+ @error_count = 0
37
+ end
38
+
39
+ def handle_error(error)
40
+ @error_count += 1
41
+ if @error_count >= @threshold
42
+ @state = OPEN
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'dotenv'
4
+ require 'ostruct'
5
+ require 'logger'
6
+ require 'thron/root'
7
+
8
+ module Thron
9
+ module Config
10
+ extend self
11
+
12
+ CONFIG_YML = Thron::root.join('config', 'thron.yml')
13
+
14
+ def dump_yaml
15
+ Dotenv.load
16
+ @yaml ||= YAML.load(ERB.new(File.read(CONFIG_YML)).result)
17
+ end
18
+
19
+ def logger
20
+ @logger ||= begin
21
+ level = dump_yaml.fetch('logger').fetch('level')
22
+ verbose = dump_yaml.fetch('logger').fetch('verbose')
23
+ OpenStruct.new(level: Logger::const_get(level.upcase), verbose: verbose)
24
+ end
25
+ end
26
+
27
+ def circuit_breaker
28
+ @circuit_breaker ||= OpenStruct.new(dump_yaml['circuit_breaker'])
29
+ end
30
+
31
+ def thron
32
+ @thron ||= OpenStruct.new(dump_yaml['thron'])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,78 @@
1
+ require 'ostruct'
2
+ require 'time'
3
+ require 'thron/string_extensions'
4
+
5
+ module Thron
6
+ using StringExtensions
7
+ module Entity
8
+ class Base < OpenStruct
9
+ TIME_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.+/
10
+ DATE_REGEX = /\A\d{4}-\d{2}-\d{2}/
11
+
12
+ def self.factory(args)
13
+ case args
14
+ when Hash
15
+ new(args)
16
+ when Array
17
+ args.map { |data| new(data) }
18
+ end
19
+ end
20
+
21
+ def initialize(hash = {})
22
+ @table = {}
23
+ hash.each do |k,v|
24
+ k = k.to_s.snakecase.to_sym
25
+ @table[k] = case v
26
+ when Hash
27
+ self.class.new(v)
28
+ when Array
29
+ if v.first.is_a?(Hash)
30
+ v.map { |e| self.class.new(e) }
31
+ else
32
+ v
33
+ end
34
+ when TIME_REGEX
35
+ Time::parse(v)
36
+ when DATE_REGEX
37
+ Date::parse(v)
38
+ else
39
+ v
40
+ end
41
+ new_ostruct_member(k)
42
+ end
43
+ end
44
+
45
+ def to_h
46
+ self.each_pair.reduce({}) do |acc, (k,v)|
47
+ acc[k] = fetch_value(v, :to_h); acc
48
+ end
49
+ end
50
+
51
+ def to_payload
52
+ self.each_pair.reduce({}) do |acc, (k,v)|
53
+ k = k.to_s.camelize_low
54
+ acc[k] = fetch_value(v, :to_payload); acc
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def fetch_value(value, message)
61
+ case value
62
+ when Base
63
+ value.send(message)
64
+ when Array
65
+ if value.first.is_a?(Base)
66
+ value.map { |entity| entity.send(message) }
67
+ else
68
+ value
69
+ end
70
+ when Date, Time
71
+ value.iso8601
72
+ else
73
+ value
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end