mno-enterprise-core 2.0.0

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