osm 0.1.17 → 0.2.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.
@@ -1,3 +1,48 @@
1
+ ## Version 0.2.0
2
+
3
+ * Raises Forbidden exception if:
4
+ * You try to use a feature which requires an OSM subscription above your current one
5
+ * You try to access a feature which you don't have the correct permissions for
6
+ * You try to access a Section (or it's Grouping) you shouldn't be accessing
7
+ * All Model classes:
8
+ * Addition of changed\_attributes method to get a list of attributes which have changed
9
+ * Addition of reset\_changed\_attributes method to reset the list of attributes which have changed
10
+ * Activity
11
+ * Check user has permission to view before returning from cache
12
+ * Addition of osm\_link method to get the URL for viewing in OSM
13
+ * Add updating of Grouping
14
+ * Evening:
15
+ * Rename to Meeting
16
+ * Rename meeting\_date attribute to date
17
+ * Rename get\_programme method to get\_for\_section
18
+ * Event:
19
+ * Removal of add\_field method (use add\_column instead)
20
+ * Removal of fields attribute (use columns instead)
21
+ * FlexiRecord:
22
+ * Addition of id, section\_id and name attributes (these no longer need to be passed to methods)
23
+ * FlexiRecord::Field renamed to FlexiRecord::Column
24
+ * The following methods are now instance not class methods:
25
+ * get\_fields (also renamed to get\_columns)
26
+ * add\_field (also renamed to add\_column)
27
+ * get\_data
28
+ * The following methods have bceome instance methods of a subclasses:
29
+ * update\_field (moved to column.update)
30
+ * delete\_field (moved to column.delete)
31
+ * update\_data (moved to data.update)
32
+ * Member:
33
+ * Removal of grouping attribute
34
+ * Removal of grouping\_label attribute
35
+ * Addition of myscout\_link method (used to get the link to the member's My.SCOUT page)
36
+ * Section:
37
+ * subscription\_level attribute is now a Fixnum not Symbol
38
+ * Addition of subscription\_level\_name method to get the name of the subscription level for the section
39
+ * flexi\_records attribute now contains an Array of Osm::FlexiRecord
40
+ * "Under the hood" changes:
41
+ * Instead of caching individual items and a list of items the gem now caches a list of IDs. This should reduce the cache size.
42
+ * When updating items requires multiple OSM requests, now only updates what changed
43
+ * Updating of cached data when deleting/updating items from OSM
44
+ >>>>>>> dev_v_0.2.0
45
+
1
46
  ## Version 0.1.17
2
47
 
3
48
  * Add comparison to Evening
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch(%r{^lib/([^/]+)/(.+)\.rb$}) { |m| "spec/#{m[1]}/#{m[2]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+ end
data/README.md CHANGED
@@ -80,10 +80,10 @@ however it should be noted that when the OSM API adds a feature it can be diffic
80
80
  * Badge requirements for evening
81
81
  * Due Badges
82
82
  * Evening
83
- * Event
84
- * Events
85
- * Event Columns
86
- * Event Attendance
83
+ * Event (Silver required)
84
+ * Events (Silver required)
85
+ * Event Columns (Silver required)
86
+ * Event Attendance (Silver required)
87
87
  * Flexi Record Data
88
88
  * Flexi Record Structure
89
89
  * Groupings (e.g. Sixes, Patrols)
@@ -102,24 +102,25 @@ however it should be noted that when the OSM API adds a feature it can be diffic
102
102
  ### Update
103
103
  * Activity
104
104
  * Evening
105
- * Event
106
- * Event Attendance
107
- * Event Column
105
+ * Event (Silver required)
106
+ * Event Attendance (Silver required)
107
+ * Event Column (Silver required)
108
108
  * Flexi Record Column
109
109
  * Flexi Record Data
110
+ * Grouping
110
111
  * Member
111
112
  * Register Attendance
112
113
 
113
114
  ### Create
114
115
  * Evening
115
- * Event
116
+ * Event (Silver required)
116
117
  * Member
117
118
  * Flexi Record Column
118
119
 
119
120
  ### Delete
120
121
  * Evening
121
- * Event
122
- * Event Column
122
+ * Event (Silver required)
123
+ * Event Column (Silver required)
123
124
  * Flexi Record Column
124
125
 
125
126
  ### Actions
@@ -129,15 +130,16 @@ however it should be noted that when the OSM API adds a feature it can be diffic
129
130
 
130
131
  ## Parts of the OSM API currently NOT supported (may not be an exhaustive list):
131
132
 
132
- * Badges:
133
+ * Badges (Silver required for activity, Bronze for core, challenge and staged):
133
134
  * Which requirements each member has met:
134
135
  * Retreive [issue 21]
135
136
  * Update [issue 22]
136
137
  * Retrieve details for each badge (stock, short column names etc.) [issue 20]
137
138
  * Update badge stock [issue 56]
139
+ * Event - Create column (Silver required)
138
140
  * SMS:
139
141
  * Retrieval of delivery reports [issue 54]
140
142
  * Sending a message [issue 54]
141
- * Gift aid (Everything)
142
- * Finances (Everything)
143
+ * Gift aid (Everything) (Gold required)
144
+ * Finances (Everything) (Gold required)
143
145
  * MyScout (Everything) (Maybe)
data/lib/osm.rb CHANGED
@@ -122,6 +122,20 @@ module Osm
122
122
  hash_out
123
123
  end
124
124
 
125
+ def self.make_permissions_hash(permissions)
126
+ return {} unless permissions.is_a?(Hash)
127
+
128
+ permissions_map = {
129
+ 10 => [:read],
130
+ 20 => [:read, :write],
131
+ 100 => [:read, :write, :administer],
132
+ }
133
+
134
+ return permissions.inject({}) do |new_hash, (key, value)|
135
+ new_hash[key.to_sym] = (permissions_map[value.to_i] || [])
136
+ new_hash
137
+ end
138
+ end
125
139
 
126
140
  def self.epoch_date?(date)
127
141
  [OSM_EPOCH, OSM_EPOCH_HUMAN].include?(date)
@@ -96,15 +96,21 @@ module Osm
96
96
 
97
97
  # Get activity details
98
98
  # @param [Osm::Api] api The api to use to make the request
99
- # @param [Fixnum] activity_id the activity ID
100
- # @param [Fixnum] version the version of the activity to retreive, if nil the latest version will be assumed
99
+ # @param [Fixnum] activity_id The activity ID
100
+ # @param [Fixnum] version The version of the activity to retreive, if nil the latest version will be assumed
101
101
  # @!macro options_get
102
102
  # @return [Osm::Activity]
103
103
  def self.get(api, activity_id, version=nil, options={})
104
104
  cache_key = ['activity', activity_id]
105
105
 
106
- if !options[:no_cache] && cache_exist?(api, [*cache_key, version]) # TODO work out permission check
107
- return cache_read(api, [*cache_key, version])
106
+ if !options[:no_cache] && cache_exist?(api, [*cache_key, version])
107
+ activity = cache_read(api, [*cache_key, version])
108
+ if (activity.shared == 2) || (activity.user_id == api.user_id) || # Shared or owned by this user
109
+ Osm::Section.get_all(api).map{ |s| s.group_id }.uniq.include?(activity.group_id) # user belomngs to the group owning the activity
110
+ return activity
111
+ else
112
+ return nil
113
+ end
108
114
  end
109
115
 
110
116
  data = nil
@@ -174,16 +180,26 @@ module Osm
174
180
 
175
181
  # @!method initialize
176
182
  # Initialize a new Term
177
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
183
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
178
184
 
179
185
 
186
+ # Get the link to display this activity in OSM
187
+ # @return [String] the link for this member's My.SCOUT
188
+ # @raise [Osm::ObjectIsInvalid] If the Activity is invalid
189
+ def osm_link
190
+ raise Osm::ObjectIsInvalid, 'activity is invalid' unless valid?
191
+ return "https://www.onlinescoutmanager.co.uk/?l=p#{self.id}"
192
+ end
193
+
180
194
  # Add this activity to the programme in OSM
181
195
  # @param [Osm::Api] api The api to use to make the request
182
- # @param [Osm::Section, Fixnum] section The Section (or it's ID) to add the Activity to
196
+ # @param [Osm::Section, Fixnum, #to_i] section The Section (or it's ID) to add the Activity to
183
197
  # @param [Date, DateTime] date The date of the Evening to add the Activity to (OSM will create the Evening if it doesn't already exist)
184
198
  # @param [String] notes The notes which should appear for this Activity on this Evening
185
199
  # @return [Boolean] Whether the activity was successfully added
186
200
  def add_to_programme(api, section, date, notes="")
201
+ require_ability_to(api, :write, :programme, section)
202
+
187
203
  data = api.perform_query("programme.php?action=addActivityToProgramme", {
188
204
  'meetingdate' => date.strftime(Osm::OSM_DATE_FORMAT),
189
205
  'activityid' => id,
@@ -191,16 +207,25 @@ module Osm
191
207
  'notes' => notes,
192
208
  })
193
209
 
194
- return (data == {'result'=>0})
210
+ if (data == {'result'=>0})
211
+ # The cached activity will be out of date - remove it
212
+ cache_delete(api, ['activity', self.id])
213
+ return true
214
+ else
215
+ return false
216
+ end
195
217
  end
196
218
 
197
219
  # Update this activity in OSM
198
220
  # @param [Osm::Api] api The api to use to make the request
199
- # @param [Osm::Section, Fixnum] section The Section (or it's ID)
221
+ # @param [Osm::Section, Fixnum, #to_i] section The Section (or it's ID)
200
222
  # @param [Boolean] secret_update Whether this is a secret update
201
223
  # @return [Boolean] Whether the activity was successfully added
224
+ # @raise [Osm::ObjectIsInvalid] If the Activity is invalid
225
+ # @raise [Osm::Forbidden] If the Activity is not editable
202
226
  def update(api, section, secret_update=false)
203
- raise ObjectIsInvalid, 'activity is invalid' unless valid?
227
+ raise Osm::ObjectIsInvalid, 'activity is invalid' unless valid?
228
+ raise Osm::Forbidden, "You are not allowed to update this activity" unless self.editable
204
229
 
205
230
  data = api.perform_query("programme.php?action=update", {
206
231
  'title' => title,
@@ -219,7 +244,13 @@ module Osm
219
244
  'secretEdit' => secret_update,
220
245
  })
221
246
 
222
- return (data == {'result'=>true})
247
+ if (data == {'result'=>true})
248
+ # The cached activity will be out of date - remove it
249
+ cache_delete(api, ['activity', self.id])
250
+ return true
251
+ else
252
+ return false
253
+ end
223
254
  end
224
255
 
225
256
 
@@ -251,7 +282,7 @@ module Osm
251
282
 
252
283
  # @!method initialize
253
284
  # Initialize a new Term
254
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
285
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
255
286
 
256
287
  end # Class Activity::File
257
288
 
@@ -292,7 +323,7 @@ module Osm
292
323
 
293
324
  # @!method initialize
294
325
  # Initialize a new Badge
295
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
326
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
296
327
 
297
328
  end # Class Activity::Badge
298
329
 
@@ -323,7 +354,7 @@ module Osm
323
354
 
324
355
  # @!method initialize
325
356
  # Initialize a new Version
326
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
357
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
327
358
 
328
359
  end # Class Activity::Version
329
360
 
@@ -51,9 +51,9 @@ module Osm
51
51
  end
52
52
 
53
53
  # Initialize a new API connection
54
- # @param [String] user_id osm userid of the user to act as (get this by using the authorize method)
55
- # @param [String] secret osm secret of the user to act as (get this by using the authorize method)
56
- # @param [Symbol] site whether to use OSM (:osm) or OGM (:ogm), defaults to the value set for the class
54
+ # @param [String] user_id OSM userid of the user to act as (get this by using the authorize method)
55
+ # @param [String] secret OSM secret of the user to act as (get this by using the authorize method)
56
+ # @param [Symbol] site Whether to use OSM (:osm) or OGM (:ogm), defaults to the value set for the class
57
57
  # @return nil
58
58
  def initialize(user_id, secret, site=@@site)
59
59
  raise ArgumentError, 'You must pass a secret (get this by using the authorize method)' if secret.nil?
@@ -98,8 +98,8 @@ module Osm
98
98
 
99
99
  # Get the userid and secret to be able to act as a certain user on the OSM/OGM system
100
100
  # @param [Symbol] site The site to use either :osm or :ogm (defaults to whatever was set in the configure method)
101
- # @param [String] email the login email address of the user on OSM
102
- # @param [String] password the login password of the user on OSM
101
+ # @param [String] email The login email address of the user on OSM
102
+ # @param [String] password The login password of the user on OSM
103
103
  # @return [Hash] a hash containing the following keys:
104
104
  # * :user_id - the userid to use in future requests
105
105
  # * :secret - the secret to use in future requests
@@ -117,8 +117,8 @@ module Osm
117
117
 
118
118
 
119
119
  # Set the OSM user to make future requests as
120
- # @param [String] user_id the OSM userid to use (get this using the authorize method)
121
- # @param [String] secret the OSM secret to use (get this using the authorize method)
120
+ # @param [String] user_id The OSM userid to use (get this using the authorize method)
121
+ # @param [String] secret The OSM secret to use (get this using the authorize method)
122
122
  # @return [Osm::Api] self
123
123
  def set_user(user_id, secret)
124
124
  @user_id = user_id
@@ -128,8 +128,8 @@ module Osm
128
128
 
129
129
 
130
130
  # Make a query to the OSM/OGM API
131
- # @param [String] url the script on the remote server to invoke
132
- # @param [Hash] api_data a hash containing the values to be sent to the server in the body of the request
131
+ # @param [String] url The script on the remote server to invoke
132
+ # @param [Hash] api_data A hash containing the values to be sent to the server in the body of the request
133
133
  # @return [Hash, Array, String] the parsed JSON returned by OSM
134
134
  def perform_query(url, api_data={})
135
135
  self.class.perform_query(@site, url, api_data.merge({
@@ -138,12 +138,48 @@ module Osm
138
138
  }))
139
139
  end
140
140
 
141
+ # Get API user's permissions
142
+ # @!macro options_get
143
+ # @return nil if an error occured or the user does not have access to that section
144
+ # @return [Hash] {section_id => permissions_hash}
145
+ def get_user_permissions(options={})
146
+ cache_key = ['permissions', user_id]
147
+
148
+ if !options[:no_cache] && Osm::Model.cache_exist?(self, cache_key)
149
+ return Osm::Model.cache_read(self, cache_key)
150
+ end
151
+
152
+ data = perform_query('api.php?action=getUserRoles')
153
+
154
+ all_permissions = Hash.new
155
+ data.each do |item|
156
+ unless item['section'].eql?('discount') # It's not an actual section
157
+ all_permissions.merge!(Osm::to_i_or_nil(item['sectionid']) => Osm.make_permissions_hash(item['permissions']))
158
+ end
159
+ end
160
+ Osm::Model.cache_write(self, cache_key, all_permissions)
161
+
162
+ return all_permissions
163
+ end
164
+
165
+ # Set access permission for an API user for a given Section
166
+ # @param [Section, Fixnum] section The Section to set permissions for
167
+ # @param [Hash] permissions The permissions Hash
168
+ def set_user_permissions(section, permissions)
169
+ key = ['permissions', user_id]
170
+ permissions = get_user_permissions.merge(section.to_i => permissions)
171
+ Osm::Model.cache_write(self, key, permissions)
172
+ end
173
+
174
+
141
175
  private
142
176
  # Make a query to the OSM/OGM API
143
177
  # @param [Symbol] site The site to use either :osm or :ogm
144
- # @param [String] url the script on the remote server to invoke
145
- # @param [Hash] api_data a hash containing the values to be sent to the server in the body of the request
178
+ # @param [String] url The script on the remote server to invoke
179
+ # @param [Hash] api_data A hash containing the values to be sent to the server in the body of the request
146
180
  # @return [Hash, Array, String] the parsed JSON returned by OSM
181
+ # @raise [Osm::Error] If an error was returned by OSM
182
+ # @raise [Osm::ConnectionError] If an error occured connecting to OSM
147
183
  def self.perform_query(site, url, api_data={})
148
184
  raise ArgumentError, 'site is invalid, this should be set to either :osm or :ogm' unless [:osm, :ogm].include?(site)
149
185
 
@@ -180,7 +216,7 @@ module Osm
180
216
  end
181
217
 
182
218
  # Check if text looks like it's JSON
183
- # @param [String] text what to look at
219
+ # @param [String] text What to look at
184
220
  # @return [Boolean]
185
221
  def self.looks_like_json?(text)
186
222
  (['[', '{'].include?(text[0]))
@@ -23,7 +23,7 @@ module Osm
23
23
 
24
24
  # Get API access details for a given section
25
25
  # @param [Osm::Api] api The api to use to make the request
26
- # @param [Osm::Section, Fixnum] section the section (or its ID) to get the details for
26
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the details for
27
27
  # @!macro options_get
28
28
  # @return [Array<Osm::ApiAccess>]
29
29
  def self.get_all(api, section, options={})
@@ -31,7 +31,8 @@ module Osm
31
31
  cache_key = ['api_access', api.user_id, section_id]
32
32
 
33
33
  if !options[:no_cache] && cache_exist?(api, cache_key)
34
- return cache_read(api, cache_key)
34
+ ids = cache_read(api, cache_key)
35
+ return get_from_ids(api, ids, cache_key, section, options, :get_all)
35
36
  end
36
37
 
37
38
  data = api.perform_query("users.php?action=getAPIAccess&sectionid=#{section_id}")
@@ -41,6 +42,7 @@ module Osm
41
42
  20 => [:read, :write],
42
43
  }
43
44
  result = Array.new
45
+ ids = Array.new
44
46
  data['apis'].each do |item|
45
47
  attributes = {}
46
48
  attributes[:id] = item['apiid'].to_i
@@ -57,9 +59,10 @@ module Osm
57
59
 
58
60
  this_item = new(attributes)
59
61
  result.push this_item
62
+ ids.push this_item.id
60
63
  cache_write(api, [*cache_key, this_item.id], this_item)
61
64
  end
62
- cache_write(api, cache_key, result)
65
+ cache_write(api, cache_key, ids)
63
66
 
64
67
  return result
65
68
  end
@@ -67,7 +70,7 @@ module Osm
67
70
 
68
71
  # Get our API access details for a given section
69
72
  # @param [Osm::Api] api The api to use to make the request
70
- # @param [Osm::Section, Fixnum] section the section (or its ID) to get the details for
73
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the details for
71
74
  # @!macro options_get
72
75
  # @return [Osm::ApiAccess]
73
76
  def self.get_ours(api, section, options={})
@@ -77,7 +80,7 @@ module Osm
77
80
 
78
81
  # Get API Access for a given API
79
82
  # @param [Osm::Api] api The api to use to make the request
80
- # @param [Osm::Section, Fixnum] section the section (or its ID) to get the details for
83
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the details for
81
84
  # @param [Osm::Api] for_api The api (or its ID) to get access for
82
85
  # @!macro options_get
83
86
  # @return [Osm::ApiAccess]
@@ -101,7 +104,7 @@ module Osm
101
104
 
102
105
  # @!method initialize
103
106
  # Initialize a new Term
104
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
107
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
105
108
 
106
109
  end # Class ApiAccess
107
110
 
@@ -30,16 +30,17 @@ module Osm
30
30
 
31
31
  # Get due badges
32
32
  # @param [Osm::Api] api The api to use to make the request
33
- # @param [Osm::Section, Fixnum] section the section (or its ID) to get the due badges for
34
- # @param [Osm::Term, Fixnum, nil] term the term (or its ID) to get the due badges for, passing nil causes the current term to be used
33
+ # @param [Osm::Section, Fixnum, #to_i] section The section (or its ID) to get the due badges for
34
+ # @param [Osm::Term, Fixnum, #to_i, nil] term The term (or its ID) to get the due badges for, passing nil causes the current term to be used
35
35
  # @!macro options_get
36
36
  # @return [Osm::DueBadges]
37
37
  def self.get(api, section, term=nil, options={})
38
+ require_ability_to(api, :read, :badge, section, options)
38
39
  section = Osm::Section.get(api, section, options) if section.is_a?(Fixnum)
39
40
  term_id = (term.nil? ? Osm::Term.get_current_term_for_section(api, section, options) : term).to_i
40
41
  cache_key = ['due_badges', section.id, term_id]
41
42
 
42
- if !options[:no_cache] && cache_exist?(api, cache_key) && get_user_permission(api, section.id, :badge).include?(:read)
43
+ if !options[:no_cache] && cache_exist?(api, cache_key)
43
44
  return cache_read(api, cache_key)
44
45
  end
45
46
 
@@ -74,7 +75,7 @@ module Osm
74
75
 
75
76
  # @!method initialize
76
77
  # Initialize a new Term
77
- # @param [Hash] attributes the hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
78
+ # @param [Hash] attributes The hash of attributes (see attributes for descriptions, use Symbol of attribute name as the key)
78
79
 
79
80
 
80
81
  # Check if there are no badges due