erp_tech_svcs 4.0.0 → 4.2.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -24
  3. data/app/controllers/api/v1/audit_log_items_controller.rb +33 -0
  4. data/app/controllers/api/v1/audit_logs_controller.rb +32 -0
  5. data/app/controllers/api/v1/capabilities_controller.rb +160 -0
  6. data/app/controllers/api/v1/file_assets_controller.rb +40 -0
  7. data/app/controllers/api/v1/groups_controller.rb +236 -0
  8. data/app/controllers/api/v1/security_roles_controller.rb +276 -0
  9. data/app/controllers/api/v1/users_controller.rb +262 -0
  10. data/app/controllers/erp_tech_svcs/session_controller.rb +8 -5
  11. data/app/controllers/erp_tech_svcs/user_controller.rb +14 -15
  12. data/app/mailers/user_mailer.rb +8 -5
  13. data/app/models/audit_log.rb +111 -36
  14. data/app/models/audit_log_item.rb +30 -0
  15. data/app/models/audit_log_item_type.rb +1 -0
  16. data/app/models/audit_log_type.rb +19 -0
  17. data/app/models/capability.rb +22 -6
  18. data/app/models/extensions/tracked_status_type.rb +3 -0
  19. data/app/models/file_asset.rb +245 -20
  20. data/app/models/file_asset_holder.rb +20 -0
  21. data/app/models/group.rb +38 -25
  22. data/app/models/notification.rb +32 -13
  23. data/app/models/notification_type.rb +13 -0
  24. data/app/models/security_role.rb +17 -4
  25. data/app/models/user.rb +116 -29
  26. data/app/validators/password_strength_validator.rb +1 -1
  27. data/app/views/user_mailer/activation_needed_email.html.erb +293 -15
  28. data/app/views/user_mailer/reset_password_email.html.erb +268 -13
  29. data/config/initializers/logger.rb +19 -0
  30. data/config/initializers/sorcery.rb +2 -0
  31. data/config/initializers/wickedpdf.rb +4 -0
  32. data/config/routes.rb +64 -0
  33. data/db/data_migrations/20110802200222_schedule_delete_expired_sessions_job.rb +1 -5
  34. data/db/data_migrations/20150819140550_create_job_tracker_for_notification.rb +14 -0
  35. data/db/migrate/20080805000010_base_tech_services.rb +99 -39
  36. data/db/migrate/20150414151421_add_nested_set_columns_to_security_role.rb +13 -0
  37. data/db/migrate/20150609003216_update_user_for_sorcery.rb +11 -0
  38. data/db/migrate/20150819135108_add_custom_fields_to_notifications.rb +5 -0
  39. data/db/migrate/20160122155402_add_description_to_file_asset.rb +13 -0
  40. data/db/migrate/20160310163060_add_created_by_updated_by_to_erp_tech_svcs.rb +35 -0
  41. data/db/migrate/20160313161611_add_tenant_id_to_audit_log.rb +16 -0
  42. data/lib/erp_tech_svcs.rb +6 -10
  43. data/lib/erp_tech_svcs/config.rb +7 -2
  44. data/lib/erp_tech_svcs/delayed_jobs/delete_expired_sessions_job.rb +49 -0
  45. data/lib/erp_tech_svcs/delayed_jobs/notification_job.rb +50 -0
  46. data/lib/erp_tech_svcs/engine.rb +0 -1
  47. data/lib/erp_tech_svcs/erp_tech_svcs_audit_log.rb +12 -6
  48. data/lib/erp_tech_svcs/extensions.rb +0 -1
  49. data/lib/erp_tech_svcs/extensions/active_record/has_capability_accessors.rb +57 -29
  50. data/lib/erp_tech_svcs/extensions/active_record/has_file_assets.rb +57 -31
  51. data/lib/erp_tech_svcs/extensions/active_record/has_security_roles.rb +12 -4
  52. data/lib/erp_tech_svcs/extensions/active_record/is_json.rb +22 -15
  53. data/lib/erp_tech_svcs/extensions/active_record/scoped_by.rb +16 -13
  54. data/lib/erp_tech_svcs/extensions/compass_ae/erp_base_erp_svcs/controllers/api/parties_controller.rb +15 -0
  55. data/lib/erp_tech_svcs/file_support.rb +1 -0
  56. data/lib/erp_tech_svcs/file_support/file_system_manager.rb +77 -44
  57. data/lib/erp_tech_svcs/file_support/manager.rb +12 -3
  58. data/lib/erp_tech_svcs/file_support/railties/compass_ae_resolver.rb +49 -0
  59. data/lib/erp_tech_svcs/file_support/s3_manager.rb +73 -51
  60. data/lib/erp_tech_svcs/utils/compass_access_negotiator.rb +11 -2
  61. data/lib/erp_tech_svcs/utils/default_nested_set_methods.rb +238 -46
  62. data/lib/erp_tech_svcs/version.rb +1 -1
  63. data/lib/tasks/erp_tech_svcs_tasks.rake +43 -5
  64. metadata +73 -42
  65. data/app/models/user_defined_data.rb +0 -6
  66. data/app/models/user_defined_field.rb +0 -8
  67. data/config/initializers/pdfkit.rb +0 -18
  68. data/db/data_migrations/20121130212146_note_capabilities.rb +0 -23
  69. data/db/migrate/20121116151510_create_groups.rb +0 -18
  70. data/db/migrate/20121126171612_upgrade_security.rb +0 -53
  71. data/db/migrate/20121126173506_upgrade_security2.rb +0 -274
  72. data/db/migrate/20130410135419_add_queue_to_delayed_jobs.rb +0 -13
  73. data/db/migrate/20130610163240_create_notifications.rb +0 -37
  74. data/db/migrate/20130725212647_add_party_id_idx_to_users.rb +0 -9
  75. data/db/migrate/20131113213843_add_audit_log_item_old_value.rb +0 -13
  76. data/db/migrate/20131113213844_add_erp_tech_svcs_missing_indexes.rb +0 -31
  77. data/db/migrate/20131129203603_add_user_defined_fields.rb +0 -43
  78. data/db/migrate/20141013060204_add_custom_fields_to_notifications.rb +0 -12
  79. data/db/migrate/20141108182427_add_scoped_by_to_file_assets.rb +0 -14
  80. data/lib/erp_tech_svcs/extensions/active_record/has_user_defined_data.rb +0 -147
  81. data/lib/erp_tech_svcs/sessions/delete_expired_sessions_job.rb +0 -47
  82. data/lib/erp_tech_svcs/sessions/delete_expired_sessions_service.rb +0 -15
  83. data/lib/erp_tech_svcs/utils/compass_logger.rb +0 -87
