openstax_accounts 2.0.0 → 3.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.
Files changed (77) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +1 -0
  3. data/Rakefile +6 -6
  4. data/app/models/openstax/accounts/account.rb +34 -24
  5. data/app/models/openstax/accounts/application_group.rb +7 -0
  6. data/app/models/openstax/accounts/group.rb +132 -0
  7. data/app/models/openstax/accounts/group_member.rb +40 -0
  8. data/app/models/openstax/accounts/group_nesting.rb +62 -0
  9. data/app/models/openstax/accounts/group_owner.rb +40 -0
  10. data/app/representers/openstax/accounts/api/v1/application_group_representer.rb +25 -0
  11. data/app/representers/openstax/accounts/api/v1/application_groups_representer.rb +16 -0
  12. data/app/representers/openstax/accounts/api/v1/group_nesting_representer.rb +18 -0
  13. data/app/representers/openstax/accounts/api/v1/group_representer.rb +50 -0
  14. data/app/representers/openstax/accounts/api/v1/group_user_representer.rb +21 -0
  15. data/app/routines/openstax/accounts/search_accounts.rb +7 -14
  16. data/app/routines/openstax/accounts/sync_accounts.rb +32 -18
  17. data/app/routines/openstax/accounts/sync_groups.rb +61 -0
  18. data/app/routines/openstax/accounts/update_group_caches.rb +27 -0
  19. data/app/views/openstax/accounts/shared/accounts/_index.html.erb +0 -3
  20. data/config/initializers/action_interceptor.rb +1 -1
  21. data/db/migrate/20140811182433_create_openstax_accounts_groups.rb +16 -0
  22. data/db/migrate/20140811182505_create_openstax_accounts_group_members.rb +13 -0
  23. data/db/migrate/20140811182527_create_openstax_accounts_group_owners.rb +13 -0
  24. data/db/migrate/20140811182553_create_openstax_accounts_group_nestings.rb +13 -0
  25. data/lib/generators/openstax/accounts/schedule/templates/schedule.rb +1 -0
  26. data/lib/openstax/accounts/current_user_manager.rb +2 -2
  27. data/lib/openstax/accounts/has_many_through_groups.rb +45 -0
  28. data/lib/openstax/accounts/version.rb +1 -1
  29. data/lib/openstax_accounts.rb +165 -11
  30. data/spec/controllers/openstax/accounts/dev/accounts_controller_spec.rb +1 -1
  31. data/spec/controllers/openstax/accounts/sessions_controller_spec.rb +1 -1
  32. data/spec/dummy/app/controllers/api/application_groups_controller.rb +11 -0
  33. data/spec/dummy/app/controllers/api/dummy_controller.rb +2 -1
  34. data/spec/dummy/app/controllers/api/group_members_controller.rb +11 -0
  35. data/spec/dummy/app/controllers/api/group_nestings_controller.rb +11 -0
  36. data/spec/dummy/app/controllers/api/group_owners_controller.rb +11 -0
  37. data/spec/dummy/app/controllers/api/groups_controller.rb +15 -0
  38. data/spec/dummy/app/controllers/api/users_controller.rb +4 -0
  39. data/spec/dummy/app/models/ownership.rb +7 -0
  40. data/spec/dummy/app/models/user.rb +11 -8
  41. data/spec/dummy/config/application.rb +0 -33
  42. data/spec/dummy/config/boot.rb +4 -9
  43. data/spec/dummy/config/database.yml +8 -8
  44. data/spec/dummy/config/environment.rb +3 -3
  45. data/spec/dummy/config/environments/development.rb +20 -12
  46. data/spec/dummy/config/environments/production.rb +42 -29
  47. data/spec/dummy/config/environments/test.rb +16 -12
  48. data/spec/dummy/config/initializers/assets.rb +8 -0
  49. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  50. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  51. data/spec/dummy/config/initializers/inflections.rb +6 -5
  52. data/spec/dummy/config/initializers/mime_types.rb +0 -1
  53. data/spec/dummy/config/initializers/session_store.rb +1 -6
  54. data/spec/dummy/config/initializers/wrap_parameters.rb +6 -6
  55. data/spec/dummy/config/routes.rb +23 -0
  56. data/spec/dummy/config/secrets.yml +8 -0
  57. data/spec/dummy/db/migrate/1_create_users.rb +2 -2
  58. data/spec/dummy/db/migrate/2_create_ownerships.rb +11 -0
  59. data/spec/dummy/db/schema.rb +72 -20
  60. data/spec/dummy/db/test.sqlite3 +0 -0
  61. data/spec/dummy/log/development.log +186 -0
  62. data/spec/dummy/log/test.log +2078 -0
  63. data/spec/factories/openstax_accounts_account.rb +3 -2
  64. data/spec/factories/openstax_accounts_group.rb +7 -0
  65. data/spec/factories/openstax_accounts_group_member.rb +6 -0
  66. data/spec/factories/openstax_accounts_group_nesting.rb +6 -0
  67. data/spec/factories/openstax_accounts_group_owner.rb +6 -0
  68. data/spec/lib/openstax/accounts/current_user_manager_spec.rb +9 -3
  69. data/spec/lib/openstax/accounts/has_many_through_groups_spec.rb +53 -0
  70. data/spec/lib/openstax_accounts_spec.rb +189 -25
  71. data/spec/models/openstax/accounts/account_spec.rb +16 -1
  72. data/spec/models/openstax/accounts/anonymous_account_spec.rb +1 -1
  73. data/spec/models/openstax/accounts/group_spec.rb +20 -0
  74. data/spec/routines/openstax/accounts/sync_accounts_spec.rb +70 -0
  75. data/spec/routines/openstax/accounts/sync_groups_spec.rb +125 -0
  76. metadata +73 -56
  77. data/spec/dummy/config/initializers/secret_token.rb +0 -7
