atlas_rb 1.3.1 → 1.3.3
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 +70 -0
- data/Gemfile.lock +1 -1
- data/lib/atlas_rb/file_set.rb +16 -3
- data/lib/atlas_rb/user.rb +91 -0
- data/lib/atlas_rb/work.rb +72 -0
- data/lib/atlas_rb.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a36183e05b8c32292a5b0201db841ad961bcffcd51d546c0203d591cfbcfefce
|
|
4
|
+
data.tar.gz: 81d85278032e45d983dd5007b25ca3688cea9f23e3c8b5ce94a04727ef5d9497
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4866c59b9c8a8321a0cc440139a5382ac03129e29e0130447da6ade868059dd484bed4029de404a61292058a0f32407266b20f707aa8757e65112d231c57374
|
|
7
|
+
data.tar.gz: ff821a348a98120bfe12da5f0e0de0594810ab93848f3db20e549951eabb205b330df5954d2e375836dab316d50178e80545cd1b69133165aee46567600f02b7
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.3.
|
|
1
|
+
1.3.3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.3
|
|
4
|
+
|
|
5
|
+
### Added — multipage bindings (FileSet ordinality)
|
|
6
|
+
|
|
7
|
+
Bindings for Atlas's FileSet-ordinality surface (Atlas v0.6.53) — the
|
|
8
|
+
primitive behind multipage Works (postcards, scanned books, photo albums):
|
|
9
|
+
one Work, N ordered page FileSets.
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Ordered create — one FileSet per page
|
|
13
|
+
page = AtlasRb::FileSet.create("w-789", "image", position: 1)
|
|
14
|
+
AtlasRb::FileSet.update(page["id"], "/tmp/page-001.tiff")
|
|
15
|
+
|
|
16
|
+
# Ordered listing — the read a IIIF manifest assembler needs
|
|
17
|
+
AtlasRb::Work.file_sets("w-789")
|
|
18
|
+
# => [{ "noid" => ..., "position" => 1, "assets" => [...] }, ...]
|
|
19
|
+
|
|
20
|
+
# Preservation-record view — the Work-level METS physical structMap
|
|
21
|
+
AtlasRb::Work.mets("w-789").mets.pages.map(&:order)
|
|
22
|
+
# => [1, 2, 3]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `FileSet.create` gains an optional `position:` kwarg — 1-based page
|
|
26
|
+
order, set at create time only; omitted = unordered (every existing
|
|
27
|
+
call is unaffected). Sequence validation (contiguity, uniqueness) stays
|
|
28
|
+
the caller's job — Atlas stores what it is given.
|
|
29
|
+
- `Work.file_sets` wraps `GET /works/<id>/file_sets`: one entry per
|
|
30
|
+
page-bearing FileSet, `position` ascending with unordered FileSets
|
|
31
|
+
last, each nesting its downloadable assets (content Blobs + per-page
|
|
32
|
+
IIIF Delegates). **Unpaginated** by design — the whole page sequence
|
|
33
|
+
arrives in one call. Grouped sibling of `.assets`, which flattens.
|
|
34
|
+
- `Work.mets` wraps `GET /works/<id>/mets`: the Work-level METS JSON
|
|
35
|
+
projection; `mets.pages` carries the preserved page order. Atlas builds
|
|
36
|
+
the document at `Work.complete`, so a never-completed Work has no METS
|
|
37
|
+
yet — the binding returns `nil` on the 404, matching
|
|
38
|
+
`User.find_by_nuid`'s convention.
|
|
39
|
+
|
|
40
|
+
## 1.3.2
|
|
41
|
+
|
|
42
|
+
### Added — `AtlasRb::User` (read-only user directory)
|
|
43
|
+
|
|
44
|
+
A user-context binding for Atlas's user directory endpoints — recipient
|
|
45
|
+
typeahead and NUID → name resolution for any surface that today renders a
|
|
46
|
+
bare NUID (User Inbox sender display, Audit History chips, Rights history).
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
AtlasRb::User.search("jan", nuid: "000000002")
|
|
50
|
+
# => [{ "nuid" => "001234567", "name" => "Doe, Jane" }, ...]
|
|
51
|
+
|
|
52
|
+
AtlasRb::User.find_by_nuid("001234567")
|
|
53
|
+
# => { "nuid" => "001234567", "name" => "Doe, Jane" }
|
|
54
|
+
|
|
55
|
+
AtlasRb::User.resolve(["001234567", "007654321"])
|
|
56
|
+
# => one entry per resolvable NUID, ordered by name
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `search` is the typeahead: case-insensitive match on name, prefix match
|
|
60
|
+
on NUID. Atlas caps the list at 10 and orders by name.
|
|
61
|
+
- `resolve` batch-resolves up to 100 NUIDs in one round-trip (an inbox
|
|
62
|
+
page of senders in one call). Unresolvable NUIDs are dropped — callers
|
|
63
|
+
index by `nuid`.
|
|
64
|
+
- `find_by_nuid` resolves a single NUID; returns `nil` on Atlas's 404
|
|
65
|
+
(unknown NUID, or one held by an excluded role — indistinguishable on
|
|
66
|
+
the wire by design).
|
|
67
|
+
- Minimal disclosure is enforced server-side: entries carry `nuid` +
|
|
68
|
+
`name` only, and `anonymous` / `guest` / `system` rows never appear.
|
|
69
|
+
- Deliberately **not** under `AtlasRb::System` — this is an acting-user
|
|
70
|
+
capability on the ordinary `ATLAS_TOKEN` + `User:` header pairing; the
|
|
71
|
+
`System` namespace stays reserved for system-token calls.
|
|
72
|
+
|
|
3
73
|
## 1.3.1
|
|
4
74
|
|
|
5
75
|
### Added — `AtlasRb::Resource.mods_versions` / `mods_version` (MODS version history)
|
data/Gemfile.lock
CHANGED
data/lib/atlas_rb/file_set.rb
CHANGED
|
@@ -39,6 +39,12 @@ module AtlasRb
|
|
|
39
39
|
# @param classification [String] role tag for the FileSet — e.g.
|
|
40
40
|
# `"primary"`, `"supplemental"`, `"thumbnail"`. The exact set is
|
|
41
41
|
# defined by the Atlas server.
|
|
42
|
+
# @param position [Integer, nil] optional 1-based page order within the
|
|
43
|
+
# parent Work, for multipage Works (one FileSet per page). Omit for
|
|
44
|
+
# unordered FileSets — every non-multipage FileSet stays unordered.
|
|
45
|
+
# Set at create time only; Atlas stores what it is given (sequence
|
|
46
|
+
# validation — contiguity, uniqueness — is the caller's job, e.g. the
|
|
47
|
+
# Cerberus loader rejecting bad manifests before any create).
|
|
42
48
|
# @param idempotency_key [String, nil] optional UUID. A repeat call with
|
|
43
49
|
# the same key returns the originally-created FileSet instead of
|
|
44
50
|
# creating a new one. See {AtlasRb::Work.create} for full semantics.
|
|
@@ -49,18 +55,25 @@ module AtlasRb
|
|
|
49
55
|
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
50
56
|
# omitted.
|
|
51
57
|
# @return [Hash] the created `"file_set"` payload, including its `"id"`
|
|
52
|
-
# which can then be passed to {.update} to attach a binary
|
|
58
|
+
# which can then be passed to {.update} to attach a binary, and its
|
|
59
|
+
# `"position"` (`nil` when unordered).
|
|
53
60
|
#
|
|
54
61
|
# @example
|
|
55
62
|
# fs = AtlasRb::FileSet.create("w-789", "primary")
|
|
56
63
|
# AtlasRb::FileSet.update(fs["id"], "/tmp/article.pdf")
|
|
57
64
|
#
|
|
65
|
+
# @example Multipage ingest — one ordered FileSet per page
|
|
66
|
+
# page = AtlasRb::FileSet.create("w-789", "image", position: 1)
|
|
67
|
+
# AtlasRb::FileSet.update(page["id"], "/tmp/page-001.tiff")
|
|
68
|
+
#
|
|
58
69
|
# @example Retry-safe bulk-deposit create
|
|
59
70
|
# key = SecureRandom.uuid
|
|
60
71
|
# AtlasRb::FileSet.create("w-789", "primary", idempotency_key: key)
|
|
61
|
-
def self.create(id, classification, idempotency_key: nil, nuid: nil, on_behalf_of: nil)
|
|
72
|
+
def self.create(id, classification, position: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil)
|
|
73
|
+
params = { work_id: id, classification: classification }
|
|
74
|
+
params[:position] = position if position
|
|
62
75
|
AtlasRb::Mash.new(JSON.parse(
|
|
63
|
-
connection(
|
|
76
|
+
connection(params, nuid,
|
|
64
77
|
on_behalf_of: on_behalf_of, idempotency_key: idempotency_key).post(ROUTE)&.body
|
|
65
78
|
))["file_set"]
|
|
66
79
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtlasRb
|
|
4
|
+
# Read-only user directory lookups — typeahead search and NUID → name
|
|
5
|
+
# resolution.
|
|
6
|
+
#
|
|
7
|
+
# This is a **user-context** binding: calls authenticate as the acting
|
|
8
|
+
# user via the standard `ATLAS_TOKEN` + `User:` header pairing, like every
|
|
9
|
+
# other top-level class. It is deliberately *not* part of
|
|
10
|
+
# {AtlasRb::System} — that namespace is structurally reserved for
|
|
11
|
+
# system-token calls ({System::User.find_or_create}), and directory
|
|
12
|
+
# lookups are an ordinary logged-in-user capability.
|
|
13
|
+
#
|
|
14
|
+
# Atlas enforces minimal disclosure: every entry carries `nuid` + `name`
|
|
15
|
+
# only (no email, role, or groups), and rows with role `anonymous`,
|
|
16
|
+
# `guest`, or `system` are never returned. Per the layering principle the
|
|
17
|
+
# gem adds nothing on top — no caching, no name parsing, no result
|
|
18
|
+
# shaping; presentation belongs to the host application.
|
|
19
|
+
class User
|
|
20
|
+
extend AtlasRb::FaradayHelper
|
|
21
|
+
|
|
22
|
+
# Atlas REST endpoint prefix for the user directory.
|
|
23
|
+
# @api private
|
|
24
|
+
ROUTE = "/users"
|
|
25
|
+
|
|
26
|
+
# Typeahead search of the user directory.
|
|
27
|
+
#
|
|
28
|
+
# Case-insensitive match on name, prefix match on NUID (so typing a
|
|
29
|
+
# known NUID works too). Atlas caps the result (10 entries) and orders
|
|
30
|
+
# it by name; a blank query resolves to an empty list.
|
|
31
|
+
#
|
|
32
|
+
# @param query [String] name fragment or NUID prefix to match.
|
|
33
|
+
# @param nuid [String, nil] optional acting user's NUID, forwarded as the
|
|
34
|
+
# `User:` header. Required for cerberus-token requests; legacy bearer
|
|
35
|
+
# tokens still resolve without it.
|
|
36
|
+
# @return [Array<AtlasRb::Mash>] matching directory entries, each
|
|
37
|
+
# carrying `nuid` and `name`.
|
|
38
|
+
#
|
|
39
|
+
# @example Recipient typeahead
|
|
40
|
+
# AtlasRb::User.search("jan", nuid: "000000002")
|
|
41
|
+
# # => [{ "nuid" => "001234567", "name" => "Doe, Jane" }, ...]
|
|
42
|
+
def self.search(query, nuid: nil)
|
|
43
|
+
JSON.parse(
|
|
44
|
+
connection({ q: query }, nuid).get(ROUTE)&.body
|
|
45
|
+
).map { |entry| AtlasRb::Mash.new(entry) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Resolve a single NUID to a directory entry.
|
|
49
|
+
#
|
|
50
|
+
# @param target_nuid [String] the NUID being looked up — the *subject*
|
|
51
|
+
# of the call, distinct from the acting `nuid:` kwarg.
|
|
52
|
+
# @param nuid [String, nil] optional acting user's NUID, forwarded as the
|
|
53
|
+
# `User:` header. Required for cerberus-token requests; legacy bearer
|
|
54
|
+
# tokens still resolve without it.
|
|
55
|
+
# @return [AtlasRb::Mash, nil] the `nuid` + `name` entry, or `nil` when
|
|
56
|
+
# Atlas reports the NUID as absent (unknown, or held by an excluded
|
|
57
|
+
# role — the two are indistinguishable on the wire by design).
|
|
58
|
+
#
|
|
59
|
+
# @example Sender-name display
|
|
60
|
+
# AtlasRb::User.find_by_nuid("001234567")
|
|
61
|
+
# # => { "nuid" => "001234567", "name" => "Doe, Jane" }
|
|
62
|
+
def self.find_by_nuid(target_nuid, nuid: nil)
|
|
63
|
+
response = connection({}, nuid).get("#{ROUTE}/by_nuid/#{target_nuid}")
|
|
64
|
+
return nil if response.status == 404
|
|
65
|
+
|
|
66
|
+
AtlasRb::Mash.new(JSON.parse(response.body))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Batch-resolve a set of NUIDs to directory entries in one call.
|
|
70
|
+
#
|
|
71
|
+
# Same response shape as {.search}. Unresolvable NUIDs (unknown or
|
|
72
|
+
# excluded-role) are dropped, so the result may be shorter than the
|
|
73
|
+
# input — callers index by `nuid`. Atlas caps the batch at 100.
|
|
74
|
+
#
|
|
75
|
+
# @param nuids [Array<String>, String] the NUIDs to resolve.
|
|
76
|
+
# @param nuid [String, nil] optional acting user's NUID, forwarded as the
|
|
77
|
+
# `User:` header. Required for cerberus-token requests; legacy bearer
|
|
78
|
+
# tokens still resolve without it.
|
|
79
|
+
# @return [Array<AtlasRb::Mash>] resolved entries, each carrying `nuid`
|
|
80
|
+
# and `name`, ordered by name.
|
|
81
|
+
#
|
|
82
|
+
# @example Resolve an inbox page of senders in one round-trip
|
|
83
|
+
# senders = AtlasRb::User.resolve(["001234567", "007654321"])
|
|
84
|
+
# by_nuid = senders.index_by { |entry| entry["nuid"] }
|
|
85
|
+
def self.resolve(nuids, nuid: nil)
|
|
86
|
+
JSON.parse(
|
|
87
|
+
connection({ nuids: Array(nuids).join(",") }, nuid).get(ROUTE)&.body
|
|
88
|
+
).map { |entry| AtlasRb::Mash.new(entry) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/atlas_rb/work.rb
CHANGED
|
@@ -371,6 +371,78 @@ module AtlasRb
|
|
|
371
371
|
).map { |entry| AtlasRb::Mash.new(entry) }
|
|
372
372
|
end
|
|
373
373
|
|
|
374
|
+
# List a Work's page FileSets in order, each with its assets.
|
|
375
|
+
#
|
|
376
|
+
# Wraps `GET /works/<id>/file_sets` — the ordered, grouped sibling of
|
|
377
|
+
# {.assets} (which flattens FileSet membership away). One entry per
|
|
378
|
+
# page-bearing FileSet, sorted `position` ascending with unordered
|
|
379
|
+
# (`null`-position) FileSets last; metadata and derivative-container
|
|
380
|
+
# FileSets are excluded as entries. Each entry nests its downloadable
|
|
381
|
+
# assets — the page's content Blobs plus any per-page IIIF Delegates —
|
|
382
|
+
# in the same polymorphic shape {.assets} returns.
|
|
383
|
+
#
|
|
384
|
+
# This is the read a IIIF Presentation manifest assembler needs: the
|
|
385
|
+
# response is **unpaginated** by design, so the whole page sequence
|
|
386
|
+
# arrives in one call.
|
|
387
|
+
#
|
|
388
|
+
# @param id [String] the Work ID.
|
|
389
|
+
# @param nuid [String, nil] optional acting user's NUID, forwarded as the
|
|
390
|
+
# `User:` header. Required for cerberus-token requests; legacy bearer
|
|
391
|
+
# tokens still resolve without it.
|
|
392
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
393
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
394
|
+
# omitted.
|
|
395
|
+
# @return [Array<AtlasRb::Mash>] one entry per page FileSet, in page
|
|
396
|
+
# order: `{ "noid", "type", "position", "tombstoned", "assets" => [...] }`.
|
|
397
|
+
#
|
|
398
|
+
# @example Assemble manifest canvases in page order
|
|
399
|
+
# AtlasRb::Work.file_sets("w-789").each do |page|
|
|
400
|
+
# iiif = page.assets.find { |a| a["uri"] }
|
|
401
|
+
# add_canvas(order: page.position, image: iiif&.uri)
|
|
402
|
+
# end
|
|
403
|
+
def self.file_sets(id, nuid: nil, on_behalf_of: nil)
|
|
404
|
+
JSON.parse(
|
|
405
|
+
connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/file_sets')&.body
|
|
406
|
+
).map { |entry| AtlasRb::Mash.new(entry) }
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Fetch the Work-level METS structural metadata (page order).
|
|
410
|
+
#
|
|
411
|
+
# Wraps `GET /works/<id>/mets` — the JSON projection of the Work's METS
|
|
412
|
+
# document, whose physical structMap is the preservation record of page
|
|
413
|
+
# order. The page sequence surfaces under `"mets" => "pages"` (one entry
|
|
414
|
+
# per page: `noid` / `order` / `label`). Atlas builds the document when
|
|
415
|
+
# the Work is completed ({.complete}) and rebuilds it on page changes
|
|
416
|
+
# thereafter, so a Work that has never been completed has no METS yet —
|
|
417
|
+
# Atlas answers `404` and this binding returns `nil` (matching
|
|
418
|
+
# {User.find_by_nuid}'s missing-resource convention).
|
|
419
|
+
#
|
|
420
|
+
# For runtime page listing (e.g. manifest assembly) prefer {.file_sets},
|
|
421
|
+
# which needs no completion and carries each page's assets; this read is
|
|
422
|
+
# the preservation-record view.
|
|
423
|
+
#
|
|
424
|
+
# @param id [String] the Work ID.
|
|
425
|
+
# @param nuid [String, nil] optional acting user's NUID, forwarded as the
|
|
426
|
+
# `User:` header. Required for cerberus-token requests; legacy bearer
|
|
427
|
+
# tokens still resolve without it.
|
|
428
|
+
# @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
|
|
429
|
+
# header. Falls through to {AtlasRb.config}.default_on_behalf_of when
|
|
430
|
+
# omitted.
|
|
431
|
+
# @return [Hash, nil] the `"work"` object, already unwrapped: `{ "id",
|
|
432
|
+
# "mets" => { "created_at_iso", "agent", "files", "structure_label",
|
|
433
|
+
# "pages" => [...] } }` — or `nil` when the Work has no METS yet
|
|
434
|
+
# (never completed) or does not exist.
|
|
435
|
+
#
|
|
436
|
+
# @example
|
|
437
|
+
# AtlasRb::Work.mets("w-789").mets.pages.map(&:order)
|
|
438
|
+
# # => [1, 2, 3]
|
|
439
|
+
def self.mets(id, nuid: nil, on_behalf_of: nil)
|
|
440
|
+
response = connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/mets')
|
|
441
|
+
return nil if response.status == 404
|
|
442
|
+
|
|
443
|
+
AtlasRb::Mash.new(JSON.parse(response.body))["work"]
|
|
444
|
+
end
|
|
445
|
+
|
|
374
446
|
# Fetch the Work's MODS representation in the requested format.
|
|
375
447
|
#
|
|
376
448
|
# @param id [String] the Work ID.
|
data/lib/atlas_rb.rb
CHANGED
|
@@ -18,6 +18,7 @@ require_relative "atlas_rb/work"
|
|
|
18
18
|
require_relative "atlas_rb/file_set"
|
|
19
19
|
require_relative "atlas_rb/blob"
|
|
20
20
|
require_relative "atlas_rb/delegate"
|
|
21
|
+
require_relative "atlas_rb/user"
|
|
21
22
|
require_relative "atlas_rb/admin"
|
|
22
23
|
require_relative "atlas_rb/admin/work"
|
|
23
24
|
require_relative "atlas_rb/admin/collection"
|
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.3.
|
|
4
|
+
version: 1.3.3
|
|
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-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -134,6 +134,7 @@ files:
|
|
|
134
134
|
- lib/atlas_rb/middleware/raise_on_stale_resource.rb
|
|
135
135
|
- lib/atlas_rb/resource.rb
|
|
136
136
|
- lib/atlas_rb/system/user.rb
|
|
137
|
+
- lib/atlas_rb/user.rb
|
|
137
138
|
- lib/atlas_rb/version.rb
|
|
138
139
|
- lib/atlas_rb/work.rb
|
|
139
140
|
- sig/atlas_rb.rbs
|