immoscout 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +30 -0
  3. data/.gitignore +14 -0
  4. data/.reek +6 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +15 -0
  7. data/.simplecov +3 -0
  8. data/.travis.yml +20 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +8 -0
  11. data/LICENSE +21 -0
  12. data/README.md +250 -0
  13. data/Rakefile +8 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/doc/assets/logo.png +0 -0
  17. data/doc/assets/project.png +0 -0
  18. data/doc/assets/project.xcf +0 -0
  19. data/immoscout.gemspec +47 -0
  20. data/lib/immoscout.rb +27 -0
  21. data/lib/immoscout/api/client.rb +27 -0
  22. data/lib/immoscout/api/connection.rb +34 -0
  23. data/lib/immoscout/api/request.rb +33 -0
  24. data/lib/immoscout/configuration.rb +27 -0
  25. data/lib/immoscout/errors/failed.rb +11 -0
  26. data/lib/immoscout/models.rb +9 -0
  27. data/lib/immoscout/models/actions/attachment.rb +82 -0
  28. data/lib/immoscout/models/actions/contact.rb +75 -0
  29. data/lib/immoscout/models/actions/publish.rb +33 -0
  30. data/lib/immoscout/models/actions/real_estate.rb +122 -0
  31. data/lib/immoscout/models/apartment_buy.rb +90 -0
  32. data/lib/immoscout/models/base.rb +48 -0
  33. data/lib/immoscout/models/concerns/modelable.rb +55 -0
  34. data/lib/immoscout/models/concerns/propertiable.rb +56 -0
  35. data/lib/immoscout/models/concerns/renderable.rb +63 -0
  36. data/lib/immoscout/models/contact.rb +59 -0
  37. data/lib/immoscout/models/document.rb +31 -0
  38. data/lib/immoscout/models/house_buy.rb +87 -0
  39. data/lib/immoscout/models/parts/address.rb +26 -0
  40. data/lib/immoscout/models/parts/api_search_data.rb +19 -0
  41. data/lib/immoscout/models/parts/contact.rb +18 -0
  42. data/lib/immoscout/models/parts/coordinate.rb +18 -0
  43. data/lib/immoscout/models/parts/courtage.rb +19 -0
  44. data/lib/immoscout/models/parts/energy_certificate.rb +19 -0
  45. data/lib/immoscout/models/parts/energy_source.rb +17 -0
  46. data/lib/immoscout/models/parts/firing_type.rb +17 -0
  47. data/lib/immoscout/models/parts/geo_code.rb +18 -0
  48. data/lib/immoscout/models/parts/geo_hierarchy.rb +23 -0
  49. data/lib/immoscout/models/parts/price.rb +20 -0
  50. data/lib/immoscout/models/parts/publish_channel.rb +17 -0
  51. data/lib/immoscout/models/parts/real_estate.rb +17 -0
  52. data/lib/immoscout/models/parts/url.rb +18 -0
  53. data/lib/immoscout/models/parts/urls.rb +18 -0
  54. data/lib/immoscout/models/picture.rb +33 -0
  55. data/lib/immoscout/models/publish.rb +23 -0
  56. data/lib/immoscout/version.rb +5 -0
  57. metadata +268 -0
