loggable_activity 0.1.56 → 0.1.58

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -1
  3. data/CHANGELOG.md +9 -0
  4. data/GETTING-STARTED.md +8 -47
  5. data/PAYLOAD_EXAMPLE.md +63 -0
  6. data/docs/LoggableActivity/Activity.html +92 -241
  7. data/docs/LoggableActivity/Configuration.html +162 -15
  8. data/docs/LoggableActivity/ConfigurationError.html +148 -0
  9. data/docs/LoggableActivity/DataOwner.html +138 -0
  10. data/docs/LoggableActivity/Encryption.html +18 -14
  11. data/docs/LoggableActivity/EncryptionError.html +47 -1
  12. data/docs/LoggableActivity/EncryptionKey.html +54 -35
  13. data/docs/LoggableActivity/Error.html +48 -0
  14. data/docs/LoggableActivity/Hooks.html +221 -89
  15. data/docs/LoggableActivity/Payload.html +103 -27
  16. data/docs/LoggableActivity/Services/BasePayloadsBuilder.html +442 -0
  17. data/docs/LoggableActivity/Services/DestroyPayloadsBuilder.html +395 -0
  18. data/docs/LoggableActivity/Services/PayloadsBuilder.html +342 -0
  19. data/docs/LoggableActivity/Services/UpdatePayloadsBuilder.html +490 -0
  20. data/docs/LoggableActivity/Services.html +93 -0
  21. data/docs/created.rid +14 -10
  22. data/docs/index.html +10 -2
  23. data/docs/js/navigation.js.gz +0 -0
  24. data/docs/js/search_index.js +1 -1
  25. data/docs/js/search_index.js.gz +0 -0
  26. data/docs/js/searcher.js.gz +0 -0
  27. data/docs/table_of_contents.html +253 -85
  28. data/lib/generators/loggable_activity/install_generator.rb +2 -18
  29. data/lib/generators/loggable_activity/templates/binary_ids/create_loggable_activities.rb +9 -10
  30. data/lib/generators/loggable_activity/templates/create_loggable_activities.rb +16 -13
  31. data/lib/loggable_activity/activity.rb +53 -102
  32. data/lib/loggable_activity/configuration.rb +60 -3
  33. data/lib/loggable_activity/data_owner.rb +18 -0
  34. data/lib/loggable_activity/encryption.rb +14 -14
  35. data/lib/loggable_activity/encryption_key.rb +26 -25
  36. data/lib/loggable_activity/error.rb +34 -0
  37. data/lib/loggable_activity/hooks.rb +78 -43
  38. data/lib/loggable_activity/payload.rb +70 -20
  39. data/lib/loggable_activity/services/base_payloads_builder.rb +127 -0
  40. data/lib/loggable_activity/services/destroy_payloads_builder.rb +127 -0
  41. data/lib/loggable_activity/services/payloads_builder.rb +98 -0
  42. data/lib/loggable_activity/services/update_payloads_builder.rb +168 -0
  43. data/lib/loggable_activity/version.rb +1 -1
  44. data/lib/loggable_activity.rb +6 -6
  45. data/lib/schemas/config_schema.json +90 -0
  46. metadata +37 -26
  47. data/docs/LoggableActivity/PayloadsBuilder.html +0 -441
  48. data/docs/LoggableActivity/UpdatePayloadsBuilder.html +0 -424
  49. data/lib/generators/loggable_activity/install_templates_generator.rb +0 -105
  50. data/lib/generators/loggable_activity/templates/helpers/activity_helper.rb +0 -37
  51. data/lib/generators/loggable_activity/templates/helpers/router.rb +0 -52
  52. data/lib/generators/loggable_activity/templates/helpers/routes_helper.rb +0 -20
  53. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_create.html.erb +0 -23
  54. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_create.html.slim +0 -18
  55. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_destroy.html.erb +0 -18
  56. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_destroy.html.slim +0 -17
  57. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_show.html.erb +0 -18
  58. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_show.html.slim +0 -17
  59. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_update.html.erb +0 -18
  60. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_update.html.slim +0 -12
  61. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_activity_info.html.erb +0 -12
  62. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_activity_info.html.slim +0 -11
  63. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_list_attrs.html.erb +0 -8
  64. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_list_attrs.html.slim +0 -6
  65. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_update_attrs.html.erb +0 -17
  66. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_update_attrs.html.slim +0 -14
  67. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_updated_relations.html.erb +0 -23
  68. data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_updated_relations.html.slim +0 -21
  69. data/lib/loggable_activity/payloads_builder.rb +0 -153
  70. data/lib/loggable_activity/update_payloads_builder.rb +0 -127
