atlas_rb 1.20.0 → 1.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ec8ebfc7fd51516a1d73d28bc426153f99a1d7d91c6b1b1bf80ffc965396b8f
4
- data.tar.gz: 284df1abb1bef59118f44483e3ba2b3680daacd03717795417395ee095950f55
3
+ metadata.gz: 547ea9704ea6c2845a7573bd9518cca8d96b3a65eb3792642e3944f99ae1853a
4
+ data.tar.gz: 6c3754d4d4a6c509599e66c9062268b3c30c1b967a536b0231249a0010567c19
5
5
  SHA512:
6
- metadata.gz: e3fc55be0dde3a6d5ff4603284de7e7b2ed4e29c628afbab32d2026befbfc2cc93d4304e4a294043319c60d79a5f51f2e06cf0957a4ffd9e28af8ffc56f64e36
7
- data.tar.gz: a8dced08d932a266c77d70cfaa90cf7d81c957efce818ce42c5a000508b48d6501ca449a1863f3a73017b2131692c6930bdcd53e282823f7957a43e5e30e38a9
6
+ metadata.gz: b259694bf763b09743fc3c64fcd7f612202dec0249f35a8f3c1e211007235425de0c8d1f4c6235a5c9d3256022adb87cfffa9a43275eb25e29f2051579cc128e
7
+ data.tar.gz: 8e09316edfca23f4d966ca9c4e49d7d410711241e420fa0c4f01dfa1541c66d07907e6263640b1749b2b19203709441d3282f494b5526218c583116cc19b24e5
data/.version CHANGED
@@ -1 +1 @@
1
- 1.20.0
1
+ 1.22.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.0
4
+
5
+ These bindings need Atlas 0.6.211 or later.
6
+
7
+ ### Added — `Blob.update(original_filename:)`, renaming the file on a replace
8
+
9
+ ```ruby
10
+ AtlasRb::Blob.update(blob_id, "/tmp/upload.tmp", original_filename: "report.pdf")
11
+ ```
12
+
13
+ A replacement of a different type used to keep the old name, so a `.docx`
14
+ replaced by a `.pdf` downloaded as `.docx`. Atlas now records the name for the
15
+ new revision and re-derives the MIME type from it. For the original file it also
16
+ re-derives the label and the FileSet's classification. Omit the keyword to keep
17
+ the current name. `Blob.rollback` needs no change: Atlas restores the revision's
18
+ own name.
19
+
20
+ ### Added — `language:` and `track_label:` on `Blob.create` and `Blob.update`
21
+
22
+ ```ruby
23
+ AtlasRb::Blob.create(work_id, "/tmp/es.vtt", "es.vtt", language: "es", track_label: "Español")
24
+ ```
25
+
26
+ A caption's BCP 47 language and the name a player shows for it. Each is sent
27
+ only when given, so an update keeps a value it does not mention, and `""`
28
+ clears one. Atlas refuses a malformed value with a `422`, which raises
29
+ `ResourceError`. `Work.assets` and `Work.file_sets` return both fields on Blob
30
+ entries with no reader change.
31
+
32
+ ### Documented — tombstoning a FileSet, and the `original` image tier
33
+
34
+ - `Resource.tombstone` and `Admin::Resource.restore` accept a FileSet id. That
35
+ withdraws a caption or other attached file reversibly. Atlas allows it for the
36
+ admin and devolved-admin tiers only. Despite its namespace,
37
+ `Admin::Resource.restore` is called by delegates too.
38
+ - `Work.set_derivative_permissions` documents the image floor as `original`, not
39
+ `master`. Atlas accepts `master` on write for one release and stores it as
40
+ `original`.
41
+
42
+ ## 1.21.0
43
+
44
+ ### Added — `System::Work.remove_linked_member`, unlinking a showcase Work
45
+
46
+ ```ruby
47
+ AtlasRb::System::Work.remove_linked_member(work_id, old_showcase_id, on_behalf_of: depositor_nuid)
48
+ # => ["def456"]
49
+ ```
50
+
51
+ The mirror of `System::Work.add_linked_member`, over the same system connection.
52
+ It binds `DELETE /works/:id/linked_members/:collection_id`, which Atlas already
53
+ serves. Atlas runs the same two checks as the add: the Collection must be
54
+ featured, and `on_behalf_of` must own the Work. Otherwise it raises
55
+ `ForbiddenError`.
56
+
57
+ A depositor holds no `:link_member` grant, so `Work.remove_linked_member` works
58
+ only for an admin. This binding lets Cerberus change a depositor's showcase
59
+ category: remove the link to the old showcase, then add the link to the new one.
60
+
3
61
  ## 1.20.0
4
62
 
