bridgetown_directus 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6834fa39c0868873782728053787ca9fbcbd0defb231b70621c8737ba6e996ee
4
- data.tar.gz: 9dfb896e01a1a7e48d0a6db38b41c12a092195650ea8945083b417d34a76bda6
3
+ metadata.gz: 2212f8e06630de61f4abdc8f50fb65339d3928e9a39e6658fd16f5af37986fdd
4
+ data.tar.gz: 2e02c25a8dcf030d9497caab17a1970c1170151ef777c26538ef2d0f47fb06c2
5
5
  SHA512:
6
- metadata.gz: e2394537291cd18e7887344e9d37c21973eafc19ec2b89dfc38d7c6796011ac8d22ca4777bacf052cf5f4e5cd2ba1dd26019a897cf0a0d26a901fec4aaafba5e
7
- data.tar.gz: a72244a4b1bf68e1e4d8834c5c0548c4a2a3287267a040f3133b5b5cca2a67432948adbc8c0abaf2bb577d852023300aa7dd80c765c374e8c17f72771ed63625
6
+ metadata.gz: 8843466b19c819f8f8cbc3e36188524f4f46774f95ee79b9cd4b2042fd39a38a640accc54addae8666e0249384619ec23ea3c2379daf5d91c0085f3e8019009a
7
+ data.tar.gz: bc119472aa06bfebf3a09357f622224248f4405b5f5bb8d895c215e71b4ddff7d7be2064eac98940dc3c8ebf6857d617ec6529004b7f4cd5da8430930917c73c
@@ -16,5 +16,6 @@ jobs:
16
16
  steps:
17
17
  - uses: googleapis/release-please-action@v4
18
18
  with:
19
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
19
20
  config-file: .release-please-config.json
20
21
  manifest-file: .release-please-manifest.json
@@ -3,11 +3,12 @@ name: Release
3
3
  on:
4
4
  push:
5
5
  tags:
6
- - "v*"
6
+ - "bridgetown_directus/v*"
7
7
  workflow_dispatch:
8
8
 
9
9
  permissions:
10
10
  contents: read
11
+ id-token: write
11
12
 
12
13
  jobs:
13
14
  publish:
@@ -22,7 +23,6 @@ jobs:
22
23
  run: bundle exec rake test
23
24
  - name: Build gem
24
25
  run: bundle exec rake build
26
+ - uses: rubygems/configure-rubygems-credentials@v1.0.0
25
27
  - name: Push gem