@@ -1,32 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateLoggableActivities < ActiveRecord::Migration[6.1]
3
+ class CreateLoggableActivities < ActiveRecord::Migration[7.1]
4
4
  def change
5
+ create_table :loggable_data_owners do |t|
6
+ t.references :record, polymorphic: true, null: true, index: true
7
+ t.references :encryption_key, null: false, foreign_key: { to_table: 'loggable_encryption_keys' }
8
+ end
9
+
5
10
  create_table :loggable_activities do |t|
6
11
  t.string :action
7
12
  t.references :actor, polymorphic: true, null: true
8
- t.string :encrypted_actor_display_name
9
- t.string :encrypted_record_display_name
13
+ t.string :encrypted_actor_name
10
14
  t.references :record, polymorphic: true, null: true
11
-
12
15
  t.timestamps
13
16
  end
14
17
 
15
18
  create_table :loggable_payloads do |t|
16
- t.references :record, polymorphic: true, null: true
19
+ t.references :activity, null: false, foreign_key: { to_table: 'loggable_activities' }
20
+ t.references :encryption_key, null: false, foreign_key: { to_table: 'loggable_encryption_keys' }
21
+ t.references :record, polymorphic: true, null: true, index: true
22
+ t.string :encrypted_record_name
17
23
  t.json :encrypted_attrs
18
- t.integer :payload_type, default: 0
24
+ t.integer :related_to_activity_as, default: 0
19
25
  t.boolean :data_owner, default: false
20
- t.string :route, default: nil
21
- t.references :activity, foreign_key: { to_table: 'loggable_activities', class_name: 'LoggableActivity::Activity' }
22
-
23
- t.timestamps
26
+ t.string :route
27
+ t.boolean :current_payload, default: true
24
28
  end
25
29
 
26
30
  create_table :loggable_encryption_keys do |t|
27
- t.references :parent_key, foreign_key: { to_table: 'loggable_encryption_keys', on_delete: :nullify }
28
- t.string :key
29
- t.references :record, polymorphic: true, on_delete: :nullify
31
+ t.references :record, polymorphic: true, null: true, index: true
32
+ t.string :secret_key
30
33
  end
31
34
  end
32
35
  end
@@ -7,9 +7,9 @@ module LoggableActivity
7
7
  class Activity < ActiveRecord::Base
8
8
  self.table_name = 'loggable_activities'
9
9
  # Associations
10
- has_many :payloads, class_name: 'LoggableActivity::Payload', dependent: :destroy
11
- belongs_to :record, polymorphic: true, optional: true
10
+ has_many :payloads, class_name: '::LoggableActivity::Payload', dependent: :destroy
12
11
  belongs_to :actor, polymorphic: true, optional: true
12
+ belongs_to :record, polymorphic: true, optional: true
13
13
 
14
14
  accepts_nested_attributes_for :payloads
15
15
 
@@ -22,7 +22,7 @@ module LoggableActivity
22
22
  #
23
23
  # Each hash in the array contains:
24
24
  # - :record_type: The class name of the record.