5
63
  ### Added — `System.release_embargoes`, recording lapsed embargoes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- atlas_rb (1.20.0)
4
+ atlas_rb (1.22.0)
5
5
  faraday (~> 2.7)
6
6
  faraday-follow_redirects (~> 0.3.0)
7
7
  faraday-multipart (~> 1)
@@ -53,6 +53,13 @@ module AtlasRb
53
53
  # stub. No `confirm:` marker — restoring is itself reversible, by
54
54
  # tombstoning again.
55
55
  #
56
+ # Accepts a FileSet id too (Atlas 0.6.211 or later), which puts a withdrawn
57
+ # caption or other attached file back in {Work.assets}.
58
+ #
59
+ # The devolved-admin tier calls this as well, although the namespace reads
60
+ # as admin-only. Atlas owns the gate; this namespace only marks the call as
61
+ # an operator action.
62
+ #
56
63
  # @param id [String] the resource's NOID.
57
64
  # @param nuid [String, nil] optional acting user's NUID.
58
65
  # @param on_behalf_of [String, nil] optional NUID for the `On-Behalf-Of`
data/lib/atlas_rb/blob.rb CHANGED
@@ -144,6 +144,12 @@ module AtlasRb
144
144
  # @param blob_path [String] path to the binary file on disk to upload.
145
145
  # @param original_filename [String] the user-facing filename Atlas
146
146
  # should record (e.g. `"final_thesis.pdf"`).
147
+ # @param language [String, nil] optional BCP 47 language of a caption or
148
+ # other text track (e.g. `"en"`, `"es-MX"`). Atlas checks the shape of the
149
+ # tag, not the registry, and refuses a malformed one with `422`
150
+ # `invalid_language` before storing anything. Needs Atlas 0.6.211 or later.
151
+ # @param track_label [String, nil] optional name a player shows for the track
152
+ # (e.g. `"Español"`), at most 64 characters (`422 invalid_track_label`).
147
153
  # @param idempotency_key [String, nil] optional UUID. A repeat call with
148
154
  # the same key returns the originally-created Blob instead of creating
149
155
  # a new one. See {AtlasRb::Work.create} for full semantics.
@@ -178,11 +184,15 @@ module AtlasRb
178
184
  # key = SecureRandom.uuid
179
185
  # AtlasRb::Blob.create("w-789", "/tmp/upload.tmp", "thesis.pdf",
180
186
  # idempotency_key: key, expected_digest: "sha256:#{sha}")