26
- env:
27
- GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
28
28
  run: gem push pkg/*.gem
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.3.0"
2
+ ".": "0.4.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.1](https://github.com/Munkun-Estudio/bridgetown_directus/compare/bridgetown_directus/v0.4.0...bridgetown_directus/v0.4.1) (2026-02-16)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * match release-please tag format in release workflow ([70bd081](https://github.com/Munkun-Estudio/bridgetown_directus/commit/70bd08125477acb33f10e06de5fd41ba889d472a))
14
+
15
+ ## [0.4.0](https://github.com/Munkun-Estudio/bridgetown_directus/compare/bridgetown_directus/v0.3.0...bridgetown_directus/v0.4.0) (2026-02-16)
16
+
17
+
18
+ ### Features
19
+
20
+ * add data collections, singleton support, flatten_m2m, and BridgetownDirectus.configure API ([31e99ac](https://github.com/Munkun-Estudio/bridgetown_directus/commit/31e99ac31463b944d20de1e81931219d2c0de59a))
21
+ * make SSL verify configurable via Configuration#ssl_verify ([6cb9033](https://github.com/Munkun-Estudio/bridgetown_directus/commit/6cb90331b0b2e99bd26ecde862c26d6969b0fbf6))
22
+
8
23
  ## [0.3.0](https://github.com/Munkun-Estudio/bridgetown_directus/compare/bridgetown_directus-v0.2.0...bridgetown_directus/v0.3.0) (2026-01-27)
9
24
 
10
25
 
data/README.md CHANGED
@@ -2,132 +2,223 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/bridgetown_directus.svg)](https://badge.fury.io/rb/bridgetown_directus)
4
4
 
5
- This Bridgetown plugin integrates with [Directus](https://directus.io/), a flexible headless CMS. The plugin allows Bridgetown to pull content from a Directus API during the build process and generate static content in your site. It supports both single-language and multilingual content through Directus translations.
5
+ A [Bridgetown](https://www.bridgetownrb.com/) plugin that syncs content from [Directus](https://directus.io/) at build time. It fetches collections from the Directus API and either generates static Markdown files or injects data directly into `site.data`.
6
6
 
7
7
  ## Features
8
8
 
9
- - Fetch content from **multiple Directus collections** during the build process
10
- - Support for **flexible field mapping** and custom converters
11
- - Support for **multilingual content** via Directus translations
12
- - **Experimental**: Advanced **filtering, sorting, and pagination** options
13
- - Simple configuration for any Bridgetown collection (posts, pages, or custom types)
9
+ - **Output collections** fetch Directus items and generate Markdown files (posts, pages, custom types)
10
+ - **Data collections** fetch Directus items and inject into `site.data` (no file generation)
11
+ - **Singletons** single-object collections (e.g. site settings)
12
+ - **M2M junction flattening** unwrap Directus many-to-many junction objects automatically
13
+ - **Flexible field mapping** with custom converters
14
+ - **Multilingual content** via Directus translations
15
+ - **Configurable SSL verification** for environments with strict OpenSSL
16
+ - **Graceful skip** — build succeeds even without Directus credentials configured
14
17
 
15
18
  ## Installation
16
19
 
17
- Before installing the plugin, make sure you have an [Auth Token](https://docs.directus.io/reference/authentication.html#access-tokens) in your Directus instance.
20
+ ### Recommended (Bridgetown Automation)
18
21
 
19
- ### Recommended Installation (Bridgetown Automation)
22
+ ```bash
23
+ bin/bridgetown apply https://github.com/munkun-estudio/bridgetown_directus
24
+ ```
20
25
 
21
- 1. Run the plugin's automation setup:
26
+ ### Manual
22
27
 
23
- ```bash
24
- bin/bridgetown apply https://github.com/munkun-estudio/bridgetown_directus
25
- ```
28
+ ```ruby
29
+ bundle add "bridgetown_directus"
30
+ ```
26
31
 
27
- This will:
28
- - Prompt for your Directus API URL, token, Directus collection name, and Bridgetown collection name
29
- - Generate a minimal `config/initializers.rb`
30
- - All further customization is done in Ruby, not YAML
32
+ ```bash
33
+ bundle install
34
+ ```
31
35
 
32
- ### Manual Installation
36
+ ## Configuration
33
37
 
34
- 1. Add the gem to your Gemfile:
38
+ ### Basic Setup
35
39
 
36
- ```ruby
37
- bundle add "bridgetown_directus"
38
- ```
40
+ ```ruby
41
+ # config/initializers.rb
42
+ init :bridgetown_directus
39
43
 
40
- 2. Run `bundle install` to install the gem.
41
- 3. Create `config/initializers.rb` (see below for configuration).
44
+ BridgetownDirectus.configure do |directus|
45
+ directus.api_url = ENV["DIRECTUS_API_URL"]
46
+ directus.token = ENV["DIRECTUS_API_TOKEN"]
42
47
 
43
- ## Configuration
48
+ directus.register_collection(:posts) do |c|
49
+ c.endpoint = "blog_posts"
50
+ c.layout = "post"
51
+ end
52
+ end
53
+ ```
54
+
55
+ The plugin reads `DIRECTUS_API_URL` and `DIRECTUS_API_TOKEN` from the environment by default. If neither is set, the build skips Directus sync gracefully.
56
+
57
+ ### Data Collections
44
58
 
45
- ### Minimal Example
59
+ Data collections populate `site.data` without generating files. Useful for navigation, settings, or any shared data.
46
60
 
47
61
  ```ruby
48
- # config/initializers/bridgetown_directus.rb
49
- init :bridgetown_directus do |directus|
50
- directus.api_url = ENV["DIRECTUS_API_URL"] || "https://your-directus-instance.com"
51
- directus.token = ENV["DIRECTUS_API_TOKEN"] || "your-token"
62
+ directus.register_collection(:navigation) do |c|
63
+ c.endpoint = "navigation_items"
64
+ c.resource_type = :data
65
+ c.default_query = {
66
+ sort: "sort",
67
+ filter: { status: { _eq: "published" } }.to_json
68
+ }
69
+ end
70
+ ```
52
71
 
53
- directus.register_collection(:posts) do |c|
54
- c.endpoint = "posts"
55
- c.layout = "post" # Use the singular layout for individual pages
56
- # Minimal mapping (optional):
57
- c.field :id, "id"
58
- c.field :title, "title"
59
- # To enable translations, uncomment and edit:
60
- # c.enable_translations([:title, :content])
61
- end
72
+ Access in templates via `site.data.navigation`.
73
+
74
+ ### Singletons
75
+
76
+ For collections that contain a single record (e.g. site settings):
77
+
78
+ ```ruby
79
+ directus.register_collection(:site_settings) do |c|
80
+ c.endpoint = "site_settings"
81
+ c.resource_type = :data
82
+ c.singleton = true
62
83
  end
63
84
  ```
64
85
 
65
- For custom collections, create a layout file at `src/_layouts/[singular].erb` (e.g., `staff_member.erb`) to control the page rendering.
86
+ Returns a single hash instead of an array. Access via `site.data.site_settings`.
87
+
88
+ ### Output Collections
66
89
 
67
- **By default, all Directus fields will be written to the front matter of generated Markdown files.**
68
- You only need to declare fields with `c.field` if you want to:
90
+ Generate Markdown files for posts, pages, or custom collection types:
69
91
 
70
- - Rename a field in the output
71
- - Transform/convert a field value (e.g., format a date, generate a slug, etc.)
72
- - Set a default value if a field is missing
92
+ ```ruby
93
+ directus.register_collection(:events) do |c|
94
+ c.endpoint = "events"
95
+ c.resource_type = :custom_collection
96
+ c.layout = "event"
97
+ c.default_query = {
98
+ filter: { status: { _eq: "published" } }.to_json
99
+ }
100
+ end
101
+ ```
73
102
 
74
- Mapped fields are merged into the full Directus payload, so you still retain access to original fields unless you override them.
103
+ Generated files include `directus_generated: true` in front matter. Only these files are cleaned up on rebuild user-authored files are never deleted.
75
104
 
76
- ### Environment Variables
105
+ ### M2M Junction Flattening
77
106
 
78
- - `DIRECTUS_API_URL` (required unless you set `directus.api_url` in config)
79
- - `DIRECTUS_API_TOKEN` (required unless you set `directus.token` in config)
80
- - `DIRECTUS_TOKEN` (legacy fallback; supported for backward compatibility)
107
+ Directus returns many-to-many relationships wrapped in junction objects:
81
108
 
82
- #### Example: Customizing a Field
109
+ ```json
110
+ [{ "raus_stats_id": { "id": 1, "value": "8+" } }]
111
+ ```
112
+
113
+ Use `flatten_m2m` to unwrap them:
114
+
115
+ ```ruby
116
+ directus.register_collection(:pages) do |c|
117
+ c.endpoint = "pages"
118
+ c.resource_type = :data
119
+ c.default_query = {
120
+ fields: "id,title,sections.stats.raus_stats_id.*"
121
+ }
122
+ c.flatten_m2m "sections.stats", key: "raus_stats_id"
123
+ end
124
+ ```
125
+
126
+ After flattening:
127
+
128
+ ```json
129
+ [{ "id": 1, "value": "8+" }]
130
+ ```
131
+
132
+ ### Field Mapping
133
+
134
+ All Directus fields are included in the output by default. Use `c.field` only when you need to rename or transform a value:
83
135
 
84
136
  ```ruby
85
137
  c.field :slug, "slug" do |value|
86
- value || "staff_member-#{SecureRandom.hex(4)}"
138
+ value || "fallback-#{SecureRandom.hex(4)}"
87
139
  end
88
140
  ```
89
141
 
90
142
  ### Translations
91
143
 
92
- To enable translations for specific fields, add this inside your collection block:
93
-
94
144
  ```ruby
95
145
  c.enable_translations([:title, :content])
96
146
  ```
97
147
 
98
- - You can list any field that exists in your Directus collection, even if it's not declared above with `c.field`.
99
- - Only declare a field with `c.field` if you want to rename, transform, or set a default for it.
148
+ ### SSL Verification
149
+
150
+ If your local OpenSSL (3.6+) fails with CRL verification errors, you can disable SSL verification:
100
151
 
101
- ### File Generation & Cleanup
152
+ ```ruby
153
+ directus.ssl_verify = false
154
+ ```
102
155
 
103
- - **Generated files**: The plugin writes Markdown files to `src/_[bridgetown_collection]/` (e.g., `src/_staff_members/`).
104
- - **Safety**: Only files with the `directus_generated: true` flag in their front matter are deleted during cleanup. User-authored files are never removed.
105
- - **Posts**: If `date` or `published_at` is present, filenames are generated as `YYYY-MM-DD-slug.md` to preserve date-based permalinks.
156
+ This is a client-side issue with recent OpenSSL versions that enforce CRL checking but cannot auto-download CRLs during the TLS handshake. It typically only affects local development — CI/CD environments use standard OpenSSL builds without this issue.
157
+
158
+ Default: `true`.
106
159
 
107
160
  ### Debug Logging
108
161
 
109
- Set `BRIDGETOWN_DIRECTUS_LOG=1` to print per-collection activity logs during builds.
162
+ ```bash
163
+ BRIDGETOWN_DIRECTUS_LOG=1 bin/bt build
164
+ ```
110
165
 
111
- ### Advanced Configuration
166
+ ### Environment Variables
112
167
 
113
- See the plugin source and inline documentation for advanced features such as:
168
+ | Variable | Description |
169
+ | -------- | ----------- |
170
+ | `DIRECTUS_API_URL` | Directus instance URL |
171
+ | `DIRECTUS_API_TOKEN` | Static access token |
172
+ | `DIRECTUS_TOKEN` | Legacy fallback for token |
173
+ | `BRIDGETOWN_DIRECTUS_LOG` | Set to `1` for verbose logging |
114
174
 
115
- - Multiple collections
116
- - Custom layouts per collection
117
- - Filtering, sorting, and pagination via `c.default_query` (**experimental**; not fully tested in production—see notes below)
118
- - Selective field output
175
+ ## Full Example
119
176
 
120
- **Note:** Filtering, sorting, and pagination via `c.default_query` is experimental and not yet fully tested in real Bridgetown projects. Please report issues or contribute test cases if you use this feature!
177
+ ```ruby
178
+ # config/initializers.rb
179
+ init :bridgetown_directus
180
+
181
+ BridgetownDirectus.configure do |directus|
182
+ directus.api_url = ENV["DIRECTUS_API_URL"]
183
+ directus.token = ENV["DIRECTUS_API_TOKEN"]
184
+ directus.ssl_verify = false
185
+
186
+ # Data-only: populates site.data
187
+ directus.register_collection(:site_settings) do |c|
188
+ c.endpoint = "site_settings"
189
+ c.resource_type = :data
190
+ c.singleton = true
191
+ end
121
192
 
122
- ### Migrating from 0.1.x
193
+ directus.register_collection(:navigation) do |c|
194
+ c.endpoint = "navigation_items"
195
+ c.resource_type = :data
196
+ c.default_query = { sort: "sort" }
197
+ end
123
198
 
124
- - **YAML config is no longer used.** All configuration is now in Ruby in `config/initializers.rb`.
125
- - Field mapping, transformation, and translations are handled in the initializer.
126
- - All Directus fields are output by default; use `c.field` for customization.
127
- - **Upgrading?** The `resource_type` option is no longer required. Use the Bridgetown collection name and layout instead. See the [CHANGELOG](CHANGELOG.md) for details.
199
+ # Output: generates Markdown files
200
+ directus.register_collection(:events) do |c|
201
+ c.endpoint = "events"
202
+ c.resource_type = :custom_collection
203
+ c.layout = "event"
204
+ c.default_query = {
205
+ filter: { status: { _eq: "published" } }.to_json
206
+ }
207
+ end
208
+ end
209
+ ```
128
210
 
129
- ---
211
+ ## Migrating from 0.2.x
130
212
 
131
- For more details and advanced usage, see the [plugin README](https://github.com/Munkun-Estudio/bridgetown_directus).
213
+ - Configuration now uses `BridgetownDirectus.configure` block after `init :bridgetown_directus`
214
+ - New `resource_type: :data` for collections that inject into `site.data`
215
+ - New `singleton: true` for single-record collections
216
+ - New `flatten_m2m` for M2M junction unwrapping
217
+ - Build no longer fails when Directus credentials are missing — it skips gracefully
218
+ - SSL verification is configurable via `ssl_verify`
132
219
 
133
220
  See [CHANGELOG.md](CHANGELOG.md) for upgrade notes and detailed changes.
221
+
222
+ ## License
223
+
224
+ See [LICENSE](LICENSE) for details.
data/RELEASING.md ADDED
@@ -0,0 +1,109 @@
1
+ # Releasing `bridgetown_directus`
2
+
3
+ This document explains the release workflow for this gem and how to recover if automation fails.
4
+
5
+ ## Overview
6
+
7
+ There are three parts:
8
+
9
+ 1. **release-please** creates a release PR and a `vX.Y.Z` tag.
10
+ 2. **Release** workflow publishes the gem to RubyGems.
11
+ 3. **CI** runs tests on PRs and pushes.
12
+
13
+ ## Normal automated release flow (recommended)
14
+
15
+ 1. **Make changes on a branch** and merge to `main` with a Conventional Commit title:
16
+ - `fix:` → patch (e.g., `0.3.1`)
17
+ - `feat:` → minor (e.g., `0.4.0`)
18
+ - `feat!:` or `BREAKING CHANGE:` → major (e.g., `1.0.0`)
19
+ 2. `release-please` runs on push to `main` and **opens a release PR**.
20
+ 3. Merge the release PR:
21
+ - `release-please` **creates a tag** `vX.Y.Z` and a GitHub Release.
22
+ 4. The **Release workflow** runs on the tag and **publishes to RubyGems** via Trusted Publishing.
23
+
24
+ ### Requirements for the automated flow
25
+
26
+ - A **PAT** stored as the repo secret `RELEASE_PLEASE_TOKEN` so the tag created by release-please triggers other workflows.
27
+ - RubyGems **Trusted Publisher** configured for this repo/workflow (see below).
28
+
29
+ ## Workflows (GitHub Actions)
30
+
31
+ ### CI
32
+
33
+ File: `.github/workflows/ci.yml`
34
+ Runs tests on PRs and pushes.
35
+
36
+ ### Release Please
37
+
38
+ File: `.github/workflows/release-please.yml`
39
+ Creates release PRs and tags.
40
+
41
+ Important: it uses a PAT:
42
+
43
+ ```yaml
44
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
45
+ ```
46
+
47
+ ### Release
48
+
49
+ File: `.github/workflows/release.yml`
50
+ Runs tests and publishes using OIDC (Trusted Publishing).
51
+
52
+ ## Secrets you need
53
+
54
+ 1. **RELEASE_PLEASE_TOKEN** (GitHub PAT, classic token with `repo` scope)
55
+ - Used by release-please to create tags that trigger the Release workflow.
56
+
57
+ > NOTE: You no longer need `RUBYGEMS_API_KEY` once Trusted Publishing is configured.
58
+
59
+ ## RubyGems Trusted Publishing setup
60
+
61
+ 1. Go to RubyGems → your gem → **Trusted Publishers** → **Create**.
62
+ 2. Fill in:
63
+ - Owner: `Munkun-Estudio`
64
+ - Repo: `bridgetown_directus`
65
+ - Workflow file: `release.yml`
66
+ - Environment: leave blank (unless you use one in GitHub)
67
+ 3. Save.
68
+
69
+ After this, GitHub can publish without API keys or OTP.
70
+
71
+ ## Local release (manual fallback)
72
+
73
+ Use this if the Release workflow fails or you need to publish immediately:
74
+
75
+ ```bash
76
+ cd /Users/pablo/projects/bridgetown_directus
77
+ bundle install
78
+ bundle exec rake build
79
+ ls pkg
80
+ gem push pkg/bridgetown_directus-X.Y.Z.gem
81
+ ```
82
+
83
+ If RubyGems MFA is enabled, you will be prompted for an OTP.
84
+
85
+ ## Troubleshooting
86
+
87
+ ### Release PR merged but gem not published
88
+
89
+ Likely cause: tag created by `release-please` didn’t trigger the Release workflow.
90
+ Fix: ensure `RELEASE_PLEASE_TOKEN` is set and the workflow uses it.
91
+
92
+ ### Release workflow failed with MFA/OTP
93
+
94
+ You are not using Trusted Publishing. Configure it as above, then re-run the Release workflow.
95
+
96
+ ### Tag exists but no Release workflow run
97
+
98
+ 1. Verify the tag is `vX.Y.Z`.
99
+ 2. Go to **Actions → Release → Run workflow** (manual run).
100
+ 3. If it still fails, check the workflow logs.
101
+
102
+ ## Version file
103
+
104
+ `lib/bridgetown_directus/version.rb` is updated by release-please.
105
+ Do **not** bump it manually if you use release-please.
106
+
107
+ ## Changelog
108
+
109
+ `CHANGELOG.md` is updated by release-please. Keep entries under `Unreleased` if editing manually.
@@ -9,17 +9,16 @@ module BridgetownDirectus
9
9
  def build
10
10
  config = site.config.bridgetown_directus
11
11
  return if site.ssr?
12
+ return unless config&.api_url && config&.token
13
+
14
+ client = Client.new(api_url: config.api_url, token: config.token, ssl_verify: config.ssl_verify)
12
15
 
13
16
  config.collections.each_value do |collection_config|
14
- next unless [:posts, :pages, :custom_collection].include?(collection_config.resource_type)
15
-
16
- process_collection(
17
- client: Client.new(
18
- api_url: config.api_url,
19
- token: config.token
20
- ),
21
- collection_config: collection_config
22
- )
17
+ if collection_config.data?
18
+ process_data_collection(client: client, collection_config: collection_config)
19
+ elsif [:posts, :pages, :custom_collection].include?(collection_config.resource_type)
20
+ process_collection(client: client, collection_config: collection_config)
21
+ end
23
22
  end
24
23
  end
25
24
 
@@ -166,6 +165,34 @@ module BridgetownDirectus
166
165
  deleted
167
166
  end
168
167
 
168
+ # Fetch a data-only collection and inject into site.data (no file generation)
169
+ def process_data_collection(client:, collection_config:)
170
+ endpoint = collection_config.endpoint || collection_config.name.to_s
171
+ begin
172
+ response = client.fetch_collection(endpoint, collection_config.default_query)
173
+ rescue StandardError => e
174
+ warn "Error fetching data collection '#{endpoint}': #{e.message}"
175
+ return
176
+ end
177
+
178
+ process_data_collection_with_data(response, collection_config)
179
+ end
180
+
181
+ # Process already-fetched data for a data collection (also used in tests)
182
+ def process_data_collection_with_data(response, collection_config)
183
+ data = sanitize_keys(response)
184
+
185
+ if collection_config.singleton
186
+ data = data.is_a?(Array) ? data.first : data
187
+ end
188
+
189
+ # Apply M2M flattening if configured
190
+ apply_m2m_flattenings!(data, collection_config)
191
+
192
+ site.data[collection_config.name.to_s] = data
193
+ log_directus("Loaded data collection: #{collection_label(collection_config)}#{collection_config.singleton ? ' (singleton)' : " (#{Array(data).size} items)"}")
194
+ end
195
+
169
196
  def process_collection(client:, collection_config:)
170
197
  endpoint = collection_config.endpoint || collection_config.name.to_s
171
198
  begin
@@ -197,6 +224,42 @@ module BridgetownDirectus
197
224
  log_directus("Updated #{collection_label(collection_config)}: wrote #{written}, skipped #{skipped}, deleted #{deleted}")
198
225
  end
199
226
 
227
+ # Apply M2M junction flattening to fetched data.
228
+ # Walks the configured dot-paths and unwraps junction objects.
229
+ def apply_m2m_flattenings!(data, collection_config)
230
+ return if collection_config.m2m_flattenings.empty?
231
+
232
+ items = data.is_a?(Array) ? data : [data].compact
233
+ collection_config.m2m_flattenings.each do |flattening|
234
+ path_parts = flattening[:path].split(".")
235
+ junction_key = flattening[:key]
236
+ items.each { |item| flatten_at_path!(item, path_parts, junction_key) }
237
+ end
238
+ end
239
+
240
+ # Recursively walk a dot-path and flatten the M2M array at the leaf.
241
+ def flatten_at_path!(obj, path_parts, junction_key)
242
+ return unless obj.is_a?(Hash)
243
+
244
+ key = path_parts.first
245
+ remaining = path_parts[1..]
246
+
247
+ if remaining.empty?
248
+ # We're at the leaf — flatten the junction array
249
+ return unless obj[key].is_a?(Array)
250
+
251
+ obj[key] = obj[key].filter_map { |junction| junction[junction_key] if junction.is_a?(Hash) }
252
+ else
253
+ # Intermediate path — recurse into nested object(s)
254
+ target = obj[key]
255
+ if target.is_a?(Array)
256
+ target.each { |nested| flatten_at_path!(nested, remaining, junction_key) }
257
+ elsif target.is_a?(Hash)
258
+ flatten_at_path!(target, remaining, junction_key)
259
+ end
260
+ end
261
+ end
262
+
200
263
  def log_directus(message)
201
264
  return unless directus_logging_enabled?
202
265
 
@@ -8,9 +8,10 @@ module BridgetownDirectus
8
8
  class Client
9
9
  attr_reader :api_url, :token
10
10
 
11
- def initialize(api_url:, token:)
11
+ def initialize(api_url:, token:, ssl_verify: true)
12
12
  @api_url = api_url
13
13
  @token = token
14
+ @ssl_verify = ssl_verify
14
15
  return unless @token.nil? || @api_url.nil?
15
16
 
16
17
  raise StandardError, "Invalid Directus configuration: missing API token or URL"
@@ -45,7 +46,7 @@ module BridgetownDirectus
45
46
  private
46
47
 
47
48
  def connection
48
- @connection ||= Faraday.new(url: @api_url) do |faraday|
49
+ @connection ||= Faraday.new(url: @api_url, ssl: { verify: @ssl_verify }) do |faraday|
49
50
  faraday.headers["Authorization"] = "Bearer #{@token}"
50
51
  faraday.headers["Content-Type"] = "application/json"
51
52
  faraday.adapter Faraday.default_adapter
@@ -43,11 +43,33 @@ module BridgetownDirectus
43
43
  @translations_enabled = false
44
44
  @translatable_fields = []
45
45
  @endpoint = nil
46
+ @singleton = false
47
+ @m2m_flattenings = []
46
48
  end
47
49
 
48
50
  # Set up accessors for collection configuration properties
49
51
  attr_accessor :endpoint, :fields, :default_query, :resource_type, :layout,
50
- :translations_enabled, :translatable_fields
52
+ :translations_enabled, :translatable_fields, :singleton
53
+ attr_reader :m2m_flattenings
54
+
55
+ # Register a many-to-many junction to flatten after fetching.
56
+ # Directus returns M2M data wrapped in junction objects like:
57
+ # [{"raus_stats_id": {"id": 1, "value": "8+"}}]
58
+ # This unwraps them to:
59
+ # [{"id": 1, "value": "8+"}]
60
+ #
61
+ # @param path [String] Dot-separated path to the M2M field (e.g. "sections.stats")
62
+ # @param key [String] The junction key containing the actual related item
63
+ # @return [void]
64
+ def flatten_m2m(path, key:)
65
+ @m2m_flattenings << { path: path, key: key }
66
+ end
67
+
68
+ # Check if this collection is a data-only collection
69
+ # @return [Boolean]
70
+ def data?
71
+ @resource_type == :data
72
+ end
51
73
 
52
74
  # Define a field mapping with optional converter
53
75
  # @param bridgetown_field [Symbol] The field name in Bridgetown
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BridgetownDirectus
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
@@ -8,25 +8,53 @@ require_relative "bridgetown_directus/configuration"
8
8
  require_relative "bridgetown_directus/builder"
9
9
 
10
10
  module BridgetownDirectus
11
- # Bridgetown initializer for the plugin
12
- Bridgetown.initializer :bridgetown_directus do |config|
13
- # Only assign config.bridgetown_directus if not already set
14
- config.bridgetown_directus ||= Configuration.new
11
+ # Bridgetown initializer for the plugin.
12
+ #
13
+ # Usage in config/initializers.rb:
14
+ #
15
+ # init :bridgetown_directus do
16
+ # api_url ENV["DIRECTUS_API_URL"]
17
+ # token ENV["DIRECTUS_API_TOKEN"]
18
+ # end
19
+ #
20
+ # Then configure collections separately:
21
+ #
22
+ # BridgetownDirectus.configure do |directus|
23
+ # directus.register_collection(:posts) { |c| ... }
24
+ # end
25
+ #
26
+ Bridgetown.initializer :bridgetown_directus do |config, **kwargs|
27
+ bd_config = Configuration.instance
15
28
 
16
- # Set up configuration directly (leave to user initializer if possible)
17
- config.bridgetown_directus.api_url ||= ENV["DIRECTUS_API_URL"]
18
- config.bridgetown_directus.token ||= ENV["DIRECTUS_API_TOKEN"] || ENV["DIRECTUS_TOKEN"]
29
+ # Apply keyword args from init block (e.g. api_url, token)
30
+ bd_config.api_url ||= kwargs[:api_url]&.to_s || ENV["DIRECTUS_API_URL"]
31
+ bd_config.token ||= kwargs[:token]&.to_s || ENV["DIRECTUS_API_TOKEN"] || ENV["DIRECTUS_TOKEN"]
32
+
33
+ # Store on the Bridgetown config so the Builder can access it
34
+ config.bridgetown_directus = bd_config
19
35
 
20
36
  # Register the builder
21
37
  config.builder BridgetownDirectus::Builder
22
38
  end
23
39
 
40
+ # Global configuration singleton. Call BridgetownDirectus.configure to register collections.
24
41
  class Configuration
25
- attr_accessor :api_url, :token
42
+ attr_accessor :api_url, :token, :ssl_verify
26
43
  attr_reader :collections
27
44
 
28
45
  def initialize
29
46
  @collections = {}
47
+ @ssl_verify = true
48
+ end
49
+
50
+ # Returns the singleton Configuration instance
51
+ def self.instance
52
+ @instance ||= new
53
+ end
54
+
55
+ # Reset the singleton (useful in tests)
56
+ def self.reset!
57
+ @instance = nil
30
58
  end
31
59
 
32
60
  def register_collection(name, &block)
@@ -36,4 +64,15 @@ module BridgetownDirectus
36
64
  collection
37
65
  end
38
66
  end
67
+
68
+ # Configure the plugin. Call this after `init :bridgetown_directus`.
69
+ #
70
+ # BridgetownDirectus.configure do |directus|
71
+ # directus.api_url = ENV["DIRECTUS_API_URL"]
72
+ # directus.register_collection(:posts) { |c| ... }
73
+ # end
74
+ #
75
+ def self.configure
76
+ yield Configuration.instance if block_given?
77
+ end
39
78
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridgetown_directus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Munkun
@@ -171,6 +171,7 @@ files:
171
171
  - Gemfile
172
172
  - LICENSE.txt
173
173
  - README.md
174
+ - RELEASING.md
174
175
  - Rakefile
175
176
  - bridgetown.automation.rb
176
177
  - bridgetown_directus.gemspec
@@ -206,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
207
  - !ruby/object:Gem::Version
207
208
  version: '0'
208
209
  requirements: []
209
- rubygems_version: 3.7.2
210
+ rubygems_version: 3.6.9
210
211
  specification_version: 4
211
212
  summary: Use Directus as headless CMS for Bridgetown
212
213
  test_files: []