@@ -0,0 +1,18 @@
1
+ module OpenStax
2
+ module Accounts
3
+ module Api
4
+ module V1
5
+ class GroupNestingRepresenter < Roar::Decorator
6
+ include Roar::Representer::JSON
7
+
8
+ property :container_group_id,
9
+ type: Integer
10
+
11
+ property :member_group_id,
12
+ type: Integer
13
+
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,50 @@
1
+ module OpenStax
2
+ module Accounts
3
+ module Api
4
+ module V1
5
+ class GroupRepresenter < Roar::Decorator
6
+ include Roar::Representer::JSON
7
+
8
+ property :openstax_uid,
9
+ as: :id,
10
+ type: Integer
11
+
12
+ property :name,
13
+ type: String
14
+
15
+ property :is_public
16
+
17
+ collection :group_owners,
18
+ as: :owners,
19
+ class: GroupOwner,
20
+ decorator: GroupUserRepresenter
21
+
22
+ collection :group_members,
23
+ as: :members,
24
+ class: GroupMember,
25
+ decorator: GroupUserRepresenter
26
+
27
+ collection :member_group_nestings,
28
+ as: :nestings,
29
+ class: GroupNesting,
30
+ decorator: GroupNestingRepresenter
31
+
32
+ property :cached_supertree_group_ids,
33
+ as: :supertree_group_ids,
34
+ type: Array,
35
+ schema_info: {
36
+ items: "integer"
37
+ }
38
+
39
+ property :cached_subtree_group_ids,
40
+ as: :subtree_group_ids,
41
+ type: Array,
42
+ schema_info: {
43
+ items: "integer"
44
+ }
45
+
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ module OpenStax
2
+ module Accounts
3
+ module Api
4
+ module V1
5
+ class GroupUserRepresenter < Roar::Decorator
6
+ include Roar::Representer::JSON
7
+
8
+ property :group_id,
9
+ type: Integer
10
+
11
+ nested :user do
12
+ property :user_id,
13
+ as: :id,
14
+ type: Integer
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -65,7 +65,7 @@ module OpenStax
65
65
  else
66
66
 
67
67
  # Local search
68
- accounts = OpenStax::Accounts::Account.scoped
68
+ accounts = OpenStax::Accounts::Account.all
69
69
 
70
70
  KeywordSearch.search(query) do |with|
71
71
 
@@ -73,7 +73,7 @@ module OpenStax
73
73
 
74
74
  with.keyword :username do |usernames|
75
75
  accounts = accounts.where{username
76
- .like_any my{prep_usernames(usernames)}}
76
+ .like_any my{prep_names(usernames)}}
77
77
  end
