pastvu 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1ede36b18a77672d8488001474123f2e4a869ee521d2006c8a56186a6f3c9b30
4
+ data.tar.gz: 6e222696f72e76757ea24a0f8dd7dfc0d089f1b4081665708bc7d531548fda22
5
+ SHA512:
6
+ metadata.gz: e34ccfa15c79465afa76b9ea236f53675caaa5f7e8e88d6734b7d72fef2a39a49ef5d24c7bc559778c083a13ed1c6490bbbb08b5e8e9cad4b5b5b55fcd84b624
7
+ data.tar.gz: 26f7aaaa4014f9afb341876fa932354687ab0568c5257bb6bf88b6567d028a627c5a3ca6242780676c78e46fbf088be1dea92bf5e879a5fbc81b46634e154508
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## [1.0.0] - 2023-1-12
2
+
3
+ - First release!
4
+
5
+ ## [0.0.1] - 2023-1
6
+
7
+ - Unreleased pre-release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 projecteurlumiere
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # PastVu
2
+
3
+ PastVu gem is a Ruby wrapper for [PastVu API](https://docs.pastvu.com/en/dev/api). It allows convenient interaction with the API in your Ruby code.
4
+
5
+ [PastVu](https://pastvu.com) is an open-source online platform for gathering, geo-tagging, attributing and discussing retro photos. Its [repository](https://github.com/PastVu/pastvu) can be found on GitHub.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add pastvu --version "~> 1.0.0"
12
+
13
+ or add the following line to the Gemfile manually
14
+
15
+ # gem "pastvu", "~> 1.0.0"
16
+
17
+ If bundler is not being used to manage dependencies, install the gem by executing:
18
+
19
+ $ gem install pastvu
20
+
21
+ and do not forget to require the gem in your code:
22
+
23
+ ```ruby
24
+ require "pastvu"
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Refer to [PastVu API documentation](https://docs.pastvu.com/en/dev/api) for available interactions, parameters, parameter types and response examples.
30
+
31
+ ### Scenario: Getting nearest photos
32
+
33
+ #### Step one - request data
34
+
35
+ ```ruby
36
+ golden_gate_coordinates = [37.82,-122.469322]
37
+
38
+ # It sends photo.giveNearestPhotos request
39
+ # Returns a PhotoCollection instance on success
40
+ photo_collection = Pastvu.nearest_photos(geo: golden_gate_coordinates)
41
+
42
+ # Optional params, too, go as keyword arguments:
43
+ photo_collection = Pastvu.nearest_photos(
44
+ geo: golden_gate_coordinates,
45
+ except: 228481,
46
+ limit: 12
47
+ )
48
+
49
+ # PastVu API does not allow requesting >30 nearest photos
50
+ # but you can skip the ones you have already requested
51
+ new_photo_collection = photo_collection.next
52
+
53
+ # The previous limit (12 in the case above or 30 if no limit was specified) defines how many photos the new collection gets
54
+ # It is possible to set a new limit in the argument (no more than 30!):
55
+ new_photo_collection = photo_collection.next(5)
56
+ ```
57
+
58
+ #### Step two - work with data
59
+
60
+ ##### Manipulate attributes:
61
+
62
+ ```ruby
63
+ # The full response can be immediately transformed into JSON or Hash
64
+ photo_collection.to_json
65
+ photo_collection.to_hash
66
+
67
+ # On iteration pastvu gem extracts photo information into Photo object
68
+ photo_collection.each do |photo|
69
+ # Photo attributes are available as methods:
70
+ puts photo.title
71
+ puts photo.year
72
+ puts photo.geo
73
+ # etc
74
+
75
+ # Photo can be transformed into hash:
76
+ hash = photo.to_hash
77
+ puts hash["title"]
78
+ puts hash["year"]
79
+ puts hash["geo"]
80
+ # etc
81
+
82
+ # and into json:
83
+ photo.to_json
84
+ end
85
+ ```
86
+
87
+ ##### Download photos:
88
+
89
+ ```ruby
90
+ photo_collection.each_with_index do |photo, i|
91
+ # All return download URL as String:
92
+ photo.standard
93
+ photo.original
94
+ photo.thumb # or thumbnail
95
+
96
+ # The path to a new photo must end with ".jpg" or ".jpeg"
97
+ desired_path_to_photo = "awesome_photo_number_#{i + 1}.jpg"
98
+
99
+ # All return File object:
100
+ photo.download(desired_path_to_photo, :standard)
101
+ photo.download(desired_path_to_photo, :original)
102
+ photo.download(desired_path_to_photo, :thumb) # or :thumbnail
103
+ end
104
+ ```
105
+
106
+ ##### Request more data about the photo:
107
+
108
+ ```ruby
109
+ photo_collection.each do |photo|
110
+ # To make a request for comments:
111
+ photo.comments # returns CommentCollection
112
+ # See also the section on comments
113
+
114
+ # To make a request for full photo information
115
+ photo.reload # returns a new Photo object
116
+ # See also the section on photo information
117
+ end
118
+ ```
119
+
120
+ ### Scenario: Getting photos inside geographical bounds
121
+
122
+ #### Step one - prepare request
123
+
124
+ PastVu API accepts geoJSON polygons and geoJSON multipolygons
125
+
126
+ ```ruby
127
+ # use JSON string in the GeoJSON format:
128
+ paris_montmartre = '{"coordinates":[[[2.34218629483172,48.88623415508624],[2.34218629483172,48.88425956838617],[2.3449771858020085,48.88425956838617],[2.3449771858020085,48.88623415508624],[2.34218629483172,48.88623415508624]]],"type":"Polygon"}'
129
+
130
+ # or use hash in the GeoJSON format:
131
+ paris_montmartre = {
132
+ "type" => "Polygon",
133
+ "coordinates" => [
134
+ [
135
+ [
136
+ 2.34218629483172,
137
+ 48.88623415508624
138
+ ],
139
+ [
140
+ 2.34218629483172,
141
+ 48.88425956838617
142
+ ],
143
+ [
144
+ 2.3449771858020085,
145
+ 48.88425956838617
146
+ ],
147
+ [
148
+ 2.3449771858020085,
149
+ 48.88623415508624
150
+ ],
151
+ [
152
+ 2.34218629483172,
153
+ 48.88623415508624
154
+ ]
155
+ ]
156
+ ]
157
+ }
158
+ ```
159
+
160
+ #### Step two - request data
161
+
162
+ ```ruby
163
+ # It sends photo.getByBounds request
164
+ # It returns BoundsResponse
165
+ bounds_response = Pastvu.by_bounds(
166
+ geometry: paris_montmartre,
167
+ z: 16
168
+ )
169
+ ```
170
+
171
+ The response may contain clusters, photos, or both - see [API docs](https://docs.pastvu.com/en/dev/api)
172
+
173
+ ```ruby
174
+ photo_collection = bounds_response.photos # returns PhotoCollection
175
+
176
+ cluster_collection = bounds_response.clusters # returns ClusterCollection
177
+ ```
178
+
179
+ #### Step three - manipulate data
180
+
181
+ ##### Photos
182
+
183
+ PhotoCollection and each photo object have almost all the same methods as discussed in the previous scenario section.
184
+
185
+ Note that PastVu API may send different photo data for by_bounds and nearest_photos requests. This, however, does not obstruct in-built convenience methods for downloading.
186
+
187
+ For instance:
188
+ ```ruby
189
+ photo_collection.each do |photo|
190
+ photo.original # will work
191
+ photo.year2 # should work for by_bounds but might not work for nearest_photos
192
+ end
193
+
194
+ photo_collection.next # will not work for by_bounds
195
+ ```
196
+
197
+ ##### Clusters
198
+
199
+ On the [Pastvu website](https://pastvu.com), clusters are representations of multiple photos. Users see clusters when zooming out.
200
+
201
+ ```ruby
202
+ # The full response can be immediately transformed into JSON or Hash
203
+ cluster_collection.to_json
204
+ cluster_collection.to_hash
205
+
206
+ cluster_collection.each do |cluster|
207
+ # Cluster attributes are available as methods:
208
+ puts cluster.c
209
+ # etc
210
+
211
+ # Cluster can be transformed into Hash
212
+ hash = cluster.to_hash
213
+ puts hash["c"]
214
+ # etc
215
+
216
+ # and into JSON
217
+ cluster.to_json
218
+
219
+ cluster.photo # returns Photo object corresponding to the clusters cover thumbnail
220
+ end
221
+ ```
222
+
223
+ ### Scenario: Getting full photo information
224
+
225
+ ```ruby
226
+ photo_cid = 5
227
+
228
+ # It sends photo.giveForPage request
229
+ # Returns informationResponse on success
230
+ photo_information = Pastvu.photo_info(photo_cid)
231
+
232
+ # informationResponse can be transformed into JSON and Hash
233
+ photo_information.to_json
234
+ photo_information.to_hash
235
+
236
+ photo = photo_information.to_photo # returns Photo object
237
+
238
+ # There is also a shorthand to return Photo object immediately instead of informationResponse
239
+ photo = Pastvu.photo(photo_cid)
240
+ ```
241
+
242
+ The created Photo object will respond to all the methods discussed previously but it tends to have more attributes. Finally, it is possible to request full information for any Photo object by calling `Photo#reload` on Photo instance, which returns a new Photo instance.
243
+
244
+ ### Scenario: Getting commentaries for a photo
245
+
246
+ ```ruby
247
+ photo_cid = 5
248
+
249
+ # It makes comment.giveForObj request
250
+ # It returns CommentCollection on success
251
+ comment_collection = Pastvu.comments(photo_cid)
252
+
253
+ comment_collection.users # returns hash with data about all the users who left a comment under the photo
254
+
255
+ comment_collection.each do |comment|
256
+ # Comment attributes are available as methods
257
+ puts cluster.user
258
+ puts cluster.txt
259
+ # etc
260
+
261
+ # Cluster can be transformed into Hash
262
+ hash = cluster.to_hash
263
+ puts hash["user"]
264
+ puts hash["txt"]
265
+ # etc
266
+
267
+ # and into JSON
268
+ cluster.to_json
269
+
270
+
271
+ comment.replies # returns Array containing replies to the given comment
272
+ end
273
+ ```
274
+
275
+ ### Configuration
276
+
277
+ ```ruby
278
+ Pastvu.configure do |c|
279
+
280
+ c.host # "pastvu.com"
281
+ c.path # "api2"
282
+ c.user_agent # "Ruby PastVu Gem/#{VERSION}, #{RUBY_PLATFORM}, Ruby/#{RUBY_VERSION}"
283
+
284
+ # Raise when API response is not of expected format
285
+ c.ensure_successful_responses # "true"
286
+
287
+ # Raise when supplied params are not of the required type
288
+ c.check_params_type # "true"
289
+
290
+ # Raise when supplied params are not of the required values
291
+ c.check_params_value # "true"
292
+ end
293
+ ```
294
+
295
+ ## Contributing
296
+
297
+ Bug reports and pull requests are welcome on GitHub at https://github.com/projecteurlumiere/pastvu.
298
+
299
+ ## License
300
+
301
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,21 @@
1
+ module Pastvu
2
+ class BasicResponse
3
+ attr_accessor :json, :hash
4
+
5
+ def initialize(response_body)
6
+ @json = response_body
7
+ if Pastvu.config.ensure_successful_responses
8
+ @hash = self.to_hash
9
+ raise RuntimeError, "Unexpected response from the server: #{@hash}" unless @hash["result"]
10
+ end
11
+ end
12
+
13
+ def to_json
14
+ @hash ? Parser.to_json(@hash) : @json
15
+ end
16
+
17
+ def to_hash
18
+ @hash || Parser.to_hash(@json)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Pastvu
2
+ class Cluster < Model
3
+ def photo
4
+ Photo.new @p
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Pastvu
2
+ class ClusterCollection < Collection
3
+ def initialize(attributes)
4
+ super attributes
5
+ @path = %w[result clusters]
6
+ @model = Cluster
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ module Pastvu
2
+ class Collection < BasicResponse
3
+ include Enumerable
4
+
5
+ def initialize(attr)
6
+ attr.instance_of?(Hash) ? @hash = attr : super
7
+ end
8
+
9
+ def each
10
+ return to_enum(:each) unless block_given?
11
+
12
+ @hash ||= self.to_hash
13
+
14
+ reduce_hash.each do |model_hash|
15
+ yield @model.new model_hash
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def reduce_hash
22
+ @path.reduce(@hash) do |hash, path_key|
23
+ hash.fetch(path_key)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module Pastvu
2
+ class Comment < Model
3
+ def replies
4
+ populate_replies unless @comments.nil?
5
+ @replies ||= []
6
+ end
7
+
8
+ def replies=(value)
9
+ @replies = value
10
+ end
11
+
12
+ def to_hash
13
+ instance_variables.each_with_object({}) do |var, object|
14
+ next if var == :@replies
15
+
16
+ var = var[1..-1]
17
+ object[camelize(var).to_sym] = method(var).call
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def populate_replies
24
+ @replies = @comments.map do |comment|
25
+ Comment.new comment
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module Pastvu
2
+ class CommentCollection < Collection
3
+ def initialize(attributes)
4
+ super attributes
5
+ @path = %w[result comments]
6
+ @model = Comment
7
+ end
8
+
9
+ def users
10
+ @hash ||= self.to_hash
11
+ @hash["result"]["users"]
12
+ end
13
+
14
+ def photo
15
+ Pastvu.photo @hash["result"]["cid"]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module Pastvu
2
+ class Configuration
3
+ VALID_OPTIONS = %i[
4
+ host
5
+ path
6
+ user_agent
7
+ ensure_successful_responses
8
+ check_params_type
9
+ check_params_value
10
+ ]
11
+
12
+ DEFAULT_VALUES = {
13
+ default_host: "pastvu.com",
14
+ default_path: "api2",
15
+ default_user_agent: "Ruby PastVu Gem/#{VERSION}, #{RUBY_PLATFORM}, Ruby/#{RUBY_VERSION}",
16
+ default_ensure_successful_responses: true,
17
+ default_check_params_type: true,
18
+ default_check_params_value: true
19
+ }
20
+
21
+ attr_accessor *VALID_OPTIONS
22
+
23
+ def initialize
24
+ reset!
25
+ end
26
+
27
+ def reset!
28
+ VALID_OPTIONS.each do |option|
29
+ self.send(option.to_s.concat("=").to_sym, DEFAULT_VALUES["default_".concat(option.to_s).to_sym])
30
+ end
31
+ end
32
+
33
+ def configure
34
+ yield self
35
+ self
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Pastvu
2
+ class Model
3
+ def initialize(attributes)
4
+ attributes.each do |key, value|
5
+ key = snakecase(key)
6
+ instance_variable_set("@#{key}", value)
7
+ self.class.send(:attr_accessor, key)
8
+ end
9
+ end
10
+
11
+ def to_hash
12
+ instance_variables.each_with_object({}) do |var, object|
13
+ var = var[1..-1]
14
+ object[camelize(var).to_sym] = method(var).call
15
+ end
16
+ end
17
+
18
+ def to_json
19
+ Parser.to_json(to_hash)
20
+ end
21
+
22
+ private
23
+
24
+ def snakecase(camel_cased_word)
25
+ camel_cased_word.to_s.gsub(/::/, '/').
26
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
27
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
28
+ tr("-", "_").
29
+ downcase
30
+ end
31
+
32
+ def camelize(snake_cased_word, capitalize_first_letter = false)
33
+ array = snake_cased_word.to_s.split('_').collect(&:capitalize)
34
+ array[0]&.downcase! unless capitalize_first_letter
35
+ array.join
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ module Pastvu
2
+ class TypeCheck
3
+ ALLOWED_TYPES = {
4
+ cid: Integer,
5
+ distance: Integer, # <= 1000000 (meters)
6
+ except: Integer,
7
+ geo: Array, # [lat and lon]
8
+ geometry: Hash, # [geoJSON]
9
+ isPainting: [TrueClass, FalseClass],
10
+ limit: Integer, # <= 30
11
+ localWork: [TrueClass, FalseClass],
12
+ skip: Integer,
13
+ type: String, # "photo" or "painting"
14
+ year: Integer,
15
+ year2: Integer,
16
+ z: Integer
17
+ }
18
+
19
+ def self.validate(params)
20
+ errors = {}
21
+ ALLOWED_TYPES.each do |k, type|
22
+ param = params[k]
23
+ next if param.nil?
24
+ next if self.correct_type? param, type
25
+
26
+ errors.merge!({ k.to_sym => [param, type] })
27
+ end
28
+
29
+ errors.empty? ? true : report_errors(errors)
30
+ end
31
+
32
+ def self.report_errors(errors)
33
+ report = errors.map do |k, v|
34
+ "\n#{k}: #{v[0]} must be #{v[1]}"
35
+ end.join(", ")
36
+
37
+ raise ArgumentError, "expect correct params type\n #{report}"
38
+ end
39
+
40
+ def self.correct_type?(param, type)
41
+ if type.instance_of?(Array)
42
+ type.any? { |type| param.instance_of?(type) }
43
+ else
44
+ param.instance_of?(type)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ module Pastvu
2
+ class ValueCheck
3
+ VALIDATIONS = {
4
+ distance: ->(d) { d.between?(1, 1_000_000) },
5
+ geo: [->(g) { g.size == 2 },
6
+ ->(g) { g.all? { |coordinate| coordinate.instance_of?(Float) || coordinate.instance_of?(Integer) } }],
7
+ geometry: ->(g) do
8
+ begin
9
+ permitted_types = %w[Polygon Multipolyigon]
10
+ permitted_types.any? { |t| t == g["type"] }
11
+ rescue TypeError
12
+ false
13
+ end
14
+ end,
15
+ limit: ->(l) { l.between?(1, 30) },
16
+ type: ->(t) { %w[photo painting].any?(t.downcase) }
17
+ }
18
+
19
+ def self.validate(params)
20
+ errors = {}
21
+
22
+ VALIDATIONS.each do |k, v|
23
+ next if params[k].nil?
24
+
25
+ next if v.instance_of?(Array) ? call_each(v, params[k]) : v.call(params[k])
26
+
27
+ errors.merge!({ k.to_sym => [params[k], v] })
28
+ end
29
+
30
+ if errors.empty?
31
+ true
32
+ else
33
+ report_errors(errors)
34
+ end
35
+ end
36
+
37
+ def self.report_errors(errors)
38
+ report = errors.map do |k, v|
39
+ "\n#{k}: #{v[0]}"
40
+ end.join(", ")
41
+
42
+ raise ArgumentError, "expect params to pass validations\n #{report}"
43
+ end
44
+
45
+ def self.call_each(array, argument)
46
+ array.each do |lambda|
47
+ return false unless lambda.call(argument)
48
+ end
49
+
50
+ true
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ module Pastvu
2
+ class ParamsValidator
3
+ def self.validate(params)
4
+ TypeCheck.validate(params) if Pastvu.config.check_params_type
5
+ ValueCheck.validate(params) if Pastvu.config.check_params_value
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ module Pastvu
4
+ class Parser
5
+ def self.to_json(hash)
6
+ JSON.dump(hash)
7
+ end
8
+
9
+ def self.to_hash(json)
10
+ hash = JSON.parse(json)
11
+ # symbolize_keys hash
12
+ end
13
+
14
+ # def self.symbolize_keys(hash)
15
+ # hash.transform_keys do |k|
16
+ # k.to_sym
17
+ # symbolize_keys(hash[k]) if hash[k].instance_of?(Hash)
18
+ # end
19
+ # end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Pastvu
2
+ class Photo < Model
3
+ # VALID_ATTRIBUTES = %w[cid s file title dir geo year ccount]
4
+ # attr_accessor *VALID_ATTRIBUTES
5
+
6
+ def reload
7
+ Pastvu.photo @cid
8
+ end
9
+
10
+ def comments
11
+ Pastvu.comments @cid
12
+ end
13
+
14
+ def download(size, path)
15
+ raise ArgumentError, "expect size to be correct symbol" unless %i[original standard thumbnail thumb].include?(size)
16
+ raise ArgumentError, "expect file extension to be .jpeg or .jpg" unless path[-4..-1] == ".jpg" || path[-5..-1] == ".jpeg"
17
+
18
+ Request.download(method(size).call, path)
19
+ end
20
+
21
+ def standard
22
+ "https://pastvu.com/_p/d/".concat(file)
23
+ end
24
+
25
+ def original
26
+ "https://pastvu.com/_p/a/".concat(file)
27
+ end
28
+
29
+ def thumbnail
30
+ "https://pastvu.com/_p/h/".concat(file)
31
+ end
32
+
33
+ def thumb
34
+ thumbnail
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ module Pastvu
2
+ class PhotoCollection < Collection
3
+ def initialize(attributes, params = nil)
4
+ super attributes
5
+ @params = params
6
+ @path = %w[result photos]
7
+ @model = Photo
8
+ end
9
+
10
+ def next(n_photos = nil)
11
+ raise "next can be used with nearest photos requests only" if @params.nil?
12
+
13
+ n_photos ||= @params[:limit] || 30
14
+ # params get passed to PhotoCollection when it is a request for nearest photos on.y
15
+ raise ArgumentError, "n_photos must be Integer between 1 & 30" unless n_photos.instance_of?(Integer) && n_photos.between?(1, 30)
16
+
17
+ new_skip = {
18
+ skip: (@params[:skip] || 0) + n_photos
19
+ }
20
+
21
+ new_params = @params.merge(new_skip)
22
+
23
+ Pastvu.nearest_photos(geo: @params[:geo], **new_params)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ require "net/http"
2
+ require "addressable"
3
+ require "open-uri"
4
+
5
+ module Pastvu
6
+ class Request
7
+ attr_reader :response
8
+
9
+ def initialize(method, params)
10
+ @method = method
11
+ @params = params
12
+
13
+ request(build_uri)
14
+ end
15
+
16
+ def self.download(uri, path)
17
+ download = URI.parse(uri).open("User-Agent" => Pastvu.config.user_agent)
18
+ IO.copy_stream(download, path)
19
+ File.new(path)
20
+ end
21
+
22
+ def request(uri)
23
+ Net::HTTP.get_response(uri, "User-Agent" => Pastvu.config.user_agent) do |response|
24
+ @response = response.body
25
+ end
26
+ end
27
+
28
+ def build_uri
29
+ uri = Addressable::URI.new({ scheme: 'https', host: Pastvu.config.host })
30
+
31
+ template = Addressable::Template.new(uri.to_s + "{/path*}" + "{?query*}")
32
+
33
+ template.expand({
34
+ "path" => Pastvu.config.path,
35
+ "query" => {
36
+ "method" => @method,
37
+ "params" => JSON.dump(@params)
38
+ }
39
+ })
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ module Pastvu
2
+ class BoundsResponse < BasicResponse
3
+ def clusters
4
+ @hash ||= self.to_hash
5
+ ClusterCollection.new @hash
6
+ end
7
+
8
+ def photos
9
+ @hash ||= self.to_hash
10
+ PhotoCollection.new @hash
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Pastvu
2
+ class InformationResponse < BasicResponse
3
+ def to_photo
4
+ @hash ||= self.to_hash
5
+ Photo.new @hash["result"]["photo"]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pastvu
4
+ VERSION = "1.0.0"
5
+ end
data/lib/pastvu.rb ADDED
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pastvu/version"
4
+ require_relative "pastvu/configuration"
5
+ require_relative "pastvu/request"
6
+ require_relative "pastvu/basic_response"
7
+ require_relative "pastvu/model"
8
+ require_relative "pastvu/collection"
9
+ require_relative "pastvu/parser"
10
+ require_relative "pastvu/params_validator"
11
+
12
+ require_relative "pastvu/response/bounds_response"
13
+ require_relative "pastvu/response/information_response"
14
+ require_relative "pastvu/cluster/cluster_collection"
15
+ require_relative "pastvu/cluster/cluster"
16
+ require_relative "pastvu/comment/comment_collection"
17
+ require_relative "pastvu/comment/comment"
18
+ require_relative "pastvu/photo/photo_collection"
19
+ require_relative "pastvu/photo/photo"
20
+
21
+ require_relative "pastvu/params_validator/type_check"
22
+ require_relative "pastvu/params_validator/value_check"
23
+
24
+ module Pastvu
25
+ METHODS = {
26
+ photo_info: "photo.giveForPage",
27
+ comments: "comment.giveForObj",
28
+ nearest_photos: "photo.giveNearestPhotos",
29
+ by_bounds: "photo.getByBounds"
30
+ }
31
+
32
+ def self.photo_info(cid)
33
+ params = {
34
+ cid: cid
35
+ }
36
+
37
+ ParamsValidator.validate params
38
+
39
+ InformationResponse.new request(__method__, params)
40
+ end
41
+
42
+ def self.photo(cid)
43
+ self.photo_info(cid).to_photo
44
+ end
45
+
46
+ def self.comments(cid)
47
+ params = {
48
+ cid: cid
49
+ }
50
+
51
+ ParamsValidator.validate params
52
+
53
+ CommentCollection.new request(__method__, params)
54
+ end
55
+
56
+ def self.nearest_photos(geo:, **params)
57
+ params = {
58
+ geo: geo,
59
+ except: params[:except],
60
+ distance: params[:distance],
61
+ year: params[:year],
62
+ year2: params[:year2],
63
+ type: params[:type],
64
+ limit: params[:limit],
65
+ skip: params[:skip]
66
+ }.compact
67
+
68
+ ParamsValidator.validate params
69
+
70
+ PhotoCollection.new request(__method__, params), params
71
+ end
72
+
73
+ def self.by_bounds(geometry:, z:, **params)
74
+ params[:localWork] = true if z >= 17
75
+
76
+ params = {
77
+ geometry: geometry.instance_of?(Hash) ? geometry : Parser.to_hash(geometry),
78
+ z: z,
79
+ isPainting: params[:isPainting] || params[:is_painting],
80
+ year: params[:year],
81
+ year2: params[:year2],
82
+ localWork: params[:localWork] || params[:local_work]
83
+ }.compact
84
+
85
+ ParamsValidator.validate params
86
+
87
+ BoundsResponse.new request(__method__, params)
88
+ end
89
+
90
+ def self.request(method, params)
91
+ Request.new(METHODS[method], params).response
92
+ end
93
+
94
+ def self.config
95
+ @config ||= Configuration.new
96
+ end
97
+
98
+ def self.configure(&block)
99
+ config.configure(&block)
100
+ end
101
+ end
data/pastvu.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/pastvu/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pastvu"
7
+ spec.version = Pastvu::VERSION
8
+ spec.authors = ["Evgeny Nedoborskiy"]
9
+ spec.email = ["129510705+projecteurlumiere@users.noreply.github.com"]
10
+ spec.homepage = "https://github.com/projecteurlumiere/pastvu"
11
+ spec.summary = "A Ruby wrapper for PastVu API"
12
+
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.0.0"
15
+
16
+ spec.files = Dir.chdir(__dir__) do
17
+ `git ls-files -z`.split("\x0").reject do |f|
18
+ (File.expand_path(f) == __FILE__) ||
19
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .rspec .circleci appveyor Gemfile])
20
+ end
21
+ end
22
+
23
+ spec.add_dependency "addressable", "~> 2.8"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ spec.add_development_dependency "webmock", "~> 3.19"
26
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pastvu
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Evgeny Nedoborskiy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: addressable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.19'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.19'
55
+ description:
56
+ email:
57
+ - 129510705+projecteurlumiere@users.noreply.github.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CHANGELOG.md
63
+ - LICENSE.txt
64
+ - README.md
65
+ - lib/pastvu.rb
66
+ - lib/pastvu/basic_response.rb
67
+ - lib/pastvu/cluster/cluster.rb
68
+ - lib/pastvu/cluster/cluster_collection.rb
69
+ - lib/pastvu/collection.rb
70
+ - lib/pastvu/comment/comment.rb
71
+ - lib/pastvu/comment/comment_collection.rb
72
+ - lib/pastvu/configuration.rb
73
+ - lib/pastvu/model.rb
74
+ - lib/pastvu/params_validator.rb
75
+ - lib/pastvu/params_validator/type_check.rb
76
+ - lib/pastvu/params_validator/value_check.rb
77
+ - lib/pastvu/parser.rb
78
+ - lib/pastvu/photo/photo.rb
79
+ - lib/pastvu/photo/photo_collection.rb
80
+ - lib/pastvu/request.rb
81
+ - lib/pastvu/response/bounds_response.rb
82
+ - lib/pastvu/response/information_response.rb
83
+ - lib/pastvu/version.rb
84
+ - pastvu.gemspec
85
+ homepage: https://github.com/projecteurlumiere/pastvu
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 3.0.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.4.20
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: A Ruby wrapper for PastVu API
108
+ test_files: []