25
- # - :payload_type: A descriptor of the payload's role (e.g., 'primary_payload' or 'current_association').
25
+ # - :related_to_activity_as: A descriptor of the payload's role (e.g., 'primary_payload' or 'current_association').
26
26
  # - :attrs: A hash of the record's attributes.
27
27
  #
28
28
  # Example usage:
@@ -30,65 +30,36 @@ module LoggableActivity
30
30
  #
31
31
  # Sample return value:
32
32
  # [
33
- # { record_type: MODEL_NAME, payload_type: "primary_payload", attrs: { "KEY" => "VALUE", ... } },
34
- # { record_type: MODEL_NAME, payload_type: "current_association", attrs: { "KEY" => "VALUE", ... } },
33
+ # { record_type: MODEL_NAME, related_to_activity_as: "primary_payload", attrs: { "KEY" => "VALUE", ... } },
34
+ # { record_type: MODEL_NAME, related_to_activity_as: "current_association", attrs: { "KEY" => "VALUE", ... } },
35
35
  # ...
36
36
  # ]
37
37
  def attrs
38
+ {
39
+ actor_type:,
40
+ actor_id:,
41
+ action:,
42
+ actor_display_name:,
43
+ record_display_name:,
44
+ payloads: payloads_attrs
45
+ }
46
+ end
47
+
48
+ def payloads_attrs
38
49
  ordered_payloads.map do |payload|
39
50
  {
51
+ related_to_activity_as: payload.related_to_activity_as,
40
52
  record_type: payload.record_type,
41
- record_id: payload.record_id,
42
- payload_type: payload.payload_type,
53
+ record_id: payload.deleted? ? nil : payload.record_id,
43
54
  attrs: payload.attrs,
44
- route: payload.payload_route
55
+ route: payload.payload_route,
56
+ record_display_name: payload.record_display_name,
57
+ current_payload: payload.current_payload,
58
+ data_owner: payload.data_owner
45
59
  }
46
60
  end
47
61
  end
48
62
 
49
- # Returns a hash describing the attributes of an update activity, including updated attributes for a record and any updated related attributes.
50
- #
51
- # Example:
52
- # @activity.update_activity_attrs
53
- #
54
- # The return value is structured as follows:
55
- # - :update_attrs contains the attributes of the record being updated, detailing changes from previous to new values.
56
- # - :updated_relations_attrs is an array of hashes, each representing an updated related record. Each hash details the previous and current attributes of the relation.
57
- #
58
- # Example Return Structure:
59
- # {
60
- # update_attrs: {
61
- # record_class: "CLASS.NAME",
62
- # attrs: [{ "KEY" => { from: "OLD_VALUE", to: "NEW_VALUE" } }]
63
- # },
64
- # updated_relations_attrs: [
65
- # {
66
- # record_class: "CLASS.NAME",
67
- # previous_attrs: { attrs: { "KEY" => "VALUE", ... } },
68
- # current_attrs: { attrs: { "KEY" => "VALUE", ... } }
69
- # }
70
- # ]
71
- # }
72
- def update_activity_attrs
73
- {
74
- update_attrs:,
75
- updated_relations_attrs:
76
- }
77
- end
78
-
79
- # Returns the attributes for the primary payload, without the relations.
80
- #
81
- # Example:
82
- #
83
- # @activity.primary_payload_attrs
84
- #
85
- # Returns:
86
- # { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
87
- #
88
- def primary_payload_attrs
89
- primary_payload ? primary_payload.attrs : {}
90
- end
91
-
92
63
  # Returns the attributes for the relations.
93
64
  #
94
65
  # Example:
@@ -100,13 +71,13 @@ module LoggableActivity
100
71
  # {
101
72
  # record_type: CLASS.NAME,
102
73
  # record_id: INTEGER,
103
- # payload_type: ENUM
74
+ # related_to_activity_as: ENUM
104
75
  # attrs: { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
105
76
  # }
106
77
  # ]