78
78
 
79
79
  with.keyword :first_name do |first_names|
@@ -103,7 +103,7 @@ module OpenStax
103
103
  end
104
104
 
105
105
  with.keyword :email do |emails|
106
- accounts = OpenStax::Accounts::Account.where('0=1')
106
+ accounts = OpenStax::Accounts::Account.none
107
107
  end
108
108
 
109
109
  # Rerun the queries above for 'any' terms (which are ones without a
@@ -113,7 +113,7 @@ module OpenStax
113
113
  names = prep_names(terms)
114
114
 
115
115
  accounts = accounts.where{
116
- ( lower(username).like_any my{prep_usernames(terms)}) |
116
+ (lower(username).like_any names) |
117
117
  (lower(first_name).like_any names) |
118
118
  (lower(last_name).like_any names) |
119
119
  (lower(full_name).like_any names) |
@@ -175,21 +175,14 @@ module OpenStax
175
175
  # Return no results if query exceeds maximum allowed number of matches
176
176
  max_accounts = options[:max_matching_accounts] || \
177
177
  OpenStax::Accounts.configuration.max_matching_accounts
178
- outputs[:accounts] = OpenStax::Accounts::Account.where('0=1') \
178
+ outputs[:accounts] = OpenStax::Accounts::Account.none \
179
179
  if outputs[:num_matching_accounts] > max_accounts
180
180
 
181
181
  end
182
182
 
183
- # Downcase, and put a wildcard at the end.
184
- # For the moment don't exclude characters.
183
+ # Downcase, remove all wildcards and put a wildcard at the end.
185
184
  def prep_names(names)
186
- names.collect{|name| name.downcase + '%'}
187
- end
188
-
189
- def prep_usernames(usernames)
190
- usernames.collect{|username| username.gsub(
191
- OpenStax::Accounts::Account::USERNAME_DISCARDED_CHAR_REGEX, '')
192
- .downcase + '%'}
185
+ names.collect{|name| "#{name.downcase.gsub('%', '')}%"}
193
186
  end
194
187
 
195
188
  end
@@ -16,28 +16,42 @@ module OpenStax
16
16
 
17
17
  def exec(options={})
18
18
 
19
- return if OpenStax::Accounts.configuration.enable_stubbing?
19
+ begin
20
+ OpenStax::Accounts.syncing = true
20
21
 
21
- response = OpenStax::Accounts.get_application_account_updates
22
+ return if OpenStax::Accounts.configuration.enable_stubbing?
22
23
 
23
- app_accounts = []
24
- app_accounts_rep = OpenStax::Accounts::Api::V1::ApplicationAccountsRepresenter
24
+ response = OpenStax::Accounts.get_application_account_updates
25
+
26
+ app_accounts = []
27
+ app_accounts_rep = OpenStax::Accounts::Api::V1::ApplicationAccountsRepresenter
25
28
  .new(app_accounts)
26
- app_accounts_rep.from_json(response.body)
27
-
28
- return if app_accounts.empty?
29
-
30
- app_accounts_hash = {}
31
- app_accounts.each do |app_account|
32
- account = OpenStax::Accounts::Account.where(
33
- :openstax_uid => app_account.account.openstax_uid).first
34
- account.syncing_with_accounts = true
35
- next unless account.update_attributes(
36
- app_account.account.attributes.slice(*SYNC_ATTRIBUTES))
37
- app_accounts_hash[app_account.id] = app_account.unread_updates
38
- end
29
+ app_accounts_rep.from_json(response.body)
30
+
31
+ return if app_accounts.empty?
32
+
33
+ updated_app_accounts = []
34
+ app_accounts.each do |app_account|
35
+ account = OpenStax::Accounts::Account.where(
36
+ :openstax_uid => app_account.account.openstax_uid).first ||\
37
+ app_account.account
39
38
 
40
- OpenStax::Accounts.mark_updates_as_read(app_accounts_hash)
39
+ if account != app_account.account
40
+ SYNC_ATTRIBUTES.each do |attribute|
41
+ account.send("#{attribute}=", app_account.account.send(attribute))
42
+ end
43
+ end
44
+
45
+ next unless account.save
46
+
47
+ updated_app_accounts << {user_id: account.openstax_uid,
48
+ read_updates: app_account.unread_updates}
49
+ end
50
+
51
+ OpenStax::Accounts.mark_account_updates_as_read(updated_app_accounts)
52
+ ensure
53
+ OpenStax::Accounts.syncing = false
54
+ end
41
55
 
42
56
  end
43
57
 
@@ -0,0 +1,61 @@
1
+ # Routine for getting group updates from the Accounts server
2
+ #
3
+ # Should be scheduled to run regularly
4
+
5
+ module OpenStax
6
+ module Accounts
7
+
8
+ class SyncGroups
9
+
10
+ SYNC_ATTRIBUTES = ['name', 'is_public', 'group_members',
11
+ 'group_owners', 'member_group_nestings',
12
+ 'cached_supertree_group_ids', 'cached_subtree_group_ids']
13
+
14
+ lev_routine transaction: :no_transaction
15
+
16
+ protected
17
+
18
+ def exec(options={})
19
+
20
+ begin
21
+ OpenStax::Accounts.syncing = true
22
+
23
+ return if OpenStax::Accounts.configuration.enable_stubbing?
24
+
25
+ response = OpenStax::Accounts.get_application_group_updates
26
+
27
+ app_groups = []
28
+ app_groups_rep = OpenStax::Accounts::Api::V1::ApplicationGroupsRepresenter
29
+ .new(app_groups)
30
+ app_groups_rep.from_json(response.body)
31
+
32
+ return if app_groups.empty?
33
+
34
+ updated_app_groups = []
35
+ app_groups.each do |app_group|
36
+ group = OpenStax::Accounts::Group.where(
37
+ :openstax_uid => app_group.group.openstax_uid).first || app_group.group
38
+
39
+ if group != app_group.group
40
+ SYNC_ATTRIBUTES.each do |attribute|
41
+ group.send("#{attribute}=", app_group.group.send(attribute))
42
+ end
43
+ end
44
+
45
+ next unless group.save
46
+
47
+ updated_app_groups << {group_id: group.openstax_uid,
48
+ read_updates: app_group.unread_updates}
49
+ end
50
+
51
+ OpenStax::Accounts.mark_group_updates_as_read(updated_app_groups)
52
+ ensure
53
+ OpenStax::Accounts.syncing = false
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ # Routine for updating group caches when a group_nesting is created or destroyed
2
+ #
3
+ # Caller provides the group_nesting object
4
+
5
+ module OpenStax
6
+ module Accounts
7
+
8
+ class UpdateGroupCaches
9
+
10
+ # This transaction needs :repeatable_read to prevent missed updates
11
+ lev_routine transaction: :repeatable_read
12
+
13
+ protected
14
+
15
+ def exec(group_nesting)
16
+ subtree_group_ids = group_nesting.member_group.subtree_group_ids
17
+ supertree_group_ids = group_nesting.container_group.supertree_group_ids
18
+ tree_group_ids = (subtree_group_ids + supertree_group_ids).uniq
19
+
20
+ Group.where(id: subtree_group_ids).update_all(cached_supertree_group_ids: nil)
21
+ Group.where(id: supertree_group_ids).update_all(cached_subtree_group_ids: nil)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -1,7 +1,6 @@
1
1
  <%
2
2
  # Clients of this partial can override the following variables:
3
3
  search_action_path ||= nil
4
- results_list_id ||= 'search-results-list'
5
4
  method ||= :post
6
5
  remote ||= false
7
6
  form_html ||= {id: 'search-form',
@@ -23,5 +22,3 @@
23
22
  <%= f.submit 'Search', class: 'btn btn-primary' %>
24
23
 
25
24
  <% end %>
26
-
27
- <div id=<%= results_list_id %>></div>
@@ -6,7 +6,7 @@ ActionInterceptor.configure do
6
6
  return if account && !account.is_anonymous?
7
7
 
8
8
  respond_to do |format|
9
- format.html { redirect_to registration_path }
9
+ format.html { redirect_to openstax_accounts.login_url }
10
10
  format.json { head(:forbidden) }
11
11
  end
12
12
  end
@@ -0,0 +1,16 @@
1
+ class CreateOpenStaxAccountsGroups < ActiveRecord::Migration
2
+ def change
3
+ create_table :openstax_accounts_groups do |t|
4
+ t.integer :openstax_uid, :null => false
5
+ t.boolean :is_public, null: false, default: false
6
+ t.string :name
7
+ t.text :cached_subtree_group_ids
8
+ t.text :cached_supertree_group_ids
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :openstax_accounts_groups, :openstax_uid, :unique => true
14
+ add_index :openstax_accounts_groups, :is_public
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class CreateOpenStaxAccountsGroupMembers < ActiveRecord::Migration
2
+ def change
3
+ create_table :openstax_accounts_group_members do |t|
4
+ t.references :group, null: false
5
+ t.references :user, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :openstax_accounts_group_members, [:group_id, :user_id], unique: true
11
+ add_index :openstax_accounts_group_members, :user_id
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateOpenStaxAccountsGroupOwners < ActiveRecord::Migration
2
+ def change
3
+ create_table :openstax_accounts_group_owners do |t|
4
+ t.references :group, null: false
5
+ t.references :user, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :openstax_accounts_group_owners, [:group_id, :user_id], unique: true
11
+ add_index :openstax_accounts_group_owners, :user_id
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateOpenStaxAccountsGroupNestings < ActiveRecord::Migration
2
+ def change
3
+ create_table :openstax_accounts_group_nestings do |t|
4
+ t.references :member_group, null: false
5
+ t.references :container_group, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :openstax_accounts_group_nestings, :member_group_id, unique: true
11
+ add_index :openstax_accounts_group_nestings, :container_group_id
12
+ end
13
+ end
@@ -1,3 +1,4 @@
1
1
  every 5.minutes do
2
2
  runner "OpenStax::Accounts::SyncAccounts.call"
3
+ runner "OpenStax::Accounts::SyncGroups.call"
3
4
  end
@@ -50,7 +50,7 @@ module OpenStax
50
50
  @cookies.delete(:secure_account_id)
51
51
  else
52
52
  @session[:account_id] = account.id
53
- @cookies.signed[:secure_account_id] = { secure: true,
53
+ @cookies.encrypted[:secure_account_id] = { secure: true,
54
54
  httponly: true, value: "secure#{account.id}" }
55
55
  end
56
56
  end
@@ -64,7 +64,7 @@ module OpenStax
64
64
  # and the signed secure ID doesn't match the unsecure ID
65
65
  # http://railscasts.com/episodes/356-dangers-of-session-hijacking
66
66
  if @request.ssl? && \
67
- @cookies.signed[:secure_account_id] != "secure#{@session[:account_id]}"
67
+ @cookies.encrypted[:secure_account_id] != "secure#{@session[:account_id]}"
68
68
  sign_out!
69
69
  return
70
70
  end
@@ -0,0 +1,45 @@
1
+ module OpenStax
2
+ module Accounts
3
+ module HasManyThroughGroups
4
+ module ActiveRecord
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def has_many_through_groups(groups_name, name, options = {})
11
+ options = {class_name: name.to_s.classify}.merge(options)
12
+ association_name = "direct_#{name.to_s}".to_sym
13
+
14
+ OpenStax::Accounts::Group.class_exec do
15
+ has_many association_name, options
16
+
17
+ define_method(name) do
18
+ OpenStax::Accounts::Group.includes(association_name)
19
+ .where(id: supertree_group_ids)
20
+ .collect{|g| g.send(association_name).to_a}.flatten.uniq
21
+ end
22
+ end
23
+
24
+ class_exec do
25
+ has_many association_name, options if options[:as]
26
+
27
+ define_method(name) do
28
+ direct_records = respond_to?(association_name) ? \
29
+ send(association_name).to_a : []
30
+ indirect_records = OpenStax::Accounts::Group
31
+ .includes(association_name).where(
32
+ id: send(groups_name).collect{|g| g.supertree_group_ids}.flatten.uniq
33
+ )
34
+ .collect{|g| g.send(association_name).to_a}
35
+ (direct_records + indirect_records).flatten.uniq
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveRecord::Base.send :include, OpenStax::Accounts::HasManyThroughGroups::ActiveRecord