openstax_accounts 2.0.0 → 3.0.0

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