rails_audit_log-graphql 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +85 -0
- data/ROADMAP.md +0 -17
- data/lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb +64 -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 +47 -3
- 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: 6ca2df96cd0a69c6449fc8585832293c9a06d487a7525f40c1d15762c61710b5
|
|
4
|
+
data.tar.gz: 8262257281bef88c4ab07700ec88b5755ca6b2a095fd879bf5462d403ec1ac00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4598525fe46095cbd1209e9bc3af297950526eb9c7ecfab9e382ee3554f376e73b0db3073986d49cdd05190e42f90e1d09e3f1a00c3ecb948a0f02d04f5ba9f0
|
|
7
|
+
data.tar.gz: 2069e44509afdccf33c443bcf443da4480e83e60034f73bf24b0a7139ab012910d60e17dd85d06897b0642a6e4cb0d0d548a08c6bfbd5ac8318f6bd787d998b3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-06-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `AuditLogJsonScalar` (`AuditLogJson`) — custom scalar type for `objectChanges`, `object`, and `metadata` fields; replaces the generic `JSON` scalar with a self-documenting type
|
|
8
|
+
- `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
|
|
9
|
+
- `record` field on `AuditLogActor` and `AuditedResource` — returns the database record as JSON, batch-loaded via dataloader
|
|
10
|
+
- `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
|
|
11
|
+
- `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.
|
|
12
|
+
|
|
13
|
+
## [0.5.0] - 2026-06-04
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `forTenant:` argument on `auditLogEntry`, `auditLogEntries`, and `auditLogEntriesConnection` — explicitly scope results to a tenant ID; overrides auto-tenant
|
|
18
|
+
- Auto-tenant scoping — when `RailsAuditLog.current_tenant` is configured, all queries automatically filter to the current tenant without requiring an explicit argument
|
|
19
|
+
- `auditLogEntriesCount(event:, itemType:, since:)` — new aggregation query returning the count of matching entries; respects auto-tenant
|
|
20
|
+
|
|
3
21
|
## [0.4.0] - 2026-06-04
|
|
4
22
|
|
|
5
23
|
### Added
|
data/README.md
CHANGED
|
@@ -20,7 +20,11 @@ A [graphql-ruby](https://graphql-ruby.org) API layer for the [`rails_audit_log`]
|
|
|
20
20
|
- [auditLogEntry](#auditlogentryid-id-auditlogentry)
|
|
21
21
|
- [auditLogEntries](#auditlogentries-auditlogentry)
|
|
22
22
|
- [auditLogEntriesConnection](#auditlogentriesconnection-auditlogentryconnection)
|
|
23
|
+
- [auditLogEntriesCount](#auditlogentriescount-int)
|
|
24
|
+
- [Tenant scoping](#tenant-scoping)
|
|
23
25
|
- [Authentication](#authentication)
|
|
26
|
+
- [SchemaPlugin](#schemaplugin)
|
|
27
|
+
- [auditLogReify](#auditlogreify)
|
|
24
28
|
- [Subscriptions](#subscriptions)
|
|
25
29
|
- [AuditLogSubscriptionsMixin](#auditlogsubscriptionsmixin)
|
|
26
30
|
- [auditLogEntryCreated](#auditlogentrycreated)
|
|
@@ -152,6 +156,7 @@ List entries with optional filters and offset pagination.
|
|
|
152
156
|
| `until` | `ISO8601DateTime` | — | Return entries created at or before this time |
|
|
153
157
|
| `touching` | `String` | — | Filter to entries that changed a specific attribute |
|
|
154
158
|
| `orderBy` | `AuditLogEntrySortInput` | `CREATED_AT DESC` | Sort field and direction |
|
|
159
|
+
| `forTenant` | `String` | — | Scope to a specific tenant ID; overrides auto-tenant |
|
|
155
160
|
| `page` | `Int` | `1` | Page number (1-based) |
|
|
156
161
|
| `perPage` | `Int` | `25` | Results per page |
|
|
157
162
|
|
|
@@ -167,6 +172,7 @@ Same filters as `auditLogEntries`, but returns a [Relay-style connection](https:
|
|
|
167
172
|
| `itemType` | `String` | Filter by audited model class name |
|
|
168
173
|
| `itemId` | `ID` | Filter by audited record ID |
|
|
169
174
|
| `actorId` | `ID` | Filter by actor ID |
|
|
175
|
+
| `forTenant` | `String` | Scope to a specific tenant ID; overrides auto-tenant |
|
|
170
176
|
| `first` | `Int` | Return the first N edges after `after` |
|
|
171
177
|
| `after` | `String` | Cursor to paginate forward from |
|
|
172
178
|
| `last` | `Int` | Return the last N edges before `before` |
|
|
@@ -207,6 +213,40 @@ Results are ordered by `created_at DESC`.
|
|
|
207
213
|
|
|
208
214
|
[↑ Back to top](#table-of-contents)
|
|
209
215
|
|
|
216
|
+
#### `auditLogEntriesCount(...): Int!`
|
|
217
|
+
|
|
218
|
+
Returns the count of matching audit log entries. Respects auto-tenant when `RailsAuditLog.current_tenant` is configured.
|
|
219
|
+
|
|
220
|
+
| Argument | Type | Description |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `event` | `String` | Filter by event type (`create`, `update`, `destroy`) |
|
|
223
|
+
| `itemType` | `String` | Filter by audited model class name |
|
|
224
|
+
| `since` | `ISO8601DateTime` | Count entries created at or after this time |
|
|
225
|
+
|
|
226
|
+
```graphql
|
|
227
|
+
{ auditLogEntriesCount(event: "update", itemType: "Post") }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
[↑ Back to top](#table-of-contents)
|
|
231
|
+
|
|
232
|
+
#### Tenant scoping
|
|
233
|
+
|
|
234
|
+
When `RailsAuditLog.current_tenant` is configured, all queries automatically filter to the current tenant:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
RailsAuditLog.configure do |c|
|
|
238
|
+
c.current_tenant { Current.tenant_id }
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
To override or explicitly specify a tenant per query, use the `forTenant:` argument (available on `auditLogEntry`, `auditLogEntries`, and `auditLogEntriesConnection`):
|
|
243
|
+
|
|
244
|
+
```graphql
|
|
245
|
+
{ auditLogEntries(forTenant: "acme") { id event } }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
[↑ Back to top](#table-of-contents)
|
|
249
|
+
|
|
210
250
|
### Authentication
|
|
211
251
|
|
|
212
252
|
If `RailsAuditLog.authenticate` is configured, the block is called with the GraphQL context before every query. Return a truthy value to allow access; return falsy to raise `GraphQL::ExecutionError` with `"Unauthorized"`.
|
|
@@ -221,6 +261,51 @@ If no authenticate block is set, all queries are permitted.
|
|
|
221
261
|
|
|
222
262
|
[↑ Back to top](#table-of-contents)
|
|
223
263
|
|
|
264
|
+
### SchemaPlugin
|
|
265
|
+
|
|
266
|
+
Include `RailsAuditLog::Graphql::SchemaPlugin` into your schema to enable query protection and dataloader batching in one step:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
class MySchema < GraphQL::Schema
|
|
270
|
+
include RailsAuditLog::Graphql::SchemaPlugin
|
|
271
|
+
query Types::QueryType
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This applies the following defaults (all overridable via `RailsAuditLog::Graphql.*=`):
|
|
276
|
+
|
|
277
|
+
| Setting | Default | Description |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| `max_complexity` | `200` | Reject queries whose field-complexity sum exceeds this |
|
|
280
|
+
| `max_depth` | `10` | Reject queries nested deeper than this |
|
|
281
|
+
| `default_max_page_size` | `25` | Assumed page size for connection complexity calculation |
|
|
282
|
+
|
|
283
|
+
Override in an initializer:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
RailsAuditLog::Graphql.max_complexity = 500
|
|
287
|
+
RailsAuditLog::Graphql.max_depth = 15
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
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.
|
|
291
|
+
|
|
292
|
+
[↑ Back to top](#table-of-contents)
|
|
293
|
+
|
|
294
|
+
### `auditLogReify(itemType:, itemId:, at:): JSON`
|
|
295
|
+
|
|
296
|
+
Reconstructs the attribute state of a record at a given point in time. Returns the attributes as JSON, or `nil` when no entry exists at or before `at` or the record was destroyed at that time.
|
|
297
|
+
|
|
298
|
+
```graphql
|
|
299
|
+
{
|
|
300
|
+
auditLogReify(itemType: "Post", itemId: "42", at: "2026-01-15T12:00:00Z") {
|
|
301
|
+
title
|
|
302
|
+
publishedAt
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
[↑ Back to top](#table-of-contents)
|
|
308
|
+
|
|
224
309
|
### Subscriptions
|
|
225
310
|
|
|
226
311
|
Requires Action Cable in the host application.
|
data/ROADMAP.md
CHANGED
|
@@ -4,23 +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.5.0 — Multi-tenancy & Advanced Filtering
|
|
8
|
-
|
|
9
|
-
- **Tenant scoping** — automatically scope queries via `RailsAuditLog.current_tenant` when configured
|
|
10
|
-
- **`forTenant:` argument** — explicit tenant filter on `auditLogEntries`
|
|
11
|
-
- **Aggregations** — `auditLogEntriesCount(event:, itemType:, since:)` for dashboard metrics
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 0.6.0 — Performance & Safety
|
|
16
|
-
|
|
17
|
-
- **Dataloader batch loading** — batch-resolve polymorphic `actor` and `item` associations using graphql-ruby's `dataloader` to eliminate N+1 queries on list responses
|
|
18
|
-
- **`auditLogReify` query** — `auditLogReify(itemType:, itemId:, at:)` returns the reconstructed object state as JSON at a given point in time, backed by `RailsAuditLog.version_at`
|
|
19
|
-
- **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
|
|
20
|
-
- **`AuditLogJsonScalar`** — proper JSON scalar type for `objectChanges` and `metadata` fields, replacing opaque String serialization and making the schema self-documenting
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
7
|
## 1.0.0 — Stable API
|
|
25
8
|
|
|
26
9
|
- Full YARD documentation
|
|
@@ -14,6 +14,8 @@ module RailsAuditLog
|
|
|
14
14
|
) do
|
|
15
15
|
argument :id, GraphQL::Types::ID, required: true,
|
|
16
16
|
description: "ID of the audit log entry."
|
|
17
|
+
argument :for_tenant, String, required: false,
|
|
18
|
+
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
base.field(
|
|
@@ -31,6 +33,8 @@ module RailsAuditLog
|
|
|
31
33
|
argument :until, GraphQL::Types::ISO8601DateTime, required: false, as: :until_time, description: "Return entries created at or before this time."
|
|
32
34
|
argument :touching, String, required: false, description: "Filter to entries that changed a specific attribute (matches object_changes keys)."
|
|
33
35
|
argument :order_by, RailsAuditLog::Graphql::InputObjects::AuditLogEntrySortInput, required: false, description: "Sort order. Defaults to CREATED_AT DESC."
|
|
36
|
+
argument :for_tenant, String, required: false,
|
|
37
|
+
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
34
38
|
argument :page, GraphQL::Types::Int, required: false, default_value: 1, description: "Page number (1-based)."
|
|
35
39
|
argument :per_page, GraphQL::Types::Int, required: false, default_value: 25, description: "Number of results per page."
|
|
36
40
|
end
|
|
@@ -50,28 +54,79 @@ module RailsAuditLog
|
|
|
50
54
|
argument :until, GraphQL::Types::ISO8601DateTime, required: false, as: :until_time, description: "Return entries created at or before this time."
|
|
51
55
|
argument :touching, String, required: false, description: "Filter to entries that changed a specific attribute (matches object_changes keys)."
|
|
52
56
|
argument :order_by, RailsAuditLog::Graphql::InputObjects::AuditLogEntrySortInput, required: false, description: "Sort order. Defaults to CREATED_AT DESC."
|
|
57
|
+
argument :for_tenant, String, required: false,
|
|
58
|
+
description: "Scope to a specific tenant ID. Overrides auto-tenant when RailsAuditLog.current_tenant is configured."
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
base.field(
|
|
62
|
+
:audit_log_reify,
|
|
63
|
+
GraphQL::Types::JSON,
|
|
64
|
+
null: true,
|
|
65
|
+
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.",
|
|
66
|
+
resolver_method: :resolve_audit_log_reify
|
|
67
|
+
) do
|
|
68
|
+
argument :item_type, String, required: true, description: "The audited model class name."
|
|
69
|
+
argument :item_id, GraphQL::Types::ID, required: true, description: "The audited record ID."
|
|
70
|
+
argument :at, GraphQL::Types::ISO8601DateTime, required: true, description: "Reconstruct state as of this time."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
base.field(
|
|
74
|
+
:audit_log_entries_count,
|
|
75
|
+
GraphQL::Types::Int,
|
|
76
|
+
null: false,
|
|
77
|
+
description: "Count audit log entries with optional filters. Respects auto-tenant when RailsAuditLog.current_tenant is configured.",
|
|
78
|
+
resolver_method: :resolve_audit_log_entries_count
|
|
79
|
+
) do
|
|
80
|
+
argument :event, String, required: false, description: "Filter by event type (create, update, destroy)."
|
|
81
|
+
argument :item_type, String, required: false, description: "Filter by audited model class name."
|
|
82
|
+
argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Count entries created at or after this time."
|
|
53
83
|
end
|
|
54
84
|
end
|
|
55
85
|
|
|
56
|
-
def resolve_audit_log_entry(id:)
|
|
86
|
+
def resolve_audit_log_entry(id:, for_tenant: nil)
|
|
57
87
|
check_authentication!
|
|
58
|
-
RailsAuditLog
|
|
88
|
+
tenant_id = for_tenant || RailsAuditLog.current_tenant&.call
|
|
89
|
+
base = tenant_id ? RailsAuditLog::AuditLogEntry.for_tenant(tenant_id) : RailsAuditLog::AuditLogEntry
|
|
90
|
+
base.find_by(id: id)
|
|
59
91
|
end
|
|
60
92
|
|
|
61
|
-
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, page: 1, per_page: 25)
|
|
93
|
+
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)
|
|
62
94
|
check_authentication!
|
|
63
|
-
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)
|
|
95
|
+
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)
|
|
64
96
|
scope.limit(per_page).offset((page - 1) * per_page)
|
|
65
97
|
end
|
|
66
98
|
|
|
67
|
-
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)
|
|
99
|
+
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)
|
|
100
|
+
check_authentication!
|
|
101
|
+
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
|
+
end
|
|
103
|
+
|
|
104
|
+
def resolve_audit_log_reify(item_type:, item_id:, at:)
|
|
105
|
+
check_authentication!
|
|
106
|
+
entry = RailsAuditLog::AuditLogEntry
|
|
107
|
+
.where(item_type: item_type, item_id: item_id)
|
|
108
|
+
.where("created_at <= ?", at)
|
|
109
|
+
.order(created_at: :desc, id: :desc)
|
|
110
|
+
.first
|
|
111
|
+
return nil if entry.nil? || entry.event == "destroy"
|
|
112
|
+
to_attrs = (entry.object_changes || {}).transform_values { |v| v[1] }
|
|
113
|
+
entry.object.present? ? entry.object.merge(to_attrs) : to_attrs
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def resolve_audit_log_entries_count(event: nil, item_type: nil, since: nil)
|
|
68
117
|
check_authentication!
|
|
69
|
-
|
|
118
|
+
scope = RailsAuditLog::AuditLogEntry.all
|
|
119
|
+
scope = scope.where(event: event) if event
|
|
120
|
+
scope = scope.where(item_type: item_type) if item_type
|
|
121
|
+
scope = scope.where("created_at >= ?", since) if since
|
|
122
|
+
tenant_id = RailsAuditLog.current_tenant&.call
|
|
123
|
+
scope = scope.for_tenant(tenant_id) if tenant_id
|
|
124
|
+
scope.count
|
|
70
125
|
end
|
|
71
126
|
|
|
72
127
|
private
|
|
73
128
|
|
|
74
|
-
def build_scope(event: nil, item_type: nil, item_id: nil, actor_id: nil, since: nil, until_time: nil, touching: nil, order_by: nil)
|
|
129
|
+
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)
|
|
75
130
|
sort_field = order_by&.field || :created_at
|
|
76
131
|
sort_direction = order_by&.direction || :desc
|
|
77
132
|
scope = RailsAuditLog::AuditLogEntry.order(sort_field => sort_direction)
|
|
@@ -85,6 +140,8 @@ module RailsAuditLog
|
|
|
85
140
|
safe = ActiveRecord::Base.sanitize_sql_like(touching)
|
|
86
141
|
scope = scope.where("object_changes LIKE ?", "%\"#{safe}\":%")
|
|
87
142
|
end
|
|
143
|
+
tenant_id = for_tenant || RailsAuditLog.current_tenant&.call
|
|
144
|
+
scope = scope.for_tenant(tenant_id) if tenant_id
|
|
88
145
|
scope
|
|
89
146
|
end
|
|
90
147
|
|
|
@@ -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
|
|
@@ -76,7 +105,7 @@ module RailsAuditLog
|
|
|
76
105
|
module AuditLogEntriesQueryMixin
|
|
77
106
|
def self.included: (untyped base) -> void
|
|
78
107
|
|
|
79
|
-
def resolve_audit_log_entry: (id: String) -> untyped
|
|
108
|
+
def resolve_audit_log_entry: (id: String, ?for_tenant: String?) -> untyped
|
|
80
109
|
|
|
81
110
|
def resolve_audit_log_entries: (
|
|
82
111
|
?event: String?,
|
|
@@ -87,6 +116,7 @@ module RailsAuditLog
|
|
|
87
116
|
?until_time: Time?,
|
|
88
117
|
?touching: String?,
|
|
89
118
|
?order_by: InputObjects::AuditLogEntrySortInput?,
|
|
119
|
+
?for_tenant: String?,
|
|
90
120
|
?page: Integer,
|
|
91
121
|
?per_page: Integer
|
|
92
122
|
) -> untyped
|
|
@@ -99,9 +129,22 @@ module RailsAuditLog
|
|
|
99
129
|
?since: Time?,
|
|
100
130
|
?until_time: Time?,
|
|
101
131
|
?touching: String?,
|
|
102
|
-
?order_by: InputObjects::AuditLogEntrySortInput
|
|
132
|
+
?order_by: InputObjects::AuditLogEntrySortInput?,
|
|
133
|
+
?for_tenant: String?
|
|
103
134
|
) -> untyped
|
|
104
135
|
|
|
136
|
+
def resolve_audit_log_reify: (
|
|
137
|
+
item_type: String,
|
|
138
|
+
item_id: String,
|
|
139
|
+
at: Time
|
|
140
|
+
) -> Hash[String, untyped]?
|
|
141
|
+
|
|
142
|
+
def resolve_audit_log_entries_count: (
|
|
143
|
+
?event: String?,
|
|
144
|
+
?item_type: String?,
|
|
145
|
+
?since: Time?
|
|
146
|
+
) -> Integer
|
|
147
|
+
|
|
105
148
|
private
|
|
106
149
|
|
|
107
150
|
def build_scope: (
|
|
@@ -112,7 +155,8 @@ module RailsAuditLog
|
|
|
112
155
|
?since: Time?,
|
|
113
156
|
?until_time: Time?,
|
|
114
157
|
?touching: String?,
|
|
115
|
-
?order_by: InputObjects::AuditLogEntrySortInput
|
|
158
|
+
?order_by: InputObjects::AuditLogEntrySortInput?,
|
|
159
|
+
?for_tenant: String?
|
|
116
160
|
) -> untyped
|
|
117
161
|
|
|
118
162
|
def check_authentication!: () -> void
|
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.0
|
|
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
|