@@ -1,12 +1,27 @@
1
+ # create_table :notifications do |t|
2
+ # t.string :type
3
+ # t.references :created_by
4
+ # t.text :message
5
+ # t.references :notification_type
6
+ # t.string :current_state
7
+ # t.text :custom_fields
8
+ #
9
+ # t.timestamps
10
+ # end
11
+ #
12
+ # add_index :notifications, :notification_type_id
13
+ # add_index :notifications, :created_by_id
14
+ # add_index :notifications, :type
15
+
1
16
  class Notification < ActiveRecord::Base
2
17
  attr_protected :created_at, :updated_at
3
-
4
- # serialize custom attributes
5
- is_json :custom_fields
6
18
 
7
19
  belongs_to :notification_type
8
20
  belongs_to :created_by, :foreign_key => 'created_by_id', :class_name => 'Party'
9
-
21
+
22
+ # serialize custom attributes
23
+ is_json :custom_fields
24
+
10
25
  include AASM
11
26
 
12
27
  aasm_column :current_state
@@ -22,28 +37,32 @@ class Notification < ActiveRecord::Base
22
37
 
23
38
  class << self
24
39
 
25
- def create_notification_of_type(notification_type, dyn_attributes={}, created_by=nil)
26
- notification_type = notification_type.class == NotificationType ? notification_type : NotificationType.find_by_internal_identifier(notification_type)
40
+ # Creates a Notification record with the notification type passed
41
+ #
42
+ # @param [NotificationType | String] the notification type to set, can be a NotificationType record or InternalIdentifier
43
+ # @param [Hash] custom fields to set on the notification
44
+ # @param [Party] the party that created the notification
45
+ def create_notification_of_type(notification_type, custom_fields={}, created_by=nil)
46
+ notification_type = notification_type.class == NotificationType ? notification_type : NotificationType.iid(notification_type)
27
47
 