107
- def relations_attrs
108
- attrs.filter { |p| p[:payload_type] == 'current_association' }
109
- end
78
+ # def relations_attrs
79
+ # attrs.filter { |p| p[:related_to_activity_as] == 'current_association' }
80
+ # end
110
81
 
111
82
  # Returns the display name for a record. what method to use if defined in '/config/loggable_activity.yaml'
112
83
  #
@@ -118,9 +89,7 @@ module LoggableActivity
118
89
  # "David Bowie"
119
90
  #
120
91
  def record_display_name
121
- return I18n.t('loggable.activity.deleted') if encrypted_record_display_name.nil?
122
-
123
- LoggableActivity::Encryption.decrypt(encrypted_record_display_name, record_key)
92
+ primary_payload.record_display_name
124
93
  end
125
94
 
126
95
  # Returns the path for the activity.
@@ -132,8 +101,8 @@ module LoggableActivity
132
101
  # Returns:
133
102
  # "/path/to/activity"
134
103
  #
135
- def primary_route
136
- primary_payload&.payload_route
104
+ def primary_route
105
+ primary_payload.payload_route
137
106
  end
138
107
 
139
108
  # Returns the display name for a actor. what method to use if defined in '/config/loggable_activity.yaml'
@@ -146,28 +115,30 @@ module LoggableActivity
146
115
  # "Elvis Presley"
147
116
  #
148
117
  def actor_display_name
149
- return I18n.t('loggable.activity.deleted') if encrypted_actor_display_name.nil?
118
+ return I18n.t('loggable.activity.deleted') if actor_deleted?
150
119
 
151
- LoggableActivity::Encryption.decrypt(encrypted_actor_display_name, actor_key)
120
+ ::LoggableActivity::Encryption.decrypt(encrypted_actor_name, actor_secret_key)
152
121
  end
153
122
 
154
123
  # Returns a list of activities for a given actor.
155
124
  def self.activities_for_actor(actor, limit = 20, params = { offset: 0 })
156
- LoggableActivity::Activity.latest(limit, params).where(actor:)
125
+ ::LoggableActivity::Activity.latest(limit, params).where(actor:)
157
126
  end
158
127
 
159
128
  # Returns a list of activities ordered by creation date.
129
+ # This is done to support UUID primary keys.
160
130
  def self.latest(limit = 20, params = { offset: 0 })
161
131
  offset = params[:offset] || 0
162
- LoggableActivity::Activity
132
+ ::LoggableActivity::Activity
163
133
  .all
164
134
  .order(created_at: :desc)
165
- .includes(:payloads)
135
+ .includes(payloads: :encryption_key)
166
136
  .offset(offset)
167
137
  .limit(limit)
168
138
  end
169
139
 
170
140
  # Returns the last activity.
141
+ # This is done to support of UUID primary keys.
171
142
  def self.last(limit = 1)
172
143
  return latest(1).first if limit == 1
173
144
 
@@ -180,57 +151,37 @@ module LoggableActivity
180
151
  #
181
152
  # Example usage:
182
153
  # payload = @activity.primary_payload
183
- # puts payload.record_type # => 'SOMD_MODEL_NAME'
154
+ # payload.record_type # => 'SOMD_MODEL_NAME'
184
155
  #
185
156
  def primary_payload
186
- ordered_payloads.find { |p| p.payload_type == 'primary_payload' }
157
+ related_to_activity_as = %w[primary_payload primary_update_payload primary_destroy_payload]
158
+ payloads.detect { |p| related_to_activity_as.include?(p.related_to_activity_as) }
187
159
  end
188
160
 
