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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa8cb5bbcea60e8d4d85a3a8f3f71ec7e798a811221a53292f9481ab844f6047
4
- data.tar.gz: 9d72d547fe715ba4c9cf168652b61606b3354ab1a7d929e221d9d7691be01db9
3
+ metadata.gz: 9ecdf5a4da61e030d7220fc9094a43bd91a850eaa1c599179d75c3d5381b300a
4
+ data.tar.gz: eea705d4f0601b112b2182848241c5439ef4beb10b87e87939e73ea70eb30907
5
5
  SHA512:
6
- metadata.gz: 951c36792f8fa9c3f767da1bea737df2165211402ca8c22577925c96c0af81bdaa24cdf77343d6d515625627550a5cebb4e02267e59e0e54ce2cb391e9000e52
7
- data.tar.gz: 19e89a98c99c0819bcd3558ef27131c75a2510c57f318c9fe596a82b18abe866f24b1b69610a417f7d47b456de4dd275c88338bbacd43be8d3114e8993237582
6
+ metadata.gz: 4aa3b1a2367bde1309f589eebf0bf9bf8ff9f744dc1cdf5648c820cd4d38b4e2afa496a9b50308947f00cb9ceab4d10df4e92018d7715ad7928aa415fb556e0c
7
+ data.tar.gz: b006492ee9c5adde683c863d06cf7cbd233848709e6aa6176e0e6b2fbd663f81f70166b0e7da4889a9005e47c7ecd3cc39e7aa58bde32461b15b4448b18d786d
data/.version CHANGED
@@ -1 +1 @@
1
- 0.0.91
1
+ 0.0.92
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- atlas_rb (0.0.91)
4
+ atlas_rb (0.0.92)
5
5
  faraday (~> 2.7)
6
6
  faraday-follow_redirects (~> 0.3.0)
7
7
  faraday-multipart (~> 1)
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
- def self.create(id, blob_path, original_filename)
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(multipart({}).post(ROUTE, payload)&.body))['blob']
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),
@@ -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
- def self.create(id, classification)
41
- AtlasRb::Mash.new(JSON.parse(connection({ work_id: id, classification: classification }).post(ROUTE)&.body))["file_set"]
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
- def self.create(id, xml_path = nil)
48
- result = AtlasRb::Mash.new(JSON.parse(connection({ collection_id: id }).post(ROUTE)&.body))["work"]
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.91
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-09 00:00:00.000000000 Z
11
+ date: 2026-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday