erp_tech_svcs 4.0.0 → 4.2.0

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