189
- # Returns the attributes for the update+payload.
190
- def update_attrs
191
- update_payload_attrs = attrs.find { |p| p[:payload_type] == 'update_payload' }
192
- return nil unless update_payload_attrs
193
-
194
- update_payload_attrs.delete(:payload_type)
195
- update_payload_attrs
196
- end
197
-
198
-
199
- # Returns the attributes for the updated relations.
200
- def updated_relations_attrs
201
- grouped_associations = attrs.group_by { |p| p[:record_type] }
202
-
203
- grouped_associations.map do |record_type, payloads|
204
- previous_attrs = payloads.find { |p| p[:payload_type] == 'previous_association' }
205
- current_attrs = payloads.find { |p| p[:payload_type] == 'current_association' }
206
- next if previous_attrs.nil? && current_attrs.nil?
207
-
208
- { record_type:, previous_attrs:, current_attrs: }
209
- end.compact
210
- end
211
-
212
- # Returns the previous association attributes.
213
- def previous_associations_attrs
214
- attrs.select { |p| p[:payload_type] == 'previous_association' }
215
- end
216
-
217
- # Returns payloads sorted by :payload_type.
161
+ # # Returns payloads sorted by :related_to_activity_as.
218
162
  def ordered_payloads
219
- payloads.order(:payload_type)
163
+ # payloads.order(related_to_activity_as: :desc)
164
+ payloads
220
165
  end
221
166
 
222
167
  # Returns the key for the logged record.
223
168
  def record_key
224
169
  return nil if record.nil?
225
170
 
226
- LoggableActivity::EncryptionKey.for_record(record)&.key
171
+ ::LoggableActivity::EncryptionKey.for_record(record)&.secret_key
227
172
  end
228
173
 
229
- # Returns the key for the actor.
230
- def actor_key
231
- return nil if actor.nil?
174
+ # Check if the actor is deleted.
175
+ # If the actor is deleted, it will return true.
176
+ # This way we don't rely on access to the main DB to check if the actor is deleted.
177
+ def actor_deleted?
178
+ actor_secret_key.nil?
179
+ end
232
180
 
233
- LoggableActivity::EncryptionKey.for_record(actor)&.key
181
+ # Returns the key for the actor.
182
+ def actor_secret_key
183
+ # @actor_key_secret_key ||=
184
+ ::LoggableActivity::EncryptionKey.for_record_by_type_and_id(actor_type, actor_id)&.secret_key
234
185
  end
235
186
 
236
187
  # Validates that the activity has at least one payload.
@@ -1,20 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json-schema'
4
+ require 'json'
5
+
3
6
  module LoggableActivity
4
7
  # This class is used to load the configuration file located at config/loggable_activity.yml
8
+ class ConfigurationError < StandardError
9
+ def initialize(msg = '')
10
+ # https://api.loggable_activity.com/msg
11
+ puts '---------------- LOGGABLE ACTIVITY -----------------'
12
+ puts msg
13
+ puts '----------------------------------------------------'
14
+ super(msg)
15
+ end
16
+ end
17
+
18
+ # This class is used to load the configuration file located at config/loggable_activity.yml
19
+ # When the LoggableActivity::Hook is included in a model
20
+ # it takes the model's name and find the configuration for that model in the configuration file.
5
21
  class Configuration
6
22
  # Loads the configuration file
7
23
  def self.load_config_file(config_file_path)
8
24
  @config_data = YAML.load_file(config_file_path)
25
+ validate_config_file
26
+ rescue Errno::ENOENT
27
+ raise ConfigurationError, 'config/loggable_activity.yaml not found'
28
+ end
29
+
30
+ # Loads the schema file for the configuration file
31
+ def self.load_schema
32
+ schema_path = File.join(__dir__, '..', 'schemas', 'config_schema.json')
33
+ JSON.parse(File.read(schema_path))
34
+ end
35
+
36
+ # Validates the configuration file againss the schema
37
+ def self.validate_config_file
38
+ schema = load_schema
39
+ errors = JSON::Validator.fully_validate(schema, @config_data)
40
+ return unless errors.any?
41
+
42
+ raise ConfigurationError,
43
+ "config/loggable_activity.yaml is invalid: #{errors.join(', ')}"
44
+ end
45
+
46
+ # Returns true if the configuration file has been loaded
47
+ def self.loaded?
48
+ !@config_data.nil?
49
+ end
50
+
51
+ # Returns the configuration data
52
+ class << self
53
+ # @return [Hash]
54
+ attr_reader :config_data
9
55
  end