@@ -0,0 +1,122 @@
1
+ require 'json'
2
+ require_relative '../concerns/modelable'
3
+
4
+ module Immoscout
5
+ module Models
6
+ module Actions
7
+ module RealEstate
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include Immoscout::Models::Concerns::Modelable
12
+
13
+ self.unpack_collection = proc do |hash|
14
+ hash
15
+ .fetch("realestates.realEstates", {})
16
+ .fetch("realEstateList", {})
17
+ .fetch("realEstateElement", nil)
18
+ end
19
+
20
+ def save
21
+ response = \
22
+ if id
23
+ api.put("user/#{api.user_name}/realestate/#{id}", as_json)
24
+ else
25
+ api.post("user/#{api.user_name}/realestate", as_json)
26
+ end
27
+
28
+ handle_response(response)
29
+ self.id = id_from_response(response) unless id
30
+ self
31
+ end
32
+
33
+ def destroy
34
+ response = api.delete("user/#{api.user_name}/realestate/#{id}")
35
+ handle_response(response)
36
+ self
37
+ end
38
+
39
+ def publish(channel = 10_000)
40
+ publisher = Immoscout::Models::Publish.new(
41
+ real_estate: { id: id },
42
+ publish_channel: { id: channel }
43
+ )
44
+ publisher.save
45
+ publisher
46
+ end
47
+
48
+ def unpublish(channel = 10_000)
49
+ publisher = Immoscout::Models::Publish.new(
50
+ real_estate: { id: id },
51
+ publish_channel: { id: channel }
52
+ )
53
+ publisher.destroy
54
+ publisher
55
+ end
56
+
57
+ def place(type)
58
+ check_placement_type(type)
59
+ response = api.post(
60
+ "user/#{api.user_name}/realestate/#{id}/#{type}"
61
+ )
62
+ handle_response(response)
63
+ self
64
+ end
65
+
66
+ def unplace(type)
67
+ check_placement_type(type)
68
+ response = api.delete(
69
+ "user/#{api.user_name}/realestate/#{id}/#{type}"
70
+ )
71
+ handle_response(response)
72
+ self
73
+ end
74
+
75
+ private
76
+
77
+ def check_placement_type(type)
78
+ raise ArgumentError, "Unknown placement type '#{type}'" unless %w[
79
+ topplacement premiumplacement showcaseplacement
80
+ ].include?(type.to_s)
81
+ end
82
+ end
83
+
84
+ class_methods do
85
+ def find(id)
86
+ response = api.get("user/#{api.user_name}/realestate/#{id}")
87
+ handle_response(response)
88
+ from_raw(response.body)
89
+ end
90
+
91
+ def find_by(hash)
92
+ external_id = hash.symbolize_keys.fetch(:external_id)
93
+ find("ext-#{external_id}")
94
+ end
95
+
96
+ def all
97
+ response = api.get("user/#{api.user_name}/realestate")
98
+ handle_response(response)
99
+ objects = unpack_collection.call(response.body)
100
+ objects
101
+ .map { |object| new(object) }
102
+ .select { |object| object.type =~ /#{name.demodulize}/i }
103
+ end
104
+
105
+ def first
106
+ all.first
107
+ end
108
+
109
+ def last
110
+ all.last
111
+ end
112
+
113
+ def create(hash)
114
+ instance = new(hash)
115
+ instance.save
116
+ instance
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/renderable'
4
+ require_relative 'concerns/propertiable'
5
+ require_relative 'actions/real_estate'
6
+ require_relative 'parts/api_search_data'
7
+ require_relative 'parts/address'
8
+ require_relative 'parts/contact'
9
+ require_relative 'parts/price'
10
+ require_relative 'parts/courtage'
11
+ require_relative 'parts/energy_source'
12
+
13
+ module Immoscout
14
+ module Models
15
+ class ApartmentBuy < Base
16
+ include Immoscout::Models::Concerns::Renderable
17
+ include Immoscout::Models::Concerns::Propertiable
18
+ include Immoscout::Models::Actions::RealEstate
19
+
20
+ self.json_wrapper = "realestates.apartmentBuy"
21
+
22
+ property :id, alias: :@id
23
+ property :external_id
24
+ property :title
25
+ property :creation_date
26
+ property :last_modification_date
27
+ property :type, alias: :'@xsi.type'
28
+ property :href, alias: :'@xlink.href'
29
+ property :publish_date, alias: :@publish_date
30
+ property :creation, alias: :@creation
31
+ property :modification, alias: :@modification
32
+ property :group_number
33
+ property :real_estate_project_id
34
+ property :address, coerce: Immoscout::Models::Parts::Address
35
+ property :api_search_data, coerce: Immoscout::Models::Parts::ApiSearchData
36
+ property :real_estate_state
37
+ property :description_note
38
+ property :furnishing_note
39
+ property :location_note
40
+ property :other_note
41
+ property :show_address
42
+ property :contact, coerce: Immoscout::Models::Parts::Contact
43
+ property :apartment_type
44
+ property :price, coerce: Immoscout::Models::Parts::Price
45
+ property :courtage, coerce: Immoscout::Models::Parts::Courtage
46
+ property :energy_sources_enev2014,
47
+ coerce: Immoscout::Models::Parts::EnergySource
48
+ property :energy_certificate,
49
+ coerce: Immoscout::Models::Parts::EnergyCertificate
50
+ property :firing_types,
51
+ coerce: Immoscout::Models::Parts::FiringType, array: true
52
+ property :cellar
53
+ property :handicapped_accessible
54
+ property :number_of_parking_spaces
55
+ property :condition
56
+ property :last_refurbishment
57
+ property :interior_quality
58
+ property :construction_year
59
+ property :free_from
60
+ property :heating_type
61
+ property :heating_type_enev2014
62
+ property :building_energy_rating_type
63
+ property :thermal_characteristic
64
+ property :energy_consumption_contains_warm_water
65
+ property :number_of_floors
66
+ property :usable_floor_space
67
+ property :number_of_bed_rooms
68
+ property :number_of_bath_rooms
69
+ property :guest_toilet
70
+ property :parking_space_type
71
+ property :rented
72
+ property :rental_income
73
+ property :listed
74
+ property :parking_space_price
75
+ property :summer_residence_practical
76
+ property :living_space
77
+ property :number_of_rooms
78
+ property :energy_performance_certificate
79
+ # apartment specific properties
80
+ property :floor
81
+ property :lift
82
+ property :assisted_living
83
+ property :built_in_kitchen
84
+ property :balcony
85
+ property :certificate_of_eligibility_needed
86
+ property :garden
87
+ property :service_charge
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Immoscout
4
+ module Models
5
+ class Base
6
+ attr_reader :base
7
+
8
+ def initialize(hash = {})
9
+ define_singleton_method(:base) { hash }
10
+ set_properties
11
+ end
12
+
13
+ private
14
+
15
+ def prepared_hash
16
+ base
17
+ .deep_stringify_keys
18
+ .deep_transform_keys(&:underscore)
19
+ .deep_symbolize_keys
20
+ end
21
+
22
+ def set_properties
23
+ prepared_hash.each do |key, value|
24
+ property = self.class.find_property(key)
25
+ unless property
26
+ # TODO: add optional logger
27
+ # puts "#{self.class.name} - missing property '#{key}'"
28
+ next
29
+ end
30
+ set_property(property, key, value)
31
+ end
32
+ end
33
+
34
+ def set_property(property, key, value)
35
+ coerce_klass = property.fetch(:coerce, nil)
36
+ if coerce_klass
37
+ if property.fetch(:array, false)
38
+ send("#{key}=", value.map { |elem| coerce_klass.new(elem) })
39
+ else
40
+ send("#{key}=", coerce_klass.new(value))
41
+ end
42
+ else
43
+ send("#{key}=", value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'json'
2
+
3
+ module Immoscout
4
+ module Models
5
+ module Concerns
6
+ module Modelable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ cattr_accessor :json_wrapper, :unpack_collection
11
+ cattr_reader :api, instance_accessor: false do
12
+ Immoscout::Api::Client.instance
13
+ end
14
+
15
+ def api
16
+ self.class.api
17
+ end
18
+
19
+ def handle_response(response)
20
+ self.class.handle_response(response)
21
+ end
22
+
23
+ def id_from_response(response)
24
+ self.class.id_from_response(response)
25
+ end
26
+ end
27
+
28
+ class_methods do
29
+ def unpack(hash)
30
+ hash.values.first
31
+ end
32
+
33
+ def from_raw(raw_hash)
34
+ hash = raw_hash.is_a?(String) ? JSON.parse(raw_hash) : raw_hash
35
+ new(unpack(hash))
36
+ end
37
+
38
+ def handle_response(response)
39
+ return response if response.success?
40
+ raise Immoscout::Errors::Failed, response
41
+ end
42
+
43
+ def id_from_response(response)
44
+ response
45
+ .body
46
+ .fetch("common.messages")
47
+ .first
48
+ .fetch("message", {})
49
+ .fetch("id", nil)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ module Immoscout
2
+ module Models
3
+ module Concerns
4
+ module Propertiable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ cattr_reader :properties, instance_accessor: false do
9
+ {}
10
+ end
11
+
12
+ # :reek:ControlParameter - standard stuff, reek!
13
+ # :reek:TooManyStatements
14
+ def method_missing(method_name, *arguments, &block)
15
+ if method_name =~ /build\_(\w+)/
16
+ match = Regexp.last_match(1).intern
17
+ properties = self.class.properties
18
+ coerce_klass = properties.fetch(match).fetch(:coerce, nil)
19
+ return super if !properties.keys.include?(match) || !coerce_klass
20
+ send("#{match}=", coerce_klass.new)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def attributes
27
+ self.class.properties.keys.sort
28
+ end
29
+
30
+ # :reek:BooleanParameter - standard stuff, reek!
31
+ def respond_to_missing?(method_name, include_private = false)
32
+ method_name.to_s.start_with?('build_') || super
33
+ end
34
+ end
35
+
36
+ class_methods do
37
+ def property(name, **opts)
38
+ attr_accessor(name)
39
+ alias_name = opts.fetch(:alias, false)
40
+ if alias_name
41
+ alias_method alias_name, name
42
+ alias_method "#{opts.fetch(:alias)}=", "#{name}="
43
+ end
44
+ properties[name] = opts
45
+ end
46
+
47
+ def find_property(name)
48
+ properties[name] || properties.values.detect do |value|
49
+ value[:alias] == name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ require 'json'
2
+
3
+ module Immoscout
4
+ module Models
5
+ module Concerns
6
+ module Renderable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def as_json
11
+ wrapped? ? to_json_wrapped : to_json_unwrapped
12
+ end
13
+
14
+ def to_json
15
+ as_json.to_json
16
+ end
17
+ alias_method :to_s, :to_json
18
+
19
+ private
20
+
21
+ def wrapped?
22
+ self.class.try(:json_wrapper)
23
+ end
24
+
25
+ def to_h
26
+ self.class.properties.each_with_object({}) do |(key, value), memo|
27
+ # skip if it's readonly and should not be exposed in #as_json
28
+ readonly = value.fetch(:readonly, false)
29
+ next if readonly.try(:call, self) || readonly == true
30
+ # use :alias instead of key as json-key
31
+ property = value.fetch(:alias, key)
32
+ # use :default if present AND value is nil
33
+ rendered = send(key) || value.fetch(:default, nil)
34
+ memo[property] = \
35
+ if rendered.is_a?(Array)
36
+ rendered.map do |elem|
37
+ elem.try(:as_json) || elem
38
+ end
39
+ else
40
+ rendered.try(:as_json) || rendered
41
+ end
42
+ memo
43
+ end
44
+ end
45
+
46
+ def to_json_wrapped
47
+ { self.class.try(:json_wrapper) => to_json_unwrapped }
48
+ end
49
+
50
+ def to_json_unwrapped
51
+ to_h
52
+ .compact
53
+ .stringify_keys
54
+ .deep_transform_keys { |key| key.camelize :lower }
55
+ end
56
+ end
57
+
58
+ class_methods do
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/renderable'
4
+ require_relative 'concerns/propertiable'
5
+ require_relative 'actions/contact'
6
+ require_relative 'parts/address'
7
+
8
+ module Immoscout
9
+ module Models
10
+ class Contact < Base
11
+ include Immoscout::Models::Concerns::Renderable
12
+ include Immoscout::Models::Concerns::Propertiable
13
+ include Immoscout::Models::Actions::Contact
14
+
15
+ self.json_wrapper = "common.realtorContactDetail"
16
+
17
+ property :id, alias: :@id
18
+ property :email
19
+ property :salutation
20
+ property :firstname
21
+ property :lastname
22
+ property :fax_number
23
+ property :fax_number_country_code,
24
+ readonly: proc { |contact| contact.fax_number.present? }
25
+ property :fax_number_area_code,
26
+ readonly: proc { |contact| contact.fax_number.present? }
27
+ property :fax_number_subscriber,
28
+ readonly: proc { |contact| contact.fax_number.present? }
29
+ property :phone_number
30
+ property :phone_number_country_code,
31
+ readonly: proc { |contact| contact.phone_number.present? }
32
+ property :phone_number_area_code,
33
+ readonly: proc { |contact| contact.phone_number.present? }
34
+ property :phone_number_subscriber,
35
+ readonly: proc { |contact| contact.phone_number.present? }
36
+ property :cell_phone_number
37
+ property :cell_phone_number_country_code,
38
+ readonly: proc { |contact| contact.cell_phone_number.present? }
39
+ property :cell_phone_number_area_code,
40
+ readonly: proc { |contact| contact.cell_phone_number.present? }
41
+ property :cell_phone_number_subscriber,
42
+ readonly: proc { |contact| contact.cell_phone_number.present? }
43
+ property :address, coerce: Immoscout::Models::Parts::Address
44
+ property :country_code
45
+ property :title
46
+ property :addition_name
47
+ property :company
48
+ property :homepage_url
49
+ property :position
50
+ property :office_hours
51
+ property :default_contact
52
+ property :local_partner_contact
53
+ property :business_card_contact
54
+ property :real_estate_reference_count
55
+ property :external_id
56
+ property :show_on_profile_page
57
+ end
58
+ end
59
+ end