osm 0.1.17 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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