10
56
 
11
57
  # Returns the configuration data for the given class
12
58
  #
13
59
  # Example:
14
- # LoggableActivity::Configuration.for_class('User')
60
+ # ::LoggableActivity::Configuration.for_class('User')
15
61
  # Returns:
16
62
  # {
17
- # "record_display_name": "full_name",
63
+ # "fetch_record_name_from": "full_name",
18
64
  # "loggable_attrs": [
19
65
  # "first_name",
20
66
  # "last_name",
@@ -23,10 +69,21 @@ module LoggableActivity
23
69
  # "create",
24
70
  # "update",
25
71
  # "destroy"
26
- # ]
72
+ # ],
73
+ # ....
27
74
  # }
28
75
  def self.for_class(class_name)
29
76
  @config_data[class_name]
30
77
  end
78
+
79
+ # Returns the name of the field or method to use for the actor's display name.
80
+ def self.fetch_current_user_name_from
81
+ @config_data['fetch_current_user_name_from']
82
+ end
83
+
84
+ # Returns the name of the model to use for the current user.
85
+ def self.current_user_model_name
86
+ @config_data['current_user_model_name']
87
+ end
31
88
  end
32
89
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+
5
+ module LoggableActivity
6
+ # This class represends an additional data owner for a record.
7
+ # For it to kick in, the data_owner configuration has to be set to true in the loggable_activity.yaml file.
8
+ class DataOwner < ActiveRecord::Base
9
+ self.table_name = 'loggable_data_owners'
10
+ belongs_to :record, polymorphic: true, optional: true
11
+ belongs_to :encryption_key, class_name: '::LoggableActivity::EncryptionKey'
12
+
13
+ # When a record is deleted, all data owner added to the record is also deleted.
14
+ def mark_as_deleted!
15
+ encryption_key.mark_as_deleted!
16
+ end
17
+ end
18
+ end
@@ -5,24 +5,21 @@ require 'openssl'
5
5
  require 'base64'
6
6
 
7
7
  module LoggableActivity
8
- # This error is raised when encryption or decryption fails
9
- class EncryptionError < StandardError
10
- end
11
-
12
8
  # This module is used to encrypt and decrypt attributes
13
9
  module Encryption
14
10
  # Encrypts the given data using the given encryption key
15
11
  #
16
12
  # Example:
17
- # LoggableActivity::Encryption.encrypt('my secret data', 'my secret key')
13
+ # ::LoggableActivity::Encryption.encrypt('my secret data', 'my secret key')
18
14
  #
19
15
  # Returns:
20
16
  # "SOME_ENCRYPTED_STRING"
21
17
  #
22
- def self.encrypt(data, encoded_key)
23
- return nil if data.nil? || encoded_key.nil?
18
+ def self.encrypt(data, secret_key)
19
+ return nil if secret_key.nil?
20
+ return nil if data.nil?
24
21
 
25
- encryption_key = Base64.decode64(encoded_key)
22
+ encryption_key = Base64.decode64(secret_key)
26
23
  raise EncryptionError, "Encryption failed: Invalid encoded_key length #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
27
24
 
28
25
  cipher = OpenSSL::Cipher.new('AES-256-CBC').encrypt
@@ -39,19 +36,21 @@ module LoggableActivity
39
36
  # Decrypts the given data using the given encryption key
40
37
  #
41
38
  # Example:
42
- # LoggableActivity::Encryption.decrypt('SOME_ENCRYPTED_STRING', 'SECRET_KEY')
39
+ # ::LoggableActivity::Encryption.decrypt('SOME_ENCRYPTED_STRING', 'SECRET_KEY')
43
40
  #
44
41
  # Returns:
45
42
  # "my secret data"
46
43
  #
