atlas_rb 0.0.91 → 0.0.92
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/Gemfile.lock +1 -1
- data/README.md +28 -0
- data/lib/atlas_rb/blob.rb +12 -2
- data/lib/atlas_rb/faraday_helper.rb +12 -2
- data/lib/atlas_rb/file_set.rb +12 -2
- data/lib/atlas_rb/work.rb +66 -2
- 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: 9ecdf5a4da61e030d7220fc9094a43bd91a850eaa1c599179d75c3d5381b300a
|
|
4
|
+
data.tar.gz: eea705d4f0601b112b2182848241c5439ef4beb10b87e87939e73ea70eb30907
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4aa3b1a2367bde1309f589eebf0bf9bf8ff9f744dc1cdf5648c820cd4d38b4e2afa496a9b50308947f00cb9ceab4d10df4e92018d7715ad7928aa415fb556e0c
|
|
7
|
+
data.tar.gz: b006492ee9c5adde683c863d06cf7cbd233848709e6aa6176e0e6b2fbd663f81f70166b0e7da4889a9005e47c7ecd3cc39e7aa58bde32461b15b4448b18d786d
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.92
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -73,6 +73,34 @@ AtlasRb::FileSet.create("w-789", "primary") # file_set under work w-789, classif
|
|
|
73
73
|
AtlasRb::Blob.create("w-789", path, name) # blob under work w-789 with original filename preserved
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
`Work.create`, `FileSet.create`, and `Blob.create` each accept an optional
|
|
77
|
+
`idempotency_key:` kwarg for retry-safe bulk-deposit jobs. The caller
|
|
78
|
+
generates the UUID; the Atlas server enforces uniqueness scoped to the
|
|
79
|
+
acting user. A repeat call with the same key returns the originally-created
|
|
80
|
+
resource (or `410` if it has since been tombstoned). The gem does **not**
|
|
81
|
+
generate keys, cache responses, or retry — those concerns belong to the
|
|
82
|
+
calling job runner (e.g. Cerberus's Solid Queue).
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
key = SecureRandom.uuid
|
|
86
|
+
AtlasRb::Work.create("col-456", idempotency_key: key)
|
|
87
|
+
AtlasRb::FileSet.create("w-789", "primary", idempotency_key: key)
|
|
88
|
+
AtlasRb::Blob.create("w-789", path, name, idempotency_key: key)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Listing and monitoring Works
|
|
92
|
+
|
|
93
|
+
`Work.list` exposes the paginated `GET /works` index, with an optional
|
|
94
|
+
`in_progress:` filter for finding deposits that haven't yet been marked
|
|
95
|
+
complete. `Work.complete` flips a Work's `in_progress` flag to `false`
|
|
96
|
+
once a bulk-deposit job confirms all expected children have been deposited.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
AtlasRb::Work.list(in_progress: true) # stuck deposits
|
|
100
|
+
AtlasRb::Work.list(in_progress: false, page: 2) # completed deposits, page 2
|
|
101
|
+
AtlasRb::Work.complete("w-789") # mark w-789 done
|
|
102
|
+
```
|
|
103
|
+
|
|
76
104
|
## End-to-end example
|
|
77
105
|
|
|
78
106
|
JSON responses come back as `AtlasRb::Mash` (a `Hashie::Mash` subclass), so
|
data/lib/atlas_rb/blob.rb
CHANGED
|
@@ -65,19 +65,29 @@ module AtlasRb
|
|
|
65
65
|
# @param blob_path [String] path to the binary file on disk to upload.
|
|
66
66
|
# @param original_filename [String] the user-facing filename Atlas
|
|
67
67
|
# should record (e.g. `"final_thesis.pdf"`).
|
|
68
|
+
# @param idempotency_key [String, nil] optional UUID. A repeat call with
|
|
69
|
+
# the same key returns the originally-created Blob instead of creating
|
|
70
|
+
# a new one. See {AtlasRb::Work.create} for full semantics.
|
|
68
71
|
# @return [Hash] the created `"blob"` payload, including its `"id"`.
|
|
69
72
|
#
|
|
70
73
|
# @example
|
|
71
74
|
# AtlasRb::Blob.create("w-789", "/tmp/upload.tmp", "final_thesis.pdf")
|
|
72
75
|
# # => { "id" => "b-321", "original_filename" => "final_thesis.pdf", ... }
|
|
73
|
-
|
|
76
|
+
#
|
|
77
|
+
# @example Retry-safe bulk-deposit create
|
|
78
|
+
# key = SecureRandom.uuid
|
|
79
|
+
# AtlasRb::Blob.create("w-789", "/tmp/upload.tmp", "thesis.pdf",
|
|
80
|
+
# idempotency_key: key)
|
|
81
|
+
def self.create(id, blob_path, original_filename, idempotency_key: nil)
|
|
74
82
|
payload = { work_id: id,
|
|
75
83
|
original_filename: original_filename,
|
|
76
84
|
binary: Faraday::Multipart::FilePart.new(File.open(blob_path),
|
|
77
85
|
"application/octet-stream",
|
|
78
86
|
File.basename(blob_path)) }
|
|
79
87
|
|
|
80
|
-
AtlasRb::Mash.new(JSON.parse(
|
|
88
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
89
|
+
multipart(nil, idempotency_key: idempotency_key).post(ROUTE, payload)&.body
|
|
90
|
+
))['blob']
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
# Delete a Blob (the bytes *and* the metadata record).
|
|
@@ -23,17 +23,23 @@ module AtlasRb
|
|
|
23
23
|
# or `metadata:` without manually serializing.
|
|
24
24
|
# @param nuid [String, nil] optional Northeastern University ID to send in
|
|
25
25
|
# the `User` header. Defaults to `nil` (no NUID context).
|
|
26
|
+
# @param idempotency_key [String, nil] optional UUID to send in the
|
|
27
|
+
# `Idempotency-Key` header. Used by retry-safe create flows (currently
|
|
28
|
+
# `POST /works`, `POST /file_sets`, `POST /files`) to deduplicate replays
|
|
29
|
+
# against the originally-created resource. Generated by the caller —
|
|
30
|
+
# this gem does not mint keys.
|
|
26
31
|
# @return [Faraday::Connection] a connection that follows redirects and
|
|
27
32
|
# uses Faraday's default adapter.
|
|
28
33
|
#
|
|
29
34
|
# @example Fetching a community
|
|
30
35
|
# AtlasRb::Community.connection({}).get('/communities/abc123')
|
|
31
|
-
def connection(params, nuid=nil)
|
|
36
|
+
def connection(params, nuid=nil, idempotency_key: nil)
|
|
32
37
|
headers = {
|
|
33
38
|
"Content-Type" => "application/json",
|
|
34
39
|
"Authorization" => "Bearer #{ENV.fetch("ATLAS_TOKEN", nil)}"
|
|
35
40
|
}
|
|
36
41
|
headers["User"] = "NUID #{nuid}" if nuid
|
|
42
|
+
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
37
43
|
|
|
38
44
|
Faraday.new(
|
|
39
45
|
url: ENV.fetch("ATLAS_URL", nil),
|
|
@@ -53,6 +59,9 @@ module AtlasRb
|
|
|
53
59
|
# `Faraday::Multipart::FilePart` instances.
|
|
54
60
|
#
|
|
55
61
|
# @param nuid [String, nil] optional NUID for the `User` header.
|
|
62
|
+
# @param idempotency_key [String, nil] optional UUID to send in the
|
|
63
|
+
# `Idempotency-Key` header. See {#connection} for semantics; the
|
|
64
|
+
# `POST /files` (Blob) create flow uses this transport.
|
|
56
65
|
# @return [Faraday::Connection] a multipart-aware connection.
|
|
57
66
|
#
|
|
58
67
|
# @example Posting a binary blob
|
|
@@ -63,11 +72,12 @@ module AtlasRb
|
|
|
63
72
|
# "scan.pdf")
|
|
64
73
|
# }
|
|
65
74
|
# AtlasRb::Blob.multipart({}).post('/files/', payload)
|
|
66
|
-
def multipart(nuid=nil)
|
|
75
|
+
def multipart(nuid=nil, idempotency_key: nil)
|
|
67
76
|
headers = {
|
|
68
77
|
"Authorization" => "Bearer #{ENV.fetch("ATLAS_TOKEN", nil)}"
|
|
69
78
|
}
|
|
70
79
|
headers["User"] = "NUID #{nuid}" if nuid
|
|
80
|
+
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
71
81
|
|
|
72
82
|
Faraday.new(
|
|
73
83
|
url: ENV.fetch("ATLAS_URL", nil),
|
data/lib/atlas_rb/file_set.rb
CHANGED
|
@@ -31,14 +31,24 @@ module AtlasRb
|
|
|
31
31
|
# @param classification [String] role tag for the FileSet — e.g.
|
|
32
32
|
# `"primary"`, `"supplemental"`, `"thumbnail"`. The exact set is
|
|
33
33
|
# defined by the Atlas server.
|
|
34
|
+
# @param idempotency_key [String, nil] optional UUID. A repeat call with
|
|
35
|
+
# the same key returns the originally-created FileSet instead of
|
|
36
|
+
# creating a new one. See {AtlasRb::Work.create} for full semantics.
|
|
34
37
|
# @return [Hash] the created `"file_set"` payload, including its `"id"`
|
|
35
38
|
# which can then be passed to {.update} to attach a binary.
|
|
36
39
|
#
|
|
37
40
|
# @example
|
|
38
41
|
# fs = AtlasRb::FileSet.create("w-789", "primary")
|
|
39
42
|
# AtlasRb::FileSet.update(fs["id"], "/tmp/article.pdf")
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
#
|
|
44
|
+
# @example Retry-safe bulk-deposit create
|
|
45
|
+
# key = SecureRandom.uuid
|
|
46
|
+
# AtlasRb::FileSet.create("w-789", "primary", idempotency_key: key)
|
|
47
|
+
def self.create(id, classification, idempotency_key: nil)
|
|
48
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
49
|
+
connection({ work_id: id, classification: classification }, nil,
|
|
50
|
+
idempotency_key: idempotency_key).post(ROUTE)&.body
|
|
51
|
+
))["file_set"]
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
# Delete a FileSet.
|
data/lib/atlas_rb/work.rb
CHANGED
|
@@ -26,6 +26,33 @@ module AtlasRb
|
|
|
26
26
|
AtlasRb::Mash.new(JSON.parse(connection({}).get(ROUTE + id)&.body))["work"]
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
# List Works, paginated.
|
|
30
|
+
#
|
|
31
|
+
# Wraps `GET /works`. Returns the full pagination envelope rather than a
|
|
32
|
+
# bare array so callers can page through results — the shape matches
|
|
33
|
+
# {AtlasRb::Community.children} and {AtlasRb::Collection.children}.
|
|
34
|
+
#
|
|
35
|
+
# @param in_progress [Boolean, nil] when set, filter to Works whose
|
|
36
|
+
# `in_progress` flag matches. Omit (or pass `nil`) for "all works".
|
|
37
|
+
# @param page [Integer, nil] 1-indexed page number.
|
|
38
|
+
# @param per_page [Integer, nil] page size override.
|
|
39
|
+
# @return [AtlasRb::Mash] `{ "works" => [...], "pagination" => {...} }`.
|
|
40
|
+
# Each entry in `"works"` is a Work summary (`id`, `title`,
|
|
41
|
+
# `description`, `in_progress`).
|
|
42
|
+
#
|
|
43
|
+
# @example Find stuck deposits
|
|
44
|
+
# AtlasRb::Work.list(in_progress: true)
|
|
45
|
+
#
|
|
46
|
+
# @example Page through all works
|
|
47
|
+
# AtlasRb::Work.list(page: 2, per_page: 50)
|
|
48
|
+
def self.list(in_progress: nil, page: nil, per_page: nil)
|
|
49
|
+
params = {}
|
|
50
|
+
params[:in_progress] = in_progress unless in_progress.nil?
|
|
51
|
+
params[:page] = page if page
|
|
52
|
+
params[:per_page] = per_page if per_page
|
|
53
|
+
AtlasRb::Mash.new(JSON.parse(connection(params).get(ROUTE)&.body))
|
|
54
|
+
end
|
|
55
|
+
|
|
29
56
|
# Create a new Work in an existing Collection.
|
|
30
57
|
#
|
|
31
58
|
# **Note**: unlike {Community.create} and {Collection.create}, the `id`
|
|
@@ -36,6 +63,14 @@ module AtlasRb
|
|
|
36
63
|
# @param xml_path [String, nil] optional path to a MODS XML file. When
|
|
37
64
|
# given, the Work is created and immediately patched with the metadata
|
|
38
65
|
# in the file.
|
|
66
|
+
# @param idempotency_key [String, nil] optional UUID. A repeat call with
|
|
67
|
+
# the same key returns the originally-created Work instead of creating
|
|
68
|
+
# a new one (or `410` if it has since been tombstoned, or `410` with
|
|
69
|
+
# no body if it has been hard-deleted). Keys are scoped to the acting
|
|
70
|
+
# user and only apply to the initial `POST /works` — the optional
|
|
71
|
+
# follow-up PATCH/GET when `xml_path` is given do not carry the key.
|
|
72
|
+
# The caller (e.g. Cerberus's Solid Queue job) generates and persists
|
|
73
|
+
# the UUID; this gem does not mint keys.
|
|
39
74
|
# @return [Hash] the created Work payload (post-update if `xml_path` was
|
|
40
75
|
# supplied).
|
|
41
76
|
#
|
|
@@ -44,8 +79,14 @@ module AtlasRb
|
|
|
44
79
|
#
|
|
45
80
|
# @example Work seeded from MODS
|
|
46
81
|
# AtlasRb::Work.create("col-456", "/tmp/work-mods.xml")
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
#
|
|
83
|
+
# @example Retry-safe bulk-deposit create
|
|
84
|
+
# key = SecureRandom.uuid
|
|
85
|
+
# AtlasRb::Work.create("col-456", idempotency_key: key)
|
|
86
|
+
def self.create(id, xml_path = nil, idempotency_key: nil)
|
|
87
|
+
result = AtlasRb::Mash.new(JSON.parse(
|
|
88
|
+
connection({ collection_id: id }, nil, idempotency_key: idempotency_key).post(ROUTE)&.body
|
|
89
|
+
))["work"]
|
|
49
90
|
return result unless xml_path.present?
|
|
50
91
|
|
|
51
92
|
update(result["id"], xml_path)
|
|
@@ -82,6 +123,29 @@ module AtlasRb
|
|
|
82
123
|
connection({}, nuid).post(ROUTE + id + '/tombstone')
|
|
83
124
|
end
|
|
84
125
|
|
|
126
|
+
# Mark a Work complete.
|
|
127
|
+
#
|
|
128
|
+
# Cerberus's bulk-deposit job calls this once it has confirmed all
|
|
129
|
+
# expected children (FileSets / Blobs) are deposited. Atlas's monitoring
|
|
130
|
+
# query `GET /works?in_progress=true` then drops this Work from the
|
|
131
|
+
# "stuck" list.
|
|
132
|
+
#
|
|
133
|
+
# Idempotent on the server: calling `complete` on an already-complete
|
|
134
|
+
# Work is a no-op — Atlas simply re-saves with `in_progress: false`.
|
|
135
|
+
# Atlas does not currently stamp a `completed_by` audit field; the
|
|
136
|
+
# `nuid:` parameter is plumbed through for parity with the other
|
|
137
|
+
# lifecycle bindings and in case Atlas adds completion audit later.
|
|
138
|
+
#
|
|
139
|
+
# @param id [String] the Work ID.
|
|
140
|
+
# @param nuid [String, nil] optional NUID of the acting user.
|
|
141
|
+
# @return [Faraday::Response] the raw response. Status `200` on success.
|
|
142
|
+
#
|
|
143
|
+
# @example
|
|
144
|
+
# AtlasRb::Work.complete("w-789")
|
|
145
|
+
def self.complete(id, nuid: nil)
|
|
146
|
+
connection({}, nuid).post(ROUTE + id + '/complete')
|
|
147
|
+
end
|
|
148
|
+
|
|
85
149
|
# Restore a previously tombstoned Work.
|
|
86
150
|
#
|
|
87
151
|
# **Operator-only.** Restoration is intentionally not exposed in any
|
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: 0.0.
|
|
4
|
+
version: 0.0.92
|
|
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-05-
|
|
11
|
+
date: 2026-05-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|