181
- def self.create(id, blob_path, original_filename, expected_digest: nil,
187
+ #
188
+ # @example A Spanish caption track
189
+ # AtlasRb::Blob.create("w-789", "/tmp/es.vtt", "es.vtt", language: "es", track_label: "Español")
190
+ def self.create(id, blob_path, original_filename, expected_digest: nil, language: nil, track_label: nil,
182
191
  idempotency_key: nil, nuid: nil, on_behalf_of: nil)
183
192
  with_file_part(blob_path) do |part|
193
+ # compact drops only nil, so an empty string still reaches Atlas.
184
194
  payload = { work_id: id, original_filename: original_filename, binary: part }
185
- payload[:expected_digest] = expected_digest if expected_digest
195
+ .merge({ expected_digest: expected_digest, language: language, track_label: track_label }.compact)
186
196
 
187
197
  AtlasRb::Mash.new(write_resource(
188
198
  multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key)
@@ -215,12 +225,22 @@ module AtlasRb
215
225
 
216
226
  # Replace the bytes of an existing Blob in-place.
217
227
  #
218
- # The Blob ID is preserved; only the underlying content changes. The
219
- # original filename is *not* updated by this call — use a new
220
- # {.create} if you need a different `original_filename`.
228
+ # The Blob ID is preserved and a new revision is appended. Pass
229
+ # `original_filename` when the replacement is a different file, above all a
230
+ # different type: Atlas records the name for this revision, and re-derives
231
+ # the MIME type from it. For the original file it also re-derives the label
232
+ # and the FileSet's classification; a derivative keeps its tier label.
233
+ # {.rollback} restores a revision's own name, so it needs no name.
221
234
  #
222
235
  # @param id [String] the Blob ID.
223
236
  # @param blob_path [String] path to the replacement binary on disk.
237
+ # @param original_filename [String, nil] the replacement's own filename.
238
+ # Omit it to keep the current name. Needs Atlas 0.6.211 or later; an older
239
+ # Atlas ignores it and keeps the name.
240
+ # @param language [String, nil] a new BCP 47 language for the track. Omit it
241
+ # to keep the current one; pass `""` to clear it. See {.create}.
242
+ # @param track_label [String, nil] a new display name for the track. Omit it
243
+ # to keep the current one; pass `""` to clear it.
224
244
  # @param expected_digest [String, nil] optional verify-on-ingest checksum,
225
245
  # `"<algorithm>:<hexvalue>"`. 422 ({AtlasRb::FixityMismatchError}) on mismatch.
226
246
  # @param idempotency_key [String, nil] optional UUID. A double-submit of the
@@ -249,10 +269,15 @@ module AtlasRb
249
269
  #
250
270
  # @example Retry-safe replace
251
271
  # AtlasRb::Blob.update("b-321", "/tmp/revised.pdf", idempotency_key: SecureRandom.uuid)
252
- def self.update(id, blob_path, expected_digest: nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil)
272
+ #
273
+ # @example Replace a Word file with a PDF
274
+ # AtlasRb::Blob.update("b-321", "/tmp/upload.tmp", original_filename: "report.pdf")
275
+ def self.update(id, blob_path, original_filename: nil, expected_digest: nil, language: nil, track_label: nil,
276
+ idempotency_key: nil, nuid: nil, on_behalf_of: nil)
253
277
  with_file_part(blob_path) do |part|
254
- payload = { binary: part }
255
- payload[:expected_digest] = expected_digest if expected_digest
278
+ # compact drops only nil, so `""` still reaches Atlas and clears a field.
279
+ payload = { binary: part, original_filename: original_filename, expected_digest: expected_digest,
280
+ language: language, track_label: track_label }.compact
256
281
 
257
282
  AtlasRb::Mash.new(write_resource(
258
283
  multipart(nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key)
@@ -268,7 +293,8 @@ module AtlasRb
268
293
  # envelope: one descriptor per retained content revision, each carrying its
269
294
  # OCFL `version_id` label, the `file_identifier` appended for that revision,
270
295
  # the `created` timestamp, the `digest`/`size` recorded at that version,
271
- # the stable `original_filename`, and actor attribution (`actor_nuid` /
296
+ # that revision's own `original_filename` (a replace can rename the file),
297
+ # and actor attribution (`actor_nuid` /
272
298
  # `on_behalf_of_nuid`, null when no audit event correlates).
273
299
  #
274
300
  # Server admin-gates this endpoint (it exposes edit attribution), so
@@ -138,6 +138,12 @@ module AtlasRb
138
138
  # `has_live_children` — a legitimate answer the caller has to read, not an
139
139
  # error. Reversible via {.restore}.
140
140
  #
141
+ # Accepts a FileSet id as well as a Community, Collection or Work (Atlas
142
+ # 0.6.211 or later). That withdraws an attached file, such as a caption,
143
+ # reversibly: its Blobs ride along and it drops out of {Work.assets} and
144
+ # {Work.file_sets}. Atlas refuses a FileSet from anyone outside the admin and
145
+ # devolved-admin tiers with `403`, even a user who can edit the Work.
146
+ #
141
147
  # @param id [String] the resource's NOID.
142
148
  # @param nuid [String, nil] the acting user's NUID, stamped on the resource
143
149
  # as `tombstoned_by`.
@@ -2,15 +2,15 @@
2
2
 
3
3
  module AtlasRb
4
4
  module System
5
- # Showcase publishing: link a freshly-created Work into a depositor's
5
+ # Showcase publishing: link a depositor's Work into (or out of) a
6
6
  # featured showcase Collection on their behalf, without granting the
7
7
  # depositor themselves standing edit rights on that shared Collection.
8
8
  #
9
9
  # Atlas scopes this narrowly on both sides (see Atlas's `Ability`): the
10
10
  # target Collection must be `featured`, and the Work must belong to the
11
11
  # `on_behalf_of` NUID — never an arbitrary or private Work. Cerberus's
12
- # `WorkDeposit#create_published` ("Publish to my community") is the one
13
- # caller today.
12
+ # `WorkDeposit#create_published` ("Publish to my community") adds the link;
13
+ # changing a Work's showcase category swaps it with a remove and an add.
14
14
  #
15
15
  # Always authenticates via {FaradayHelper#system_connection}, so there is
16
16
  # no way to issue this as a regular user.
@@ -43,6 +43,35 @@ module AtlasRb
43
43
  .post("/works/#{work_id}/linked_members")&.body
44
44
  )
45
45
  end
46
+
47
+ # The mirror of {.add_linked_member}: unlink a depositor's Work from a
48
+ # featured showcase Collection on their behalf. Atlas applies the same
49
+ # two checks as the add, so a depositor can undo only a link they could
50
+ # have made.
51
+ #
52
+ # @param work_id [String] the Work ID to unlink.
53
+ # @param collection_id [String] the (must-be-featured) showcase
54
+ # Collection to unlink the Work from.
55
+ # @param on_behalf_of [String] the depositor NUID this write is
56
+ # attributed to; Atlas requires it to match the Work's depositor.
57
+ # @return [Array<String>] the Work's full set of linked Collection
58
+ # noids after the remove.
59
+ # @raise [AtlasRb::StaleResourceError] optimistic-lock conflict.
60
+ # @raise [AtlasRb::LinkedMemberError] structural rejection (HTTP 422) —
61
+ # e.g. the target Collection does not exist.
62
+ # @raise [AtlasRb::ForbiddenError] Atlas refused the unlink (HTTP 403) —
63
+ # e.g. the target Collection isn't featured, or on_behalf_of doesn't
64
+ # own the Work.
65
+ #
66
+ # @example Swapping a Work's showcase from Cerberus's Work Edit page
67
+ # AtlasRb::System::Work.remove_linked_member(work.id, old_showcase.id, on_behalf_of: depositor_nuid)
68
+ # AtlasRb::System::Work.add_linked_member(work.id, new_showcase.id, on_behalf_of: depositor_nuid)
69
+ def self.remove_linked_member(work_id, collection_id, on_behalf_of:)
70
+ JSON.parse(
71
+ system_connection({}, on_behalf_of: on_behalf_of)
72
+ .delete("/works/#{work_id}/linked_members/#{collection_id}")&.body
73
+ )
74
+ end
46
75
  end
47
76
  end
48
77
  end
data/lib/atlas_rb/work.rb CHANGED
@@ -328,7 +328,7 @@ module AtlasRb
328
328
  # Grouper group names, `[]` = private). Two media families:
329
329
  #
330
330
  # * the image ladder `small` / `medium` / `large` / `service` (deep-zoom) /
331
- # `master` (the original image), and
331
+ # `original` (the deposited image), and
332
332
  # * independent media `audio` / `video` / `pdf`.
333
333
  #
334
334
  # Unlike {.set_image_derivatives} (which upserts URIs) this is a whole-object
@@ -340,18 +340,20 @@ module AtlasRb
340
340
  #
341
341
  # Atlas enforces: a tier may not be more visible than the Work, and — within
342
342
  # the image ladder — visibility must narrow as resolution grows
343
- # (`master` ⊆ `service` ⊆ `large` ⊆ `medium` ⊆ `small`; independent media
343
+ # (`original` ⊆ `service` ⊆ `large` ⊆ `medium` ⊆ `small`; independent media
344
344
  # impose no ordering). The gate is advisory — it surfaces on {.assets} as
345
- # `gated` / `permission` for BOTH Delegate (image tier) and Blob (master /
345
+ # `gated` / `permission` for BOTH Delegate (image tier) and Blob (original /
346
346
  # pdf / audio / video, classified by media type) entries, for the display
347
347
  # layer (Cerberus / the IIIF auth service; Cerberus's download :read check)
348
348
  # to enforce.
349
349
  #
350
350
  # @param id [String] the Work ID.
351
351
  # @param policy [Hash] tier => Array(read groups), e.g.
352
- # `{ large: ["northeastern:drs:repository:archives"], master: [...] }`.
352
+ # `{ large: ["northeastern:drs:repository:archives"], original: [...] }`.
353
353
  # Keys may be strings or symbols; recognized keys are `small` / `medium` /
354
- # `large` / `service` / `master` / `audio` / `video` / `pdf`.
354
+ # `large` / `service` / `original` / `audio` / `video` / `pdf`. Atlas
355
+ # 0.6.211 renamed the image floor from `master`; for one release it still
356
+ # accepts `master` on write and stores it as `original`.
355
357
  # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
356
358
  # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
357
359
  # path it is ignored (identity lives in the token).
@@ -435,6 +437,11 @@ module AtlasRb
435
437
  # authorized rather than fetched directly) and `permission` (the effective
436
438
  # read-group set, or `nil` for guests, to whom group names are withheld).
437
439
  #
440
+ # A Blob entry also carries `language` and `track_label`, set by {Blob.create}
441
+ # and {Blob.update} for a caption or other text track and `nil` otherwise
442
+ # (Atlas 0.6.211 or later). A FileSet withdrawn with {Resource.tombstone}
443
+ # drops out of this listing and {.file_sets}.
444
+ #
438
445
  # @param id [String] the Work ID.
439
446
  # @param nuid [String, nil] optional acting user's NUID. On the relay-signing
440
447
  # path it is signed into the assertion `sub`; on the BYO-JWT (`ATLAS_JWT`)
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.20.0
4
+ version: 1.22.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-09-28 00:00:00.000000000 Z
11
+ date: 2026-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday