mno-enterprise-core 2.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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/Rakefile +12 -0
  4. data/app/assets/images/mno_enterprise/main-logo.png +0 -0
  5. data/app/controllers/mno_enterprise/application_controller.rb +116 -0
  6. data/app/helpers/mno_enterprise/application_helper.rb +67 -0
  7. data/app/helpers/mno_enterprise/impersonate_helper.rb +27 -0
  8. data/app/models/mno_enterprise/ability.rb +6 -0
  9. data/app/models/mno_enterprise/app.rb +72 -0
  10. data/app/models/mno_enterprise/app_instance.rb +36 -0
  11. data/app/models/mno_enterprise/app_instances_sync.rb +6 -0
  12. data/app/models/mno_enterprise/arrears_situation.rb +6 -0
  13. data/app/models/mno_enterprise/audit_event.rb +21 -0
  14. data/app/models/mno_enterprise/base_resource.rb +228 -0
  15. data/app/models/mno_enterprise/credit_card.rb +40 -0
  16. data/app/models/mno_enterprise/deletion_request.rb +35 -0
  17. data/app/models/mno_enterprise/impac/dashboard.rb +36 -0
  18. data/app/models/mno_enterprise/impac/dashboard_provisioner.rb +5 -0
  19. data/app/models/mno_enterprise/impac/kpi.rb +9 -0
  20. data/app/models/mno_enterprise/impac/widget.rb +13 -0
  21. data/app/models/mno_enterprise/invoice.rb +53 -0
  22. data/app/models/mno_enterprise/org_invite.rb +50 -0
  23. data/app/models/mno_enterprise/organization.rb +33 -0
  24. data/app/models/mno_enterprise/team.rb +50 -0
  25. data/app/models/mno_enterprise/tenant.rb +5 -0
  26. data/app/models/mno_enterprise/tenant_invoice.rb +5 -0
  27. data/app/models/mno_enterprise/user.rb +183 -0
  28. data/app/pdf/mno_enterprise/invoice_pdf.rb +516 -0
  29. data/config/initializers/audit_log.rb +5 -0
  30. data/config/locales/devise.en.yml +60 -0
  31. data/config/routes.rb +2 -0
  32. data/config/styleguide.yml +106 -0
  33. data/lib/accountingjs_serializer.rb +51 -0
  34. data/lib/devise/controllers/extension_helpers.rb +52 -0
  35. data/lib/devise/extension_routes.rb +11 -0
  36. data/lib/devise/hooks/password_expirable.rb +5 -0
  37. data/lib/devise/models/password_expirable.rb +28 -0
  38. data/lib/devise/models/remote_authenticatable.rb +48 -0
  39. data/lib/devise/strategies/remote_authenticatable.rb +44 -0
  40. data/lib/devise_extension.rb +36 -0
  41. data/lib/faraday/adapter/net_http_no_proxy.rb +19 -0
  42. data/lib/generators/mno_enterprise/database_extension/USAGE +11 -0
  43. data/lib/generators/mno_enterprise/database_extension/database_extension_generator.rb +36 -0
  44. data/lib/generators/mno_enterprise/database_extension/templates/model.rb +9 -0
  45. data/lib/generators/mno_enterprise/dummy/dummy_generator.rb +98 -0
  46. data/lib/generators/mno_enterprise/dummy/templates/rails/application.rb.erb +9 -0
  47. data/lib/generators/mno_enterprise/dummy/templates/rails/boot.rb.erb +6 -0
  48. data/lib/generators/mno_enterprise/dummy/templates/rails/database.yml +22 -0
  49. data/lib/generators/mno_enterprise/dummy/templates/rails/routes.rb +8 -0
  50. data/lib/generators/mno_enterprise/dummy/templates/rails/test-env.rb +45 -0
  51. data/lib/generators/mno_enterprise/install/install_generator.rb +140 -0
  52. data/lib/generators/mno_enterprise/install/templates/Procfile +1 -0
  53. data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +135 -0
  54. data/lib/generators/mno_enterprise/install/templates/config/mno_enterprise_styleguide.yml +104 -0
  55. data/lib/generators/mno_enterprise/install/templates/javascripts/mno_enterprise_extensions.js +7 -0
  56. data/lib/generators/mno_enterprise/install/templates/stylesheets/main.less_erb +25 -0
  57. data/lib/generators/mno_enterprise/install/templates/stylesheets/theme.less_erb +59 -0
  58. data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +337 -0
  59. data/lib/generators/mno_enterprise/install/templates/tasks/sprites.rake +14 -0
  60. data/lib/generators/mno_enterprise/puma_stack/puma_stack_generator.rb +58 -0
  61. data/lib/generators/mno_enterprise/templates/scripts/monit/app-server.conf +8 -0
  62. data/lib/generators/mno_enterprise/templates/scripts/nginx/app +51 -0
  63. data/lib/generators/mno_enterprise/templates/scripts/puma.rb +25 -0
  64. data/lib/generators/mno_enterprise/templates/scripts/setup.sh +27 -0
  65. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-hotrestart.conf +26 -0
  66. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-server.conf +34 -0
  67. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web.conf +2 -0
  68. data/lib/generators/mno_enterprise/templates/scripts/upstart/app.conf +11 -0
  69. data/lib/her_extension/her_orm_adapter.rb +54 -0
  70. data/lib/her_extension/middleware/mnoe_api_v1_parse_json.rb +54 -0
  71. data/lib/her_extension/model/associations/association.rb +61 -0
  72. data/lib/her_extension/model/associations/association_proxy.rb +34 -0
  73. data/lib/her_extension/model/associations/has_many_association.rb +115 -0
  74. data/lib/her_extension/model/attributes.rb +43 -0
  75. data/lib/her_extension/model/orm.rb +59 -0
  76. data/lib/her_extension/model/parse.rb +40 -0
  77. data/lib/her_extension/model/relation.rb +92 -0
  78. data/lib/her_extension/validations/remote_uniqueness_validation.rb +33 -0
  79. data/lib/html_processor.rb +106 -0
  80. data/lib/mandrill_client.rb +58 -0
  81. data/lib/mno-enterprise-core.rb +1 -0
  82. data/lib/mno_enterprise/concerns.rb +4 -0
  83. data/lib/mno_enterprise/concerns/controllers.rb +6 -0
  84. data/lib/mno_enterprise/concerns/controllers/angular_csrf.rb +59 -0
  85. data/lib/mno_enterprise/concerns/controllers/auth.rb +9 -0
  86. data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +187 -0
  87. data/lib/mno_enterprise/concerns/controllers/auth/passwords_controller.rb +54 -0
  88. data/lib/mno_enterprise/concerns/controllers/auth/registrations_controller.rb +136 -0
  89. data/lib/mno_enterprise/concerns/controllers/auth/sessions_controller.rb +54 -0
  90. data/lib/mno_enterprise/concerns/controllers/auth/unlocks_controller.rb +50 -0
  91. data/lib/mno_enterprise/concerns/models.rb +6 -0
  92. data/lib/mno_enterprise/concerns/models/ability.rb +108 -0
  93. data/lib/mno_enterprise/concerns/models/app_instance.rb +100 -0
  94. data/lib/mno_enterprise/concerns/models/organization.rb +102 -0
  95. data/lib/mno_enterprise/core.rb +279 -0
  96. data/lib/mno_enterprise/database_extendable.rb +57 -0
  97. data/lib/mno_enterprise/engine.rb +33 -0
  98. data/lib/mno_enterprise/testing_support/ability_test_helper.rb +10 -0
  99. data/lib/mno_enterprise/testing_support/common_rake.rb +19 -0
  100. data/lib/mno_enterprise/testing_support/factories.rb +13 -0
  101. data/lib/mno_enterprise/testing_support/factories/app_instances.rb +30 -0
  102. data/lib/mno_enterprise/testing_support/factories/apps.rb +45 -0
  103. data/lib/mno_enterprise/testing_support/factories/arrears_situation.rb +14 -0
  104. data/lib/mno_enterprise/testing_support/factories/audit_event.rb +15 -0
  105. data/lib/mno_enterprise/testing_support/factories/credit_card.rb +33 -0
  106. data/lib/mno_enterprise/testing_support/factories/deletion_request.rb +17 -0
  107. data/lib/mno_enterprise/testing_support/factories/impac/dashboards.rb +15 -0
  108. data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +20 -0
  109. data/lib/mno_enterprise/testing_support/factories/impac/widgets.rb +15 -0
  110. data/lib/mno_enterprise/testing_support/factories/invoices.rb +51 -0
  111. data/lib/mno_enterprise/testing_support/factories/org_invite.rb +24 -0
  112. data/lib/mno_enterprise/testing_support/factories/organizations.rb +25 -0
  113. data/lib/mno_enterprise/testing_support/factories/team.rb +17 -0
  114. data/lib/mno_enterprise/testing_support/factories/tenant.rb +12 -0
  115. data/lib/mno_enterprise/testing_support/factories/tenant_invoice.rb +29 -0
  116. data/lib/mno_enterprise/testing_support/factories/users.rb +48 -0
  117. data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +49 -0
  118. data/lib/mno_enterprise/testing_support/mno_enterprise_api_test_helper.rb +167 -0
  119. data/lib/mno_enterprise/testing_support/mnoe_faraday_test_adapter.rb +173 -0
  120. data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +175 -0
  121. data/lib/mno_enterprise/testing_support/user_action_shared.rb +47 -0
  122. data/lib/mno_enterprise/version.rb +3 -0
  123. data/lib/tasks/mno_enterprise_tasks.rake +22 -0
  124. data/spec/controllers/mno_enterprise/angular_csrf_spec.rb +42 -0
  125. data/spec/lib/her_extension/her_orm_adapter.rb +7 -0
  126. data/spec/lib/her_extension/model/relation_spec.rb +7 -0
  127. data/spec/lib/mandrill_client_spec.rb +64 -0
  128. data/spec/mno_enterprise_spec.rb +79 -0
  129. data/spec/models/mno_enterprise/app_instance_spec.rb +7 -0
  130. data/spec/models/mno_enterprise/app_spec.rb +62 -0
  131. data/spec/models/mno_enterprise/base_resource_spec.rb +28 -0
  132. data/spec/models/mno_enterprise/deletion_request_spec.rb +26 -0
  133. data/spec/models/mno_enterprise/invoice_spec.rb +7 -0
  134. data/spec/models/mno_enterprise/organization_spec.rb +7 -0
  135. data/spec/models/mno_enterprise/user_spec.rb +44 -0
  136. data/spec/rails_helper.rb +73 -0
  137. data/spec/spec_helper.rb +78 -0
  138. metadata +421 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3986ecdbc3ba353f17f75ca72fde9e71fdde276
4
+ data.tar.gz: c338d9efa311e9ce8cb372e764572a3f1f14ba08
5
+ SHA512:
6
+ metadata.gz: 2f8a203a4719b6e6f19e5f104b363dd50fb431c24b8e2dab49e86d2f5077d423e93b5060e33f224377f104a6c482668bc157800468797e670b537fab58caa2bb
7
+ data.tar.gz: aa56b90af90560b69a6af6f412b1cffdbd71f2765fb2705037bb43b2d2495f5a9fa0fa540dce18d9bf5a983a642f93ad05f5aaa67c4df60ec02bdfb17af764ac
data/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Copyright 2015 Maestrano Pty Ltd
@@ -0,0 +1,12 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'mno_enterprise/testing_support/common_rake'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task default: :spec
7
+
8
+ desc "Generates a dummy app for testing"
9
+ task :test_app do
10
+ ENV['LIB_NAME'] = 'mno_enterprise/core'
11
+ Rake::Task['mno_enterprise:testing:create_dummy_app'].invoke
12
+ end
@@ -0,0 +1,116 @@
1
+ module MnoEnterprise
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery
4
+ include ApplicationHelper
5
+ prepend_before_filter :skip_devise_trackable_on_xhr
6
+
7
+ before_filter :set_default_meta
8
+ before_filter :store_location
9
+ before_filter :perform_return_to
10
+
11
+ # Angular CSRF
12
+ if MnoEnterprise.include_angular_csrf
13
+ include MnoEnterprise::Concerns::Controllers::AngularCSRF
14
+ end
15
+
16
+ #============================================
17
+ # CanCan Authorization Rescue
18
+ #============================================
19
+ # Rescue the CanCan permission denied error
20
+ rescue_from CanCan::AccessDenied do |_exception|
21
+ respond_to do |format|
22
+ format.html { redirect_to main_app.root_path, alert: 'Unauthorized Action' }
23
+ format.json { render nothing: true, status: :forbidden }
24
+ end
25
+ end
26
+
27
+ def current_ability
28
+ MnoEnterprise::Ability.new(current_user)
29
+ end
30
+
31
+ def set_default_meta
32
+ @meta = {}
33
+ @meta[:title] = "Application"
34
+ @meta[:description] = "Enterprise Applications"
35
+ end
36
+
37
+ #============================================
38
+ # Devise
39
+ #============================================
40
+ protected
41
+
42
+ # Do not updated devise last access timestamps on ajax call so that
43
+ # timeout feature works properly
44
+ # Only GET request get ignored - POST/PUT/DELETE requests reflect a
45
+ # user action and should therefore be taken into account
46
+ def skip_devise_trackable_on_xhr
47
+ if request.format == 'application/json' && request.get?
48
+ request.env["devise.skip_trackable"] = true
49
+ end
50
+ end
51
+
52
+ # Return the user to the 'return_to' url if one was specified
53
+ # previously. Only if user is signed in
54
+ def perform_return_to
55
+ return true unless current_user && (url = return_to_url(current_user))
56
+ redirect_to url
57
+ end
58
+
59
+ # Devise will always redirect to the last non devise route
60
+ # (alias not starting with /auth)
61
+ # ---
62
+ # WARNING: if one day you change the below please also check that
63
+ # the new behaviour fits with ConfirmationsController (yes...I know...it's not clean)
64
+ def store_location
65
+ capture_return_to_redirection || capture_previous_url
66
+ end
67
+
68
+ def capture_previous_url
69
+ if request.format == 'text/html' && request.fullpath =~ /\/(myspace|deletion_requests|org_invites|provision)/
70
+ session[:previous_url] = request.original_url
71
+ end
72
+ end
73
+
74
+ # Handle return_to parameter in URL if present
75
+ def capture_return_to_redirection
76
+ return false unless request.format == 'text/html' && params[:return_to].present?
77
+
78
+ # Capture return url
79
+ session[:return_to] = params[:return_to]
80
+ end
81
+
82
+ # Return the URL that the user should be immediately returned to
83
+ def return_to_url(resource)
84
+ return nil unless (url = session.delete(:return_to)).present?
85
+
86
+ # Add Web Token to URL
87
+ separator = (url =~ /\?/ ? '&' : '?')
88
+
89
+ url + "#{separator}wtk=#{MnoEnterprise.jwt({user_id: resource.uid})}"
90
+ end
91
+
92
+ # Redirect to previous url and reset it
93
+ def after_sign_in_path_for(resource)
94
+ previous_url = session.delete(:previous_url)
95
+ url = mno_enterprise.respond_to?(:myspace_url) ? mno_enterprise.myspace_url : main_app.root_url
96
+ return (return_to_url(resource) || previous_url || url)
97
+ end
98
+
99
+ # Some controllers needs to redirect to 'MySpace' which breaks if you dont use mnoe-frontend
100
+ # Rather than relying on the MainApp to define myspace_path we check it here
101
+ # The MainApp can redefine this two methods to fit its structure
102
+ # Some of these are extracted to individuals methods like after_provision_path.
103
+ def mnoe_home_path
104
+ mno_enterprise.respond_to?(:myspace_path) ? mno_enterprise.myspace_path : main_app.root_path
105
+ end
106
+
107
+ def mnoe_home_url
108
+ mno_enterprise.respond_to?(:myspace_url) ? mno_enterprise.myspace_url : main_app.root_url
109
+ end
110
+
111
+ # Overwriting the sign_out redirect path method
112
+ def after_sign_out_path_for(resource_or_scope)
113
+ MnoEnterprise.router.after_sign_out_url || super
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,67 @@
1
+ module MnoEnterprise
2
+ module ApplicationHelper
3
+
4
+ def support_email
5
+ MnoEnterprise.support_email
6
+ end
7
+
8
+ # Re-implement Devise filter
9
+ # For some reasons the original Devise filter seems to ignore the
10
+ # mnoe prefix when using custom devise controllers
11
+ def authenticate_user!
12
+ redirect_to(new_user_session_path) unless current_user
13
+ true
14
+ end
15
+
16
+ # Redirect a signed in user to the confirmation
17
+ # lounge if unconfirmed
18
+ def redirect_to_lounge_if_unconfirmed
19
+ if current_user && !current_user.confirmed?
20
+ redirect_to user_confirmation_lounge_path
21
+ end
22
+ return true
23
+ end
24
+
25
+ # Redirect to signup page if user not authenticated
26
+ def authenticate_user_or_signup!
27
+ unless current_user
28
+ redirect_to new_user_registration_path
29
+ false
30
+ end
31
+
32
+ true
33
+ end
34
+
35
+ def notice_hash(notice)
36
+ return {} unless notice
37
+ # TODO: refactor
38
+ auto_close = (notice =~ /signed (in|out)/i ? 5*1000 : nil)
39
+ # Check if a timeout has been defined in flash
40
+ unless auto_close
41
+ auto_close = flash[:flash_options][:timeout] if flash[:flash_options] && flash[:flash_options][:timeout]
42
+ end
43
+
44
+ {
45
+ type:'success',
46
+ msg: (notice || '').html_safe,
47
+ timeout: auto_close
48
+ }
49
+ end
50
+
51
+ def alert_hash(alert)
52
+ return {} unless alert
53
+ {
54
+ type:'danger',
55
+ msg: (alert || '').html_safe,
56
+ timeout: -1
57
+ }
58
+ end
59
+
60
+ # This helper converts markdown content
61
+ # to html, using the HtmlProcessor (see /lib)
62
+ def markdown(text)
63
+ return text unless text.present?
64
+ HtmlProcessor.new(text, format: :markdown).html.html_safe
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,27 @@
1
+ module MnoEnterprise
2
+ module ImpersonateHelper
3
+
4
+ # current_user changes from a staff user to
5
+ # +new_user+; current user stored in +session[:impersonator_user_id]+
6
+ def impersonate(new_user)
7
+ session[:impersonator_user_id] = current_user.id
8
+ sign_out(current_user)
9
+ sign_in new_user
10
+ end
11
+
12
+ # revert the +current_user+ back to the staff user
13
+ # stored in +session[:impersonator_user_id]+
14
+ def revert_impersonate
15
+ return unless current_impersonator
16
+ sign_out(current_user)
17
+ sign_in(current_impersonator)
18
+ session[:impersonator_user_id] = nil
19
+ end
20
+
21
+ def current_impersonator
22
+ return unless session[:impersonator_user_id]
23
+ @admin_user ||= MnoEnterprise::User.find(session[:impersonator_user_id])
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module MnoEnterprise
2
+ class Ability
3
+ include CanCan::Ability
4
+ include MnoEnterprise::Concerns::Models::Ability
5
+ end
6
+ end
@@ -0,0 +1,72 @@
1
+ # == Schema Information
2
+ #
3
+ # Endpoint: /v1/apps
4
+ #
5
+ # id :integer not null, primary key
6
+ # nid :string e.g.: 'wordpress'
7
+ # name :string(255)
8
+ # description :text
9
+ # created_at :datetime not null
10
+ # updated_at :datetime not null
11
+ # logo :string(255)
12
+ # version :string(255)
13
+ # website :string(255)
14
+ # slug :string(255)
15
+ # categories :text
16
+ # key_benefits :text
17
+ # key_features :text
18
+ # testimonials :text
19
+ # worldwide_usage :integer
20
+ # tiny_description :text
21
+ # popup_description :text
22
+ # stack :string(255)
23
+ # terms_url :string(255)
24
+ # tags :text
25
+ #
26
+
27
+ module MnoEnterprise
28
+ class App < BaseResource
29
+ scope :active, -> { where(active: true) }
30
+ scope :cloud, -> { where(stack: 'cloud') }
31
+
32
+ attributes :id, :uid, :nid, :name, :description, :tiny_description, :created_at, :updated_at, :logo, :website, :slug,
33
+ :categories, :key_benefits, :key_features, :testimonials, :worldwide_usage, :tiny_description,
34
+ :popup_description, :stack, :terms_url, :pictures, :tags, :api_key, :metadata_url, :metadata, :details
35
+
36
+ # Return the list of available categories
37
+ def self.categories(list = nil)
38
+ app_list = list || self.all.to_a
39
+ app_list.select { |a| a.categories.present? }.map(&:categories).flatten.uniq { |e| e.downcase }.sort
40
+ end
41
+
42
+ def to_audit_event
43
+ {
44
+ app_id: id,
45
+ app_nid: nid,
46
+ app_name: name
47
+ }
48
+ end
49
+
50
+ # Sanitize the app description
51
+ # E.g.: replace any mention of Maestrano by the tenant name
52
+ def sanitized_description
53
+ @sanitized_description ||= (self.description || '').gsub(/maestrano/i,MnoEnterprise.app_name)
54
+ end
55
+
56
+ # Methods for appinfo flags
57
+ %w(coming_soon single_billing).each do |method|
58
+ define_method "#{method}?" do
59
+ appinfo.presence && appinfo[method]
60
+ end
61
+ end
62
+
63
+ def regenerate_api_key!
64
+ data = self.put(operation: 'regenerate_api_key')
65
+ self.api_key = data[:data][:api_key]
66
+ end
67
+
68
+ def refresh_metadata!(metadata_url)
69
+ self.put(operation: 'refresh_metadata', metadata_url: metadata_url)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ # == Schema Information
2
+ #
3
+ # Endpoint:
4
+ # - /v1/app_instances
5
+ # - /v1/organizations/:organization_id/app_instances
6
+ #
7
+ # id :integer not null, primary key
8
+ # uid :string(255)
9
+ # name :string(255)
10
+ # status :string(255)
11
+ # app_id :integer
12
+ # created_at :datetime not null
13
+ # updated_at :datetime not null
14
+ # started_at :datetime
15
+ # stack :string(255)
16
+ # owner_id :integer
17
+ # owner_type :string(255)
18
+ # terminated_at :datetime
19
+ # stopped_at :datetime
20
+ # billing_type :string(255)
21
+ # autostop_at :datetime
22
+ # autostop_interval :integer
23
+ # next_status :string(255)
24
+ # soa_enabled :boolean default(FALSE)
25
+ #
26
+ # ===> to be confirmed
27
+ # http_url
28
+ # durations :text
29
+ # microsoft_licence_id :integer
30
+ #
31
+
32
+ module MnoEnterprise
33
+ class AppInstance < BaseResource
34
+ include MnoEnterprise::Concerns::Models::AppInstance
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module MnoEnterprise
2
+ class AppInstancesSync < BaseResource
3
+ attributes :connectors, :mode
4
+ belongs_to :organization, class_name: 'MnoEnterprise::Organization'
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module MnoEnterprise
2
+ class ArrearsSituation < BaseResource
3
+ belongs_to :organization, class_name: 'MnoEnterprise::Organization'
4
+ attributes :payment
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module MnoEnterprise
2
+ class AuditEvent < BaseResource
3
+ def formatted_details
4
+ case details
5
+ when String
6
+ details
7
+ when Hash
8
+ format_serialized_details
9
+ else
10
+ nil
11
+ end
12
+ end
13
+
14
+ def format_serialized_details
15
+ AUDIT_LOG_CONFIG.fetch('events', {}).fetch(key, '') % details.symbolize_keys
16
+ rescue KeyError => e
17
+ e.message
18
+ # details.inspect
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,228 @@
1
+ # TODO: spec the ActiveRecord behaviour
2
+ # - processing of remote errors
3
+ # - response parsing (using data: [] format)
4
+ # - save methods
5
+ module MnoEnterprise
6
+ class BaseResource
7
+ include Her::Model
8
+ include HerExtension::Validations::RemoteUniquenessValidation
9
+
10
+ include_root_in_json :data
11
+ use_api MnoEnterprise.mnoe_api_v1
12
+
13
+ # TODO: spec that changed_attributes is empty
14
+ # after a KLASS.all / KLASS.where etc..
15
+ after_find { |res| res.instance_variable_set(:@changed_attributes, {}) }
16
+
17
+ # Attributes common to all classes
18
+ attributes :id, :created_at, :updated_at
19
+
20
+ # Class query methods
21
+ class << self
22
+ # Delegate the following methods to `scoped`
23
+ # Clear relation params for each class level query
24
+ [:all, :where, :create, :find, :first_or_create, :first_or_initialize, :limit, :skip, :order_by, :sort_by, :order, :sort].each do |method|
25
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
26
+ def #{method}(*params)
27
+ Her::Model::Relation.new(self).send(#{method.to_sym.inspect}, *params)
28
+ end
29
+ RUBY
30
+ end
31
+
32
+ # ActiveRecord Compatibility for Her
33
+ def first(n = 1)
34
+ return [] unless n > 0
35
+ q = self.order_by('id.asc').limit(n)
36
+ n == 1 ? q.to_a.first : q.to_a
37
+ end
38
+
39
+ # ActiveRecord Compatibility for Her
40
+ def last(n = 1)
41
+ return [] unless n > 0
42
+ q = self.order_by('id.desc').limit(n)
43
+ n == 1 ? q.to_a.first : q.to_a
44
+ end
45
+
46
+ # Find first record using a hash of attributes
47
+ def find_by(hash)
48
+ self.where(hash).limit(1).first
49
+ end
50
+
51
+ # ActiveRecord Compatibility for Her
52
+ # Returns the class descending directly from MnoEnterprise::BaseResource, or
53
+ # an abstract class, if any, in the inheritance hierarchy.
54
+ #
55
+ # If A extends MnoEnterprise::BaseResource, A.base_class will return A. If B descends from A
56
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
57
+ #
58
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
59
+ # and C.base_class would return B as the answer since A is an abstract_class.
60
+ def base_class
61
+ unless self < BaseResource
62
+ raise Error, "#{name} doesn't belong in a hierarchy descending from BaseResource"
63
+ end
64
+
65
+ if superclass == BaseResource || superclass.abstract_class?
66
+ self
67
+ else
68
+ superclass.base_class
69
+ end
70
+ end
71
+ end
72
+
73
+ #======================================================================
74
+ # Instance methods
75
+ #======================================================================
76
+ # Returns a cache key that can be used to identify this record.
77
+ #
78
+ # Product.new.cache_key # => "products/new"
79
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
80
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
81
+ #
82
+ # You can also pass a list of named timestamps, and the newest in the list will be
83
+ # used to generate the key:
84
+ #
85
+ # Person.find(5).cache_key(:updated_at, :last_reviewed_at)
86
+ #
87
+ # Notes: copied from ActiveRecord
88
+ def cache_key(*timestamp_names)
89
+ case
90
+ when new?
91
+ "#{model_name.cache_key}/new"
92
+ when timestamp_names.any?
93
+ timestamp = max_updated_column_timestamp(timestamp_names)
94
+ timestamp = timestamp.utc.to_s(:nsec)
95
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
96
+ when timestamp = max_updated_column_timestamp
97
+ timestamp = timestamp.utc.to_s(:nsec)
98
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
99
+ else
100
+ "#{model_name.cache_key}/#{id}"
101
+ end
102
+ end
103
+
104
+ def max_updated_column_timestamp(timestamp_names = [:updated_at])
105
+ timestamp_names
106
+ .map { |attr| self[attr] }
107
+ .compact
108
+ .map(&:to_time)
109
+ .max
110
+ end
111
+
112
+ def clear_association_cache
113
+ self.class.associations[:has_many].each do |assoc|
114
+ instance_variable_set(:"@_her_association_#{assoc[:name]}", nil)
115
+ end
116
+ end
117
+
118
+ # ActiveRecord Compatibility for Her
119
+ def read_attribute(attr_name)
120
+ get_attribute(attr_name)
121
+ end
122
+
123
+ # ActiveRecord Compatibility for Her
124
+ def write_attribute(attr_name, value)
125
+ assign_attributes(attr_name => value)
126
+ end
127
+ alias []= write_attribute
128
+
129
+ # ActiveRecord Compatibility for Her
130
+ def save(options={})
131
+ if perform_validations(options)
132
+ ret = super()
133
+ process_response_errors
134
+ ret
135
+ else
136
+ false
137
+ end
138
+ end
139
+
140
+ # ActiveRecord Compatibility for Her
141
+ def save!(options={})
142
+ if perform_validations(options)
143
+ ret = super()
144
+ process_response_errors
145
+ raise_record_invalid
146
+ else
147
+ false
148
+ end
149
+ end
150
+
151
+ # ActiveRecord Compatibility for Her
152
+ def reload(options = nil)
153
+ @attributes.update(self.class.find(self.id).attributes)
154
+ self.run_callbacks :find
155
+ self
156
+ end
157
+
158
+ # ActiveRecord Compatibility for Her
159
+ def update(attributes)
160
+ assign_attributes(attributes)
161
+ save
162
+ end
163
+
164
+ # Reset the ActiveModel hash containing all attribute changes
165
+ # Useful when initializing a existing resource using a hash fetched
166
+ # via http call (e.g.: MnoEnterprise::User.authenticate)
167
+ def clear_attribute_changes!
168
+ self.instance_variable_set(:@changed_attributes, {})
169
+ end
170
+
171
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
172
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
173
+ #
174
+ # Note that new records are different from any other record by definition, unless the
175
+ # other record is the receiver itself. Besides, if you fetch existing records with
176
+ # +select+ and leave the ID out, you're on your own, this predicate will return false.
177
+ #
178
+ # Note also that destroying a record preserves its ID in the model instance, so deleted
179
+ # models are still comparable.
180
+ def ==(comparison_object)
181
+ super ||
182
+ comparison_object.instance_of?(self.class) &&
183
+ !id.nil? &&
184
+ comparison_object.id == id
185
+ end
186
+ alias :eql? :==
187
+
188
+ protected
189
+ # Process errors from the servers and add them to the
190
+ # model
191
+ # Servers are returned using the jsonapi format
192
+ # E.g.:
193
+ # errors: [
194
+ # {
195
+ # :id=>"f720ca10-b104-0132-dbc0-600308937d74",
196
+ # :href=>"http://maestrano.github.io/enterprise/#users-users-list-post",
197
+ # :status=>"400",
198
+ # :code=>"name-can-t-be-blank",
199
+ # :title=>"Name can't be blank",
200
+ # :detail=>"Name can't be blank"
201
+ # :attribute => "name"
202
+ # :value => "can't be blank"
203
+ # }
204
+ # ]
205
+ def process_response_errors
206
+ if self.response_errors && self.response_errors.any?
207
+ self.response_errors.each do |error|
208
+ key = error[:attribute] && !error[:attribute].empty? ? error[:attribute] : :base
209
+ val = error[:value] && !error[:value].empty? ? error[:value] : error[:title]
210
+ self.errors[key.to_sym] << val
211
+ end
212
+ end
213
+ end
214
+
215
+ # ActiveRecord Compatibility for Her
216
+ def raise_record_invalid
217
+ raise(Her::Errors::ResourceInvalid.new(self))
218
+ end
219
+
220
+ # ActiveRecord Compatibility for Her
221
+ def perform_validations(options={}) # :nodoc:
222
+ # errors.blank? to avoid the unexpected case when errors is nil...
223
+ # -> THIS IS A TEMPORARY UGLY FIX
224
+ options[:validate] == false || self.errors.nil? || valid?(options[:context])
225
+ end
226
+
227
+ end
228
+ end