immobilienscout 0.0.1

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: 4b25d3436d988c560e5b5cbc17fe0e4ab6dc5b5d
4
+ data.tar.gz: '059a52d68bcaf1cc768681b00546e4e9c3024c46'
5
+ SHA512:
6
+ metadata.gz: e848fdd1d25c07d6923a3a47ef31c87c100f05a24ad5dc8ab21d3f997b94e93924081714a749687851b3923c18c64cef4f50e306b53fc7e4ef845f7d0f50c560
7
+ data.tar.gz: 07c04b9b76c7715a62c9673c18f1c18f2992eacc83f171bf62d365efd68a50e667aa591e1877f214306c4de91c197a0375e93897d9aea6ec28f55200ece71ab5
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Immobilienscout gem
2
+
3
+ This is an interface for Immobilienscout API
4
+
5
+ ## Example
6
+ Set up your keys in `immobilienscout.rb` inside initializers.
7
+ ```ruby
8
+ Immobilienscout.configure do |config|
9
+ config.consumer_key = 'consumer_key'
10
+ config.consumer_secret = 'consumer_secret'
11
+ config.access_token = 'access_token'
12
+ config.access_token_secret = 'access_token_secret'
13
+ config.use_sandbox = true
14
+ end
15
+
16
+ ```
17
+
18
+ ## Methods
19
+
20
+ #### Property
21
+ - Create property
22
+ ```ruby
23
+ Immobilienscout::API::Property.create({params})
24
+ ```
25
+
26
+ - Publish property
27
+ ```ruby
28
+ Immobilienscout::API::Property.publish({params})
29
+ ```
30
+
31
+ - Delete property
32
+ ```ruby
33
+ Immobilienscout::API::Property.destroy(is24_id)
34
+ ```
35
+
36
+ #### Attachment
37
+ - Add attachments to property
38
+ ```ruby
39
+ Immobilienscout::API::Attachment.add(is24_id, binary_file, {metadata})
40
+ ```
41
+
42
+ #### Report
43
+ - Get scout report
44
+ ```ruby
45
+ Immobilienscout::API::Report.retrieve(is24_id, date_from, date_to)
46
+ ```
47
+
48
+ #### IMPORTANT: Check Immobilienscout API Documentation about the needed params.
49
+
50
+
51
+ ## Immobilienscout API
52
+
53
+ https://api.immobilienscout24.de/our-apis/import-export.html
@@ -0,0 +1,27 @@
1
+ require 'byebug'
2
+ require 'timecop'
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'immobilienscout/authenticator'
6
+ require 'immobilienscout/request'
7
+ require 'immobilienscout/response_parser'
8
+ require 'immobilienscout/client'
9
+ require 'immobilienscout/api/property'
10
+ require 'immobilienscout/api/attachment'
11
+ require 'immobilienscout/api/report'
12
+ require 'immobilienscout/errors/invalid_request'
13
+
14
+ module Immobilienscout
15
+ class << self
16
+ attr_accessor :configuration
17
+ end
18
+
19
+ def self.configure
20
+ self.configuration ||= Configuration.new
21
+ yield(configuration)
22
+ end
23
+
24
+ class Configuration
25
+ attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret, :use_sandbox
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Immobilienscout
2
+ module API
3
+ class Attachment
4
+ class << self
5
+ def add(is24_id, binary_file, metadata)
6
+ raise ArgumentError unless is24_id.present? && binary_file.present? && metadata.present?
7
+
8
+ url = add_url(is24_id)
9
+ metadata_file = create_metadata_file(metadata)
10
+ params = { attachment: binary_file, metadata: metadata_file }
11
+ parsed_response = Immobilienscout::Request.new(url, params).post_with_multipart
12
+
13
+ raise Immobilienscout::Errors::InvalidRequest, parsed_response.messages.map(&:messages) unless parsed_response.success?
14
+
15
+ parsed_response
16
+ end
17
+
18
+ private
19
+
20
+ def create_metadata_file(params)
21
+ metadata_object = StringIO.new(params.to_json)
22
+ UploadIO.new(metadata_object, 'application/json')
23
+ end
24
+
25
+ def add_url(is24_id)
26
+ "#{Immobilienscout::Client.api_url}/restapi/api/offer/v1.0/user/me/realestate/#{is24_id}/attachment"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ module Immobilienscout
2
+ module API
3
+ module Property
4
+ class << self
5
+ def create(params)
6
+ raise ArgumentError unless params.present?
7
+
8
+ execute_post_request(create_url, params)
9
+ end
10
+
11
+ def publish(params)
12
+ raise ArgumentError unless params.present?
13
+
14
+ execute_post_request(publish_url, params)
15
+ end
16
+
17
+ def destroy(is24_id)
18
+ raise ArgumentError unless is24_id.present?
19
+
20
+ destroy_url = destroy_url(is24_id)
21
+
22
+ execute_delete_request(destroy_url)
23
+ end
24
+
25
+ private
26
+
27
+ def execute_post_request(url, params)
28
+ parsed_response = Immobilienscout::Request.new(url, params).post
29
+ raise Immobilienscout::Errors::InvalidRequest, parsed_response.messages.map(&:messages) unless parsed_response.success?
30
+
31
+ parsed_response
32
+ end
33
+
34
+ def execute_delete_request(url)
35
+ parsed_response = Immobilienscout::Request.new(url).delete
36
+ raise Immobilienscout::Errors::InvalidRequest, parsed_response.messages.map(&:messages) unless parsed_response.success?
37
+
38
+ parsed_response
39
+ end
40
+
41
+ def create_url
42
+ "#{Immobilienscout::Client.api_url}/restapi/api/offer/v1.0/user/me/realestate"
43
+ end
44
+
45
+ def publish_url
46
+ "#{Immobilienscout::Client.api_url}/restapi/api/offer/v1.0/publish"
47
+ end
48
+
49
+ def destroy_url(is24_id)
50
+ "#{Immobilienscout::Client.api_url}/restapi/api/offer/v1.0/user/me/realestate/#{is24_id}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+ module Immobilienscout
2
+ module API
3
+ class Report
4
+ class << self
5
+ def retrieve(is24_id, date_from, date_to)
6
+ raise ArgumentError unless is24_id.present? && date_from.present? && date_to.present?
7
+
8
+ query_params = query_params(date_from, date_to)
9
+ url = retrieve_url(is24_id)
10
+
11
+ execute_get_request(url, query_params)
12
+ end
13
+
14
+ private
15
+
16
+ def execute_get_request(url, query_params)
17
+ parsed_response = Immobilienscout::Request.new(url, query_params).get
18
+ raise Immobilienscout::Errors::InvalidRequest, parsed_response.messages.map(&:messages) unless parsed_response.success?
19
+
20
+ parsed_response
21
+ end
22
+
23
+ def query_params(date_from, date_to)
24
+ {
25
+ dateFrom: date_from.to_s,
26
+ dateTo: date_to.to_s
27
+ }
28
+ end
29
+
30
+ def retrieve_url(is24_id)
31
+ "#{Immobilienscout::Client.api_url}/restapi/api/offer/v1.0/user/me/realestate/#{is24_id}/dailyreport"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,89 @@
1
+ require 'open-uri'
2
+
3
+ module Immobilienscout
4
+ class Authenticator
5
+ ALLOWED_METHODS = %w(POST GET DELETE).freeze
6
+ OAUTH_SIGNATURE_METHOD = 'HMAC-SHA1'.freeze
7
+ OAUTH_VERSION = '1.0'.freeze
8
+ HASH_DIGEST = 'sha1'.freeze
9
+
10
+ def initialize(url, method, query_params = nil)
11
+ @url = url
12
+ @method = method
13
+ @query_params = query_params
14
+
15
+ raise ArgumentError unless valid?
16
+ end
17
+
18
+ def call
19
+ header_params.merge!(@query_params) if @query_params
20
+ auth_headers = auth_headers(header_params)
21
+
22
+ auth_header_string(auth_headers)
23
+ end
24
+
25
+ private
26
+
27
+ def valid?
28
+ @url.present? && @method.present? && ALLOWED_METHODS.include?(@method)
29
+ end
30
+
31
+ def header_params
32
+ @header_params ||= {
33
+ oauth_consumer_key: Immobilienscout.configuration.consumer_key,
34
+ oauth_nonce: generate_nonce,
35
+ oauth_signature_method: OAUTH_SIGNATURE_METHOD,
36
+ oauth_timestamp: generate_timestamp,
37
+ oauth_token: Immobilienscout.configuration.access_token,
38
+ oauth_version: OAUTH_VERSION
39
+ }
40
+ end
41
+
42
+ def signing_key
43
+ "#{url_encode(Immobilienscout.configuration.consumer_secret)}&#{url_encode(Immobilienscout.configuration.access_token_secret)}"
44
+ end
45
+
46
+ def generate_nonce
47
+ Base64.encode64(random_bytes).gsub(/\W/, '')
48
+ end
49
+
50
+ def random_bytes(size=7)
51
+ OpenSSL::Random.random_bytes(size)
52
+ end
53
+
54
+ def generate_timestamp
55
+ Time.now.utc.to_i.to_s
56
+ end
57
+
58
+ def signature_base_string(params)
59
+ "#{@method}&#{url_encode(@url)}&#{url_encode(params.to_query)}"
60
+ end
61
+
62
+ def generate_signature(signature_base_string)
63
+ url_encode(sign(signing_key, signature_base_string))
64
+ end
65
+
66
+ def url_encode(string)
67
+ CGI.escape(string)
68
+ end
69
+
70
+ def sign(key, base_string)
71
+ digest = OpenSSL::Digest.new(HASH_DIGEST)
72
+ hmac = OpenSSL::HMAC.digest(digest, key, base_string)
73
+ Base64.encode64(hmac).chomp.delete(' ')
74
+ end
75
+
76
+ def auth_headers(header_params)
77
+ signature_base_string = signature_base_string(header_params)
78
+ header_params['oauth_signature'] = generate_signature(signature_base_string)
79
+ header_params
80
+ end
81
+
82
+ def auth_header_string(params)
83
+ header_params = params.each_with_object('OAuth ') do |(key, value), header|
84
+ header << "#{key}=#{value},"
85
+ end
86
+ header_params.chop
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,12 @@
1
+ module Immobilienscout
2
+ class Client
3
+ SANDBOX_URL = 'https://rest.sandbox-immobilienscout24.de'.freeze
4
+ LIVE_URL = 'https://rest.immobilienscout24.de'.freeze
5
+
6
+ def self.api_url
7
+ return SANDBOX_URL if Immobilienscout.configuration.use_sandbox
8
+
9
+ LIVE_URL
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module Immobilienscout
2
+ module Errors
3
+ class InvalidRequest < StandardError
4
+ def initialize(msg = message)
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,86 @@
1
+ require 'net/http/post/multipart'
2
+ require 'uri'
3
+
4
+ module Immobilienscout
5
+ class Request
6
+ def initialize(url, params = nil)
7
+ @url = url
8
+ @params = params
9
+
10
+ raise ArgumentError unless valid?
11
+ end
12
+
13
+ def get
14
+ auth_header = generate_auth_header('GET', @params)
15
+ headers = generate_headers(auth_header)
16
+
17
+ url_with_query = "#{@url}?#{@params.to_query}"
18
+ uri = URI.parse(url_with_query)
19
+
20
+ request = Net::HTTP::Get.new(uri)
21
+ request.initialize_http_header(headers)
22
+
23
+ execute_request(uri, request)
24
+ end
25
+
26
+ def post
27
+ auth_header = generate_auth_header('POST')
28
+ headers = generate_headers(auth_header)
29
+
30
+ uri = URI.parse(@url)
31
+
32
+ request = Net::HTTP::Post.new(uri)
33
+ request.body = @params.to_json
34
+ request.initialize_http_header(headers)
35
+
36
+ execute_request(uri, request)
37
+ end
38
+
39
+ def post_with_multipart
40
+ auth_header = generate_auth_header('POST')
41
+
42
+ uri = URI.parse(@url)
43
+
44
+ request = Net::HTTP::Post::Multipart.new(uri, @params)
45
+ request.add_field('Authorization', auth_header)
46
+ request.add_field('Accept', 'application/json')
47
+
48
+ execute_request(uri, request)
49
+ end
50
+
51
+ def delete
52
+ auth_header = generate_auth_header('DELETE')
53
+ headers = generate_headers(auth_header)
54
+
55
+ uri = URI.parse(@url)
56
+
57
+ request = Net::HTTP::Delete.new(uri)
58
+ request.initialize_http_header(headers)
59
+
60
+ execute_request(uri, request)
61
+ end
62
+
63
+ private
64
+
65
+ def valid?
66
+ @url.present?
67
+ end
68
+
69
+ def generate_auth_header(method_type, query_params = nil)
70
+ Immobilienscout::Authenticator.new(@url, method_type, query_params).call
71
+ end
72
+
73
+ def generate_headers(auth_header)
74
+ {
75
+ 'Authorization' => auth_header,
76
+ 'Content-Type' => 'application/json',
77
+ 'Accept' => 'application/json'
78
+ }
79
+ end
80
+
81
+ def execute_request(uri, request)
82
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request request }
83
+ Immobilienscout::ResponseParser.new(response).call
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,48 @@
1
+ module Immobilienscout
2
+ class ResponseParser
3
+ HTTP_OK = %w(200 201).freeze
4
+
5
+ Message = Struct.new(:success?, :code, :messages, :id)
6
+
7
+ def initialize(response)
8
+ @response = response
9
+
10
+ raise ArgumentError unless valid?
11
+ end
12
+
13
+ def call
14
+ response_body = JSON.parse(@response.body)
15
+
16
+ Message.new(success?, code, messages(response_body), id(response_body))
17
+ end
18
+
19
+ private
20
+
21
+ def valid?
22
+ @response.present?
23
+ end
24
+
25
+ def success?
26
+ HTTP_OK.include? @response.code
27
+ end
28
+
29
+ def code
30
+ @response.code
31
+ end
32
+
33
+ def messages(response_body)
34
+ return response_body unless response_body['common.messages']
35
+
36
+ messages = response_body['common.messages'].first['message']
37
+ messages = [messages] if messages.is_a? Hash
38
+ messages.map { |msg| Message.new(nil, msg['messageCode'], msg['message'], msg['id']) }
39
+ end
40
+
41
+ def id(response_body)
42
+ return unless response_body['common.messages']
43
+
44
+ messages = response_body['common.messages'].first['message']
45
+ messages['id'] if messages.is_a? Hash
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immobilienscout
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Angela Guette
8
+ - Wiebke Frey
9
+ - Laura Garcia
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2019-03-11 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: multipart-post
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: activesupport
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: webmock
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ description: This gem provides HTTP endpoints to connect your app with Immobilienscout
72
+ API
73
+ email: backend-team@homeday.de
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - README.md
79
+ - lib/immobilienscout.rb
80
+ - lib/immobilienscout/api/attachment.rb
81
+ - lib/immobilienscout/api/property.rb
82
+ - lib/immobilienscout/api/report.rb
83
+ - lib/immobilienscout/authenticator.rb
84
+ - lib/immobilienscout/client.rb
85
+ - lib/immobilienscout/errors/invalid_request.rb
86
+ - lib/immobilienscout/request.rb
87
+ - lib/immobilienscout/response_parser.rb
88
+ homepage: https://github.com/homeday-de/immobilienscout
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.6.14.3
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: A ruby gem to handle Immobilienscout API
112
+ test_files: []