atlas_rb 1.6.6 → 1.8.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/.version +1 -1
- data/CHANGELOG.md +38 -0
- data/Gemfile.lock +2 -2
- data/lib/atlas_rb/blob.rb +114 -2
- data/lib/atlas_rb/work.rb +36 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3d2bc949790f66ee7081de2e94d427f69869fcb301c45d4b9399c072f466820
|
|
4
|
+
data.tar.gz: 466abe63f27507e1845bc1126b739d2bfa396f7c79c447b1bf57e5be42352f4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87494a7060c9b9f192edb404ecffd98ea5b8f4859f4b507e25e7a8d66717d34af7c240f3d99df13163f5e822163d5d3f146df4e9898937a5cb0687b80d12cae1
|
|
7
|
+
data.tar.gz: 04b3221d9ae88ff6bd1b4586321dbd4eda20a4f6753cedfbf465ad4ad2d27214f5252a6e7dd4259e10a24b813eda4a966a7a39fd243244ad295b9c9a749cd5a6
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.8.0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Added — `Work.set_full_text` (full-text search seam)
|
|
6
|
+
|
|
7
|
+
`Work.set_full_text(id, text:)` → `PATCH /works/:id/full_text`. Hands Atlas the
|
|
8
|
+
Work-level aggregate of Cerberus-extracted document text; Atlas stores it as the
|
|
9
|
+
Work's derived `full_text` and projects it onto the Work's Solr doc
|
|
10
|
+
(`all_text_timv`) for body-text search and the "Full Text Match" snippet. Same
|
|
11
|
+
"machine-set derived metadata" family as `set_thumbnails` / `set_image_derivatives`
|
|
12
|
+
— a regenerable search aid re-sent on any re-ingest, not user-authored content.
|
|
13
|
+
|
|
14
|
+
## 1.7.0
|
|
15
|
+
|
|
16
|
+
### Added — binary version read surface (`Blob.versions` / `version_content` / `rollback`)
|
|
17
|
+
|
|
18
|
+
The binary counterpart to `Resource.mods_versions` / `mods_version`. Replacing a
|
|
19
|
+
file (`Blob.update`) already retains prior bytes in OCFL; these bindings make the
|
|
20
|
+
retained versions addressable:
|
|
21
|
+
|
|
22
|
+
- `Blob.versions(id)` → `GET /files/:id/versions` — reverse-chronological
|
|
23
|
+
envelope (`{ "blob_id", "versions" }`), one descriptor per retained content
|
|
24
|
+
revision (`version_id`, `file_identifier`, `created`, `digest`, `size`,
|
|
25
|
+
`original_filename`, plus correlated `actor_nuid` / `on_behalf_of_nuid`).
|
|
26
|
+
Admin-gated by the server.
|
|
27
|
+
- `Blob.version_content(id, version_id, &chunk_handler)` →
|
|
28
|
+
`GET /files/:id/versions/:version_id/content` — streams a prior version's
|
|
29
|
+
bytes through a block, exactly like `Blob.content`.
|
|
30
|
+
- `Blob.rollback(id, version_id)` → `POST /files/:id/rollback` — reinstates a
|
|
31
|
+
prior version by appending its bytes as a new revision (non-destructive; NOID
|
|
32
|
+
preserved).
|
|
33
|
+
|
|
34
|
+
### Added — `Blob.update` accepts `idempotency_key:`
|
|
35
|
+
|
|
36
|
+
`Blob.update` (`PATCH /files/:id`) now takes an optional `idempotency_key:`,
|
|
37
|
+
threaded as the `Idempotency-Key` header (same semantics as `Blob.create` /
|
|
38
|
+
`FileSet.create`). A double-submitted replace sharing a key returns the existing
|
|
39
|
+
Blob instead of minting a second OCFL version.
|
|
40
|
+
|
|
3
41
|
## 1.5.0
|
|
4
42
|
|
|
5
43
|
### Added — optional auth for `Reset.clean`
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
atlas_rb (1.
|
|
4
|
+
atlas_rb (1.8.0)
|
|
5
5
|
faraday (~> 2.7)
|
|
6
6
|
faraday-follow_redirects (~> 0.3.0)
|
|
7
7
|
faraday-multipart (~> 1)
|
|
@@ -25,7 +25,7 @@ GEM
|
|
|
25
25
|
net-http (~> 0.5)
|
|
26
26
|
hashie (5.1.0)
|
|
27
27
|
logger
|
|
28
|
-
json (2.
|
|
28
|
+
json (2.20.0)
|
|
29
29
|
jwt (2.10.3)
|
|
30
30
|
base64
|
|
31
31
|
logger (1.7.0)
|
data/lib/atlas_rb/blob.rb
CHANGED
|
@@ -153,6 +153,10 @@ module AtlasRb
|
|
|
153
153
|
# @param blob_path [String] path to the replacement binary on disk.
|
|
154
154
|
# @param expected_digest [String, nil] optional verify-on-ingest checksum,
|
|
155
155
|
# `"<algorithm>:<hexvalue>"`. 422 ({AtlasRb::FixityMismatchError}) on mismatch.
|
|
156
|
+
# @param idempotency_key [String, nil] optional UUID. A double-submit of the
|
|
157
|
+
# replace with the same key returns the existing Blob instead of minting a
|
|
158
|
+
# second OCFL version — without it a retried replace appends a duplicate
|
|
159
|
+
# revision. See {AtlasRb::Work.create} for full semantics.
|
|
156
160
|
# @param nuid [String, nil] optional acting user's NUID. On the relay-signing
|
|
157
161
|
# path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
|
|
158
162
|
# path it is ignored (identity lives in the token).
|
|
@@ -168,15 +172,123 @@ module AtlasRb
|
|
|
168
172
|
#
|
|
169
173
|
# @example
|
|
170
174
|
# AtlasRb::Blob.update("b-321", "/tmp/revised.pdf")
|
|
171
|
-
|
|
175
|
+
#
|
|
176
|
+
# @example Retry-safe replace
|
|
177
|
+
# AtlasRb::Blob.update("b-321", "/tmp/revised.pdf", idempotency_key: SecureRandom.uuid)
|
|
178
|
+
def self.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil)
|
|
172
179
|
with_file_part(blob_path) do |part|
|
|
173
180
|
payload = { binary: part }
|
|
174
181
|
payload[:expected_digest] = expected_digest if expected_digest
|
|
175
182
|
|
|
176
183
|
AtlasRb::Mash.new(JSON.parse(
|
|
177
|
-
multipart(nuid, on_behalf_of: on_behalf_of
|
|
184
|
+
multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key)
|
|
185
|
+
.patch(ROUTE + id, payload)&.body
|
|
178
186
|
))
|
|
179
187
|
end
|
|
180
188
|
end
|
|
189
|
+
|
|
190
|
+
# List a Blob's retained binary version history.
|
|
191
|
+
#
|
|
192
|
+
# Wraps Atlas's `GET /files/<id>/versions` — the binary counterpart to
|
|
193
|
+
# {Resource.mods_versions}. Returns a reverse-chronological (newest first)
|
|
194
|
+
# envelope: one descriptor per retained content revision, each carrying its
|
|
195
|
+
# OCFL `version_id` label, the `file_identifier` appended for that revision,
|
|
196
|
+
# the `created` timestamp, the `digest`/`size` recorded at that version,
|
|
197
|
+
# the stable `original_filename`, and actor attribution (`actor_nuid` /
|
|
198
|
+
# `on_behalf_of_nuid`, null when no audit event correlates).
|
|
199
|
+
#
|
|
200
|
+
# Server admin-gates this endpoint (it exposes edit attribution), so
|
|
201
|
+
# `401` / `403` surface as raw Faraday responses, matching
|
|
202
|
+
# {Resource.mods_versions}. An unknown Blob id yields a `404`.
|
|
203
|
+
#
|
|
204
|
+
# @param id [String] the Blob ID.
|
|
205
|
+
# @param nuid [String, nil] optional acting user's NUID. On the relay-signing
|
|
206
|
+
# path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
|
|
207
|
+
# path it is ignored (identity lives in the token).
|
|
208
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
209
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
210
|
+
# omitted.
|
|
211
|
+
# @return [AtlasRb::Mash] the parsed envelope, with `"blob_id"` and a
|
|
212
|
+
# `"versions"` array (reverse chronological).
|
|
213
|
+
#
|
|
214
|
+
# @example
|
|
215
|
+
# history = AtlasRb::Blob.versions("b-321")
|
|
216
|
+
# history["versions"].first["version_id"] # => "v5"
|
|
217
|
+
# history["versions"].first["digest"] # => "sha512:9f86d0…"
|
|
218
|
+
def self.versions(id, nuid: nil, on_behalf_of: nil)
|
|
219
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
220
|
+
connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions")&.body
|
|
221
|
+
))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Stream the bytes of a *prior* version of a Blob through a block.
|
|
225
|
+
#
|
|
226
|
+
# Wraps `GET /files/<id>/versions/<version_id>/content` — the version-pinned
|
|
227
|
+
# twin of {.content}, and the read half of "download the superseded file".
|
|
228
|
+
# Like {.content}, the body is **not** buffered: each chunk is yielded to
|
|
229
|
+
# `chunk_handler` immediately (safe for files larger than memory), and the
|
|
230
|
+
# response headers are captured and returned.
|
|
231
|
+
#
|
|
232
|
+
# Pass a `version_id` obtained from {.versions} (an opaque OCFL `vN` label);
|
|
233
|
+
# only labels the history surfaced are addressable. An unknown id or version
|
|
234
|
+
# yields a `404` (raw Faraday response).
|
|
235
|
+
#
|
|
236
|
+
# @param id [String] the Blob ID.
|
|
237
|
+
# @param version_id [String] an OCFL version label from {.versions}, e.g. `"v1"`.
|
|
238
|
+
# @param nuid [String, nil] optional acting user's NUID. On the relay-signing
|
|
239
|
+
# path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
|
|
240
|
+
# path it is ignored (identity lives in the token).
|
|
241
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
242
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
243
|
+
# omitted.
|
|
244
|
+
# @yieldparam chunk [String] the next chunk of binary data.
|
|
245
|
+
# @return [Hash] the response headers from the version-content request.
|
|
246
|
+
#
|
|
247
|
+
# @example Download a superseded version to disk
|
|
248
|
+
# File.open("/tmp/old.pdf", "wb") do |f|
|
|
249
|
+
# AtlasRb::Blob.version_content("b-321", "v1") { |chunk| f.write(chunk) }
|
|
250
|
+
# end
|
|
251
|
+
def self.version_content(id, version_id, nuid: nil, on_behalf_of: nil, &chunk_handler)
|
|
252
|
+
headers = {}
|
|
253
|
+
connection({}, nuid, on_behalf_of: on_behalf_of).get("#{ROUTE}#{id}/versions/#{version_id}/content") do |req|
|
|
254
|
+
req.options.on_data = proc do |chunk, _bytes_received, env|
|
|
255
|
+
headers = env.response_headers if headers.empty? && env
|
|
256
|
+
chunk_handler.call(chunk)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
headers
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Roll a Blob back to a prior version.
|
|
263
|
+
#
|
|
264
|
+
# Wraps `POST /files/<id>/rollback`. Atlas promotes the given version to
|
|
265
|
+
# current by appending its bytes again as a NEW revision — so rollback is
|
|
266
|
+
# itself non-destructive (it becomes vN+1 with the bytes of vN) and the Blob
|
|
267
|
+
# NOID is preserved. OCFL dedups the identical content, so no bytes are
|
|
268
|
+
# recopied. Avoids a full round-trip of the bytes back through the caller
|
|
269
|
+
# (vs. re-streaming {.version_content} into {.update}).
|
|
270
|
+
#
|
|
271
|
+
# Pass a `version_id` obtained from {.versions}. An unknown id or version
|
|
272
|
+
# yields a `404` (raw Faraday response).
|
|
273
|
+
#
|
|
274
|
+
# @param id [String] the Blob ID.
|
|
275
|
+
# @param version_id [String] the OCFL version label to reinstate, e.g. `"v1"`.
|
|
276
|
+
# @param nuid [String, nil] optional acting user's NUID. On the relay-signing
|
|
277
|
+
# path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
|
|
278
|
+
# path it is ignored (identity lives in the token).
|
|
279
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
280
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
281
|
+
# omitted.
|
|
282
|
+
# @return [AtlasRb::Mash] the updated `"blob"` payload (NOID unchanged,
|
|
283
|
+
# `"digest"` refreshed to the reinstated bytes).
|
|
284
|
+
#
|
|
285
|
+
# @example
|
|
286
|
+
# AtlasRb::Blob.rollback("b-321", "v1")
|
|
287
|
+
def self.rollback(id, version_id, nuid: nil, on_behalf_of: nil)
|
|
288
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
289
|
+
connection({}, nuid, on_behalf_of: on_behalf_of)
|
|
290
|
+
.post("#{ROUTE}#{id}/rollback", JSON.dump(version_id: version_id))&.body
|
|
291
|
+
))['blob']
|
|
292
|
+
end
|
|
181
293
|
end
|
|
182
294
|
end
|
data/lib/atlas_rb/work.rb
CHANGED
|
@@ -344,6 +344,42 @@ module AtlasRb
|
|
|
344
344
|
))
|
|
345
345
|
end
|
|
346
346
|
|
|
347
|
+
# Store a Work's derived full-document text for search indexing.
|
|
348
|
+
#
|
|
349
|
+
# Purpose-specific PATCH in the same "machine-set derived metadata" family
|
|
350
|
+
# as {.set_thumbnails} / {.set_image_derivatives}. Hand Atlas the Work-level
|
|
351
|
+
# aggregate of the extracted body text (the concatenation of the Work's
|
|
352
|
+
# content FileSets' text); Atlas stores it as the Work's derived `full_text`
|
|
353
|
+
# and its `FullTextIndexer` projects it onto the Work's Solr doc
|
|
354
|
+
# (`all_text_timv`) for body-text search + the "Full Text Match" snippet.
|
|
355
|
+
#
|
|
356
|
+
# Distinct from {.metadata} — this is a machine-extracted search aid
|
|
357
|
+
# (pdftotext / Tika in a Cerberus job), not user-authored descriptive
|
|
358
|
+
# content, and is re-sent on any re-ingest. Empty/blank text clears it.
|
|
359
|
+
#
|
|
360
|
+
# @param id [String] the Work ID.
|
|
361
|
+
# @param text [String] the extracted plain text (Work-level aggregate).
|
|
362
|
+
# @param nuid [String, nil] optional acting user's NUID. On the relay-signing
|
|
363
|
+
# path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
|
|
364
|
+
# path it is ignored (identity lives in the token).
|
|
365
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
366
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
367
|
+
# omitted.
|
|
368
|
+
# @return [AtlasRb::Mash] the parsed JSON response (the Work; the stored text
|
|
369
|
+
# is not echoed back — it's read only through Solr).
|
|
370
|
+
# @raise [AtlasRb::StaleResourceError] if Atlas reports an optimistic-lock
|
|
371
|
+
# conflict that exhausted its internal retry budget (HTTP 409 with
|
|
372
|
+
# `error: "stale_resource"`).
|
|
373
|
+
#
|
|
374
|
+
# @example
|
|
375
|
+
# AtlasRb::Work.set_full_text("w-789", text: extracted_pdf_text)
|
|
376
|
+
def self.set_full_text(id, text:, nuid: nil, on_behalf_of: nil)
|
|
377
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
378
|
+
connection({}, nuid, on_behalf_of: on_behalf_of)
|
|
379
|
+
.patch(ROUTE + id + '/full_text', JSON.dump(text: text))&.body
|
|
380
|
+
))
|
|
381
|
+
end
|
|
382
|
+
|
|
347
383
|
# List the assets attached to a Work — {Blob}s and {Delegate}s alike.
|
|
348
384
|
#
|
|
349
385
|
# Useful for building download UIs — the response includes enough to
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atlas_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cliff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|