immoscout 1.0.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.
- checksums.yaml +7 -0
- data/.editorconfig +30 -0
- data/.gitignore +14 -0
- data/.reek +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.simplecov +3 -0
- data/.travis.yml +20 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +250 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/assets/logo.png +0 -0
- data/doc/assets/project.png +0 -0
- data/doc/assets/project.xcf +0 -0
- data/immoscout.gemspec +47 -0
- data/lib/immoscout.rb +27 -0
- data/lib/immoscout/api/client.rb +27 -0
- data/lib/immoscout/api/connection.rb +34 -0
- data/lib/immoscout/api/request.rb +33 -0
- data/lib/immoscout/configuration.rb +27 -0
- data/lib/immoscout/errors/failed.rb +11 -0
- data/lib/immoscout/models.rb +9 -0
- data/lib/immoscout/models/actions/attachment.rb +82 -0
- data/lib/immoscout/models/actions/contact.rb +75 -0
- data/lib/immoscout/models/actions/publish.rb +33 -0
- data/lib/immoscout/models/actions/real_estate.rb +122 -0
- data/lib/immoscout/models/apartment_buy.rb +90 -0
- data/lib/immoscout/models/base.rb +48 -0
- data/lib/immoscout/models/concerns/modelable.rb +55 -0
- data/lib/immoscout/models/concerns/propertiable.rb +56 -0
- data/lib/immoscout/models/concerns/renderable.rb +63 -0
- data/lib/immoscout/models/contact.rb +59 -0
- data/lib/immoscout/models/document.rb +31 -0
- data/lib/immoscout/models/house_buy.rb +87 -0
- data/lib/immoscout/models/parts/address.rb +26 -0
- data/lib/immoscout/models/parts/api_search_data.rb +19 -0
- data/lib/immoscout/models/parts/contact.rb +18 -0
- data/lib/immoscout/models/parts/coordinate.rb +18 -0
- data/lib/immoscout/models/parts/courtage.rb +19 -0
- data/lib/immoscout/models/parts/energy_certificate.rb +19 -0
- data/lib/immoscout/models/parts/energy_source.rb +17 -0
- data/lib/immoscout/models/parts/firing_type.rb +17 -0
- data/lib/immoscout/models/parts/geo_code.rb +18 -0
- data/lib/immoscout/models/parts/geo_hierarchy.rb +23 -0
- data/lib/immoscout/models/parts/price.rb +20 -0
- data/lib/immoscout/models/parts/publish_channel.rb +17 -0
- data/lib/immoscout/models/parts/real_estate.rb +17 -0
- data/lib/immoscout/models/parts/url.rb +18 -0
- data/lib/immoscout/models/parts/urls.rb +18 -0
- data/lib/immoscout/models/picture.rb +33 -0
- data/lib/immoscout/models/publish.rb +23 -0
- data/lib/immoscout/version.rb +5 -0
- 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
|