28
- notification = self.new(
48
+ notification = self.create(
29
49
  created_by: created_by,
30
50
  notification_type: notification_type
31
51
  )
32
52
 
33
- dyn_attributes.each do |k,v|
34
- notification.custom_fields[k] = v
35
- end
53
+ notification.custom_fields = custom_fields
36
54
 
37
- notification.save
55
+ notification.save!
38
56
 
39
57
  notification
40
58
  end
41
59
 
42
60
  end
43
61
 
62
+ # Delivers notification, called by the notifications delayed job
63
+ # this is a template method and should be overridden by sub class
64
+ #
44
65
  def deliver_notification
45
- # template method
46
- # should be overridden in sub class
47
66
  end
48
67
 
49
68
  end
@@ -1,5 +1,18 @@
1
+ # create_table :notification_types do |t|
2
+ # t.string :internal_identifier
3
+ # t.string :description
4
+ #
5
+ # t.timestamps
6
+ # end
7
+ #
8
+ # add_index :notification_types, :internal_identifier
9
+
1
10
  class NotificationType < ActiveRecord::Base
2
11
  attr_protected :created_at, :updated_at
3
12
 
13
+ acts_as_erp_type
14
+
4
15
  has_many :notifications
16
+
17
+ validates :internal_identifier, uniqueness: {message: "Internal Identifiers should be unique"}
5
18
  end
@@ -1,4 +1,7 @@
1
1
  class SecurityRole < ActiveRecord::Base
2
+
3
+ acts_as_nested_set
4
+ include ErpTechSvcs::Utils::DefaultNestedSetMethods
2
5
  acts_as_erp_type
3
6
  has_capability_accessors
4
7
  has_and_belongs_to_many :parties
@@ -10,10 +13,10 @@ class SecurityRole < ActiveRecord::Base
10
13
 
11
14
  attr_accessible :description, :internal_identifier
12
15
 
13
- def to_xml(options = {})
14
- default_only = []
15
- options[:only] = (options[:only] || []) + default_only
16
- super(options)
16
+ def to_xml(options = {})
17
+ default_only = []
18
+ options[:only] = (options[:only] || []) + default_only
19
+ super(options)
17
20
  end
18
21
 
19
22
  # creating method because we only want a getter, not a setter for iid
@@ -45,4 +48,14 @@ class SecurityRole < ActiveRecord::Base
45
48
  Group.joins(:party).joins("LEFT JOIN #{join_parties_security_roles}").where("parties_security_roles.security_role_id IS NULL")
46
49
  end
47
50
 
51
+ def to_data_hash
52
+ hash = to_hash(:only => [:id, :description, :internal_identifier, :created_at, :updated_at])
53
+
54
+ if parent
55
+ hash[:parent] = parent.to_data_hash
56
+ end
57
+
58
+ hash
59
+ end
60
+
48
61
  end
@@ -7,7 +7,11 @@ class User < ActiveRecord::Base
7
7
  belongs_to :party
8
8
 
9
9
  attr_accessible :email, :password, :password_confirmation
10
+
10
11
  authenticates_with_sorcery!
12
+
13
+ attr_protected :created_at, :updated_at
14
+
11
15
  has_capability_accessors
12
16
 
13
17
  #password validations
@@ -22,12 +26,20 @@ class User < ActiveRecord::Base
22
26
  validates :username, :presence => {:message => 'cannot be blank'}, :uniqueness => {:case_sensitive => false}
23
27
 
24
28
  validate :email_cannot_match_username_of_other_user
29
+
25
30
  def email_cannot_match_username_of_other_user
26
- unless User.where(:username => self.email).where('id != ?',self.id).first.nil?
31
+ unless User.where(:username => self.email).where('id != ?', self.id).first.nil?
27
32
  errors.add(:email, "In use by another user")
28
33
  end
29
34
  end
30
35
 
36
+ # auth token used for mobile app security
37
+ def generate_auth_token!
38
+ self.auth_token = SecureRandom.uuid
39
+ self.auth_token_expires_at = Time.now + 30.days
40
+ self.save
41
+ end
42
+
31
43
  # This allows the disabling of the activation email sent via the sorcery user_activation submodule
32
44
  def send_activation_needed_email!
33
45
  super unless skip_activation_email
@@ -38,7 +50,7 @@ class User < ActiveRecord::Base
38
50
  @instance_attrs.nil? ? {} : @instance_attrs
39
51
  end
40
52
 
41
- def add_instance_attribute(k,v)
53
+ def add_instance_attribute(k, v)
42
54
  @instance_attrs = {} if @instance_attrs.nil?
43
55
  @instance_attrs[k] = v
44
56
  end
@@ -57,7 +69,7 @@ class User < ActiveRecord::Base
57
69
  result = false
58
70
  passed_roles.flatten!
59
71
  passed_roles.each do |role|
60
- role_iid = role.is_a?(SecurityRole) ? role.internal_identifier : role.to_s
72
+ role_iid = role.is_a?(SecurityRole) ? role.internal_identifier : role.to_s
61
73
  all_uniq_roles.each do |this_role|
62
74
  result = true if (this_role.internal_identifier == role_iid)
63
75
  break if result
@@ -71,46 +83,45 @@ class User < ActiveRecord::Base
71
83
  party.add_role(role)
72
84
  end
73
85
 
86
+ alias :add_security_role :add_role
87
+
74
88
  def add_roles(*passed_roles)
75
89
  party.add_roles(*passed_roles)
76
90
  end
77
91
 
92
+ alias :add_security_roles :add_roles
93
+
78
94
  def remove_roles(*passed_roles)
79
95
  party.remove_roles(*passed_roles)
80
96
  end
81
97
 
98
+ alias :remove_security_roles :remove_roles
99
+
82
100
  def remove_role(role)
83
101
  party.remove_role(role)
84
102
  end
85
103
 
104
+ alias :remove_security_role :remove_role
105
+
86
106
  def remove_all_roles
87
107
  party.remove_all_roles
88
108
  end
89
109
 
90
- # user lives on FROM side of relationship
91
- def group_relationships
92
- role_type = RoleType.find_by_internal_identifier('group_member')
93
- PartyRelationship.where(:party_id_from => self.party.id, :role_type_id_from => role_type.id)
94
- end
95
-
96
- def join_party_relationships
97
- role_type = RoleType.find_by_internal_identifier('group_member')
98
- "party_relationships ON party_id_from = #{self.party.id} AND party_id_to = parties.id AND role_type_id_from=#{role_type.id}"
99
- end
110
+ alias :remove_all_security_roles :remove_all_roles
100
111
 
101
112
  # party records for the groups this user belongs to
102
113
  def group_parties
103
- Party.joins("JOIN #{join_party_relationships}")
114
+ Party.joins("JOIN #{group_member_join}")
104
115
  end
105
116
 
106
117
  # groups this user belongs to
107
118
  def groups
108
- Group.joins(:party).joins("JOIN #{join_party_relationships}")
119
+ Group.joins(:party).joins("JOIN #{group_member_join}")
109
120
  end
110
121
 
111
122
  # groups this user does NOT belong to
112
123
  def groups_not
113
- Group.joins(:party).joins("LEFT JOIN #{join_party_relationships}").where("party_relationships.id IS NULL")
124
+ Group.joins(:party).joins("LEFT JOIN #{group_member_join}").where("party_relationships.id IS NULL")
114
125
  end
115
126
 
116
127
  # roles assigned to the groups this user belongs to
@@ -120,10 +131,50 @@ class User < ActiveRecord::Base
120
131
  where("parties.business_party_id IN (#{groups.select('groups.id').to_sql})")
121
132
  end
122
133
 
134
+ # Add a group to this user
135
+ #
136
+ # @param group [Group] Group to add
137
+ def add_group(group)
138
+ group.add_user(self)
139
+ end
140
+
141
+ # Add multiple groups to this user
142
+ #
143
+ # @param _groups [Array] Groups to add
144
+ def add_groups(_groups)
145
+ _groups.each do |group|
146
+ add_group(group)
147
+ end
148
+ end
149
+
150
+ # Remove a group from this user
151
+ #
152
+ # @param group [Group] Group to remove
153
+ def remove_group(group)
154
+ group.remove_user(self)
155
+ end
156
+
157
+ # Remove multiple groups from this user
158
+ #
159
+ # @param _groups [Array] Groups to remove
160
+ def remove_groups(_groups)
161
+ _groups.each do |group|
162
+ remove_group(group)
163
+ end
164
+ end
165
+
166
+ # Remove all current groups from this user
167
+ #
168
+ def remove_all_groups
169
+ groups.each do |group|
170
+ remove_group(group)
171
+ end
172
+ end
173
+
123
174
  # composite roles for this user
124
175
  def all_roles
125
176
  SecurityRole.joins(:parties).joins("LEFT JOIN users ON parties.id=users.party_id").
126
- where("(parties.business_party_type='Group' AND
177
+ where("(parties.business_party_type='Group' AND
127
178
  parties.business_party_id IN (#{groups.select('groups.id').to_sql})) OR
128
179
  (users.id=#{self.id})")
129
180
  end
@@ -133,20 +184,20 @@ class User < ActiveRecord::Base
133
184
  end
134
185
 
135
186
  def group_capabilities
136
- Capability.joins(:capability_type).joins(:capability_accessors).
137
- where(:capability_accessors => { :capability_accessor_record_type => "Group" }).
138
- where("capability_accessor_record_id IN (#{groups.select('groups.id').to_sql})")
187
+ Capability.includes(:capability_type).joins(:capability_type).joins(:capability_accessors).
188
+ where(:capability_accessors => {:capability_accessor_record_type => "Group"}).
189
+ where("capability_accessor_record_id IN (#{groups.select('groups.id').to_sql})")
139
190
  end
140
191
 
141
192
  def role_capabilities
142
- Capability.joins(:capability_type).joins(:capability_accessors).
143
- where(:capability_accessors => { :capability_accessor_record_type => "SecurityRole" }).
144
- where("capability_accessor_record_id IN (#{all_roles.select('security_roles.id').to_sql})")
193
+ Capability.includes(:capability_type).joins(:capability_type).joins(:capability_accessors).
194
+ where(:capability_accessors => {:capability_accessor_record_type => "SecurityRole"}).
195
+ where("capability_accessor_record_id IN (#{all_roles.select('security_roles.id').to_sql})")
145
196
  end
146
197
 
147
198
  def all_capabilities
148
- Capability.joins(:capability_type).joins(:capability_accessors).
149
- where("(capability_accessors.capability_accessor_record_type = 'Group' AND
199
+ Capability.includes(:capability_type).joins(:capability_type).joins(:capability_accessors).
200
+ where("(capability_accessors.capability_accessor_record_type = 'Group' AND
150
201
  capability_accessor_record_id IN (#{groups.select('groups.id').to_sql})) OR
151
202
  (capability_accessors.capability_accessor_record_type = 'SecurityRole' AND
152
203
  capability_accessor_record_id IN (#{all_roles.select('security_roles.id').to_sql})) OR
@@ -178,11 +229,47 @@ class User < ActiveRecord::Base
178
229
  end
179
230
 
180
231
  def class_capabilities_to_hash
181
- all_uniq_class_capabilities.map {|capability|
182
- { :capability_type_iid => capability.capability_type.internal_identifier,
183
- :capability_resource_type => capability.capability_resource_type
184
- }
232
+ all_uniq_class_capabilities.map { |capability|
233
+ { capability_type_iid: capability.capability_type.internal_identifier,
234
+ capability_type_description: capability.capability_type.description,
235
+ capability_resource_type: capability.capability_resource_type
236
+ }
185
237
  }.compact
186
238
  end
187
239
 
240
+ def to_data_hash
241
+ data = to_hash(only: [
242
+ :auth_token,
243
+ :id,
244
+ :username,
245
+ :email,
246
+ :activation_state,
247
+ :last_login_at,
248
+ :last_logout_at,
249
+ :last_activity_at,
250
+ :failed_logins_count,
251
+ :created_at,
252
+ :updated_at
253
+ ],
254
+ display_name: party.description,
255
+ is_admin: party.has_security_role?('admin'),
256
+ party: party.to_data_hash
257
+ )
258
+
259
+ # add first name and last name if this party is an Individual
260
+ if self.party.business_party.is_a?(Individual)
261
+ data[:first_name] = self.party.business_party.current_first_name
262
+ data[:last_name] = self.party.business_party.current_last_name
263
+ end
264
+
265
+ data
266
+ end
267
+
268
+ protected
269
+
270
+ def group_member_join
271
+ role_type = RoleType.find_by_internal_identifier('group_member')
272
+ "party_relationships ON party_id_from = #{self.party.id} AND party_id_to = parties.id AND role_type_id_from=#{role_type.id}"
273
+ end
274
+
188
275
  end
@@ -1,7 +1,7 @@
1
1
  class PasswordStrengthValidator < ActiveModel::EachValidator
2
2
  # implement the method where the validation logic must reside
3
3
  def validate_each(record, attribute, value)
4
- password_validation_hash = record.password_validator || {:error_message => 'must be at least 8 characters long and start and end with a letter', :regex => '^[A-Za-z]\w{6,}[A-Za-z]$'}
4
+ password_validation_hash = record.password_validator || {:error_message => 'must be between 8 and 20 characters with no spaces', :regex => '^\S{8,20}$'}
5
5
  record.errors[attribute] << password_validation_hash[:error_message] unless Regexp.new(password_validation_hash[:regex]) =~ value
6
6
  end
7
7
  end
@@ -1,24 +1,302 @@
1
- <!DOCTYPE html>
2
- <html>
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
4
  <head>
4
- <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
7
+ <title>Welcome</title>
8
+ <style type="text/css">
9
+ .ReadMsgBody {
10
+ width: 100%;
11
+ background-color: #ffffff;
12
+ }
13
+
14
+ .ExternalClass {
15
+ width: 100%;
16
+ background-color: #ffffff;
17
+ }
18
+
19
+ html {
20
+ width: 100%;
21
+ }
22
+
23
+ body {
24
+ -webkit-text-size-adjust: none;
25
+ -ms-text-size-adjust: none;
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ table {
31
+ border-spacing: 0;
32
+ border-collapse: collapse;
33
+ }
34
+
35
+ img {
36
+ display: block !important;
37
+ }
38
+
39
+ table td {
40
+ border-collapse: collapse;
41
+ }
42
+
43
+ .top-margin {
44
+ height: 50px;
45
+ }
46
+
47
+ .main-header {
48
+ padding: 50px 100px;
49
+ background: rgba(0, 0, 0, 0.5);
50
+ font-weight: 300;
51
+ }
52
+
53
+ .footer {
54
+ margin-top: 20px;
55
+ }
56
+
57
+ /* ----------- media queries (responsive) ----------- */
58
+
59
+ @media only screen and (max-width: 640px) {
60
+ body .show {
61
+ display: block !important;
62
+ }
63
+
64
+ body .hide {
65
+ display: none !important;
66
+ }
67
+
68
+ body .container590 {
69
+ width: 440px !important;
70
+ }
71
+ }
72
+
73
+ @media only screen and (max-width: 480px) {
74
+ body .show {
75
+ display: block !important;
76
+ }
77
+
78
+ body .hide {
79
+ display: none !important;
80
+ }
81
+
82
+ body .container590 {
83
+ width: 280px !important;
84
+ }
85
+ }
86
+
87
+ </style>
5
88
  </head>
89
+
6
90
  <body>
7
- <h1>Welcome <%= @user.username %></h1>
8
- <% if @temp_password %>
9
- <p>An account has been created for you, to activate your account follow this link: <a href="<%= @url %>"><%= @url %></a></p>
10
91
 
11
- <p>Your username is <%= @user.username %>, your temporary password is <%= @temp_password %>.</p>
92
+ <div class="top-margin"></div>
93
+
94
+ <table border="0" width="100%" cellpadding="0" cellspacing="0">
95
+ <tr>
96
+ <td align="center" bgcolor="323232"
97
+ style="background-image: url('https://truenorthtechnology.com/download/life3.jpg?path=&disposition=inline'); background-size: cover; background-position: top center; background-repeat: repeat;">
98
+
99
+ <table border="0" align="center" cellpadding="0" cellspacing="0" class="container590">
100
+
101
+ <tr>
102
+ <td height="25" style="font-size: 25px; line-height: 25px;">&nbsp;</td>
103
+ </tr>
104
+
105
+
106
+ <tr>
107
+ <td height="30" style="font-size: 30px; line-height: 30px;">&nbsp;</td>
108
+ </tr>
109
+ <tr>
110
+ <td height="50" style="font-size: 50px; line-height: 50px;">&nbsp;</td>
111
+ </tr>
112
+
113
+ <tr>
114
+ <td class="main-header" align="center" height="42"
115
+ style="color: #ffffff; font-size: 32px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 40px;">
116
+
117
+ <!-- ============ title header ============ -->
118
+
119
+ <div style="line-height: 34px;">
120
+ <span>
121
+ <multiline>
122
+ <strong>Welcome <%= @user.username %></strong> <br><br>
123
+ An account has been created for you!
124
+ </multiline>
125
+ </span>
126
+ </div>
127
+ </td>
128
+ </tr>
129
+
130
+ <tr>
131
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
132
+ </tr>
133
+
134
+
135
+ <tr>
136
+ <td height="50" style="font-size: 50px; line-height: 50px;">&nbsp;</td>
137
+ </tr>
138
+
139
+ <tr>
140
+ <td align="center">
141
+
142
+ <table class="cta-button" border="0" align="center" width="163" cellpadding="0" cellspacing="0"
143
+ style="border: none">
144
+ <tr>
145
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
146
+ </tr>
147
+
148
+ <tr>
149
+ <td>
150
+ <table border="0" align="center" cellpadding="0" cellspacing="0">
151
+ <tr>
152
+ <td align="center"
153
+ style="color: #ffffff; font-size: 13px; font-family: 'Open Sans', Calibri, sans-serif; font-weight: 600; line-height: 24px;">
154
+ <!-- ============ headline button ============ -->
155
+ </td>
156
+
157
+ <td width="14" align="right" valign="middle">
158
+
159
+ </td>
160
+ </tr>
161
+ </table>
162
+ </td>
163
+ </tr>
164
+
165
+ <tr>
166
+ <td height="10" style="font-size: 10px; line-height: 10px;">&nbsp;</td>
167
+ </tr>
168
+
169
+ </table>
170
+ </td>
171
+ </tr>
172
+
173
+ <tr>
174
+ <td height="115" style="font-size: 115px; line-height: 115px;">&nbsp;</td>
175
+ </tr>
176
+
177
+ </table>
178
+ </td>
179
+ </tr>
180
+
181
+ </table>
182
+
183
+
184
+ <table width="100%" border="0" align="center" cellpadding="0" cellspacing="0" bgcolor="#ffffff">
185
+ <tr>
186
+ <td height="50">&nbsp;</td>
187
+ </tr>
188
+
189
+ <tr>
190
+ <td>
191
+ <table class="container590" border="0" align="center" width="590" cellpadding="0" cellspacing="0">
192
+ <tr>
193
+ <td>
194
+ <p class="instructions" style="text-align: center; color: #555; font-size: 24px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 42px;">To
195
+ Activate your new account, please follow this link:</p>
196
+
197
+ <p style="text-align: center">
198
+
199
+ <table border="0" align="center" width="124" cellpadding="0" cellspacing="0"
200
+ bgcolor="5ab600" style="border-radius: 3px;">
201
+ <tr>
202
+ <td height="6" style="font-size: 6px; line-height: 6px;">&nbsp;</td>
203
+ </tr>
204
+
205
+ <tr>
206
+ <td>
207
+ <table border="0" align="center" cellpadding="0" cellspacing="0">
208
+ <tr>
209
+ <td align="center"
210
+ style="color: #ffffff; font-size: 13px; font-family: Open Sans, Calibri, sans-serif; font-weight: 700;">
211
+
212
+ <div style="line-height: 24px;">
213
+ <span>
214
+ <a href="<%= @url %>"
215
+ style="color: #ffffff; text-decoration: none;">
216
+ <singleline>Confirm</singleline>
217
+ </a>
218
+ </span>
219
+ </div>
220
+ </td>
221
+ </tr>
222
+ </table>
223
+ </td>
224
+ </tr>
225
+
226
+ <tr>
227
+ <td height="6" style="font-size: 6px; line-height: 6px;">&nbsp;</td>
228
+ </tr>
229
+
230
+ </table>
231
+
232
+ <p style="text-align: center; color: #555; font-size: 12px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 42px;"><%= @url %></p>
233
+
234
+
235
+ <p style="text-align: center; color: #555; font-size: 12px; font-family: 'Open Sans', Calibri, sans-serif; mso-line-height-rule: exactly; line-height: 42px;">Your
236
+ username is: <strong><%= @user.username %></strong>
237
+ <% if @temp_password %>
238
+ Your password is: <strong><%= @temp_password %>
239
+ <% else %>
240
+ , your password is what was used during registration.
241
+ <% end %>
242
+ </strong></p>
243
+
244
+ </td>
245
+ </tr>
246
+ </table>
247
+
248
+ </td>
249
+ </tr>
250
+
251
+ <tr>
252
+ <td height="100" style="font-size: 100px; line-height: 100px;">&nbsp;</td>
253
+ </tr>
254
+
255
+ </table>
256
+
257
+
258
+ <table class="footer" border="0" width="100%" cellpadding="0" cellspacing="0" bgcolor="2d2d2d">
259
+
260
+ <tr>
261
+ <td height="20" style="font-size: 20px; line-height: 20px;">&nbsp;</td>
262
+ </tr>
263
+
264
+ <tr>
265
+ <td align="center">
266
+
267
+ <table border="0" align="center" width="590" class="container590" cellpadding="0" cellspacing="0">
268
+
269
+ <tr>
270
+ <td align="center">
271
+
272
+ <table border="0" class="container590" align="center" cellpadding="0" cellspacing="0">
273
+ <tr>
274
+ <td align="center"
275
+ style="color: #717171; font-size: 14px; font-family: 'Open Sans', Calibri, sans-serif; line-height: 25px;">
276
+ <div style=" line-height: 25px;">
277
+ <span>
278
+ <multiline>
279
+ © <%= Time.now.year %> Copyright. All Rights Reserved.
280
+ </multiline>
281
+ </span>
282
+ </div>
283
+ </td>
284
+ </tr>
285
+
286
+ </table>
287
+
288
+ </td>
289
+ </tr>
290
+
291
+ </table>
292
+ </td>
293
+ </tr>
12
294
 
13
- <p>Please login and update your password.</p>
14
- <% else %>
15
- <p>
16
- You have successfully registered, to activate your account follow this link: <a href="<%= @url %>"><%= @url %></a>
17
- </p>
295
+ <tr>
296
+ <td height="20" style="font-size: 20px; line-height: 20px;">&nbsp;</td>
297
+ </tr>
18
298
 
19
- <p>Your username is <%= @user.username %>, use the password you set during registration to login.</p>
299
+ </table>
20
300
 
21
- <p>Thanks for joining and have a great day!!</p>
22
- <% end %>
23
301
  </body>
24
302
  </html>