user_plane 0.0.15

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 (205) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +30 -0
  4. data/app/assets/javascripts/user_plane/application.js +13 -0
  5. data/app/assets/stylesheets/user_plane/application.css +15 -0
  6. data/app/concerns/null_object_persistable.rb +62 -0
  7. data/app/controllers/user/confirm_email_addresses_controller.rb +17 -0
  8. data/app/controllers/user/details_controller.rb +48 -0
  9. data/app/controllers/user/invites_controller.rb +69 -0
  10. data/app/controllers/user/reset_passwords_controller.rb +50 -0
  11. data/app/controllers/user/sign_ins_controller.rb +53 -0
  12. data/app/controllers/user/sign_ups_controller.rb +47 -0
  13. data/app/controllers/user_plane/application_controller.rb +5 -0
  14. data/app/helpers/user_plane/application_helper.rb +4 -0
  15. data/app/mailers/user_plane/application_mailer.rb +8 -0
  16. data/app/mailers/user_plane/invite_mailer.rb +14 -0
  17. data/app/mailers/user_plane/verification_mailer.rb +25 -0
  18. data/app/models/session_manager.rb +100 -0
  19. data/app/models/user.rb +5 -0
  20. data/app/models/user/account.rb +26 -0
  21. data/app/models/user/confirm_email_address.rb +55 -0
  22. data/app/models/user/guest.rb +18 -0
  23. data/app/models/user/identities.rb +5 -0
  24. data/app/models/user/identities/email.rb +98 -0
  25. data/app/models/user/identities/email_verification.rb +70 -0
  26. data/app/models/user/identities/facebook.rb +5 -0
  27. data/app/models/user/identities/github.rb +5 -0
  28. data/app/models/user/identities/id_token.rb +7 -0
  29. data/app/models/user/identities/o_auth.rb +67 -0
  30. data/app/models/user/identities/o_auth_endpoint.rb +28 -0
  31. data/app/models/user/identities/twitter.rb +5 -0
  32. data/app/models/user/identity.rb +26 -0
  33. data/app/models/user/reset_password.rb +59 -0
  34. data/app/models/user/send_password_reset.rb +25 -0
  35. data/app/models/user/send_sign_up_invite.rb +27 -0
  36. data/app/models/user/sign_in.rb +42 -0
  37. data/app/models/user/sign_up.rb +47 -0
  38. data/app/models/user/sign_up_invites.rb +5 -0
  39. data/app/models/user/sign_up_invites/invite.rb +46 -0
  40. data/app/models/user/sign_up_invites/stack.rb +22 -0
  41. data/app/models/user/sign_up_with_invite.rb +45 -0
  42. data/app/models/user/suspension.rb +7 -0
  43. data/app/models/user/update_details.rb +103 -0
  44. data/app/views/layouts/user_plane/application.html.erb +14 -0
  45. data/app/views/user/details/edit.html.erb +37 -0
  46. data/app/views/user/invites/edit.html.erb +34 -0
  47. data/app/views/user/invites/new.html.erb +21 -0
  48. data/app/views/user/reset_passwords/edit.html.erb +26 -0
  49. data/app/views/user/reset_passwords/new.html.erb +21 -0
  50. data/app/views/user/sign_ins/new.html.erb +25 -0
  51. data/app/views/user/sign_ups/new.html.erb +33 -0
  52. data/app/views/user_plane/invite_mailer/invite.html.erb +2 -0
  53. data/app/views/user_plane/invite_mailer/invite.text.erb +3 -0
  54. data/app/views/user_plane/verification_mailer/address_verification.html.erb +2 -0
  55. data/app/views/user_plane/verification_mailer/address_verification.text.erb +4 -0
  56. data/app/views/user_plane/verification_mailer/password_reset.html.erb +2 -0
  57. data/app/views/user_plane/verification_mailer/password_reset.text.erb +4 -0
  58. data/config/initializers/inflections.rb +3 -0
  59. data/config/locales/en.yml +63 -0
  60. data/config/locales/it.yml +62 -0
  61. data/config/routes.rb +5 -0
  62. data/db/migrate/20121128143404_create_user_accounts.rb +11 -0
  63. data/db/migrate/20121226202553_create_user_identities_o_auths.rb +12 -0
  64. data/db/migrate/20121226203032_create_user_identities_emails.rb +13 -0
  65. data/db/migrate/20121227144617_create_user_identities_email_verifications.rb +15 -0
  66. data/db/migrate/20130113120152_create_user_identities_id_tokens.rb +12 -0
  67. data/db/migrate/20141025230304_create_user_sign_up_invites_stacks.rb +10 -0
  68. data/db/migrate/20141025230500_create_user_sign_up_invites_invites.rb +13 -0
  69. data/db/migrate/20141026230208_create_user_suspensions.rb +13 -0
  70. data/lib/generators/user_plane/view/details_generator.rb +22 -0
  71. data/lib/generators/user_plane/view/helpers.rb +68 -0
  72. data/lib/generators/user_plane/view/invites_generator.rb +23 -0
  73. data/lib/generators/user_plane/view/reset_passwords_generator.rb +22 -0
  74. data/lib/generators/user_plane/view/sign_ins_generator.rb +18 -0
  75. data/lib/generators/user_plane/view/sign_ups_generator.rb +22 -0
  76. data/lib/generators/user_plane/views_generator.rb +32 -0
  77. data/lib/tasks/user_plane_tasks.rake +4 -0
  78. data/lib/user_plane.rb +43 -0
  79. data/lib/user_plane/command.rb +24 -0
  80. data/lib/user_plane/engine.rb +27 -0
  81. data/lib/user_plane/fresh_validator.rb +9 -0
  82. data/lib/user_plane/omniauth.rb +50 -0
  83. data/lib/user_plane/redirect_to_sign_in.rb +22 -0
  84. data/lib/user_plane/route_concerns.rb +167 -0
  85. data/lib/user_plane/session_manager_concern.rb +9 -0
  86. data/lib/user_plane/signed_in_constraint.rb +11 -0
  87. data/lib/user_plane/token_segment.rb +52 -0
  88. data/lib/user_plane/version.rb +3 -0
  89. data/spec/controllers/user/confirm_email_addresses_controller_spec.rb +5 -0
  90. data/spec/controllers/user/details_controller_spec.rb +5 -0
  91. data/spec/controllers/user/invites_controller_spec.rb +19 -0
  92. data/spec/controllers/user/reset_passwords_controller_spec.rb +5 -0
  93. data/spec/controllers/user/sign_ins_controller_spec.rb +34 -0
  94. data/spec/controllers/user/sign_ups_controller_spec.rb +5 -0
  95. data/spec/dummy/README.rdoc +28 -0
  96. data/spec/dummy/Rakefile +6 -0
  97. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  98. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  99. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  100. data/spec/dummy/app/controllers/welcome_controller.rb +3 -0
  101. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  102. data/spec/dummy/app/views/layouts/application.html.erb +30 -0
  103. data/spec/dummy/app/views/welcome/index.html.erb +1 -0
  104. data/spec/dummy/bin/bundle +3 -0
  105. data/spec/dummy/bin/rails +4 -0
  106. data/spec/dummy/bin/rake +4 -0
  107. data/spec/dummy/config.ru +4 -0
  108. data/spec/dummy/config/application.rb +30 -0
  109. data/spec/dummy/config/boot.rb +5 -0
  110. data/spec/dummy/config/database.yml +49 -0
  111. data/spec/dummy/config/environment.rb +5 -0
  112. data/spec/dummy/config/environments/development.rb +37 -0
  113. data/spec/dummy/config/environments/production.rb +78 -0
  114. data/spec/dummy/config/environments/test.rb +39 -0
  115. data/spec/dummy/config/initializers/assets.rb +8 -0
  116. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  117. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  118. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  119. data/spec/dummy/config/initializers/inflections.rb +16 -0
  120. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  121. data/spec/dummy/config/initializers/session_store.rb +3 -0
  122. data/spec/dummy/config/initializers/user_plane.rb +5 -0
  123. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  124. data/spec/dummy/config/locales/en.yml +23 -0
  125. data/spec/dummy/config/routes.rb +43 -0
  126. data/spec/dummy/config/secrets.yml +22 -0
  127. data/spec/dummy/db/schema.rb +101 -0
  128. data/spec/dummy/log/development.log +0 -0
  129. data/spec/dummy/log/test.log +20185 -0
  130. data/spec/dummy/public/404.html +67 -0
  131. data/spec/dummy/public/422.html +67 -0
  132. data/spec/dummy/public/500.html +66 -0
  133. data/spec/dummy/public/favicon.ico +0 -0
  134. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/-pOuxJZhYk_qXqMNKgm23KfvzyUW71NynNLlcNBOubE.cache +0 -0
  135. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/3AV9ywHBH56Leqey5LeznxK9vu4HD8fF3zSTk4MiDJA.cache +1 -0
  136. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/5fzR1G0D8ukHkPkLXsUu6rP6qV82aIdx3hugKkDy6nM.cache +0 -0
  137. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/9bKtJ2lkHPqtboGfbyknZ1OyH4xYO-aml7U3qhv-3kk.cache +0 -0
  138. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/HjDmE9SFP2wimdNHU8Nff9cm3vFZ5soO1iw7Jdlb6z8.cache +0 -0
  139. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/J9j6OdatarYW7VzVCVttmGphOhJKL0QXasdheyrgsTE.cache +2 -0
  140. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/KqrOQSlg0Th0N3XXx-h4p5BVJCfN0D8rRLoA9VxvXrc.cache +1 -0
  141. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UCB1W65KwVU8ttOY8jnPRDp8HyyYYEjeTwwPD6R4qy8.cache +1 -0
  142. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/YmMSaaBmIcNZWPVF9jXcGBi-kwEzMuxzwPT_Zrcj1Bo.cache +2 -0
  143. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/g3wU8ajFWb5ZLPvujEt5l9DesbFCiAwqjx1WQgwTtHA.cache +1 -0
  144. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i5pp88VHKoqlxQJdgmQd_lkgX1-4em_uHqNDjQ4nyHA.cache +0 -0
  145. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jL74yXjxf8cb6Olkjbw1C28MH_HbZe221l8AI6WVeH0.cache +3 -0
  146. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/puh7X4rfS3eDN9oHTXoQdAgqxivonrwAAdYZ4UB3GIg.cache +1 -0
  147. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/qxPWFIWnE6gOCY-SsdBJe7Cgm5D3YUwaEne78Y7XdRg.cache +1 -0
  148. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rM9s67WgzKMZ1bRhUdA0yhPZDlyRE5a1kmdt7cS6m4c.cache +3 -0
  149. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rVoG8EHlrCOgY4ZkzOj64f0jiTcbteQ_SYNzq9RqY0I.cache +0 -0
  150. data/spec/fabricators/user_account_fabricator.rb +11 -0
  151. data/spec/fabricators/user_guest_fabricator.rb +2 -0
  152. data/spec/fabricators/user_identities_email_fabricator.rb +8 -0
  153. data/spec/fabricators/user_identities_email_verification_fabricator.rb +5 -0
  154. data/spec/fabricators/user_identities_id_token_fabricator.rb +2 -0
  155. data/spec/fabricators/user_sign_up_fabricator.rb +51 -0
  156. data/spec/fabricators/user_sign_up_invites_invite_fabricator.rb +3 -0
  157. data/spec/fabricators/user_sign_up_invites_stack_fabricator.rb +4 -0
  158. data/spec/fabricators/user_suspension_fabricator.rb +4 -0
  159. data/spec/fabricators/user_update_detail_fabricator.rb +2 -0
  160. data/spec/features/user_plane/user_plane_invites_spec.rb +31 -0
  161. data/spec/features/user_plane/user_plane_reset_passwords_spec.rb +31 -0
  162. data/spec/features/user_plane/user_plane_sign_ins_spec.rb +44 -0
  163. data/spec/features/user_plane/user_plane_signed_in_only_spec.rb +31 -0
  164. data/spec/features/user_plane/user_plane_update_details_spec.rb +43 -0
  165. data/spec/fixtures/user_plane/invite_mailer/invite +3 -0
  166. data/spec/fixtures/user_plane/verification_mailer/address_verification +3 -0
  167. data/spec/fixtures/user_plane/verification_mailer/password_reset +3 -0
  168. data/spec/lib/generators/views_generator_spec.rb +16 -0
  169. data/spec/lib/route_concerns_spec.rb +54 -0
  170. data/spec/mailers/previews/user_plane/invite_mailer_preview.rb +11 -0
  171. data/spec/mailers/previews/user_plane/verification_mailer_preview.rb +16 -0
  172. data/spec/mailers/user_plane/invite_mailer_spec.rb +25 -0
  173. data/spec/mailers/user_plane/verification_mailer_spec.rb +52 -0
  174. data/spec/models/session_manager_spec.rb +28 -0
  175. data/spec/models/user/account_spec.rb +26 -0
  176. data/spec/models/user/confirm_email_address_spec.rb +101 -0
  177. data/spec/models/user/guest_spec.rb +5 -0
  178. data/spec/models/user/identities/email_spec.rb +5 -0
  179. data/spec/models/user/identities/email_verification_spec.rb +42 -0
  180. data/spec/models/user/identities/facebook_spec.rb +5 -0
  181. data/spec/models/user/identities/github_spec.rb +5 -0
  182. data/spec/models/user/identities/id_token_spec.rb +5 -0
  183. data/spec/models/user/identities/o_auth_spec.rb +12 -0
  184. data/spec/models/user/identities/twitter_spec.rb +5 -0
  185. data/spec/models/user/reset_password_spec.rb +141 -0
  186. data/spec/models/user/send_password_reset_spec.rb +44 -0
  187. data/spec/models/user/send_sign_up_invite_spec.rb +30 -0
  188. data/spec/models/user/sign_in_spec.rb +31 -0
  189. data/spec/models/user/sign_up_invites/invite_spec.rb +13 -0
  190. data/spec/models/user/sign_up_invites/stack_spec.rb +21 -0
  191. data/spec/models/user/sign_up_spec.rb +58 -0
  192. data/spec/models/user/sign_up_with_invite_spec.rb +83 -0
  193. data/spec/models/user/suspension_spec.rb +5 -0
  194. data/spec/models/user/update_details_spec.rb +98 -0
  195. data/spec/routing/invites_spec.rb +49 -0
  196. data/spec/routing/reset_passwords_spec.rb +31 -0
  197. data/spec/routing/sign_ins_spec.rb +36 -0
  198. data/spec/routing/update_details_spec.rb +30 -0
  199. data/spec/shared_contexts/feature_helpers.rb +12 -0
  200. data/spec/shared_contexts/routing.rb +8 -0
  201. data/spec/shared_contexts/user.rb +67 -0
  202. data/spec/spec_helper.rb +38 -0
  203. data/spec/support/fabrication.rb +7 -0
  204. data/spec/support/omniauth.rb +4 -0
  205. metadata +770 -0
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Facebook < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Github < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class User::Identities::IdToken < ActiveRecord::Base
2
+ include TokenSegment
3
+
4
+ belongs_to :identity, polymorphic: true
5
+ has_token :key
6
+
7
+ end
@@ -0,0 +1,67 @@
1
+ module User
2
+ module Identities
3
+
4
+ # The class methods have been moved into a separate module so that they
5
+ # could be added as association extensions if necessary.
6
+ module OauthBuildCreateAndFind
7
+
8
+ def find_identity sign_in
9
+ find_from_omniauth(sign_in[:oauth_data])
10
+ end
11
+
12
+ def build_identity sign_up
13
+ identity = initialize_from_omniauth(sign_up[:oauth_data])
14
+ sign_up.account.oauth_identities << identity
15
+
16
+ identity
17
+ end
18
+
19
+ def initialize_from_omniauth ominauth_data
20
+ identity_provider = provider_from_ominauth(ominauth_data)
21
+ identity_provider.new(ominauth_data: ominauth_data)
22
+ end
23
+
24
+ def find_from_omniauth ominauth_data
25
+ identity_provider = provider_from_ominauth(ominauth_data)
26
+ identity_provider.find_by(uid: ominauth_data[:uid])
27
+ end
28
+
29
+ def find_or_build_from_omniauth ominauth_data
30
+ find_from_omniauth(ominauth_data) || initialize_from_omniauth(ominauth_data)
31
+ end
32
+
33
+ private
34
+ def provider_from_ominauth ominauth_data
35
+ User::Identities.const_get(ominauth_data[:provider].camelize)
36
+ end
37
+ end
38
+
39
+ class OAuth < Identity
40
+ # Base class for omniauth-based user authentication
41
+
42
+ self.inheritance_column = :provider
43
+ include SupportSegment::StiHelpers
44
+ extend OauthBuildCreateAndFind
45
+
46
+ belongs_to :account, class_name: 'User::Account'
47
+ has_one :id_token, as: :identity
48
+
49
+ validates :uid, presence: true,
50
+ uniqueness: {scope: :provider}
51
+
52
+ def self.callback
53
+ OAuthCallback.new(self.name.underscore.to_sym)
54
+ end
55
+
56
+ before_validation do
57
+ self.build_id_token
58
+ end
59
+
60
+ def ominauth_data= new_ominauth_data
61
+ self.uid = new_ominauth_data[:uid]
62
+ self.handle = new_ominauth_data[:info][:name]
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ module User::Identities
2
+ class OAuthEndpoint
3
+ extend ActiveModel::Naming
4
+
5
+ attr_accessor :provider
6
+
7
+ def initialize provider
8
+ @provider = provider
9
+ end
10
+
11
+ def to_param
12
+ provider
13
+ end
14
+
15
+ def to_model
16
+ self
17
+ end
18
+
19
+ def persisted?
20
+ true
21
+ end
22
+
23
+ def self.model_name
24
+ ActiveModel::Name.new(self, nil, "OAuthEndpoint")
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module User::Identities
2
+ class Twitter < OAuth
3
+
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ module User
2
+ class Identity < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ def serialize
6
+ # TODO: create the token when it's missing
7
+ id_token.key
8
+ end
9
+
10
+ def self.deserialize id_token
11
+ token = Identities::IdToken.find_by_key(id_token)
12
+ token ? token.identity : nil
13
+ end
14
+
15
+ def self.deserialize! id_token
16
+ identity = deserialize(id_token)
17
+ if identity
18
+ raise User::AccountSuspended unless identity.account.suspensions.empty?
19
+ identity
20
+ else
21
+ raise ActiveRecord::RecordNotFound
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ module User
2
+ class ResetPassword < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :password
6
+ attribute :password_confirmation
7
+
8
+ attr_accessor :code
9
+ attr_accessor :verification
10
+ attr_accessor :identity
11
+
12
+ validates :password, :password_confirmation, presence: true
13
+ validate {|r| r.errors.add(:code, 'is not valid') unless r.verification}
14
+ validates :verification, presence: true,
15
+ receiver: {map_attributes: {created_at: :code,
16
+ base: :code,
17
+ spent_at: :code}}
18
+ validates :identity, receiver: {map_attributes: {password: :password,
19
+ password_confirmation: :password_confirmation}}
20
+
21
+ def to_param
22
+ self.code
23
+ end
24
+
25
+ def persisted?
26
+ verification && verification.persisted?
27
+ end
28
+
29
+ def code= token
30
+ @code = token
31
+
32
+ password_reset_query = User::Identities::EmailVerification.password_reset.where(token: code)
33
+ if @identity = User::Identities::Email.joins(:verifications).merge(password_reset_query).first
34
+ @verification = identity.verifications.detect {|v| v.token == code}
35
+ else
36
+ raise ActiveRecord::RecordNotFound
37
+ end
38
+ end
39
+
40
+ def verification= verification
41
+ return unless verification
42
+ @code = verification.token
43
+ @verification = verification
44
+ @identity = verification.email
45
+ end
46
+
47
+ before_validation do
48
+ if verification
49
+ identity.attributes = {password: password,
50
+ password_confirmation: password_confirmation}
51
+ verification.spend
52
+ end
53
+ end
54
+
55
+ action do
56
+ identity.save
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ module User
2
+ class SendPasswordReset < UserPlane::Command
3
+ attribute :email
4
+ attr_accessor :code
5
+ attr_accessor :identity
6
+ attr_accessor :verification
7
+
8
+ def email= address
9
+ @identity = User::Identities::Email.find_by_address(address)
10
+ @email = address
11
+ end
12
+
13
+ def persisited?
14
+ verification ? verification.persisted? : false
15
+ end
16
+
17
+ action do
18
+ if identity
19
+ @verification ||= identity.reset_password!
20
+ @code ||= verification.token
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module User
2
+ class SendSignUpInvite < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :recipient
6
+ attribute :invite
7
+ attribute :stack
8
+ attribute :sender
9
+
10
+ validates :invite, receiver: {map_attributes: {recipient: :recipient}}
11
+ validates :stack, receiver: {map_attributes: {remaining_invites: :remaining_invites}}
12
+
13
+ before_validation do
14
+ # The stack can be provided directly if other models have a stack of invites
15
+ @stack ||= sender.invites_stack
16
+ @invite ||= stack.invites.build(recipient: recipient, sender: sender)
17
+ end
18
+
19
+ def persisited?
20
+ invite ? invite.persisted? : false
21
+ end
22
+
23
+ action do
24
+ invite.save
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module User
2
+ class SignIn < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ model_name.instance_variable_set(:@route_key, 'user_sign_in')
6
+
7
+ attribute :email
8
+ attribute :password
9
+ attribute :oauth_data
10
+ attribute :strategy
11
+ attr_accessor :ominauth_error
12
+
13
+ attribute :identity
14
+
15
+ validates :ominauth_error, absence: true
16
+
17
+
18
+ validate do |command|
19
+ if identity = command.identity
20
+ unless identity.account.suspensions.empty?
21
+ command.errors.add(:base, :suspended)
22
+ end
23
+ elsif strategy = command.strategy
24
+ sign_in_error = :"unknown_#{strategy.model_name.singular}"
25
+ command.errors.add(:base, sign_in_error)
26
+ else
27
+ raise "Please choose a strategy to perform the sign in"
28
+ end
29
+ end
30
+
31
+ def sign_in_with strategy
32
+ @strategy = strategy
33
+ @identity = strategy.find_identity(self) if identity.nil?
34
+ self
35
+ end
36
+
37
+ action do
38
+ @identity
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module User
2
+ class SignUp < UserPlane::Command
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attribute :user_name
6
+
7
+ attribute :email
8
+ attribute :password
9
+ attribute :password_confirmation
10
+
11
+ attribute :account
12
+ attribute :identity
13
+
14
+ attribute :oauth_data
15
+
16
+ # Validate command receivers
17
+ validates :identity, receiver: {map_attributes: {address: :email,
18
+ password: :password,
19
+ password_confirmation: :password_confirmation,
20
+ uid: :account}}
21
+ validates :account, receiver: {map_attributes: {name: :user_name,
22
+ identities: :email_or_omniauth}}
23
+
24
+ def sign_up_with sign_up_strategy
25
+ @identity = sign_up_strategy.build_identity(self) if identity.nil?
26
+ self
27
+ end
28
+
29
+ def account
30
+ @account ||= new_account
31
+ end
32
+
33
+ action do
34
+ ActiveRecord::Base.transaction do
35
+ raise ActiveRecord::Rollback unless account.save
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def new_account
42
+ Account.new(name: user_name)
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,5 @@
1
+ module User::SignUpInvites
2
+ def self.table_name_prefix
3
+ 'user_sign_up_invites_'
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ module User::SignUpInvites
2
+ class Invite < ActiveRecord::Base
3
+ include TokenSegment
4
+
5
+
6
+ belongs_to :stack
7
+ belongs_to :sender, class_name: 'User::Account'
8
+ belongs_to :account, class_name: 'User::Account'
9
+ has_token :code
10
+
11
+ validates :recipient, email: true
12
+
13
+ # Once an invite has been associated with an account it cannot be reused
14
+ validate on: :update do |r|
15
+ if account_id_changed?
16
+ previous, current = account_id_change
17
+ r.errors.add(:base, :redeemed) unless previous.nil?
18
+ end
19
+ end
20
+
21
+ # Returns a Null invite
22
+ def self.find_by_code code
23
+ find_by(code: code) || Null.new(code)
24
+ end
25
+
26
+ end
27
+
28
+ # A Null invite is an invite-like object created when the invite code is not
29
+ # valid. It will
30
+ Null = Struct.new(:code) do
31
+ include ActiveModel::Validations
32
+ include NullObjectPersistable
33
+
34
+ mimics_persistence_from Invite
35
+ validate {|r| r.errors.add(:base, :invalid) }
36
+
37
+ def method_missing method_name, *arguments
38
+ return nil if [:stack, :sender, :recipient].include? method_name
39
+ super
40
+ end
41
+
42
+ def persisted?
43
+ true
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ class User::SignUpInvites::Stack < ActiveRecord::Base
2
+ belongs_to :owner, polymorphic: true
3
+ has_many :invites, before_add: :decrement_remaining_invites
4
+ after_initialize :set_remaining_invites
5
+
6
+ # FIXME: the validation above should be enough, but doesn't seem to take effect
7
+ # validates :remaining_invites, numericality: {greater_than_or_equal: 0}
8
+ validate do |record|
9
+ record.errors.add(:remaining_invites, :greater_than_or_equal, value: 0) unless (record.remaining_invites >= 0)
10
+ end
11
+
12
+ private
13
+
14
+ def decrement_remaining_invites invite
15
+ self.remaining_invites -= 1
16
+ end
17
+
18
+ def set_remaining_invites
19
+ self.remaining_invites ||= 0
20
+ end
21
+
22
+ end
@@ -0,0 +1,45 @@
1
+ # set_callback :invite_set, :after, :set_email_from_invite
2
+ module User
3
+ class SignUpWithInvite < SignUp
4
+ attribute :invite
5
+ attr_accessor :code
6
+
7
+
8
+ define_callbacks :invite_set
9
+
10
+ validates :invite, receiver: {map_attributes: {created_at: :invite,
11
+ base: :invite,
12
+ spent: :invite}}
13
+
14
+ validate do |command|
15
+ # Enforces the need of an invite if the account does not exist
16
+ if command.account.new_record?
17
+ command.errors.add_on_blank(:invite) if command.invite.nil?
18
+ else
19
+ command.errors.add(:base, :exists)
20
+ end
21
+ end
22
+
23
+ def to_param
24
+ self.code
25
+ end
26
+
27
+ def persisted?
28
+ invite && invite.persisted?
29
+ end
30
+
31
+ def code= code
32
+ self.invite = SignUpInvites::Invite.find_by_code(code)
33
+ end
34
+
35
+ def invite= invite
36
+ @code = invite.code
37
+ run_callbacks(:invite_set) {@invite = invite}
38
+ end
39
+
40
+ before_validation do
41
+ account.invite = invite
42
+ end
43
+
44
+ end
45
+ end