rails_audit_log-graphql 0.1.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 +7 -0
- data/.standard.yml +6 -0
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +89 -0
- data/CONTRIBUTING.md +51 -0
- data/LICENSE.txt +21 -0
- data/README.md +130 -0
- data/ROADMAP.md +57 -0
- data/Rakefile +9 -0
- data/codecov.yml +17 -0
- data/lib/generators/rails_audit_log/graphql/install/install_generator.rb +38 -0
- data/lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb +60 -0
- data/lib/rails_audit_log/graphql/release_tooling.rb +76 -0
- data/lib/rails_audit_log/graphql/types/audit_log_entry_type.rb +26 -0
- data/lib/rails_audit_log/graphql/types/base_object.rb +10 -0
- data/lib/rails_audit_log/graphql/version.rb +7 -0
- data/lib/rails_audit_log/graphql.rb +13 -0
- data/sig/rails_audit_log/graphql.rbs +32 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 35b1c07883c1e3b4036690804d2cf1600e484771b49a58f3954278d39fb136ae
|
|
4
|
+
data.tar.gz: 15b543bf26d1d19ff3e8fbc29488871f325d492cef51b215c60dff8d1f22906b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9409ded3977d2fa0f31f5a60be9bf841cabe5e324aed231e8119fc59c433988affe7ab7f873aa31e1de7a2538f054e281c1af46f4946632fa2cff46d39ad35f5
|
|
7
|
+
data.tar.gz: f52a3c3d184d1ff8f201011ca014338e4a13b3d1a3d055f1372ea5fa72f4a2898765cde924777d38e5843f21b3319ee13a309cb6e4b37b23130f4986a559daf1
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-06-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `AuditLogEntryType` GraphQL object type exposing all 13 `RailsAuditLog::AuditLogEntry` fields
|
|
8
|
+
- `BaseObject` base class for all gem GraphQL types
|
|
9
|
+
- `AuditLogEntriesQueryMixin` — include into host app's `QueryType` to add `auditLogEntry(id:)` and `auditLogEntries(event:, itemType:, itemId:, actorId:, page:, perPage:)` queries
|
|
10
|
+
- Authentication support — `RailsAuditLog.authenticate` is respected; block receives the GraphQL context and raises `GraphQL::ExecutionError` when it returns falsy
|
|
11
|
+
- `rails g rails_audit_log:graphql:install` generator — injects `AuditLogEntriesQueryMixin` into `app/graphql/types/query_type.rb`
|
|
12
|
+
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
`solid_queue_web` is a mountable Rails engine (published as a gem) that provides a web dashboard for [Solid Queue](https://github.com/rails/solid_queue). It has no host application — development and testing both use the dummy Rails app under `spec/dummy/`.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run the full suite (rubocop + rspec) — this is what CI runs
|
|
13
|
+
bundle exec rake
|
|
14
|
+
|
|
15
|
+
# Run only tests
|
|
16
|
+
bundle exec rspec
|
|
17
|
+
|
|
18
|
+
# Run a single spec file
|
|
19
|
+
bundle exec rspec spec/requests/solid_queue_web/jobs_spec.rb
|
|
20
|
+
|
|
21
|
+
# Run a single example by line number
|
|
22
|
+
bundle exec rspec spec/requests/solid_queue_web/jobs_spec.rb:42
|
|
23
|
+
|
|
24
|
+
# Lint
|
|
25
|
+
bin/rubocop
|
|
26
|
+
|
|
27
|
+
# Set up and seed the development database (dummy app)
|
|
28
|
+
bundle exec rake dev:setup # creates and migrates spec/dummy/db/development.sqlite3
|
|
29
|
+
bundle exec rake dev:seed # populates with realistic fake jobs/processes
|
|
30
|
+
|
|
31
|
+
# Reset dev database (setup + seed)
|
|
32
|
+
bundle exec rake dev:reset
|
|
33
|
+
|
|
34
|
+
# Start the dummy app for manual testing
|
|
35
|
+
cd spec/dummy && bin/rails server
|
|
36
|
+
# Dashboard is at http://localhost:3000/jobs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
### Engine isolation
|
|
42
|
+
|
|
43
|
+
The engine uses `isolate_namespace SolidQueueWeb`, so all routes, controllers, helpers, and models live under that namespace. The engine requires no database migrations of its own — it reads directly from the host app's Solid Queue tables via `SolidQueue::*` models (a declared gem dependency).
|
|
44
|
+
|
|
45
|
+
### Execution model pattern
|
|
46
|
+
|
|
47
|
+
Solid Queue represents job state with separate execution tables. Each status maps to a distinct ActiveRecord model:
|
|
48
|
+
|
|
49
|
+
| Status | Model |
|
|
50
|
+
|-------------|------------------------------------|
|
|
51
|
+
| `ready` | `SolidQueue::ReadyExecution` |
|
|
52
|
+
| `scheduled` | `SolidQueue::ScheduledExecution` |
|
|
53
|
+
| `claimed` | `SolidQueue::ClaimedExecution` |
|
|
54
|
+
| `blocked` | `SolidQueue::BlockedExecution` |
|
|
55
|
+
| `failed` | `SolidQueue::FailedExecution` |
|
|
56
|
+
|
|
57
|
+
`JobsController` maps the `?status=` param to these models via `EXECUTION_MODELS`. Only `ready`, `scheduled`, and `blocked` jobs can be discarded from the jobs list (`DISCARDABLE`); failed jobs have their own `FailedJobsController` with retry/discard.
|
|
58
|
+
|
|
59
|
+
### No asset pipeline dependency
|
|
60
|
+
|
|
61
|
+
CSS is delivered entirely via the `inline_styles` helper, which reads `application.css` at request time and injects it as a `<style>` tag. This prevents conflicts when mounted in any host app. There is no JavaScript — queue pause/resume and job discard use standard form POSTs or Turbo Stream responses.
|
|
62
|
+
|
|
63
|
+
### Turbo Stream responses
|
|
64
|
+
|
|
65
|
+
`JobsController#destroy` responds to both HTML and `turbo_stream` format. When a job is discarded:
|
|
66
|
+
- If more jobs remain in the filtered scope → removes that row from the DOM.
|
|
67
|
+
- If it was the last job → replaces the table with an empty-state element (`sqd-empty`).
|
|
68
|
+
|
|
69
|
+
### Authentication
|
|
70
|
+
|
|
71
|
+
`SolidQueueWeb.authenticate` stores a single block (set via an initializer in the host app). `ApplicationController#authenticate!` runs that block in controller context via `instance_exec`. If the block returns falsy, it falls back to HTTP Basic auth. No auth is enforced by default.
|
|
72
|
+
|
|
73
|
+
### Pagination
|
|
74
|
+
|
|
75
|
+
Pagy is included via `Pagy::Method` (not `Pagy::Backend`) and configured globally in the engine initializer with a limit of 25.
|
|
76
|
+
|
|
77
|
+
### Test setup
|
|
78
|
+
|
|
79
|
+
`spec/rails_helper.rb` loads `spec/dummy/db/schema.rb` directly on every test run (no migrations). Tests are all request specs that hit the dummy app's mounted engine at `/jobs`. Factories are not used — records are created directly with `SolidQueue::*` models.
|
|
80
|
+
|
|
81
|
+
### Releasing
|
|
82
|
+
|
|
83
|
+
`bin/release <version>` — bumps the version file, updates `Gemfile.lock` and `CHANGELOG.md`, commits, tags, and pushes. CI picks up the tag and publishes to RubyGems via Trusted Publishing. Must be run from `main` with a clean working tree.
|
|
84
|
+
|
|
85
|
+
### CHANGELOG conventions
|
|
86
|
+
|
|
87
|
+
- New entries go under `## [Unreleased]` on the feature branch, before opening a PR.
|
|
88
|
+
- Sections within each version must appear in this order: `### Added`, `### Changed`, `### Fixed`. Omit sections that have no entries — never add an empty section header.
|
|
89
|
+
- Branch workflow: always commit on a `feat/*` or `chore/*` branch; never commit directly to `main`.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/rails_audit_log-graphql.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/eclectic-coding/rails_audit_log-graphql.git
|
|
9
|
+
cd rails_audit_log-graphql
|
|
10
|
+
bin/setup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running the Test Suite
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle exec rake # lint (StandardRB) + tests — this is what CI runs
|
|
17
|
+
bundle exec rspec # tests only
|
|
18
|
+
bundle exec standardrb # lint only
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
All pull requests must pass the full suite before merging.
|
|
22
|
+
|
|
23
|
+
## Branch Workflow
|
|
24
|
+
|
|
25
|
+
- Branch from `main` using a `feat/*` or `chore/*` prefix
|
|
26
|
+
- One logical change per branch
|
|
27
|
+
- Open a pull request against `main`; CI must be green before merge
|
|
28
|
+
|
|
29
|
+
## Changelog
|
|
30
|
+
|
|
31
|
+
Add an entry under `## [Unreleased]` in `CHANGELOG.md` with every PR. Use the appropriate section:
|
|
32
|
+
|
|
33
|
+
- `### Added` — new functionality
|
|
34
|
+
- `### Changed` — changes to existing behaviour
|
|
35
|
+
- `### Fixed` — bug fixes
|
|
36
|
+
|
|
37
|
+
## Roadmap
|
|
38
|
+
|
|
39
|
+
When a feature listed in `ROADMAP.md` is completed, remove its bullet in the same PR that implements it.
|
|
40
|
+
|
|
41
|
+
## Code Style
|
|
42
|
+
|
|
43
|
+
This project uses [Standard Ruby](https://standardrb.com) (`standard` gem). Run `bundle exec rake standard:fix` to auto-correct violations before committing.
|
|
44
|
+
|
|
45
|
+
## RBS Signatures
|
|
46
|
+
|
|
47
|
+
Update `sig/rails_audit_log/graphql.rbs` when adding or changing public classes and modules.
|
|
48
|
+
|
|
49
|
+
## Releasing
|
|
50
|
+
|
|
51
|
+
Releases are managed by the maintainer. Do not bump `version.rb` in feature PRs.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chuck Smith
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# RailsAuditLog::Graphql
|
|
2
|
+
|
|
3
|
+
[](https://github.com/eclectic-coding/rails_audit_log-graphql/actions/workflows/main.yml)
|
|
4
|
+
[](https://rubygems.org/gems/rails_audit_log-graphql)
|
|
5
|
+
[](https://rubygems.org/gems/rails_audit_log-graphql)
|
|
6
|
+
[](https://www.ruby-lang.org)
|
|
7
|
+
[](https://codecov.io/gh/eclectic-coding/rails_audit_log-graphql)
|
|
8
|
+
|
|
9
|
+
A [graphql-ruby](https://graphql-ruby.org) API layer for the [`rails_audit_log`](https://github.com/eclectic-coding/rails_audit_log) gem. Provides ready-made GraphQL types, queries, and subscriptions for querying audit log entries — without coupling `graphql-ruby` to the base gem.
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Usage](#usage)
|
|
15
|
+
- [AuditLogEntryType](#auditlogentrytype)
|
|
16
|
+
- [AuditLogEntriesQueryMixin](#auditlogentriesquerymixin)
|
|
17
|
+
- [auditLogEntry](#auditlogentryid-id-auditlogentry)
|
|
18
|
+
- [auditLogEntries](#auditlogentries-auditlogentry)
|
|
19
|
+
- [Authentication](#authentication)
|
|
20
|
+
- [Development](#development)
|
|
21
|
+
- [Contributing](#contributing)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add to your application's Gemfile:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
gem "rails_audit_log"
|
|
30
|
+
gem "rails_audit_log-graphql"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then run the install generator to wire up your schema:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
rails g rails_audit_log:graphql:install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This injects `include RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin` into `app/graphql/types/query_type.rb`. If that file doesn't exist, the generator prints the line for you to add manually.
|
|
40
|
+
|
|
41
|
+
[↑ Back to top](#table-of-contents)
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### AuditLogEntryType
|
|
46
|
+
|
|
47
|
+
`RailsAuditLog::Graphql::Types::AuditLogEntryType` is a graphql-ruby object type that maps directly to `RailsAuditLog::AuditLogEntry`.
|
|
48
|
+
|
|
49
|
+
| GraphQL field | Type | Nullable |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `id` | `ID` | no |
|
|
52
|
+
| `event` | `String` | no |
|
|
53
|
+
| `itemType` | `String` | no |
|
|
54
|
+
| `itemId` | `ID` | no |
|
|
55
|
+
| `createdAt` | `ISO8601DateTime` | no |
|
|
56
|
+
| `objectChanges` | `JSON` | yes |
|
|
57
|
+
| `object` | `JSON` | yes |
|
|
58
|
+
| `metadata` | `JSON` | yes |
|
|
59
|
+
| `reason` | `String` | yes |
|
|
60
|
+
| `whodunnitSnapshot` | `String` | yes |
|
|
61
|
+
| `actorType` | `String` | yes |
|
|
62
|
+
| `actorId` | `ID` | yes |
|
|
63
|
+
| `tenantId` | `String` | yes |
|
|
64
|
+
|
|
65
|
+
[↑ Back to top](#table-of-contents)
|
|
66
|
+
|
|
67
|
+
### AuditLogEntriesQueryMixin
|
|
68
|
+
|
|
69
|
+
Include `RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin` into your app's `QueryType` to add two fields:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# app/graphql/types/query_type.rb
|
|
73
|
+
class Types::QueryType < Types::BaseObject
|
|
74
|
+
include RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `auditLogEntry(id: ID!): AuditLogEntry`
|
|
79
|
+
|
|
80
|
+
Fetch a single entry by ID. Returns `nil` if not found.
|
|
81
|
+
|
|
82
|
+
#### `auditLogEntries(...): [AuditLogEntry!]!`
|
|
83
|
+
|
|
84
|
+
List entries with optional filters and offset pagination.
|
|
85
|
+
|
|
86
|
+
| Argument | Type | Default | Description |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `event` | `String` | — | Filter by event type (`create`, `update`, `destroy`) |
|
|
89
|
+
| `itemType` | `String` | — | Filter by audited model class name |
|
|
90
|
+
| `itemId` | `ID` | — | Filter by audited record ID |
|
|
91
|
+
| `actorId` | `ID` | — | Filter by actor ID |
|
|
92
|
+
| `page` | `Int` | `1` | Page number (1-based) |
|
|
93
|
+
| `perPage` | `Int` | `25` | Results per page |
|
|
94
|
+
|
|
95
|
+
Results are ordered by `created_at DESC`.
|
|
96
|
+
|
|
97
|
+
[↑ Back to top](#table-of-contents)
|
|
98
|
+
|
|
99
|
+
### Authentication
|
|
100
|
+
|
|
101
|
+
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"`.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
RailsAuditLog.configure do |config|
|
|
105
|
+
config.authenticate { |ctx| ctx[:current_user]&.admin? }
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If no authenticate block is set, all queries are permitted.
|
|
110
|
+
|
|
111
|
+
[↑ Back to top](#table-of-contents)
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
bin/setup # install dependencies
|
|
117
|
+
bundle exec rake # lint + tests
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
[↑ Back to top](#table-of-contents)
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
123
|
+
|
|
124
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
125
|
+
|
|
126
|
+
[↑ Back to top](#table-of-contents)
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/ROADMAP.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
This gem adds a GraphQL API layer on top of [`rails_audit_log`](https://github.com/eclectic-coding/rails_audit_log). It is intentionally a separate gem so that `graphql-ruby` remains an optional dependency for host applications.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 0.2.0 — Filtering & Connections
|
|
8
|
+
|
|
9
|
+
- **Time-range filters** — `since:` and `until:` arguments on `auditLogEntries`
|
|
10
|
+
- **`touching:` filter** — narrow results to entries that changed a specific attribute
|
|
11
|
+
- **Relay-style connection** — replace offset pagination with cursor-based `AuditLogEntryConnection` for forward/backward pagination
|
|
12
|
+
- **Sorting** — `orderBy: { field: CREATED_AT, direction: DESC }`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 0.3.0 — Actor & Resource Resolver Types
|
|
17
|
+
|
|
18
|
+
- **`ActorType`** — resolve the polymorphic `actor` to the concrete type in the host app's schema (requires a configurable type resolver proc)
|
|
19
|
+
- **`AuditedResourceType`** — resolve `item_type`/`item_id` to the concrete audited model type
|
|
20
|
+
- **`diffType`** — structured `{ from, to }` diff type instead of raw `objectChanges` JSON
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 0.4.0 — Subscriptions
|
|
25
|
+
|
|
26
|
+
Requires Action Cable in the host application.
|
|
27
|
+
|
|
28
|
+
- **`auditLogEntryCreated(itemType:, itemId:)`** — subscribe to new entries for a specific record
|
|
29
|
+
- **`auditLogEntryCreated(actorId:)`** — subscribe to all entries by a specific actor
|
|
30
|
+
- Hooks into `RailsAuditLog::Streaming::NotificationsAdapter` to trigger broadcasts
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 0.5.0 — Multi-tenancy & Advanced Filtering
|
|
35
|
+
|
|
36
|
+
- **Tenant scoping** — automatically scope queries via `RailsAuditLog.current_tenant` when configured
|
|
37
|
+
- **`forTenant:` argument** — explicit tenant filter on `auditLogEntries`
|
|
38
|
+
- **Aggregations** — `auditLogEntriesCount(event:, itemType:, since:)` for dashboard metrics
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 0.6.0 — Performance & Safety
|
|
43
|
+
|
|
44
|
+
- **Dataloader batch loading** — batch-resolve polymorphic `actor` and `item` associations using graphql-ruby's `dataloader` to eliminate N+1 queries on list responses
|
|
45
|
+
- **`auditLogReify` query** — `auditLogReify(itemType:, itemId:, at:)` returns the reconstructed object state as JSON at a given point in time, backed by `RailsAuditLog.version_at`
|
|
46
|
+
- **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
|
|
47
|
+
- **`AuditLogJsonScalar`** — proper JSON scalar type for `objectChanges` and `metadata` fields, replacing opaque String serialization and making the schema self-documenting
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1.0.0 — Stable API
|
|
52
|
+
|
|
53
|
+
- Full YARD documentation
|
|
54
|
+
- **RSpec matchers** — `expect(response).to have_graphql_audit_entry(:update).touching(:title)`
|
|
55
|
+
- **Minitest assertions** — `assert_graphql_audit_entry`
|
|
56
|
+
- API stability guarantee — no breaking changes without a major version bump
|
|
57
|
+
- Complete README with setup guide, examples, and schema reference
|
data/Rakefile
ADDED
data/codecov.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Configure Pull Request Bot Comments
|
|
2
|
+
comment:
|
|
3
|
+
layout: "reach, diff, flags, files"
|
|
4
|
+
behavior: default
|
|
5
|
+
# Only post or update the comment if the coverage drops
|
|
6
|
+
require_changes: "coverage_drop"
|
|
7
|
+
|
|
8
|
+
# Configure Commit Status Checks (the green checkmark/red X list)
|
|
9
|
+
coverage:
|
|
10
|
+
status:
|
|
11
|
+
project:
|
|
12
|
+
default:
|
|
13
|
+
# Prevent "Informational" mode so a drop causes an actual red failure check
|
|
14
|
+
informational: false
|
|
15
|
+
patch:
|
|
16
|
+
default:
|
|
17
|
+
informational: false
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RailsAuditLog
|
|
6
|
+
module Generators
|
|
7
|
+
module Graphql
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
desc "Injects AuditLogEntriesQueryMixin into your GraphQL QueryType."
|
|
11
|
+
|
|
12
|
+
QUERY_TYPE_PATH = "app/graphql/types/query_type.rb"
|
|
13
|
+
MIXIN = "RailsAuditLog::Graphql::Queries::AuditLogEntriesQueryMixin"
|
|
14
|
+
|
|
15
|
+
def inject_mixin
|
|
16
|
+
if File.exist?(File.join(destination_root, QUERY_TYPE_PATH))
|
|
17
|
+
inject_into_file QUERY_TYPE_PATH,
|
|
18
|
+
" include #{MIXIN}\n",
|
|
19
|
+
after: /class\s+\S+\s*<\s*\S+\s*\n/
|
|
20
|
+
else
|
|
21
|
+
say ""
|
|
22
|
+
say "#{QUERY_TYPE_PATH} not found. Add this line manually to your QueryType:", :yellow
|
|
23
|
+
say " include #{MIXIN}", :green
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def print_next_steps
|
|
28
|
+
say ""
|
|
29
|
+
say "Done! Your GraphQL API now has:", :green
|
|
30
|
+
say " auditLogEntry(id: ID!): AuditLogEntry"
|
|
31
|
+
say " auditLogEntries(...): [AuditLogEntry!]!"
|
|
32
|
+
say ""
|
|
33
|
+
say "See the README for full documentation."
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAuditLog
|
|
4
|
+
module Graphql
|
|
5
|
+
module Queries
|
|
6
|
+
module AuditLogEntriesQueryMixin
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.field(
|
|
9
|
+
:audit_log_entry,
|
|
10
|
+
RailsAuditLog::Graphql::Types::AuditLogEntryType,
|
|
11
|
+
null: true,
|
|
12
|
+
description: "Fetch a single audit log entry by ID. Returns nil if not found.",
|
|
13
|
+
resolver_method: :resolve_audit_log_entry
|
|
14
|
+
) do
|
|
15
|
+
argument :id, GraphQL::Types::ID, required: true,
|
|
16
|
+
description: "ID of the audit log entry."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
base.field(
|
|
20
|
+
:audit_log_entries,
|
|
21
|
+
[RailsAuditLog::Graphql::Types::AuditLogEntryType, null: false],
|
|
22
|
+
null: false,
|
|
23
|
+
description: "List audit log entries with optional filters. Offset-paginated.",
|
|
24
|
+
resolver_method: :resolve_audit_log_entries
|
|
25
|
+
) do
|
|
26
|
+
argument :event, String, required: false, description: "Filter by event type (create, update, destroy)."
|
|
27
|
+
argument :item_type, String, required: false, description: "Filter by audited model class name."
|
|
28
|
+
argument :item_id, GraphQL::Types::ID, required: false, description: "Filter by audited record ID."
|
|
29
|
+
argument :actor_id, GraphQL::Types::ID, required: false, description: "Filter by actor ID."
|
|
30
|
+
argument :page, GraphQL::Types::Int, required: false, default_value: 1, description: "Page number (1-based)."
|
|
31
|
+
argument :per_page, GraphQL::Types::Int, required: false, default_value: 25, description: "Number of results per page."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_audit_log_entry(id:)
|
|
36
|
+
check_authentication!
|
|
37
|
+
RailsAuditLog::AuditLogEntry.find_by(id: id)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve_audit_log_entries(event: nil, item_type: nil, item_id: nil, actor_id: nil, page: 1, per_page: 25)
|
|
41
|
+
check_authentication!
|
|
42
|
+
scope = RailsAuditLog::AuditLogEntry.order(created_at: :desc)
|
|
43
|
+
scope = scope.where(event: event) if event
|
|
44
|
+
scope = scope.where(item_type: item_type) if item_type
|
|
45
|
+
scope = scope.where(item_id: item_id) if item_id
|
|
46
|
+
scope = scope.where(actor_id: actor_id) if actor_id
|
|
47
|
+
scope.limit(per_page).offset((page - 1) * per_page)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def check_authentication!
|
|
53
|
+
auth = RailsAuditLog.authenticate
|
|
54
|
+
return unless auth
|
|
55
|
+
raise GraphQL::ExecutionError, "Unauthorized" unless auth.call(context)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module RailsAuditLog
|
|
6
|
+
module Graphql
|
|
7
|
+
# @api private
|
|
8
|
+
module ReleaseTooling
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
VERSION_PATTERN = /VERSION = "[^"]+"/
|
|
12
|
+
SEMVER_PATTERN = /\A\d+\.\d+\.\d+(?:[-.][0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?\z/
|
|
13
|
+
UNRELEASED_HEADING = "## [Unreleased]"
|
|
14
|
+
|
|
15
|
+
def normalize_version(version)
|
|
16
|
+
normalized = version.to_s.sub(/\Av/, "")
|
|
17
|
+
raise ArgumentError, "version must look like x.y.z" unless normalized.match?(SEMVER_PATTERN)
|
|
18
|
+
|
|
19
|
+
normalized
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_version_file(contents, version)
|
|
23
|
+
normalized = normalize_version(version)
|
|
24
|
+
raise ArgumentError, "version file does not define VERSION" unless contents.match?(VERSION_PATTERN)
|
|
25
|
+
|
|
26
|
+
contents.sub(VERSION_PATTERN, %(VERSION = "#{normalized}"))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def finalize_changelog(contents, version, date = Date.today)
|
|
30
|
+
normalized = normalize_version(version)
|
|
31
|
+
release_heading = "## [#{normalized}] - #{date.iso8601}"
|
|
32
|
+
|
|
33
|
+
raise ArgumentError, "CHANGELOG.md must contain an Unreleased heading" unless contents.include?(UNRELEASED_HEADING)
|
|
34
|
+
|
|
35
|
+
if contents.match?(/^## \[#{Regexp.escape(normalized)}\](?: - .+)?$/)
|
|
36
|
+
raise ArgumentError, "CHANGELOG.md already contains #{normalized}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
contents.sub(UNRELEASED_HEADING, "#{UNRELEASED_HEADING}\n\n#{release_heading}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Removes milestone sections from ROADMAP.md where all feature bullets
|
|
43
|
+
# have been implemented (i.e. no remaining "- **" bullet points).
|
|
44
|
+
def prune_roadmap(contents)
|
|
45
|
+
separator = "\n\n---\n\n"
|
|
46
|
+
sections = contents.split(separator)
|
|
47
|
+
|
|
48
|
+
pruned = sections.reject do |section|
|
|
49
|
+
next false unless section.lstrip.match?(/\A## \d+\.\d+\.\d+/)
|
|
50
|
+
|
|
51
|
+
!section.include?("- **")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
pruned.join(separator)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def extract_release_notes(contents, version)
|
|
58
|
+
normalized = normalize_version(version)
|
|
59
|
+
lines = contents.lines
|
|
60
|
+
release_heading = /^## \[#{Regexp.escape(normalized)}\](?: - .+)?$/
|
|
61
|
+
start_index = lines.index { |line| line.match?(release_heading) }
|
|
62
|
+
|
|
63
|
+
raise ArgumentError, "CHANGELOG.md is missing release notes for #{normalized}" unless start_index
|
|
64
|
+
|
|
65
|
+
body = lines[(start_index + 1)..].take_while { |line| !line.start_with?("## [") }.join.strip
|
|
66
|
+
body = "- No changes listed." if body.empty?
|
|
67
|
+
|
|
68
|
+
<<~MARKDOWN
|
|
69
|
+
## RailsAuditLog::Graphql #{normalized}
|
|
70
|
+
|
|
71
|
+
#{body}
|
|
72
|
+
MARKDOWN
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAuditLog
|
|
4
|
+
module Graphql
|
|
5
|
+
module Types
|
|
6
|
+
class AuditLogEntryType < BaseObject
|
|
7
|
+
graphql_name "AuditLogEntry"
|
|
8
|
+
description "A single audited event on an ActiveRecord model."
|
|
9
|
+
|
|
10
|
+
field :id, GraphQL::Types::ID, null: false
|
|
11
|
+
field :event, String, null: false
|
|
12
|
+
field :item_type, String, null: false
|
|
13
|
+
field :item_id, GraphQL::Types::ID, null: false
|
|
14
|
+
field :object_changes, GraphQL::Types::JSON, null: true
|
|
15
|
+
field :object, GraphQL::Types::JSON, null: true, method_conflict_warning: false
|
|
16
|
+
field :metadata, GraphQL::Types::JSON, null: true
|
|
17
|
+
field :reason, String, null: true
|
|
18
|
+
field :whodunnit_snapshot, String, null: true
|
|
19
|
+
field :actor_type, String, null: true
|
|
20
|
+
field :actor_id, GraphQL::Types::ID, null: true
|
|
21
|
+
field :tenant_id, String, null: true
|
|
22
|
+
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "graphql"
|
|
4
|
+
require_relative "graphql/version"
|
|
5
|
+
require_relative "graphql/types/base_object"
|
|
6
|
+
require_relative "graphql/types/audit_log_entry_type"
|
|
7
|
+
require_relative "graphql/queries/audit_log_entries_query_mixin"
|
|
8
|
+
|
|
9
|
+
module RailsAuditLog
|
|
10
|
+
module Graphql
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module RailsAuditLog
|
|
2
|
+
module Graphql
|
|
3
|
+
VERSION: String
|
|
4
|
+
|
|
5
|
+
module Types
|
|
6
|
+
class BaseObject < GraphQL::Schema::Object
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class AuditLogEntryType < BaseObject
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Generators
|
|
14
|
+
module Graphql
|
|
15
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
16
|
+
def inject_mixin: () -> void
|
|
17
|
+
def print_next_steps: () -> void
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module Queries
|
|
23
|
+
module AuditLogEntriesQueryMixin
|
|
24
|
+
def self.included: (untyped base) -> void
|
|
25
|
+
def resolve_audit_log_entry: (id: String) -> untyped
|
|
26
|
+
def resolve_audit_log_entries: (?event: String?, ?item_type: String?, ?item_id: String?, ?actor_id: String?, ?page: Integer, ?per_page: Integer) -> untyped
|
|
27
|
+
private
|
|
28
|
+
def check_authentication!: () -> void
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_audit_log-graphql
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chuck Smith
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: graphql
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rails_audit_log
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.4'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.4'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: simplecov
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.22'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.22'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: simplecov_json_formatter
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.1'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.1'
|
|
68
|
+
description: Provides audit logging for GraphQL mutations and queries in Rails applications.
|
|
69
|
+
email:
|
|
70
|
+
- chuck@eclecticcoding.com
|
|
71
|
+
executables: []
|
|
72
|
+
extensions: []
|
|
73
|
+
extra_rdoc_files: []
|
|
74
|
+
files:
|
|
75
|
+
- ".standard.yml"
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- CLAUDE.md
|
|
78
|
+
- CONTRIBUTING.md
|
|
79
|
+
- LICENSE.txt
|
|
80
|
+
- README.md
|
|
81
|
+
- ROADMAP.md
|
|
82
|
+
- Rakefile
|
|
83
|
+
- codecov.yml
|
|
84
|
+
- lib/generators/rails_audit_log/graphql/install/install_generator.rb
|
|
85
|
+
- lib/rails_audit_log/graphql.rb
|
|
86
|
+
- lib/rails_audit_log/graphql/queries/audit_log_entries_query_mixin.rb
|
|
87
|
+
- lib/rails_audit_log/graphql/release_tooling.rb
|
|
88
|
+
- lib/rails_audit_log/graphql/types/audit_log_entry_type.rb
|
|
89
|
+
- lib/rails_audit_log/graphql/types/base_object.rb
|
|
90
|
+
- lib/rails_audit_log/graphql/version.rb
|
|
91
|
+
- sig/rails_audit_log/graphql.rbs
|
|
92
|
+
homepage: https://github.com/eclectic-coding/rails_audit_log-graphql
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata:
|
|
96
|
+
allowed_push_host: https://rubygems.org
|
|
97
|
+
homepage_uri: https://github.com/eclectic-coding/rails_audit_log-graphql
|
|
98
|
+
source_code_uri: https://github.com/eclectic-coding/rails_audit_log-graphql
|
|
99
|
+
changelog_uri: https://github.com/eclectic-coding/rails_audit_log-graphql/blob/main/CHANGELOG.md
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 3.2.0
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '0'
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 3.6.9
|
|
115
|
+
specification_version: 4
|
|
116
|
+
summary: GraphQL audit logging for Rails applications.
|
|
117
|
+
test_files: []
|