finapps 0.0.14.pre

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7258bf7333018dffd9148db974ba790c694d9608
4
+ data.tar.gz: 6ea476c463319fd83bcac0dcbca13d8ba6fe8695
5
+ SHA512:
6
+ metadata.gz: 5833149eb9fcc599b371415869a4807c300ddebef40a669d9195db7232e8cceb8dc420c6ad806f402d67dd3e66ee9ae23f49dc087401c7e04ec321a2b7a92255
7
+ data.tar.gz: ef79908cc3aee6c4a561414a782544cb5538ada3b2577ab955a49e60ae8e8680ab8435244a0b9fa1978f26a8adccdbf8e42e613abd12889ed09072eec097e96c
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ finapps
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p481
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 qbantek
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ FinApps Ruby-Client
2
+ ===================
3
+
4
+ Ruby client for [FinApps][financialapps].
5
+
6
+ A simple library for communicating with the [FinApps][financialapps] REST API.
7
+
8
+
9
+
10
+ ## Installation
11
+
12
+
13
+ To install using [Bundler][bundler], add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ # Gemfile
17
+ gem 'finapps'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```bash
23
+ $ bundle
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ $ gem install finapps
30
+ ```
31
+
32
+
33
+
34
+ ## Getting started with FinApps REST client
35
+
36
+ ### Setup
37
+
38
+ You will need to authenticate every API call using your FinApps company credentials.
39
+
40
+ Please visit [FinApps][financialapps] if you still haven't setup your account with Financial Apps or have any issues locating your company credentials.
41
+
42
+
43
+ ``` ruby
44
+ require 'finapps'
45
+
46
+ # replace with your own credentials here
47
+ company_identifier = 'my-company-identifier'
48
+ company_token = 'my-company-token'
49
+
50
+ # set up a client to talk to the FinApps REST API
51
+ @client = FinApps::REST::Client.new company_identifier, company_token
52
+ ```
53
+
54
+ ## More Information
55
+
56
+ Please check the [FinApps wiki][wiki] for extended documentation.
57
+
58
+
59
+ [FinancialApps.com][financialapps]
60
+
61
+
62
+ [bundler]: http://bundler.io
63
+ [financialapps]: https://financialapps.com
64
+ [wiki]: https://github.com/finapps/ruby-client/wiki
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
data/bin/finapps ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'finapps/cli/common'
3
+ require 'finapps/cli/users'
4
+ require 'finapps/cli/institutions'
5
+
6
+ FinApps::CLI.start
data/finapps.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'finapps/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'finapps'
8
+ spec.version = FinApps::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ['Erich Quintero']
11
+ spec.email = ['erich@financialapps.com']
12
+
13
+ spec.summary = %q{FinApps REST API ruby client.}
14
+ spec.description = %q{A simple library for communicating with the FinApps REST API.}
15
+ spec.homepage = 'http://github.com/finapps/finapps-ruby'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = Dir['spec/**/*.rb']
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_runtime_dependency 'thor', '~> 0.19', '>= 0.19.1'
24
+ spec.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.0'
25
+ spec.add_runtime_dependency 'faraday_middleware', '~> 0.9', '>= 0.9.1'
26
+ spec.add_runtime_dependency 'typhoeus', '~> 0.6', '>= 0.6.8'
27
+ spec.add_runtime_dependency 'rash', '~> 0.4', '>= 0.4.0'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.6', '>= 1.6.2'
30
+ spec.add_development_dependency 'rake', '~> 0.9', '>= 0.9.6'
31
+ spec.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
32
+
33
+ spec.extra_rdoc_files = %w(README.md LICENSE.txt)
34
+ spec.rdoc_options = %w(--line-numbers --inline-source --title finapps-ruby --main README.md)
35
+ end
@@ -0,0 +1,40 @@
1
+ require 'thor'
2
+ require 'finapps'
3
+
4
+
5
+ module FinApps
6
+ class CLI < Thor
7
+
8
+ desc 'create_client', 'initialize API REST Client'
9
+
10
+ def create_client
11
+ puts client
12
+ end
13
+
14
+ private
15
+
16
+ def client
17
+ company_id = ENV['FA_ID']
18
+ raise 'Invalid company identifier. Please setup the FA_ID environment variable.' if company_id.blank?
19
+
20
+ company_token = ENV['FA_TOKEN']
21
+ raise 'Invalid company token. Please setup the FA_TOKEN environment variable.' if company_token.blank?
22
+
23
+ host = ENV['FA_URL']
24
+ raise 'Invalid API host url. Please setup the FA_URL environment variable.' if host.blank?
25
+
26
+ @client ||= FinApps::REST::Client.new company_id, company_token, {:host => host, :log_level => Logger::DEBUG,
27
+ :request_uuid => 'le-request-uuid-z',
28
+ :session_id => 'da-session-idz'}
29
+ end
30
+
31
+ def rescue_standard_error(error)
32
+ puts '=============================='
33
+ puts 'Error:'
34
+ p error
35
+ puts "Backtrace:\n\t#{error.backtrace.join("\n\t")}"
36
+ puts '=============================='
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ require 'thor'
2
+ require 'finapps'
3
+ require 'securerandom'
4
+ require 'pp'
5
+
6
+ module FinApps
7
+ class CLI < Thor
8
+
9
+ desc 'institutions_search', 'search institutions'
10
+
11
+ def institutions_search(user_identifier, user_token, term=nil)
12
+
13
+ begin
14
+ client.user_credentials!(user_identifier, user_token)
15
+ institutions, error_messages = client.institutions.search term
16
+ if institutions.present?
17
+ puts
18
+ puts 'search results:'
19
+ pp institutions
20
+ else
21
+ puts
22
+ puts 'unable to search institutions'
23
+ error_messages.each { |m| puts m } if error_messages.present?
24
+ end
25
+ puts
26
+
27
+ rescue StandardError => error
28
+ rescue_standard_error(error)
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,87 @@
1
+ require 'thor'
2
+ require 'finapps'
3
+ require 'securerandom'
4
+ require 'pp'
5
+
6
+ module FinApps
7
+ class CLI < Thor
8
+
9
+ desc 'user_create', 'creates a new API user'
10
+
11
+ def user_create(email=nil, password=nil)
12
+
13
+ begin
14
+ email ||= "test-#{SecureRandom.uuid}@powerwallet.com"
15
+ password ||= 'Power1'
16
+ user, error_messages = client.users.create ({:email => email,
17
+ :password => password,
18
+ :password_confirm => password,
19
+ :postal_code => '33021'})
20
+ if user.present?
21
+ puts
22
+ puts 'user created:'
23
+ pp user
24
+ else
25
+ puts
26
+ puts 'unable to create user'
27
+ error_messages.each { |m| puts m } if error_messages.present?
28
+ end
29
+ puts
30
+
31
+ rescue StandardError => error
32
+ rescue_standard_error(error)
33
+ end
34
+
35
+ end
36
+
37
+ desc 'user_login', 'creates a new API user and signs in'
38
+
39
+ def user_login(email=nil, password=nil)
40
+
41
+ begin
42
+ email ||= "test-#{SecureRandom.uuid}@powerwallet.com"
43
+ password ||= 'WrongPassword'
44
+ user, error_messages = client.users.login ({:email => email, :password => password})
45
+ if user.present?
46
+ puts
47
+ puts 'user logged in:'
48
+ pp user
49
+ else
50
+ puts
51
+ puts 'unable to login user'
52
+ error_messages.each { |m| puts m } if error_messages.present?
53
+ end
54
+ puts
55
+
56
+ rescue StandardError => error
57
+ rescue_standard_error(error)
58
+ end
59
+
60
+ end
61
+
62
+ desc 'user_delete', 'deletes an API user'
63
+
64
+ def user_delete(public_id=nil)
65
+
66
+ begin
67
+ public_id ||= SecureRandom.uuid.to_s
68
+
69
+ error_messages = client.users.delete (public_id)
70
+ if error_messages.blank?
71
+ puts
72
+ puts 'user deleted!'
73
+ else
74
+ puts
75
+ puts 'unable to delete user'
76
+ error_messages.each { |m| puts m }
77
+ end
78
+ puts
79
+
80
+ rescue StandardError => error
81
+ rescue_standard_error(error)
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,25 @@
1
+ module FinApps
2
+ module Middleware
3
+
4
+ class ApiToken < Faraday::Middleware
5
+ include FinApps::Logging
6
+
7
+ def initialize(app, options={})
8
+ @app = app
9
+ @options = options
10
+ end
11
+
12
+ def call(env)
13
+ raise MissingArgumentsError.new 'Missing argument: company_identifier.' if @options[:company_identifier].blank?
14
+ raise MissingArgumentsError.new 'Missing argument: company_token.' if @options[:company_token].blank?
15
+
16
+ header_value = "#{@options[:company_identifier].trim}=#{@options[:company_token].trim}"
17
+ logger.debug "##{__method__.to_s} => Request Header X-FinApps-Token: #{header_value}"
18
+ env[:request_headers]['X-FinApps-Token'] = header_value
19
+
20
+ @app.call(env)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,91 @@
1
+ module FinApps
2
+ module Middleware
3
+
4
+ class RaiseHttpExceptions < Faraday::Response::Middleware
5
+ include FinApps::Logging
6
+
7
+ CLIENT_ERROR_STATUSES = 400...600
8
+
9
+ def on_complete(env)
10
+
11
+ case env[:status]
12
+ when 400
13
+ raise FinApps::REST::BadRequest, response_values(env, 'The request could not be understood by the server due to malformed syntax.')
14
+ when 401
15
+ raise FinApps::REST::Unauthorized, response_values(env, 'The request requires user authentication.')
16
+ when 403
17
+ raise FinApps::REST::Forbidden, response_values(env, 'Forbidden.')
18
+ when 404
19
+ raise FinApps::REST::NotFound, response_values(env, 'Page not found.')
20
+ when 405
21
+ raise FinApps::REST::MethodNotAllowed, response_values(env, 'The method specified in the Request-Line is not allowed for the resource identified by the Request-URI.')
22
+ when 406
23
+ raise FinApps::REST::NotAcceptable, response_values(env, 'The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request')
24
+ when 407
25
+ raise Faraday::Error::ConnectionFailed, response_values(env, 'Proxy Authentication Required.')
26
+ when 409
27
+ raise FinApps::REST::Conflict, response_values(env, 'The request could not be completed due to a conflict with the current state of the resource.')
28
+
29
+ when 500
30
+ raise FinApps::REST::InternalServerError, response_values(env, 'Unexpected technical condition was encountered.')
31
+ when 502
32
+ raise FinApps::REST::BadGateway, response_values(env, 'The server returned an invalid or incomplete response.')
33
+ when 503
34
+ raise FinApps::REST::ServiceUnavailable, response_values(env, 'The server is currently unavailable.')
35
+ when 504
36
+ raise FinApps::REST::GatewayTimeout, response_values(env, 'Gateway Time-out')
37
+ when 505
38
+ raise FinApps::REST::VersionNotSupported, response_values(env, 'The Web server does not support the specified HTTP protocol version.')
39
+
40
+ when CLIENT_ERROR_STATUSES
41
+ raise FinApps::REST::Error, response_values(env, 'Unexpected error.')
42
+
43
+ else
44
+ # 200..206 Success codes
45
+ # all good!
46
+ logger.debug "##{__method__.to_s} => Status code: [#{env[:status]}]."
47
+ end
48
+
49
+ end
50
+
51
+ private
52
+
53
+ def error_messages(body)
54
+ error_array = Array.new
55
+
56
+ if body.present? && body.kind_of?(String)
57
+ begin
58
+ parsed = ::JSON.parse(body)
59
+ if parsed
60
+ parsed.each do |key, value|
61
+ value.each do |message|
62
+ logger.debug "#{key} => #{message}"
63
+ error_array.push message.to_s
64
+ end
65
+ end
66
+ else
67
+ logger.info "##{__method__.to_s} => Cannot extract errors: unexpected error while parsing response."
68
+ end
69
+ rescue ::JSON::ParserError => e
70
+ logger.error "##{__method__.to_s} => Unable to parse JSON response."
71
+ logger.error e
72
+ end
73
+ end
74
+
75
+ error_array
76
+ end
77
+
78
+ def response_values(env, status_message = nil)
79
+ {
80
+ :status => env.status,
81
+ :status_message => status_message,
82
+ :headers => env.response_headers,
83
+ :body => env.body,
84
+ :error_messages => error_messages(env.body)
85
+ }
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,40 @@
1
+ module FinApps
2
+ module Middleware
3
+
4
+ class ResponseLogger < Faraday::Response::Middleware
5
+ include FinApps::Logging
6
+
7
+ def call(env)
8
+ logger.info "##{env.method} #{env.url.to_s}"
9
+ logger.debug('Request Headers') { dump_headers env.request_headers }
10
+ super
11
+ end
12
+
13
+ def on_complete(env)
14
+ logger.info "Status: #{env.status.to_s}"
15
+ logger.debug('Response Headers') { dump_headers env.response_headers }
16
+ logger.debug('Response Body') {dump_body env.body } if env.body
17
+ end
18
+
19
+ private
20
+ def dump_headers(headers)
21
+ "\n" << headers.map { |k, v| " #{k}: #{filter_sensitive_header_values(k,v)}" }.join("\n")
22
+ end
23
+
24
+ def filter_sensitive_header_values(key, value)
25
+ case key
26
+ when 'X-FinApps-Token', 'Basic-Authorization'
27
+ value.inspect
28
+ else
29
+ '[REDACTED]'
30
+ end
31
+ end
32
+
33
+ def dump_body(body)
34
+ "\n" << body
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ module FinApps
2
+ module REST
3
+
4
+ class Accounts < FinApps::REST::Resources
5
+
6
+ end
7
+
8
+ class Account < FinApps::REST::Resource
9
+ attr_accessor :public_id
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,154 @@
1
+ module FinApps
2
+ module REST
3
+ class Client
4
+ include FinApps::REST::Defaults
5
+ include FinApps::Logging
6
+ include FinApps::REST::Connection
7
+
8
+ attr_reader :users, :institutions
9
+
10
+ # @param [String] company_identifier
11
+ # @param [String] company_token
12
+ # @param [Hash] options
13
+ # @return [FinApps::REST::Client]
14
+ def initialize(company_identifier, company_token, options = {})
15
+ logger.debug "##{__method__.to_s} => Started"
16
+
17
+ @config = DEFAULTS.merge! options
18
+ if @config[:logger_tag].present?
19
+ Logging.tag= @config[:logger_tag]
20
+ logger.info "##{__method__.to_s} => Added custom tag for logger."
21
+ end
22
+
23
+ set_up_logger_level @config[:log_level]
24
+ logger.info "##{__method__.to_s} => Current logger level: #{SEVERITY_LABEL[logger.level]}"
25
+
26
+ @company_credentials = {:company_identifier => company_identifier,
27
+ :company_token => company_token}
28
+ @connection = set_up_connection(@company_credentials, @config)
29
+ logger.debug "##{__method__.to_s} => Connection initialized"
30
+
31
+ set_up_resources
32
+ logger.debug "##{__method__.to_s} => All resources initialized"
33
+
34
+ logger.debug "##{__method__.to_s} => Completed"
35
+ end
36
+
37
+ # Performs an HTTP GET request. You shouldn't need to use this method directly,
38
+ # but it can be useful for debugging. Returns a hash obtained from parsing
39
+ # the JSON object in the response body.
40
+ #
41
+ # @param [String] path
42
+ # @param [Proc] proc
43
+ # @return [Hash,Array<String>]
44
+ def get(path, &proc)
45
+ logger.debug "##{__method__.to_s} => Started"
46
+ response, result, error_messages = nil, nil, nil
47
+
48
+ begin
49
+ logger.debug "##{__method__.to_s} => GET path:#{path}"
50
+ response = @connection.get { |req| req.url path }
51
+ if response.present? && block_given?
52
+ result = proc.call(response)
53
+ logger.debug "##{__method__.to_s} => parsed result: #{result.pretty_inspect}" if result.present?
54
+
55
+ end
56
+ rescue FinApps::REST::Error => error
57
+ error_messages = error.error_messages
58
+ logger.debug "##{__method__.to_s} => Failed, error_messages: #{error_messages.pretty_inspect}" if error_messages.present?
59
+ end
60
+
61
+ logger.debug "##{__method__.to_s} => Completed"
62
+ return result, error_messages
63
+ end
64
+
65
+ # Performs an HTTP POST request. You shouldn't need to use this method directly,
66
+ # but it can be useful for debugging. Returns a hash obtained from parsing
67
+ # the JSON object in the response body.
68
+ #
69
+ # @param [String] path
70
+ # @param [Hash] params
71
+ # @param [Proc] proc
72
+ # @return [Hash,Array<String>]
73
+ def post(path, params = {}, &proc)
74
+ logger.debug "##{__method__.to_s} => Started"
75
+ response, result, error_messages = nil, nil, nil
76
+
77
+ begin
78
+ logger.debug "##{__method__.to_s} => POST path:#{path} params:#{skip_sensitive_data(params)}"
79
+ response = @connection.post do |req|
80
+ req.url path
81
+ req.body = params
82
+ end
83
+ if response.present? && block_given?
84
+ result = proc.call(response)
85
+ logger.debug "##{__method__.to_s} => parsed result: #{result.pretty_inspect}" if result.present?
86
+ end
87
+
88
+ rescue FinApps::REST::Error => error
89
+ error_messages = error.error_messages
90
+ logger.debug "##{__method__.to_s} => Failed, error_messages: #{error_messages.pretty_inspect}" if error_messages.present?
91
+ end
92
+
93
+ logger.debug "##{__method__.to_s} => Completed"
94
+ return result, error_messages
95
+ end
96
+
97
+ # Performs an HTTP DELETE request. You shouldn't need to use this method directly,
98
+ # but it can be useful for debugging. Returns a hash obtained from parsing
99
+ # the JSON object in the response body.
100
+ #
101
+ # @param [String] path
102
+ # @param [Hash] params
103
+ # @param [Proc] proc
104
+ # @return [Hash,Array<String>]
105
+ def delete(path, params = {}, &proc)
106
+ logger.debug "##{__method__.to_s} => Started"
107
+ response, result, error_messages = nil, nil, nil
108
+
109
+ begin
110
+ logger.debug "##{__method__.to_s} => DELETE path:#{path} params:#{skip_sensitive_data(params)}"
111
+ response = @connection.delete do |req|
112
+ req.url path
113
+ req.body = params
114
+ end
115
+ if response.present? && block_given?
116
+ result = proc.call(response)
117
+ logger.debug "##{__method__.to_s} => parsed result: #{result.pretty_inspect}" if result.present?
118
+ end
119
+
120
+ rescue FinApps::REST::Error => error
121
+ error_messages = error.error_messages
122
+ logger.debug "##{__method__.to_s} => Failed, error_messages: #{error_messages.pretty_inspect}" if error_messages.present?
123
+ end
124
+
125
+ logger.debug "##{__method__.to_s} => Completed"
126
+ return result, error_messages
127
+ end
128
+
129
+ # @param [String] user_identifier
130
+ # @param [String] user_token
131
+ def user_credentials!(user_identifier, user_token)
132
+ logger.debug "##{__method__.to_s} => Started"
133
+
134
+ {:user_identifier => user_identifier, :user_token => user_token}.validate_required_strings!
135
+ logger.debug "##{__method__.to_s} => Credentials passed validation. Attempting to set user credentials on current connection."
136
+
137
+
138
+ @config[:user_identifier] = user_identifier
139
+ @config[:user_token] = user_token
140
+ @connection = set_up_connection(@company_credentials, @config)
141
+
142
+ logger.debug "##{__method__.to_s} => Completed"
143
+ end
144
+
145
+ private
146
+
147
+ def set_up_resources
148
+ @users ||= FinApps::REST::Users.new self
149
+ @institutions ||= FinApps::REST::Institutions.new self
150
+ end
151
+
152
+ end
153
+ end
154
+ end