rails_audit_log-graphql 0.5.0 → 0.6.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +51 -0
- data/ROADMAP.md +0 -9
- data/lib/generators/rails_audit_log/graphql/install/install_generator.rb +22 -1
- data/lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb +41 -7
- data/lib/rails_audit_log/graphql/schema_plugin.rb +14 -0
- data/lib/rails_audit_log/graphql/sources/record_by_id_source.rb +21 -0
- data/lib/rails_audit_log/graphql/types/actor_type.rb +7 -0
- data/lib/rails_audit_log/graphql/types/audit_log_entry_type.rb +3 -3
- data/lib/rails_audit_log/graphql/types/audit_log_json_scalar.rb +22 -0
- data/lib/rails_audit_log/graphql/types/audited_resource_type.rb +6 -0
- data/lib/rails_audit_log/graphql/version.rb +1 -1
- data/lib/rails_audit_log/graphql.rb +11 -0
- data/sig/rails_audit_log/graphql.rbs +42 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1df5ae47e9bae5cc635eb04372e8a84f3c484350f50284d98ab4602d9f2175c
|
|
4
|
+
data.tar.gz: 5bd2df5463caab8646bfab17973cd85343819b94fb189d6c3d52bbe65cbf11ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3899c333a5af6c7f9c1a05cfe1bc882f8464ad9da9d21ed677c75ce7ec6546a6181893d8a27f563822d46ed4bfb7d6f91e8349285d1bc83e3cc6aaae2a8d5147
|
|
7
|
+
data.tar.gz: cdc13b1d159a794f67fa1c46a3410b3196529dba59e234eba32147338d15e85a2478bcb676821738256792e4358100a4c07bc96cfb855c00d68db828807a1d48
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.1] - 2026-06-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `actorType:` filter argument on `auditLogEntries`, `auditLogEntriesConnection`, and `auditLogEntriesCount` — filter by actor model class name (e.g. `"User"`)
|
|
8
|
+
- `forTenant:` argument on `auditLogReify` and `auditLogEntriesCount` — consistent tenant scoping across all query fields; both also respect `RailsAuditLog.current_tenant` auto-tenant
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- `auditLogReify` return type changed from generic `JSON` to `AuditLogJson` for consistency with other entry fields
|
|
13
|
+
- `rails g rails_audit_log:graphql:install` now also injects `SchemaPlugin` into the host schema file (detected via `app/graphql/**/*schema*.rb` glob); `print_next_steps` updated to mention all available queries and the complexity config override
|
|
14
|
+
|
|
15
|
+
## [0.6.0] - 2026-06-04
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `AuditLogJsonScalar` (`AuditLogJson`) — custom scalar type for `objectChanges`, `object`, and `metadata` fields; replaces the generic `JSON` scalar with a self-documenting type
|
|
20
|
+
- `RecordByIdSource` — `GraphQL::Dataloader::Source` that batch-loads AR records by class name and ID; eliminates N+1 queries when resolving `actor.record` and `auditedResource.record` on list responses
|
|
21
|
+
- `record` field on `AuditLogActor` and `AuditedResource` — returns the database record as JSON, batch-loaded via dataloader
|
|
22
|
+
- `auditLogReify(itemType:, itemId:, at:)` — reconstructs the attribute state of a record at a given point in time; returns `JSON` or `nil` when the record was destroyed or no entry exists
|
|
23
|
+
- `SchemaPlugin` — include into host schema to apply `max_complexity` (200), `max_depth` (10), `default_max_page_size` (25), and `use GraphQL::Dataloader`; all limits configurable via `RailsAuditLog::Graphql.max_complexity=` etc.
|
|
24
|
+
|
|
3
25
|
## [0.5.0] - 2026-06-04
|
|
4
26
|
|
|
5
27
|
### Added
|
data/README.md
CHANGED
|
@@ -23,6 +23,8 @@ A [graphql-ruby](https://graphql-ruby.org) API layer for the [`rails_audit_log`]
|
|
|
23
23
|
- [auditLogEntriesCount](#auditlogentriescount-int)
|
|
24
24
|
- [Tenant scoping](#tenant-scoping)
|
|
25
25
|
- [Authentication](#authentication)
|
|
26
|
+
- [SchemaPlugin](#schemaplugin)
|
|
27
|
+
- [auditLogReify](#auditlogreify)
|
|
26
28
|
- [Subscriptions](#subscriptions)
|
|
27
29
|
- [AuditLogSubscriptionsMixin](#auditlogsubscriptionsmixin)
|
|
28
30
|
- [auditLogEntryCreated](#auditlogentrycreated)
|
|
@@ -150,6 +152,7 @@ List entries with optional filters and offset pagination.
|
|
|
150
152
|
| `itemType` | `String` | — | Filter by audited model class name |
|
|
151
153
|
| `itemId` | `ID` | — | Filter by audited record ID |
|
|
152
154
|
| `actorId` | `ID` | — | Filter by actor ID |
|
|
155
|
+
| `actorType` | `String` | — | Filter by actor model class name (e.g. `"User"`) |
|
|
153
156
|
| `since` | `ISO8601DateTime` | — | Return entries created at or after this time |
|
|
154
157
|
| `until` | `ISO8601DateTime` | — | Return entries created at or before this time |
|
|
155
158
|
| `touching` | `String` | — | Filter to entries that changed a specific attribute |
|
|
@@ -170,6 +173,7 @@ Same filters as `auditLogEntries`, but returns a [Relay-style connection](https:
|
|
|
170
173
|
| `itemType` | `String` | Filter by audited model class name |
|
|
171
174
|
| `itemId` | `ID` | Filter by audited record ID |
|
|
172
175
|
| `actorId` | `ID` | Filter by actor ID |
|
|
176
|
+
| `actorType` | `String` | Filter by actor model class name (e.g. `"User"`) |
|
|
173
177
|
| `forTenant` | `String` | Scope to a specific tenant ID; overrides auto-tenant |
|
|
174
178
|
| `first` | `Int` | Return the first N edges after `after` |
|
|
175
179
|
| `after` | `String` | Cursor to paginate forward from |
|
|
@@ -219,7 +223,9 @@ Returns the count of matching audit log entries. Respects auto-tenant when `Rail
|
|
|
219
223
|
|---|---|---|
|
|
220
224
|
| `event` | `String` | Filter by event type (`create`, `update`, `destroy`) |
|
|
221
225
|
| `itemType` | `String` | Filter by audited model class name |
|
|
226
|
+
| `actorType` | `String` | Filter by actor model class name (e.g. `"User"`) |
|
|
222
227
|
| `since` | `ISO8601DateTime` | Count entries created at or after this time |
|
|
228
|
+
| `forTenant` | `String` | Scope to a specific tenant ID; overrides auto-tenant |
|
|
223
229
|
|
|
224
230
|
```graphql
|
|
225
231
|
{ auditLogEntriesCount(event: "update", itemType: "Post") }
|
|
@@ -259,6 +265,51 @@ If no authenticate block is set, all queries are permitted.
|
|
|
259
265
|
|
|
260
266
|
[↑ Back to top](#table-of-contents)
|
|
261
267
|
|
|
268
|
+
### SchemaPlugin
|
|
269
|
+
|
|
270
|
+
Include `RailsAuditLog::Graphql::SchemaPlugin` into your schema to enable query protection and dataloader batching in one step:
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
class MySchema < GraphQL::Schema
|
|
274
|
+
include RailsAuditLog::Graphql::SchemaPlugin
|
|
275
|
+
query Types::QueryType
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This applies the following defaults (all overridable via `RailsAuditLog::Graphql.*=`):
|
|
280
|
+
|
|
281
|
+
| Setting | Default | Description |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `max_complexity` | `200` | Reject queries whose field-complexity sum exceeds this |
|
|
284
|
+
| `max_depth` | `10` | Reject queries nested deeper than this |
|
|
285
|
+
| `default_max_page_size` | `25` | Assumed page size for connection complexity calculation |
|
|
286
|
+
|
|
287
|
+
Override in an initializer:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
RailsAuditLog::Graphql.max_complexity = 500
|
|
291
|
+
RailsAuditLog::Graphql.max_depth = 15
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The plugin also adds `AuditLogActor.record` and `AuditedResource.record` fields — nullable JSON fields that load the actual database record via `RecordByIdSource`, a `GraphQL::Dataloader::Source` that batches loads by class name to eliminate N+1 queries on list responses.
|
|
295
|
+
|
|
296
|
+
[↑ Back to top](#table-of-contents)
|
|
297
|
+
|
|
298
|
+
### `auditLogReify(itemType:, itemId:, at:): AuditLogJson`
|
|
299
|
+
|
|
300
|
+
Reconstructs the attribute state of a record at a given point in time. Returns the attributes as `AuditLogJson`, or `nil` when no entry exists at or before `at` or the record was destroyed at that time. Accepts `forTenant:` and respects auto-tenant.
|
|
301
|
+
|
|
302
|
+
```graphql
|
|
303
|
+
{
|
|
304
|
+
auditLogReify(itemType: "Post", itemId: "42", at: "2026-01-15T12:00:00Z") {
|
|
305
|
+
title
|
|
306
|
+
publishedAt
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
[↑ Back to top](#table-of-contents)
|
|
312
|
+
|
|
262
313
|
### Subscriptions
|
|
263
314
|
|
|
264
315
|
Requires Action Cable in the host application.
|
data/ROADMAP.md
CHANGED
|
@@ -4,15 +4,6 @@ This gem adds a GraphQL API layer on top of [`rails_audit_log`](https://github.c
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 0.6.0 — Performance & Safety
|
|
8
|
-
|
|
9
|
-
- **Dataloader batch loading** — batch-resolve polymorphic `actor` and `item` associations using graphql-ruby's `dataloader` to eliminate N+1 queries on list responses
|
|
10
|
-
- **`auditLogReify` query** — `auditLogReify(itemType:, itemId:, at:)` returns the reconstructed object state as JSON at a given point in time, backed by `RailsAuditLog.version_at`
|
|
11
|
-
- **Query complexity & depth limits** — built-in defaults (`max_complexity`, `max_depth`) with a config override (`RailsAuditLogGraphql.max_complexity = 200`) to protect against expensive queries before the API is declared stable
|
|
12
|
-
- **`AuditLogJsonScalar`** — proper JSON scalar type for `objectChanges` and `metadata` fields, replacing opaque String serialization and making the schema self-documenting
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
7
|
## 1.0.0 — Stable API
|
|
17
8
|
|
|
18
9
|
- Full YARD documentation
|
|
@@ -7,10 +7,11 @@ module RailsAuditLog
|
|
|
7
7
|
module Graphql
|
|
8
8
|
class InstallGenerator < Rails::Generators::Base
|
|
9
9
|
source_root File.expand_path("templates", __dir__)
|
|
10
|
-
desc "Injects AuditLogEntriesQueryMixin into your GraphQL QueryType."
|
|
10
|
+
desc "Injects AuditLogEntriesQueryMixin into your GraphQL QueryType and SchemaPlugin into your schema."
|
|
11
11
|
|
|
12
12
|
QUERY_TYPE_PATH = "app/graphql/types/query_type.rb"
|
|
13
13
|
MIXIN = "RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin"
|
|
14
|
+
SCHEMA_PLUGIN = "RailsAuditLog::Graphql::SchemaPlugin"
|
|
14
15
|
|
|
15
16
|
def inject_mixin
|
|
16
17
|
if File.exist?(File.join(destination_root, QUERY_TYPE_PATH))
|
|
@@ -24,11 +25,31 @@ module RailsAuditLog
|
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
def inject_schema_plugin
|
|
29
|
+
schema_files = Dir.glob(File.join(destination_root, "app/graphql/**/*schema*.rb"))
|
|
30
|
+
if schema_files.any?
|
|
31
|
+
schema_path = schema_files.first.delete_prefix("#{destination_root}/")
|
|
32
|
+
inject_into_file schema_path,
|
|
33
|
+
" include #{SCHEMA_PLUGIN}\n",
|
|
34
|
+
after: /class\s+\S+\s*<\s*GraphQL::Schema\s*\n/
|
|
35
|
+
else
|
|
36
|
+
say ""
|
|
37
|
+
say "No schema file found. Add this line manually to your GraphQL::Schema subclass:", :yellow
|
|
38
|
+
say " include #{SCHEMA_PLUGIN}", :green
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
27
42
|
def print_next_steps
|
|
28
43
|
say ""
|
|
29
44
|
say "Done! Your GraphQL API now has:", :green
|
|
30
45
|
say " auditLogEntry(id: ID!): AuditLogEntry"
|
|
31
46
|
say " auditLogEntries(...): [AuditLogEntry!]!"
|
|
47
|
+
say " auditLogReify(itemType:, itemId:, at:): AuditLogJson"
|
|
48
|
+
say " auditLogEntriesCount(...): Int!"
|
|
49
|
+
say ""
|
|
50
|
+
say "SchemaPlugin applies complexity/depth limits and enables dataloader."
|
|
51
|
+
say "Override defaults in an initializer:"
|
|
52
|
+
say " RailsAuditLog::Graphql.max_complexity = 500"
|
|
32
53
|
say ""
|
|
33
54
|
say "See the README for full documentation."
|
|
34
55
|
end
|
|
@@ -29,6 +29,7 @@ module RailsAuditLog
|
|
|
29
29
|
argument :item_type, String, required: false, description: "Filter by audited model class name."
|
|
30
30
|
argument :item_id, GraphQL::Types::ID, required: false, description: "Filter by audited record ID."
|
|
31
31
|
argument :actor_id, GraphQL::Types::ID, required: false, description: "Filter by actor ID."
|
|
32
|
+
argument :actor_type, String, required: false, description: "Filter by actor model class name (e.g. \"User\")."
|
|
32
33
|
argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Return entries created at or after this time."
|
|
33
34
|
argument :until, GraphQL::Types::ISO8601DateTime, required: false, as: :until_time, description: "Return entries created at or before this time."
|
|
34
35
|
argument :touching, String, required: false, description: "Filter to entries that changed a specific attribute (matches object_changes keys)."
|
|
@@ -50,6 +51,7 @@ module RailsAuditLog
|
|
|
50
51
|
argument :item_type, String, required: false, description: "Filter by audited model class name."
|
|
51
52
|
argument :item_id, GraphQL::Types::ID, required: false, description: "Filter by audited record ID."
|
|
52
53
|
argument :actor_id, GraphQL::Types::ID, required: false, description: "Filter by actor ID."
|
|
54
|
+
argument :actor_type, String, required: false, description: "Filter by actor model class name (e.g. \"User\")."
|
|
53
55
|
argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Return entries created at or after this time."
|
|
54
56
|
argument :until, GraphQL::Types::ISO8601DateTime, required: false, as: :until_time, description: "Return entries created at or before this time."
|
|
55
57
|
argument :touching, String, required: false, description: "Filter to entries that changed a specific attribute (matches object_changes keys)."
|
|
@@ -58,6 +60,20 @@ module RailsAuditLog
|
|
|
58
60
|
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
59
61
|
end
|
|
60
62
|
|
|
63
|
+
base.field(
|
|
64
|
+
:audit_log_reify,
|
|
65
|
+
Types::AuditLogJsonScalar,
|
|
66
|
+
null: true,
|
|
67
|
+
description: "Reconstruct the attribute state of a record at a given point in time. Returns nil when no entry exists at or before `at`, or when the record was destroyed.",
|
|
68
|
+
resolver_method: :resolve_audit_log_reify
|
|
69
|
+
) do
|
|
70
|
+
argument :item_type, String, required: true, description: "The audited model class name."
|
|
71
|
+
argument :item_id, GraphQL::Types::ID, required: true, description: "The audited record ID."
|
|
72
|
+
argument :at, GraphQL::Types::ISO8601DateTime, required: true, description: "Reconstruct state as of this time."
|
|
73
|
+
argument :for_tenant, String, required: false,
|
|
74
|
+
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
base.field(
|
|
62
78
|
:audit_log_entries_count,
|
|
63
79
|
GraphQL::Types::Int,
|
|
@@ -67,7 +83,10 @@ module RailsAuditLog
|
|
|
67
83
|
) do
|
|
68
84
|
argument :event, String, required: false, description: "Filter by event type (create, update, destroy)."
|
|
69
85
|
argument :item_type, String, required: false, description: "Filter by audited model class name."
|
|
86
|
+
argument :actor_type, String, required: false, description: "Filter by actor model class name (e.g. \"User\")."
|
|
70
87
|
argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Count entries created at or after this time."
|
|
88
|
+
argument :for_tenant, String, required: false,
|
|
89
|
+
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
71
90
|
end
|
|
72
91
|
end
|
|
73
92
|
|
|
@@ -78,31 +97,45 @@ module RailsAuditLog
|
|
|
78
97
|
base.find_by(id: id)
|
|
79
98
|
end
|
|
80
99
|
|
|
81
|
-
def resolve_audit_log_entries(event: nil, item_type: nil, item_id: nil, actor_id: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil, page: 1, per_page: 25)
|
|
100
|
+
def resolve_audit_log_entries(event: nil, item_type: nil, item_id: nil, actor_id: nil, actor_type: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil, page: 1, per_page: 25)
|
|
82
101
|
check_authentication!
|
|
83
|
-
scope = build_scope(event: event, item_type: item_type, item_id: item_id, actor_id: actor_id, since: since, until_time: until_time, touching: touching, order_by: order_by, for_tenant: for_tenant)
|
|
102
|
+
scope = build_scope(event: event, item_type: item_type, item_id: item_id, actor_id: actor_id, actor_type: actor_type, since: since, until_time: until_time, touching: touching, order_by: order_by, for_tenant: for_tenant)
|
|
84
103
|
scope.limit(per_page).offset((page - 1) * per_page)
|
|
85
104
|
end
|
|
86
105
|
|
|
87
|
-
def resolve_audit_log_entries_connection(event: nil, item_type: nil, item_id: nil, actor_id: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil)
|
|
106
|
+
def resolve_audit_log_entries_connection(event: nil, item_type: nil, item_id: nil, actor_id: nil, actor_type: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil)
|
|
88
107
|
check_authentication!
|
|
89
|
-
build_scope(event: event, item_type: item_type, item_id: item_id, actor_id: actor_id, since: since, until_time: until_time, touching: touching, order_by: order_by, for_tenant: for_tenant)
|
|
108
|
+
build_scope(event: event, item_type: item_type, item_id: item_id, actor_id: actor_id, actor_type: actor_type, since: since, until_time: until_time, touching: touching, order_by: order_by, for_tenant: for_tenant)
|
|
90
109
|
end
|
|
91
110
|
|
|
92
|
-
def
|
|
111
|
+
def resolve_audit_log_reify(item_type:, item_id:, at:, for_tenant: nil)
|
|
112
|
+
check_authentication!
|
|
113
|
+
tenant_id = for_tenant || RailsAuditLog.current_tenant&.call
|
|
114
|
+
scope = RailsAuditLog::AuditLogEntry
|
|
115
|
+
.where(item_type: item_type, item_id: item_id)
|
|
116
|
+
.where("created_at <= ?", at)
|
|
117
|
+
scope = scope.for_tenant(tenant_id) if tenant_id
|
|
118
|
+
entry = scope.order(created_at: :desc, id: :desc).first
|
|
119
|
+
return nil if entry.nil? || entry.event == "destroy"
|
|
120
|
+
to_attrs = (entry.object_changes || {}).transform_values { |v| v[1] }
|
|
121
|
+
entry.object.present? ? entry.object.merge(to_attrs) : to_attrs
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolve_audit_log_entries_count(event: nil, item_type: nil, actor_type: nil, since: nil, for_tenant: nil)
|
|
93
125
|
check_authentication!
|
|
94
126
|
scope = RailsAuditLog::AuditLogEntry.all
|
|
95
127
|
scope = scope.where(event: event) if event
|
|
96
128
|
scope = scope.where(item_type: item_type) if item_type
|
|
129
|
+
scope = scope.where(actor_type: actor_type) if actor_type
|
|
97
130
|
scope = scope.where("created_at >= ?", since) if since
|
|
98
|
-
tenant_id = RailsAuditLog.current_tenant&.call
|
|
131
|
+
tenant_id = for_tenant || RailsAuditLog.current_tenant&.call
|
|
99
132
|
scope = scope.for_tenant(tenant_id) if tenant_id
|
|
100
133
|
scope.count
|
|
101
134
|
end
|
|
102
135
|
|
|
103
136
|
private
|
|
104
137
|
|
|
105
|
-
def build_scope(event: nil, item_type: nil, item_id: nil, actor_id: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil)
|
|
138
|
+
def build_scope(event: nil, item_type: nil, item_id: nil, actor_id: nil, actor_type: nil, since: nil, until_time: nil, touching: nil, order_by: nil, for_tenant: nil)
|
|
106
139
|
sort_field = order_by&.field || :created_at
|
|
107
140
|
sort_direction = order_by&.direction || :desc
|
|
108
141
|
scope = RailsAuditLog::AuditLogEntry.order(sort_field => sort_direction)
|
|
@@ -110,6 +143,7 @@ module RailsAuditLog
|
|
|
110
143
|
scope = scope.where(item_type: item_type) if item_type
|
|
111
144
|
scope = scope.where(item_id: item_id) if item_id
|
|
112
145
|
scope = scope.where(actor_id: actor_id) if actor_id
|
|
146
|
+
scope = scope.where(actor_type: actor_type) if actor_type
|
|
113
147
|
scope = scope.where("created_at >= ?", since) if since
|
|
114
148
|
scope = scope.where("created_at <= ?", until_time) if until_time
|
|
115
149
|
if touching
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAuditLog
|
|
4
|
+
module Graphql
|
|
5
|
+
module SchemaPlugin
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.max_complexity(RailsAuditLog::Graphql.max_complexity)
|
|
8
|
+
base.max_depth(RailsAuditLog::Graphql.max_depth)
|
|
9
|
+
base.default_max_page_size(RailsAuditLog::Graphql.default_max_page_size)
|
|
10
|
+
base.use(GraphQL::Dataloader)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAuditLog
|
|
4
|
+
module Graphql
|
|
5
|
+
module Sources
|
|
6
|
+
class RecordByIdSource < GraphQL::Dataloader::Source
|
|
7
|
+
def initialize(class_name)
|
|
8
|
+
@class_name = class_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fetch(ids)
|
|
12
|
+
klass = @class_name.safe_constantize
|
|
13
|
+
return ids.map { nil } unless klass
|
|
14
|
+
|
|
15
|
+
records = klass.where(id: ids).index_by { |r| r.id.to_s }
|
|
16
|
+
ids.map { |id| records[id]&.attributes }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -9,6 +9,8 @@ module RailsAuditLog
|
|
|
9
9
|
|
|
10
10
|
field :id, GraphQL::Types::ID, null: false, description: "The actor's ID."
|
|
11
11
|
field :type_name, String, null: false, description: "The actor's model class name (e.g. \"User\")."
|
|
12
|
+
field :record, GraphQL::Types::JSON, null: true,
|
|
13
|
+
description: "The actor record loaded from the database, serialized as JSON. Batch-loaded via dataloader."
|
|
12
14
|
|
|
13
15
|
def id
|
|
14
16
|
object[:id]
|
|
@@ -17,6 +19,11 @@ module RailsAuditLog
|
|
|
17
19
|
def type_name
|
|
18
20
|
object[:type_name]
|
|
19
21
|
end
|
|
22
|
+
|
|
23
|
+
def record
|
|
24
|
+
return nil unless object[:id] && object[:type_name]
|
|
25
|
+
dataloader.with(Sources::RecordByIdSource, object[:type_name]).load(object[:id].to_s)
|
|
26
|
+
end
|
|
20
27
|
end
|
|
21
28
|
end
|
|
22
29
|
end
|
|
@@ -11,9 +11,9 @@ module RailsAuditLog
|
|
|
11
11
|
field :event, String, null: false
|
|
12
12
|
field :item_type, String, null: false
|
|
13
13
|
field :item_id, GraphQL::Types::ID, null: false
|
|
14
|
-
field :object_changes,
|
|
15
|
-
field :object,
|
|
16
|
-
field :metadata,
|
|
14
|
+
field :object_changes, Types::AuditLogJsonScalar, null: true
|
|
15
|
+
field :object, Types::AuditLogJsonScalar, null: true, method_conflict_warning: false
|
|
16
|
+
field :metadata, Types::AuditLogJsonScalar, null: true
|
|
17
17
|
field :reason, String, null: true
|
|
18
18
|
field :whodunnit_snapshot, String, null: true
|
|
19
19
|
field :actor_type, String, null: true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAuditLog
|
|
4
|
+
module Graphql
|
|
5
|
+
module Types
|
|
6
|
+
class AuditLogJsonScalar < GraphQL::Schema::Scalar
|
|
7
|
+
graphql_name "AuditLogJson"
|
|
8
|
+
description "A JSON blob stored on an audit log entry (objectChanges, object, or metadata)."
|
|
9
|
+
|
|
10
|
+
def self.coerce_input(value, _ctx)
|
|
11
|
+
value.is_a?(Hash) ? value : JSON.parse(value)
|
|
12
|
+
rescue JSON::ParserError
|
|
13
|
+
raise GraphQL::CoercionError, "#{value.inspect} is not valid JSON"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.coerce_result(value, _ctx)
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -9,6 +9,8 @@ module RailsAuditLog
|
|
|
9
9
|
|
|
10
10
|
field :id, GraphQL::Types::ID, null: false, description: "The audited record's ID."
|
|
11
11
|
field :type_name, String, null: false, description: "The audited model class name (e.g. \"Post\")."
|
|
12
|
+
field :record, GraphQL::Types::JSON, null: true,
|
|
13
|
+
description: "The audited record loaded from the database, serialized as JSON. Batch-loaded via dataloader."
|
|
12
14
|
|
|
13
15
|
def id
|
|
14
16
|
object[:id]
|
|
@@ -17,6 +19,10 @@ module RailsAuditLog
|
|
|
17
19
|
def type_name
|
|
18
20
|
object[:type_name]
|
|
19
21
|
end
|
|
22
|
+
|
|
23
|
+
def record
|
|
24
|
+
dataloader.with(Sources::RecordByIdSource, object[:type_name]).load(object[:id].to_s)
|
|
25
|
+
end
|
|
20
26
|
end
|
|
21
27
|
end
|
|
22
28
|
end
|
|
@@ -4,6 +4,7 @@ require "graphql"
|
|
|
4
4
|
require_relative "graphql/version"
|
|
5
5
|
require_relative "graphql/types/base_object"
|
|
6
6
|
require_relative "graphql/types/base_subscription"
|
|
7
|
+
require_relative "graphql/types/audit_log_json_scalar"
|
|
7
8
|
require_relative "graphql/types/diff_type"
|
|
8
9
|
require_relative "graphql/types/actor_type"
|
|
9
10
|
require_relative "graphql/types/audited_resource_type"
|
|
@@ -11,13 +12,23 @@ require_relative "graphql/types/audit_log_entry_type"
|
|
|
11
12
|
require_relative "graphql/types/sort_direction_enum"
|
|
12
13
|
require_relative "graphql/types/audit_log_entry_sort_field_enum"
|
|
13
14
|
require_relative "graphql/input_objects/audit_log_entry_sort_input"
|
|
15
|
+
require_relative "graphql/sources/record_by_id_source"
|
|
14
16
|
require_relative "graphql/queries/audit_log_entries_query_mixin"
|
|
15
17
|
require_relative "graphql/subscriptions/audit_log_entry_created"
|
|
16
18
|
require_relative "graphql/subscriptions/audit_log_subscriptions_mixin"
|
|
17
19
|
require_relative "graphql/subscriptions/broadcaster"
|
|
20
|
+
require_relative "graphql/schema_plugin"
|
|
18
21
|
|
|
19
22
|
module RailsAuditLog
|
|
20
23
|
module Graphql
|
|
21
24
|
class Error < StandardError; end
|
|
25
|
+
|
|
26
|
+
@max_complexity = 200
|
|
27
|
+
@max_depth = 10
|
|
28
|
+
@default_max_page_size = 25
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
attr_accessor :max_complexity, :max_depth, :default_max_page_size
|
|
32
|
+
end
|
|
22
33
|
end
|
|
23
34
|
end
|
|
@@ -2,6 +2,17 @@ module RailsAuditLog
|
|
|
2
2
|
module Graphql
|
|
3
3
|
VERSION: String
|
|
4
4
|
|
|
5
|
+
@max_complexity: Integer
|
|
6
|
+
@max_depth: Integer
|
|
7
|
+
@default_max_page_size: Integer
|
|
8
|
+
|
|
9
|
+
def self.max_complexity: () -> Integer
|
|
10
|
+
def self.max_complexity=: (Integer) -> Integer
|
|
11
|
+
def self.max_depth: () -> Integer
|
|
12
|
+
def self.max_depth=: (Integer) -> Integer
|
|
13
|
+
def self.default_max_page_size: () -> Integer
|
|
14
|
+
def self.default_max_page_size=: (Integer) -> Integer
|
|
15
|
+
|
|
5
16
|
module Types
|
|
6
17
|
class BaseObject < GraphQL::Schema::Object
|
|
7
18
|
end
|
|
@@ -9,6 +20,11 @@ module RailsAuditLog
|
|
|
9
20
|
class BaseSubscription < GraphQL::Schema::Subscription
|
|
10
21
|
end
|
|
11
22
|
|
|
23
|
+
class AuditLogJsonScalar < GraphQL::Schema::Scalar
|
|
24
|
+
def self.coerce_input: (untyped value, untyped ctx) -> Hash[String, untyped]
|
|
25
|
+
def self.coerce_result: (untyped value, untyped ctx) -> untyped
|
|
26
|
+
end
|
|
27
|
+
|
|
12
28
|
class DiffType < BaseObject
|
|
13
29
|
def attribute: () -> String
|
|
14
30
|
def from: () -> untyped
|
|
@@ -18,11 +34,13 @@ module RailsAuditLog
|
|
|
18
34
|
class ActorType < BaseObject
|
|
19
35
|
def id: () -> String
|
|
20
36
|
def type_name: () -> String
|
|
37
|
+
def record: () -> Hash[String, untyped]?
|
|
21
38
|
end
|
|
22
39
|
|
|
23
40
|
class AuditedResourceType < BaseObject
|
|
24
41
|
def id: () -> String
|
|
25
42
|
def type_name: () -> String
|
|
43
|
+
def record: () -> Hash[String, untyped]?
|
|
26
44
|
end
|
|
27
45
|
|
|
28
46
|
class AuditLogEntryType < BaseObject
|
|
@@ -54,6 +72,17 @@ module RailsAuditLog
|
|
|
54
72
|
end
|
|
55
73
|
end
|
|
56
74
|
|
|
75
|
+
module Sources
|
|
76
|
+
class RecordByIdSource < GraphQL::Dataloader::Source
|
|
77
|
+
def initialize: (String class_name) -> void
|
|
78
|
+
def fetch: (Array[String] ids) -> Array[Hash[String, untyped]?]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
module SchemaPlugin
|
|
83
|
+
def self.included: (untyped base) -> void
|
|
84
|
+
end
|
|
85
|
+
|
|
57
86
|
module Subscriptions
|
|
58
87
|
class AuditLogEntryCreated < Types::BaseSubscription
|
|
59
88
|
def subscribe: (?item_type: String?, ?item_id: String?, ?actor_id: String?) -> :no_response
|
|
@@ -83,6 +112,7 @@ module RailsAuditLog
|
|
|
83
112
|
?item_type: String?,
|
|
84
113
|
?item_id: String?,
|
|
85
114
|
?actor_id: String?,
|
|
115
|
+
?actor_type: String?,
|
|
86
116
|
?since: Time?,
|
|
87
117
|
?until_time: Time?,
|
|
88
118
|
?touching: String?,
|
|
@@ -97,6 +127,7 @@ module RailsAuditLog
|
|
|
97
127
|
?item_type: String?,
|
|
98
128
|
?item_id: String?,
|
|
99
129
|
?actor_id: String?,
|
|
130
|
+
?actor_type: String?,
|
|
100
131
|
?since: Time?,
|
|
101
132
|
?until_time: Time?,
|
|
102
133
|
?touching: String?,
|
|
@@ -104,10 +135,19 @@ module RailsAuditLog
|
|
|
104
135
|
?for_tenant: String?
|
|
105
136
|
) -> untyped
|
|
106
137
|
|
|
138
|
+
def resolve_audit_log_reify: (
|
|
139
|
+
item_type: String,
|
|
140
|
+
item_id: String,
|
|
141
|
+
at: Time,
|
|
142
|
+
?for_tenant: String?
|
|
143
|
+
) -> Hash[String, untyped]?
|
|
144
|
+
|
|
107
145
|
def resolve_audit_log_entries_count: (
|
|
108
146
|
?event: String?,
|
|
109
147
|
?item_type: String?,
|
|
110
|
-
?
|
|
148
|
+
?actor_type: String?,
|
|
149
|
+
?since: Time?,
|
|
150
|
+
?for_tenant: String?
|
|
111
151
|
) -> Integer
|
|
112
152
|
|
|
113
153
|
private
|
|
@@ -117,6 +157,7 @@ module RailsAuditLog
|
|
|
117
157
|
?item_type: String?,
|
|
118
158
|
?item_id: String?,
|
|
119
159
|
?actor_id: String?,
|
|
160
|
+
?actor_type: String?,
|
|
120
161
|
?since: Time?,
|
|
121
162
|
?until_time: Time?,
|
|
122
163
|
?touching: String?,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_audit_log-graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -86,12 +86,15 @@ files:
|
|
|
86
86
|
- lib/rails_audit_log/graphql/input_objects/audit_log_entry_sort_input.rb
|
|
87
87
|
- lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb
|
|
88
88
|
- lib/rails_audit_log/graphql/release_tooling.rb
|
|
89
|
+
- lib/rails_audit_log/graphql/schema_plugin.rb
|
|
90
|
+
- lib/rails_audit_log/graphql/sources/record_by_id_source.rb
|
|
89
91
|
- lib/rails_audit_log/graphql/subscriptions/audit_log_entry_created.rb
|
|
90
92
|
- lib/rails_audit_log/graphql/subscriptions/audit_log_subscriptions_mixin.rb
|
|
91
93
|
- lib/rails_audit_log/graphql/subscriptions/broadcaster.rb
|
|
92
94
|
- lib/rails_audit_log/graphql/types/actor_type.rb
|
|
93
95
|
- lib/rails_audit_log/graphql/types/audit_log_entry_sort_field_enum.rb
|
|
94
96
|
- lib/rails_audit_log/graphql/types/audit_log_entry_type.rb
|
|
97
|
+
- lib/rails_audit_log/graphql/types/audit_log_json_scalar.rb
|
|
95
98
|
- lib/rails_audit_log/graphql/types/audited_resource_type.rb
|
|
96
99
|
- lib/rails_audit_log/graphql/types/base_object.rb
|
|
97
100
|
- lib/rails_audit_log/graphql/types/base_subscription.rb
|