47
- def self.decrypt(data, encoded_key)
48
- return '' if data.nil? || encoded_key.nil?
44
+ def self.decrypt(data, secret_key)
45
+ return I18n.t('loggable.activity.deleted') if secret_key.nil?
46
+ return '' if data.blank?
49
47
 
50
- encryption_key = Base64.decode64(encoded_key)
48
+ encryption_key = Base64.decode64(secret_key)
51
49
  raise EncryptionError, "Decryption failed: Invalid encoded_key length: #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
52
50
 
53
51
  cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
54
52
  cipher.key = encryption_key
53
+ raise EncryptionError, 'Decryption failed: Invalid data length' unless data.bytesize > cipher.iv_len
55
54
 
56
55
  raw_data = Base64.decode64(data)
57
56
  cipher.iv = raw_data[0...cipher.iv_len] # Extract IV from the beginning of raw_data
@@ -59,9 +58,10 @@ module LoggableActivity
59
58
 
60
59
  decrypted_data.force_encoding('UTF-8')
61
60
  rescue OpenSSL::Cipher::CipherError => e
62
- raise EncryptionError, "Decryption failed: #{e.message}"
61
+ puts "CipherError Decryption failed: #{e.message}"
62
+ '*** DECRYPTION FAILED ***'
63
63
  rescue EncryptionError => e
64
- puts e.message
64
+ puts "EncryptionError failed: #{e.message}"
65
65
  '*** DECRYPTION FAILED ***'
66
66
  end
67
67
 
@@ -10,24 +10,29 @@ module LoggableActivity
10
10
 
11
11
  # Associations
12
12
  belongs_to :record, polymorphic: true, optional: true
13
- belongs_to :parent_key, class_name: 'LoggableActivity::EncryptionKey', optional: true,
14
- foreign_key: 'parent_key_id'
13
+ # belongs_to :payload, class_name: '::LoggableActivity::Payload', optional: true
14
+ # belongs_to :parent_key, class_name: '::LoggableActivity::EncryptionKey', optional: true,
15
+ # foreign_key: 'parent_key_id'
15
16
 
16
17
  # Marks the encryption key as deleted by updating the key to nil.
17
- def mark_as_deleted
18
- update(key: nil)
19
- parent_key.mark_as_deleted if parent_key.present?
18
+ def mark_as_deleted!
19
+ update(secret_key: nil)
20
+ end
21
+
22
+ # check if the encryption key is deleted
23
+ def deleted?
24
+ secret_key.nil?
20
25
  end
21
26
 
22
27
  # Returns an encryption key for a record by its type and ID, optionally using a parent key.
23
28
  #
24
29
  # @param record_type [String] The type of the record.
25
30
  # @param record_id [Integer] The ID of the record.
26
- # @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
27
- # @return [LoggableActivity::EncryptionKey] The encryption key for the record.
31
+ # @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
32
+ # @return [::LoggableActivity::EncryptionKey] The encryption key for the record.
28
33
  #
29
34
  # Example:
30
- # LoggableActivity::EncryptionKey.for_record_by_type_and_id('User', 1)
35
+ # ::LoggableActivity::EncryptionKey.for_record_by_type_and_id('User', 1)
31
36
  #
32
37
  # Returns:
33
38
  # {
@@ -38,22 +43,22 @@ module LoggableActivity
38
43
  # :record_id => 1
39
44
  # }
40
45
  #
41
- def self.for_record_by_type_and_id(record_type, record_id, parent_key = nil)
46
+ def self.for_record_by_type_and_id(record_type, record_id)
42
47
  encryption_key = find_by(record_type:, record_id:)
43
48
  return encryption_key if encryption_key
44
49
 
45
- create_encryption_key(record_type, record_id, parent_key)
50
+ create_encryption_key(record_type, record_id)
46
51
  end
47
52
 
48
53
  # Returns an encryption key for a record, optionally using a parent key.
49
54
  #
50
55
  # @param record [ActiveRecord::Base] The record for which to get the encryption key.
51
- # @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
52
- # @return [LoggableActivity::EncryptionKey] The encryption key for the record.
56
+ # @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
57
+ # @return [::LoggableActivity::EncryptionKey] The encryption key for the record.
53
58
  #
54
59
  # Example:
55
60
  # user = User.find(1)
56
- # LoggableActivity::EncryptionKey.for_record(user)
61
+ # ::LoggableActivity::EncryptionKey.for_record(user)
57
62
  #
58
63
  # Returns:
59
64
  # {
@@ -64,24 +69,24 @@ module LoggableActivity
64
69
  # :record_id => 1
65
70
  # }
66
71
  #
67
- def self.for_record(record, parent_key = nil)
72
+ def self.for_record(record)
68
73
  return nil if record.nil?
69
74
 
70
75
  encryption_key = find_by(record:)
71
76
  return encryption_key if encryption_key
72
77
 
73
- create_encryption_key(record.class.name, record.id, parent_key)
78
+ create_encryption_key(record.class.name, record.id)
74
79
  end
75
80
 
76
81
  # Creates an encryption key for a record, optionally using a parent key.
77
82
  #
78
83
  # @param record_type [String] The type of the record.
79
84
  # @param record_id [Integer] The ID of the record.
80
- # @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
81
- # @return [LoggableActivity::EncryptionKey] The created encryption key.
85
+ # @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
86
+ # @return [::LoggableActivity::EncryptionKey] The created encryption key.
82
87
  #
83
88
  # Example:
84
- # LoggableActivity::EncryptionKey.create_encryption_key('User', 1)
89
+ # ::LoggableActivity::EncryptionKey.create_encryption_key('User', 1)
85
90
  #
86
91
  # Returns:
87
92
  # {
@@ -92,12 +97,8 @@ module LoggableActivity
92
97
  # :record_id => 1
93
98
  # }
94
99
  #
95
- def self.create_encryption_key(record_type, record_id, parent_key = nil)
96
- if parent_key
97
- create(record_type:, record_id:, key: random_key, parent_key:)
98
- else
99
- create(record_type:, record_id:, key: random_key)
100
- end
100
+ def self.create_encryption_key(record_type, record_id)
101
+ create(record_type:, record_id:, secret_key: random_key)
101
102
  end
102
103
 
103
104
  # Generates a random encryption key.
@@ -105,7 +106,7 @@ module LoggableActivity
105
106
  # @return [String] The generated encryption key.
106
107
  #
107
108
  # Example:
108
- # LoggableActivity::EncryptionKey.random_key
109
+ # ::LoggableActivity::EncryptionKey.random_key
109
110
  #
110
111
  # Returns:
111
112
  # "a8f4774e7f42eb253045a4db7de7b79e"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoggableActivity
4
+ # Error class for loggable activity.
5
+ class Error < StandardError
6
+ def initialize(msg = '')
7
+ puts '---------------- LOGGABLE ACTIVITY -----------------'
8
+ puts msg
9
+ puts '----------------------------------------------------'
10
+ super(msg)
11
+ end
12
+ end
13
+
14
+ # Error class for encryption.
15
+ class EncryptionError < StandardError
16
+ def initialize(msg = '')
17
+ puts '---------------- LOGGABLE ACTIVITY -----------------'
18
+ puts msg
19
+ puts '----------------------------------------------------'
20
+ super(msg)
21
+ end
22
+ end
23
+
24
+ # This class is used to load the configuration file located at config/loggable_activity.yml
25
+ class ConfigurationError < StandardError
26
+ def initialize(msg = '')
27
+ # https://api.loggable_activity.com/msg
28
+ puts '---------------- LOGGABLE ACTIVITY -----------------'
29
+ puts msg
30
+ puts '----------------------------------------------------'
31
+ super(msg)
32
+ end
33
+ end
34
+ end